
Security News
Meet Socket at Black Hat Europe and BSides London 2025
Socket is heading to London! Stop by our booth or schedule a meeting to see what we've been working on.
@servicetitan/assist-utils
Advanced tools
The @servicetitan/assist-utils package provides a comprehensive framework for cross-MFE (Micro-Frontend) communication, enabling different applications to share tools and data seamlessly. Built on top of the Atlas Registry system, it uses React hooks and React IoC for dependency injection.
The Atlas Registry system requires a specific setup to ensure all applications (Host, MFEs, and TI-Assist) share the same EventBus and registry.
The Host application is responsible for creating the shared EventBus and registering the Atlas Registry.
First, create the EventBus and provide it via React IoC:
import { Provider } from '@servicetitan/react-ioc';
import { EventBus, EVENT_BUS_TOKEN } from '@servicetitan/shared-data-registry';
import { AtlasRegistryProvider, AtlasProvider } from '@servicetitan/assist-utils';
function App() {
return (
<Provider singletons={[{ provide: EVENT_BUS_TOKEN, useValue: new EventBus() }]}>
{/* EventBus created by Host */}
<AtlasRegistryProvider>
{/* This registers the AtlasRegistry with the EventBus */}
<AtlasProvider>
{/* Your host application code */}
<HostComponents />
{/* MFEs will be loaded here */}
</AtlasProvider>
</AtlasRegistryProvider>
</Provider>
);
}
Important:
Provider with EVENT_BUS_TOKEN - Creates the shared EventBus. Only the Host application creates this.AtlasRegistryProvider - Registers the Atlas Registry with the EventBus. Only use this in the Host application.AtlasProvider - Provides access to AtlasRegistryStore, AtlasRegistryReader, and AtlasRegistryWriter. Required for using hooks like useAtlasContextRegister and useAtlasHandlerRegistry.MFEs do NOT need AtlasRegistryProvider - they inherit the EventBus from the Host.
Wrap only the components that need registry access with AtlasProvider:
import { AtlasProvider } from '@servicetitan/assist-utils';
// Wrap specific components that use Atlas hooks
export const MfeCommunication = () => {
return (
<AtlasProvider>
<MfeCommunicationComponent />
</AtlasProvider>
);
};
// Your MFE root doesn't need AtlasProvider if not all components use it
function App() {
return (
<div>
<MfeCommunication /> {/* Only this uses AtlasProvider */}
<OtherComponents /> {/* These don't need it */}
</div>
);
}
Note: You can wrap at the root level if all/most components need registry access, or wrap individual components for more granular control.
Same as MFEs - wrap components that need registry access with AtlasProvider:
import { AtlasProvider } from '@servicetitan/assist-utils';
export const AssistPanel = () => {
return (
<AtlasProvider>
{/* Components using useAtlasToolInvoke, useAtlasDataConsumer, etc. */}
<AssistComponents />
</AtlasProvider>
);
};
Host Application
└── Provider (creates EventBus via EVENT_BUS_TOKEN)
└── AtlasRegistryProvider (registers 'atlas-host-communication-registry')
└── AtlasProvider (provides Store, Reader, Writer)
├── Host Components (can use hooks)
├── MFE1
│ └── AtlasProvider (connects to same EventBus)
│ └── MFE1 Components (can use hooks)
├── MFE2
│ └── AtlasProvider (connects to same EventBus)
│ └── MFE2 Components (can use hooks)
└── TI-Assist
└── AtlasProvider (connects to same EventBus)
└── Assist Components (can use hooks)
Key Points:
Provider with EVENT_BUS_TOKENAtlasRegistryProvider registers the Atlas Registry with ID: 'atlas-host-communication-registry'Provider (for EventBus) and AtlasRegistryProvider (for registry)AtlasProvider to access the registry┌─────────────────────────────────────────────────────────────────┐
│ Atlas Registry - (assist-utils) │
│ (Centralized message bus for cross-MFE communication) │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ │
┌───────▼──────┐ ┌──────▼─────┐
│ Tool Registry│ │ Data │
│ │ │ Registry │
└───────┬──────┘ └──────┬─────┘
│ │
┌───────────┼───────────────────────────┼───────────┐
│ │ │ │
┌───▼────┐ ┌───▼────┐ ┌─────▼─────┐ ┌────▼─────┐ ┌───▼────┐
│ Host │ │ MFE1 │ │ MFE2 │ │ TI-Assist│ │ MFE3 │
│ App │ │ │ │ │ │ │ │ │
└────────┘ └────────┘ └───────────┘ └──────────┘ └────────┘
The Atlas Registry uses a publish-subscribe pattern where:
All registry access uses dependency injection via @servicetitan/react-ioc:
const [reader] = useDependencies(AtlasRegistryReader);
const [writer] = useDependencies(AtlasRegistryWriter);
Why IoC?
Serializable Data → Stored in Atlas Registry
This hybrid approach allows:
Purpose: Add items to the registry
import { useDependencies } from '@servicetitan/react-ioc';
import { AtlasRegistryWriter } from '@servicetitan/assist-utils';
const [writer] = useDependencies(AtlasRegistryWriter);
// Add an item
const id = writer.add({
type: 'MY_MESSAGE_TYPE',
data: { foo: 'bar' }
});
// Remove an item
writer.remove(id);
Purpose: Read items from the registry
import { useDependencies } from '@servicetitan/react-ioc';
import { AtlasRegistryReader } from '@servicetitan/assist-utils';
const [reader] = useDependencies(AtlasRegistryReader);
// Subscribe to registry updates
useEffect(() => {
reader.subscribe(setItems);
return () => reader.unsubscribe(setItems);
}, [reader]);
Purpose: Combined reader/writer with message sending
import { useRegistry } from '@servicetitan/assist-utils';
const { items, sendMessage } = useRegistry();
// Send a message to all MFEs
sendMessage({
type: 'USER_ACTION',
data: { action: 'click', target: 'button' },
});
Tools enable cross-MFE function execution - one MFE can invoke functions registered by another MFE.
┌──────────────┐ ┌──────────────┐
│ Provider │ │ Consumer │
│ (Host) │ │ (TI-Assist) │
└──────┬───────┘ └──────┬───────┘
│ │
│ 1. Register Tool │
│ useAtlasHandlerRegistry │
│ │
├────────────────────────────────────▶
│ Metadata → Atlas Registry │
│ Handler │
│ │
│ 2. Discover Tools
│ useAtlasToolInvoke
│ │
│ 3. Invoke Tool
│◀───────────────────────────────────┤
│ │
│ 4. Execute Handler │
│ │
│ 5. Return Result │
├────────────────────────────────────▶
Purpose: Register a function that other MFEs can invoke
import { useAtlasHandlerRegistry } from '@servicetitan/assist-utils';
useAtlasHandlerRegistry('calculateTotal', {
name: 'Calculate Total',
description: 'Calculates the total price with tax',
assistantId: 'assistant-123', // Optional: Associate tool with specific assistant
parameters: [
{
name: 'price',
type: 'number',
description: 'Base price',
required: true
},
{
name: 'taxRate',
type: 'number',
description: 'Tax rate (0.0 to 1.0)',
required: true
},
],
handler: ({ price, taxRate }) => {
const tax = price * taxRate;
const total = price + tax;
return {
subtotal: price,
tax: parseFloat(tax.toFixed(2)),
total: parseFloat(total.toFixed(2)),
};
},
});
How it works:
Purpose: Discover and invoke tools from other MFEs
import { useAtlasToolInvoke } from '@servicetitan/assist-utils';
const { invokeTool, availableTools } = useAtlasToolInvoke();
// List all available tools
const tools = availableTools();
console.log(tools); // [{ toolKey: 'calculateTotal', name: 'Calculate Total', ... }]
// Invoke a tool
const result = await invokeTool('calculateTotal', {
price: 100,
taxRate: 0.15,
});
console.log(result); // { subtotal: 100, tax: 15, total: 115 }
How it works:
invokeTool() communicates with provider via the registry// Step 1: Consumer invokes tool
invokeTool('calculateTotal', { price: 100, taxRate: 0.15 })
// Step 3: Provider handler executes
handler({ price: 100, taxRate: 0.15 }) // Returns result
// Step 5: Consumer receives result
// Promise resolves with { subtotal: 100, tax: 15, total: 115 }
Purpose: Send system messages from MFEs to TI-Assist
System messages allow MFEs to provide context or trigger actions in the AI assistant without showing messages in the user-facing chat interface.
import { useAtlasSystemMessage } from '@servicetitan/assist-utils';
const MyMFEComponent = () => {
const sendSystemMessage = useAtlasSystemMessage();
const handleExport = () => {
// Send context to TI-Assist about user action
sendSystemMessage({
text: 'User initiated data export',
context: {
action: 'export',
selectedItems: [1, 2, 3],
currentView: 'dashboard'
},
assistantId: 'assistant-123', // Optional
interactionId: 'interaction-456' // Required for message creation
});
};
return <button onClick={handleExport}>Export Data</button>;
};
How it works:
sendSystemMessage() with payloadATLAS_SYSTEM_MESSAGEinteractionId is provided, a SystemMessage is created via APIParameters:
text (required): Description of the system event or contextcontext (optional): Additional structured data about the eventassistantId (optional): Specific assistant to targetinteractionId (required): The interaction ID where the message should be createdImportant Notes:
interactionId is required for the message to be createdData Registry enables real-time data synchronization - changes in one MFE automatically reflect in all subscribed MFEs.
┌──────────────┐ ┌──────────────┐
│ Provider │ │ Consumer │
│ (Host) │ │ (TI-Assist) │
└──────┬───────┘ └──────┬───────┘
│ │
│ 1. Register Data │
│ useAtlasContextRegister │
│ { key, data } │
│ │
├────────────────────────────────────▶
│ Atlas Registry │
│ │
│ 2. Subscribe
│ useAtlasDataConsumer
│ │
│ 3. Data Updates (state change) │
│ │
├────────────────────────────────────▶
│ Auto-sync via Registry │
│ │
│ 4. React re-renders
│ with new data
Purpose: Share live state data with other MFEs
import { useAtlasContextRegister } from '@servicetitan/assist-utils';
const [counter, setCounter] = useState(0);
const [status, setStatus] = useState('idle');
// Share live state - automatically updates consumers
useAtlasContextRegister({
key: 'hostLiveState',
description: 'Host application live state',
assistantId: 'assistant-123', // Optional: Associate data with specific assistant
payload: { counter, status },
});
Advanced: Memoization for Performance
const [counter, setCounter] = useState(0);
const [status, setStatus] = useState('idle');
const [otherData, setOtherData] = useState('...');
// Memoize to prevent unnecessary registry updates
const sharedData = useMemo(
() => ({ counter, status }),
[counter, status]
);
useAtlasContextRegister({
key: 'hostLiveState',
description: 'Host application live state',
payload: sharedData, // Only updates when counter or status changes
});
How it works:
payload changes, registry is updatedPurpose: Subscribe to live data from other MFEs
import { useAtlasDataConsumer } from '@servicetitan/assist-utils';
// Subscribe to data by key
const liveState = useAtlasDataConsumer<{ counter: number; status: string }>('hostLiveState');
// Use in component
return (
<div>
<p>Counter: {liveState?.counter}</p>
<p>Status: {liveState?.status}</p>
</div>
);
Type Safety:
interface HostState {
counter: number;
status: string;
}
const liveState = useAtlasDataConsumer<HostState>('hostLiveState');
// liveState is typed as HostState | undefined
if (liveState) {
console.log(liveState.counter); // TypeScript knows this is a number
}
How it works:
undefined if data doesn't exist yetPurpose: Get list of all available data sources
import { useAtlasDataList } from '@servicetitan/assist-utils';
const dataSources = useAtlasDataList();
console.log(dataSources);
// [
// { key: 'hostLiveState', description: 'Host application live state' },
// { key: 'mfe1UserInput', description: 'Live text input from MFE 1' }
// ]
Host (Provider):
// packages/sandbox/src/components/tools/tools.tsx
import { useAtlasHandlerRegistry } from '@servicetitan/assist-utils';
const HostCommunication = () => {
const [counter, setCounter] = useState(0);
useAtlasHandlerRegistry('incrementCounter', {
name: 'Increment Counter',
description: 'Increments the counter in host',
parameters: [
{ name: 'amount', type: 'number', description: 'Amount to increment', required: true }
],
handler: ({ amount }) => {
setCounter(prev => prev + amount);
return { newCounter: counter + amount };
},
});
return <div>Counter: {counter}</div>;
};
TI-Assist (Consumer):
// packages/ti-assist/src/modules/demo/demo.tsx
import { useAtlasToolInvoke } from '@servicetitan/assist-utils';
const Demo = () => {
const { invokeTool, availableTools } = useAtlasToolInvoke();
const handleIncrement = async () => {
const result = await invokeTool('incrementCounter', { amount: 5 });
console.log('New counter:', result.newCounter);
};
return (
<div>
<button onClick={handleIncrement}>Increment by 5</button>
<p>Available tools: {availableTools().length}</p>
</div>
);
};
Host (Provider):
import { useAtlasContextRegister } from '@servicetitan/assist-utils';
const HostPanel = () => {
const [userInput, setUserInput] = useState('');
// Share user input in real-time
useAtlasContextRegister({
key: 'hostUserInput',
description: 'Live text input from host',
payload: userInput,
});
return (
<input
value={userInput}
onChange={e => setUserInput(e.target.value)}
placeholder="Type something..."
/>
);
};
TI-Assist (Consumer):
import { useAtlasDataConsumer } from '@servicetitan/assist-utils';
const Demo = () => {
const hostUserInput = useAtlasDataConsumer<string>('hostUserInput');
return (
<div>
<h3>Host is typing:</h3>
<p>{hostUserInput || '(No input yet)'}</p>
<p>Characters: {hostUserInput?.length || 0}</p>
</div>
);
};
Invoke multiple tools in sequence:
const { invokeTool } = useAtlasToolInvoke();
const handleComplexOperation = async () => {
// Step 1: Calculate total
const totalResult = await invokeTool('calculateTotal', {
price: 100,
taxRate: 0.15
});
// Step 2: Update status with result
await invokeTool('updateStatus', {
status: `Total calculated: $${totalResult.total}`
});
// Step 3: Increment counter
await invokeTool('incrementCounter', {
amount: 1
});
};
Combine data from multiple sources:
const hostState = useAtlasDataConsumer<{ counter: number; status: string }>('hostLiveState');
const mfeState = useAtlasDataConsumer<{ counter: number; status: string }>('mfe1LiveState');
const totalCounter = (hostState?.counter || 0) + (mfeState?.counter || 0);
return (
<div>
<h3>Combined Counter: {totalCounter}</h3>
<p>Host: {hostState?.counter || 0}</p>
<p>MFE: {mfeState?.counter || 0}</p>
</div>
);
Build a dynamic tool explorer:
const { availableTools } = useAtlasToolInvoke();
return (
<div>
<h2>Available Tools</h2>
{availableTools().map(tool => (
<div key={tool.toolKey} style={{ border: '1px solid #ccc', padding: '10px' }}>
<h3>{tool.name}</h3>
<p>{tool.description}</p>
<h4>Parameters:</h4>
<ul>
{tool.parameters.map(param => (
<li key={param.name}>
<strong>{param.name}</strong> ({param.type})
{param.required && ' *required*'}
<br />
<em>{param.description}</em>
</li>
))}
</ul>
</div>
))}
</div>
);
✅ Do:
// Use descriptive tool keys
useAtlasHandlerRegistry('calculateTotalWithTax', { ... });
// Provide clear descriptions
description: 'Calculates the total price including tax based on the given tax rate'
// Validate parameters
handler: ({ price, taxRate }) => {
if (typeof price !== 'number' || price < 0) {
throw new Error('Price must be a positive number');
}
// ... rest of handler
}
❌ Don't:
// Vague tool keys
useAtlasHandlerRegistry('calc', { ... });
// Missing descriptions
description: ''
// No validation
handler: ({ price, taxRate }) => price + (price * taxRate)
✅ Do:
// Memoize data to prevent unnecessary updates
const sharedData = useMemo(
() => ({ counter, status }),
[counter, status]
);
useAtlasContextRegister({
key: 'myData',
payload: sharedData,
});
// Use specific keys
key: 'hostUserInputField'
❌ Don't:
// Don't pass raw objects (causes re-renders)
useAtlasContextRegister({
key: 'myData',
payload: { counter, status }, // New object every render!
});
// Don't use generic keys
key: 'data'
✅ Do:
// Wrap tool invocations in try-catch
try {
const result = await invokeTool('calculateTotal', { price, taxRate });
setResult(result);
} catch (error) {
console.error('Tool invocation failed:', error);
setError(error.message);
}
✅ Do:
// Use useMemo for expensive computations
const expensiveData = useMemo(() => {
return computeExpensiveValue(input);
}, [input]);
useAtlasContextRegister({
key: 'expensiveData',
payload: expensiveData,
});
// Only re-register when necessary
useEffect(() => {
// ... registration logic
}, [dataKey, writer]); // Minimal dependencies
✅ Do:
// Define interfaces for data
interface HostState {
counter: number;
status: string;
}
const hostState = useAtlasDataConsumer<HostState>('hostLiveState');
// Type tool parameters
interface CalculateTotalParams {
price: number;
taxRate: number;
}
handler: (params: CalculateTotalParams) => {
// TypeScript knows params.price is a number
}
✅ Do:
// Tool keys: camelCase, descriptive verbs
'calculateTotal', 'updateUserProfile', 'fetchOrderDetails'
// Data keys: camelCase, source prefix
'hostUserInput', 'mfe1LiveState', 'dashboardMetrics'
✅ Do:
useAtlasHandlerRegistry('calculateShippingCost', {
name: 'Calculate Shipping Cost',
description: 'Calculates shipping cost based on weight, distance, and shipping method. Returns cost in USD.',
parameters: [
{
name: 'weight',
type: 'number',
description: 'Package weight in pounds',
required: true
},
{
name: 'distance',
type: 'number',
description: 'Shipping distance in miles',
required: true
},
{
name: 'method',
type: 'string',
description: 'Shipping method: "standard", "express", or "overnight"',
required: true
},
],
handler: ({ weight, distance, method }) => {
// Implementation
},
});
Cause: Provider not properly configured or MFE not loaded
Solution:
AtlasRegistryProvider and AtlasProvider<AtlasRegistryProvider>
<AtlasProvider>
{/* Your app */}
</AtlasProvider>
</AtlasRegistryProvider>
AtlasProvider<AtlasProvider>
{/* Your MFE code */}
</AtlasProvider>
Cause: Data reference not changing (object identity)
Solution:
// Use useMemo to ensure reference changes only when data changes
const sharedData = useMemo(
() => ({ counter, status }),
[counter, status]
);
Cause: Dependencies array causing effect to re-run continuously
Solution:
// Only include stable dependencies
useEffect(() => {
// registration logic
}, [componentKey, writer]); // Don't include objects/arrays unless memoized
Cause: Provider not running or tool handler not registered
Solution:
[Atlas Tool Registry] Registered tool: toolName// Tool Configuration
interface FrontendToolParameter {
name: string;
type: string;
description: string;
required: boolean;
}
interface FrontendToolConfig {
assistantId?: string; // Optional: Associate tool with specific assistant
name: string;
description: string;
parameters: FrontendToolParameter[];
handler: (params: any) => Promise<any> | any;
}
// Data Configuration
interface AtlasContextConfig<T = any> {
assistantId?: string; // Optional: Associate data with specific assistant
key: string;
description?: string;
payload: T;
}
Internal ServiceTitan package
FAQs
Assist utilities for host-MFE communication
The npm package @servicetitan/assist-utils receives a total of 63 weekly downloads. As such, @servicetitan/assist-utils popularity was classified as not popular.
We found that @servicetitan/assist-utils demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket is heading to London! Stop by our booth or schedule a meeting to see what we've been working on.

Security News
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.

Research
/Security News
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.