@mcp-ts/sdk 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -10
- package/dist/adapters/agui-adapter.js +0 -1
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +0 -1
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +5 -0
- package/dist/adapters/agui-middleware.d.ts +5 -0
- package/dist/adapters/agui-middleware.js +12 -23
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs +12 -23
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/client/react.d.mts +351 -3
- package/dist/client/react.d.ts +351 -3
- package/dist/client/react.js +308 -6
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +302 -7
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.js +1 -1
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +1 -1
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +18 -1
- package/dist/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +38 -2
- package/dist/shared/index.d.ts +38 -2
- package/dist/shared/index.js +19 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +18 -1
- package/dist/shared/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/agui-adapter.ts +2 -4
- package/src/adapters/agui-middleware.ts +15 -27
- package/src/client/react/agui-subscriber.ts +275 -0
- package/src/client/react/index.ts +23 -4
- package/src/client/react/use-agui-subscriber.ts +270 -0
- package/src/client/react/{use-mcp-app.ts → use-app-host.ts} +2 -2
- package/src/client/react/use-mcp-app-iframe.ts +164 -0
- package/src/client/react/{useMcp.ts → use-mcp.ts} +2 -2
- package/src/client/vue/index.ts +1 -1
- package/src/shared/index.ts +6 -1
- package/src/shared/tool-utils.ts +61 -0
- /package/src/client/vue/{useMcp.ts → use-mcp.ts} +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AG-UI Subscriber for MCP Apps
|
|
3
|
+
*
|
|
4
|
+
* Provides a standalone subscriber pattern for listening to AG-UI agent events
|
|
5
|
+
* without depending on CopilotKit components. This allows MCP apps to be rendered
|
|
6
|
+
* based on tool call events from any AG-UI agent.
|
|
7
|
+
*
|
|
8
|
+
* @requires @ag-ui/client - Peer dependency for AG-UI types
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AgentSubscriber, AbstractAgent } from '@ag-ui/client';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MCP App UI event payload emitted from middleware
|
|
15
|
+
*/
|
|
16
|
+
export interface McpAppEvent {
|
|
17
|
+
/** Tool call ID that triggered this UI */
|
|
18
|
+
toolCallId: string;
|
|
19
|
+
/** Resource URI for the MCP app */
|
|
20
|
+
resourceUri: string;
|
|
21
|
+
/** Session ID for the MCP connection */
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
/** Name of the tool that was called */
|
|
24
|
+
toolName: string;
|
|
25
|
+
/** Tool execution result (if available) */
|
|
26
|
+
result?: any;
|
|
27
|
+
/** Tool input arguments */
|
|
28
|
+
input?: Record<string, unknown>;
|
|
29
|
+
/** Tool execution status */
|
|
30
|
+
status?: 'executing' | 'inProgress' | 'complete';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Event handler for MCP app events
|
|
35
|
+
*/
|
|
36
|
+
export type McpAppEventHandler = (event: McpAppEvent) => void;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Event handler for tool call events
|
|
40
|
+
*/
|
|
41
|
+
export interface ToolCallEventData {
|
|
42
|
+
toolCallId: string;
|
|
43
|
+
toolName: string;
|
|
44
|
+
args?: Record<string, unknown>;
|
|
45
|
+
result?: any;
|
|
46
|
+
status: 'start' | 'args' | 'end' | 'result';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type ToolCallEventHandler = (event: ToolCallEventData) => void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Configuration for MCP app subscriber
|
|
53
|
+
*/
|
|
54
|
+
export interface McpAppSubscriberConfig {
|
|
55
|
+
/** Handler for MCP app UI events */
|
|
56
|
+
onMcpApp?: McpAppEventHandler;
|
|
57
|
+
/** Handler for general tool call events */
|
|
58
|
+
onToolCall?: ToolCallEventHandler;
|
|
59
|
+
/** Custom event name to listen for (default: 'mcp-apps-ui') */
|
|
60
|
+
eventName?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates an AG-UI AgentSubscriber that listens for MCP app events
|
|
65
|
+
*
|
|
66
|
+
* This subscriber can be attached to any AG-UI agent instance using agent.subscribe()
|
|
67
|
+
* and will call the provided handlers when MCP app events are detected.
|
|
68
|
+
*
|
|
69
|
+
* @param config - Configuration with event handlers
|
|
70
|
+
* @returns AgentSubscriber that can be passed to agent.subscribe()
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { createMcpAppSubscriber } from '@mcp-ts/sdk/client/react';
|
|
75
|
+
* import { HttpAgent } from '@ag-ui/client';
|
|
76
|
+
*
|
|
77
|
+
* const agent = new HttpAgent({ url: '/api/agent' });
|
|
78
|
+
*
|
|
79
|
+
* const subscriber = createMcpAppSubscriber({
|
|
80
|
+
* onMcpApp: (event) => {
|
|
81
|
+
* console.log('MCP App:', event.resourceUri);
|
|
82
|
+
* // Render MCP app UI
|
|
83
|
+
* },
|
|
84
|
+
* onToolCall: (event) => {
|
|
85
|
+
* console.log('Tool Call:', event.toolName, event.status);
|
|
86
|
+
* }
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* const { unsubscribe } = agent.subscribe(subscriber);
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function createMcpAppSubscriber(
|
|
93
|
+
config: McpAppSubscriberConfig
|
|
94
|
+
): AgentSubscriber {
|
|
95
|
+
const eventName = config.eventName || 'mcp-apps-ui';
|
|
96
|
+
|
|
97
|
+
const subscriber: AgentSubscriber = {
|
|
98
|
+
// Listen for custom MCP app events from middleware
|
|
99
|
+
onCustomEvent: ({ event }) => {
|
|
100
|
+
if (event.name === eventName && config.onMcpApp) {
|
|
101
|
+
const payload = event.value as McpAppEvent;
|
|
102
|
+
config.onMcpApp(payload);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Listen for tool call lifecycle events
|
|
107
|
+
onToolCallStartEvent: (params) => {
|
|
108
|
+
if (config.onToolCall && params.event.toolCallName) {
|
|
109
|
+
config.onToolCall({
|
|
110
|
+
toolCallId: params.event.toolCallId || '',
|
|
111
|
+
toolName: params.event.toolCallName,
|
|
112
|
+
status: 'start',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
onToolCallArgsEvent: (params) => {
|
|
118
|
+
if (config.onToolCall) {
|
|
119
|
+
// partialToolCallArgs contains the parsed args
|
|
120
|
+
const args = params.partialToolCallArgs;
|
|
121
|
+
|
|
122
|
+
config.onToolCall({
|
|
123
|
+
toolCallId: params.event.toolCallId || '',
|
|
124
|
+
toolName: params.toolCallName || '', // toolCallName is in params, not event
|
|
125
|
+
args,
|
|
126
|
+
status: 'args',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
onToolCallEndEvent: (params) => {
|
|
132
|
+
if (config.onToolCall && params.event.toolCallId) {
|
|
133
|
+
config.onToolCall({
|
|
134
|
+
toolCallId: params.event.toolCallId,
|
|
135
|
+
toolName: params.toolCallName || '', // toolCallName is in params, not event
|
|
136
|
+
status: 'end',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
onToolCallResultEvent: (params) => {
|
|
142
|
+
if (config.onToolCall && params.event.toolCallId) {
|
|
143
|
+
config.onToolCall({
|
|
144
|
+
toolCallId: params.event.toolCallId,
|
|
145
|
+
toolName: '', // Not available in result event
|
|
146
|
+
result: params.event.content, // content contains the result
|
|
147
|
+
status: 'result',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return subscriber;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Subscribes to MCP app events from an AG-UI agent
|
|
158
|
+
*
|
|
159
|
+
* Convenience function that creates a subscriber and attaches it to an agent.
|
|
160
|
+
* Returns an unsubscribe function to clean up the subscription.
|
|
161
|
+
*
|
|
162
|
+
* @param agent - AG-UI agent instance
|
|
163
|
+
* @param config - Configuration with event handlers
|
|
164
|
+
* @returns Unsubscribe function
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```typescript
|
|
168
|
+
* const unsubscribe = subscribeMcpAppEvents(agent, {
|
|
169
|
+
* onMcpApp: (event) => {
|
|
170
|
+
* renderMcpApp(event.resourceUri, event.sessionId);
|
|
171
|
+
* }
|
|
172
|
+
* });
|
|
173
|
+
*
|
|
174
|
+
* // Later, to cleanup:
|
|
175
|
+
* unsubscribe();
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export function subscribeMcpAppEvents(
|
|
179
|
+
agent: AbstractAgent,
|
|
180
|
+
config: McpAppSubscriberConfig
|
|
181
|
+
): () => void {
|
|
182
|
+
const subscriber = createMcpAppSubscriber(config);
|
|
183
|
+
const { unsubscribe } = agent.subscribe(subscriber);
|
|
184
|
+
return unsubscribe;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Manager for MCP app events with built-in state management
|
|
189
|
+
*
|
|
190
|
+
* Provides a higher-level API for managing MCP app events with automatic
|
|
191
|
+
* state tracking. Useful for React contexts or state management systems.
|
|
192
|
+
*/
|
|
193
|
+
export class McpAppEventManager {
|
|
194
|
+
private events: Map<string, McpAppEvent> = new Map();
|
|
195
|
+
private toolCalls: Map<string, ToolCallEventData> = new Map();
|
|
196
|
+
private listeners: Set<() => void> = new Set();
|
|
197
|
+
private unsubscribe?: () => void;
|
|
198
|
+
private cachedSnapshot: Record<string, McpAppEvent> = {};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Attach to an AG-UI agent
|
|
202
|
+
*/
|
|
203
|
+
attach(agent: AbstractAgent): void {
|
|
204
|
+
if (this.unsubscribe) {
|
|
205
|
+
this.unsubscribe();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.unsubscribe = subscribeMcpAppEvents(agent, {
|
|
209
|
+
onMcpApp: (event) => {
|
|
210
|
+
this.events.set(event.toolName, event);
|
|
211
|
+
this.cachedSnapshot = Object.fromEntries(this.events);
|
|
212
|
+
this.notify();
|
|
213
|
+
},
|
|
214
|
+
onToolCall: (event) => {
|
|
215
|
+
if (event.toolCallId) {
|
|
216
|
+
this.toolCalls.set(event.toolCallId, event);
|
|
217
|
+
this.notify();
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Detach from the current agent
|
|
225
|
+
*/
|
|
226
|
+
detach(): void {
|
|
227
|
+
if (this.unsubscribe) {
|
|
228
|
+
this.unsubscribe();
|
|
229
|
+
this.unsubscribe = undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get MCP app event for a specific tool
|
|
235
|
+
*/
|
|
236
|
+
getEvent(toolName: string): McpAppEvent | undefined {
|
|
237
|
+
return this.events.get(toolName);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get all MCP app events (cached for useSyncExternalStore)
|
|
242
|
+
*/
|
|
243
|
+
getAllEvents(): Record<string, McpAppEvent> {
|
|
244
|
+
return this.cachedSnapshot;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get tool call event by ID
|
|
249
|
+
*/
|
|
250
|
+
getToolCall(toolCallId: string): ToolCallEventData | undefined {
|
|
251
|
+
return this.toolCalls.get(toolCallId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Subscribe to event changes
|
|
256
|
+
*/
|
|
257
|
+
subscribe(listener: () => void): () => void {
|
|
258
|
+
this.listeners.add(listener);
|
|
259
|
+
return () => this.listeners.delete(listener);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Clear all events
|
|
264
|
+
*/
|
|
265
|
+
clear(): void {
|
|
266
|
+
this.events.clear();
|
|
267
|
+
this.toolCalls.clear();
|
|
268
|
+
this.cachedSnapshot = {};
|
|
269
|
+
this.notify();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private notify(): void {
|
|
273
|
+
this.listeners.forEach(listener => listener());
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -3,9 +3,28 @@
|
|
|
3
3
|
* React client-side exports for MCP connection management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// React
|
|
7
|
-
export { useMcp, type UseMcpOptions, type McpClient, type McpConnection } from './
|
|
8
|
-
export {
|
|
6
|
+
// React Hooks
|
|
7
|
+
export { useMcp, type UseMcpOptions, type McpClient, type McpConnection } from './use-mcp.js';
|
|
8
|
+
export { useAppHost } from './use-app-host.js';
|
|
9
|
+
export { useMcpAppIframe, type McpAppIframeProps } from './use-mcp-app-iframe.js';
|
|
10
|
+
|
|
11
|
+
// AG-UI Subscriber Pattern (Framework-agnostic)
|
|
12
|
+
export {
|
|
13
|
+
useAguiSubscriber,
|
|
14
|
+
useMcpApps,
|
|
15
|
+
useToolCallEvents,
|
|
16
|
+
} from './use-agui-subscriber.js';
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
createMcpAppSubscriber,
|
|
20
|
+
subscribeMcpAppEvents,
|
|
21
|
+
McpAppEventManager,
|
|
22
|
+
type McpAppEvent,
|
|
23
|
+
type McpAppEventHandler,
|
|
24
|
+
type ToolCallEventData,
|
|
25
|
+
type ToolCallEventHandler,
|
|
26
|
+
type McpAppSubscriberConfig,
|
|
27
|
+
} from './agui-subscriber.js';
|
|
9
28
|
|
|
10
29
|
// Re-export shared types and client from main entry
|
|
11
|
-
export * from '../index';
|
|
30
|
+
export * from '../index.js';
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Hook for AG-UI Subscriber Pattern
|
|
3
|
+
*
|
|
4
|
+
* Provides React hooks for subscribing to AG-UI agent events without
|
|
5
|
+
* depending on CopilotKit components. Works with any AG-UI agent instance.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useState, useSyncExternalStore, useMemo } from 'react';
|
|
9
|
+
import type { AbstractAgent } from '@ag-ui/client';
|
|
10
|
+
import {
|
|
11
|
+
createMcpAppSubscriber,
|
|
12
|
+
McpAppEventManager,
|
|
13
|
+
type McpAppEvent,
|
|
14
|
+
type McpAppSubscriberConfig,
|
|
15
|
+
type ToolCallEventData,
|
|
16
|
+
} from './agui-subscriber.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* React hook to subscribe to MCP app events from an AG-UI agent
|
|
20
|
+
*
|
|
21
|
+
* This hook manages the subscription lifecycle and provides event state.
|
|
22
|
+
* It's completely independent of CopilotKit and works with any AG-UI agent.
|
|
23
|
+
*
|
|
24
|
+
* @param agent - AG-UI agent instance (can be from HttpAgent, LangGraphAgent, etc.)
|
|
25
|
+
* @param config - Configuration with event handlers
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { useAguiSubscriber } from '@mcp-ts/sdk/client/react';
|
|
30
|
+
* import { HttpAgent } from '@ag-ui/client';
|
|
31
|
+
*
|
|
32
|
+
* function MyComponent() {
|
|
33
|
+
* const [agent] = useState(() => new HttpAgent({ url: '/api/agent' }));
|
|
34
|
+
*
|
|
35
|
+
* useAguiSubscriber(agent, {
|
|
36
|
+
* onMcpApp: (event) => {
|
|
37
|
+
* console.log('MCP App:', event.resourceUri);
|
|
38
|
+
* },
|
|
39
|
+
* onToolCall: (event) => {
|
|
40
|
+
* console.log('Tool:', event.toolName, event.status);
|
|
41
|
+
* }
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* // Your UI code...
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function useAguiSubscriber(
|
|
49
|
+
agent: AbstractAgent | null | undefined,
|
|
50
|
+
config: McpAppSubscriberConfig
|
|
51
|
+
): void {
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!agent) return;
|
|
54
|
+
|
|
55
|
+
const subscriber = createMcpAppSubscriber(config);
|
|
56
|
+
const { unsubscribe } = agent.subscribe(subscriber);
|
|
57
|
+
|
|
58
|
+
return () => unsubscribe();
|
|
59
|
+
}, [agent, config]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* React hook for managing MCP apps
|
|
64
|
+
*
|
|
65
|
+
* Returns MCP apps that need to be rendered. Automatically combines:
|
|
66
|
+
* 1. Tool metadata (instant, synchronous)
|
|
67
|
+
* 2. AG-UI agent events (async, after tool execution)
|
|
68
|
+
*
|
|
69
|
+
* Prioritizes tool metadata for instant loading.
|
|
70
|
+
*
|
|
71
|
+
* @param agent - AG-UI agent instance (optional)
|
|
72
|
+
* @param mcpClient - MCP client with tool metadata (optional)
|
|
73
|
+
* @returns Object with MCP apps and helper functions
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* import { useMcpApps } from '@mcp-ts/sdk/client/react';
|
|
78
|
+
*
|
|
79
|
+
* function ToolRenderer() {
|
|
80
|
+
* const { agent } = useAgent({ agentId: "myAgent" });
|
|
81
|
+
* const { mcpClient } = useMcpContext();
|
|
82
|
+
* const { apps } = useMcpApps(agent, mcpClient);
|
|
83
|
+
*
|
|
84
|
+
* return (
|
|
85
|
+
* <>
|
|
86
|
+
* {Object.entries(apps).map(([toolName, app]) => (
|
|
87
|
+
* <McpAppUI key={toolName} {...app} />
|
|
88
|
+
* ))}
|
|
89
|
+
* </>
|
|
90
|
+
* );
|
|
91
|
+
* }
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function useMcpApps(
|
|
95
|
+
agent?: AbstractAgent | null,
|
|
96
|
+
mcpClient?: { connections: Array<{ tools: any[]; sessionId: string }> } | null
|
|
97
|
+
): {
|
|
98
|
+
/** All MCP apps indexed by tool name */
|
|
99
|
+
apps: Record<string, McpAppEvent>;
|
|
100
|
+
/** Get app for a specific tool */
|
|
101
|
+
getApp: (toolName: string) => McpAppEvent | undefined;
|
|
102
|
+
/** Clear all apps */
|
|
103
|
+
clear: () => void;
|
|
104
|
+
} {
|
|
105
|
+
// Create manager instance once
|
|
106
|
+
const [manager] = useState(() => new McpAppEventManager());
|
|
107
|
+
|
|
108
|
+
// Attach/detach manager when agent changes
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!agent) {
|
|
111
|
+
manager.detach();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
manager.attach(agent);
|
|
116
|
+
return () => manager.detach();
|
|
117
|
+
}, [agent, manager]);
|
|
118
|
+
|
|
119
|
+
// Subscribe to manager state changes using useSyncExternalStore
|
|
120
|
+
const agentApps = useSyncExternalStore(
|
|
121
|
+
(callback) => manager.subscribe(callback),
|
|
122
|
+
() => manager.getAllEvents(),
|
|
123
|
+
() => ({}) // Server-side snapshot
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Combine tool metadata with agent events
|
|
127
|
+
const apps: Record<string, McpAppEvent> = useMemo(() => {
|
|
128
|
+
const combined: Record<string, McpAppEvent> = {};
|
|
129
|
+
|
|
130
|
+
// First, add tool metadata (instant, synchronous - prioritized!)
|
|
131
|
+
if (mcpClient) {
|
|
132
|
+
for (const conn of mcpClient.connections) {
|
|
133
|
+
for (const tool of conn.tools) {
|
|
134
|
+
const meta = (tool as any)._meta;
|
|
135
|
+
if (!meta?.ui) continue;
|
|
136
|
+
|
|
137
|
+
const ui = meta.ui;
|
|
138
|
+
if (typeof ui !== 'object' || !ui) continue;
|
|
139
|
+
if (ui.visibility && !ui.visibility.includes('app')) continue;
|
|
140
|
+
|
|
141
|
+
const resourceUri =
|
|
142
|
+
typeof ui.resourceUri === 'string'
|
|
143
|
+
? ui.resourceUri
|
|
144
|
+
: typeof ui.uri === 'string'
|
|
145
|
+
? ui.uri
|
|
146
|
+
: undefined;
|
|
147
|
+
|
|
148
|
+
if (resourceUri) {
|
|
149
|
+
combined[tool.name] = {
|
|
150
|
+
toolCallId: '',
|
|
151
|
+
resourceUri,
|
|
152
|
+
sessionId: conn.sessionId,
|
|
153
|
+
toolName: tool.name,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Then, merge in agent events (may have additional data like toolCallId, result)
|
|
161
|
+
for (const [toolName, event] of Object.entries(agentApps) as [string, McpAppEvent][]) {
|
|
162
|
+
if (combined[toolName]) {
|
|
163
|
+
// Merge: keep metadata's resourceUri/sessionId, add event's toolCallId/result
|
|
164
|
+
combined[toolName] = {
|
|
165
|
+
...combined[toolName],
|
|
166
|
+
toolCallId: event.toolCallId || combined[toolName].toolCallId,
|
|
167
|
+
result: event.result,
|
|
168
|
+
input: event.input,
|
|
169
|
+
status: event.status,
|
|
170
|
+
};
|
|
171
|
+
} else {
|
|
172
|
+
// No metadata, just use the event
|
|
173
|
+
combined[toolName] = event;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Return a Proxy that handles both base and prefixed tool names transparently
|
|
178
|
+
// This allows users to lookup apps with either format:
|
|
179
|
+
// - apps["get-time"] (base name)
|
|
180
|
+
// - apps["tool_abc123_get-time"] (prefixed name from CopilotKit)
|
|
181
|
+
return new Proxy(combined, {
|
|
182
|
+
get(target, prop: string | symbol) {
|
|
183
|
+
if (typeof prop !== 'string') return undefined;
|
|
184
|
+
|
|
185
|
+
// Try exact match first (base name)
|
|
186
|
+
if (prop in target) return target[prop];
|
|
187
|
+
|
|
188
|
+
// Extract base name from prefixed format: "tool_xxx_baseName" -> "baseName"
|
|
189
|
+
const match = prop.match(/^tool_[^_]+_(.+)$/);
|
|
190
|
+
if (match && match[1] in target) {
|
|
191
|
+
return target[match[1]];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return undefined;
|
|
195
|
+
},
|
|
196
|
+
// Support Object.entries, Object.keys, etc. by returning base names
|
|
197
|
+
ownKeys(target) {
|
|
198
|
+
return Reflect.ownKeys(target);
|
|
199
|
+
},
|
|
200
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
201
|
+
return Reflect.getOwnPropertyDescriptor(target, prop);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}, [agentApps, mcpClient]);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
apps,
|
|
208
|
+
// getApp handles both base and prefixed names transparently via the Proxy
|
|
209
|
+
getApp: (toolName: string) => apps[toolName] as McpAppEvent | undefined,
|
|
210
|
+
clear: () => manager.clear(),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* React hook for tracking tool call lifecycle events
|
|
216
|
+
*
|
|
217
|
+
* Provides access to detailed tool call events (start, args, end, result)
|
|
218
|
+
* for debugging or custom UI rendering.
|
|
219
|
+
*
|
|
220
|
+
* @param agent - AG-UI agent instance
|
|
221
|
+
* @returns Object with tool call events and helper functions
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* import { useToolCallEvents } from '@mcp-ts/sdk/client/react';
|
|
226
|
+
*
|
|
227
|
+
* function ToolCallDebugger() {
|
|
228
|
+
* const [agent] = useState(() => new HttpAgent({ url: '/api/agent' }));
|
|
229
|
+
* const { toolCalls } = useToolCallEvents(agent);
|
|
230
|
+
*
|
|
231
|
+
* return (
|
|
232
|
+
* <div>
|
|
233
|
+
* {Object.entries(toolCalls).map(([id, event]) => (
|
|
234
|
+
* <div key={id}>
|
|
235
|
+
* {event.toolName} - {event.status}
|
|
236
|
+
* </div>
|
|
237
|
+
* ))}
|
|
238
|
+
* </div>
|
|
239
|
+
* );
|
|
240
|
+
* }
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
export function useToolCallEvents(agent: AbstractAgent | null | undefined): {
|
|
244
|
+
/** All tool call events indexed by tool call ID */
|
|
245
|
+
toolCalls: Record<string, ToolCallEventData>;
|
|
246
|
+
} {
|
|
247
|
+
const [toolCalls, setToolCalls] = useState<Record<string, ToolCallEventData>>({});
|
|
248
|
+
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
if (!agent) {
|
|
251
|
+
setToolCalls({});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const subscriber = createMcpAppSubscriber({
|
|
256
|
+
onToolCall: (event) => {
|
|
257
|
+
setToolCalls((prev) => ({
|
|
258
|
+
...prev,
|
|
259
|
+
[event.toolCallId]: event,
|
|
260
|
+
}));
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const { unsubscribe } = agent.subscribe(subscriber);
|
|
265
|
+
|
|
266
|
+
return () => unsubscribe();
|
|
267
|
+
}, [agent]);
|
|
268
|
+
|
|
269
|
+
return { toolCalls };
|
|
270
|
+
}
|
|
@@ -15,7 +15,7 @@ import { AppHost } from '../core/app-host';
|
|
|
15
15
|
* @param options - Optional configuration
|
|
16
16
|
* @returns Object containing the AppHost instance (or null) and error state
|
|
17
17
|
*/
|
|
18
|
-
export function
|
|
18
|
+
export function useAppHost(
|
|
19
19
|
client: SSEClient,
|
|
20
20
|
iframeRef: React.RefObject<HTMLIFrameElement>,
|
|
21
21
|
options?: {
|
|
@@ -56,7 +56,7 @@ export function useMcpApp(
|
|
|
56
56
|
// Start bridge connection (this is fast, just sets up PostMessage)
|
|
57
57
|
await appHost.start();
|
|
58
58
|
} catch (err) {
|
|
59
|
-
console.error('[
|
|
59
|
+
console.error('[useAppHost] Failed to initialize AppHost:', err);
|
|
60
60
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
61
61
|
}
|
|
62
62
|
};
|