@mcp-ts/sdk 1.3.7 → 1.3.9

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.
Files changed (61) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +398 -404
  3. package/dist/adapters/agui-middleware.js.map +1 -1
  4. package/dist/adapters/agui-middleware.mjs.map +1 -1
  5. package/dist/bin/mcp-ts.js +0 -0
  6. package/dist/bin/mcp-ts.js.map +1 -1
  7. package/dist/bin/mcp-ts.mjs +0 -0
  8. package/dist/bin/mcp-ts.mjs.map +1 -1
  9. package/dist/client/index.js.map +1 -1
  10. package/dist/client/index.mjs.map +1 -1
  11. package/dist/client/react.d.mts +2 -2
  12. package/dist/client/react.d.ts +2 -2
  13. package/dist/client/react.js +25 -2
  14. package/dist/client/react.js.map +1 -1
  15. package/dist/client/react.mjs +26 -3
  16. package/dist/client/react.mjs.map +1 -1
  17. package/dist/client/vue.js.map +1 -1
  18. package/dist/client/vue.mjs.map +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/index.mjs.map +1 -1
  23. package/dist/shared/index.js.map +1 -1
  24. package/dist/shared/index.mjs.map +1 -1
  25. package/package.json +185 -185
  26. package/src/adapters/agui-middleware.ts +382 -382
  27. package/src/bin/mcp-ts.ts +102 -102
  28. package/src/client/core/app-host.ts +417 -417
  29. package/src/client/core/sse-client.ts +371 -371
  30. package/src/client/core/types.ts +31 -31
  31. package/src/client/index.ts +27 -27
  32. package/src/client/react/index.ts +16 -16
  33. package/src/client/react/use-app-host.ts +73 -73
  34. package/src/client/react/use-mcp-apps.tsx +247 -214
  35. package/src/client/react/use-mcp.ts +641 -641
  36. package/src/client/vue/index.ts +10 -10
  37. package/src/client/vue/use-mcp.ts +617 -617
  38. package/src/index.ts +11 -11
  39. package/src/server/handlers/nextjs-handler.ts +204 -204
  40. package/src/server/handlers/sse-handler.ts +631 -631
  41. package/src/server/index.ts +57 -57
  42. package/src/server/mcp/multi-session-client.ts +228 -228
  43. package/src/server/mcp/oauth-client.ts +1188 -1188
  44. package/src/server/mcp/storage-oauth-provider.ts +272 -272
  45. package/src/server/storage/file-backend.ts +157 -157
  46. package/src/server/storage/index.ts +176 -176
  47. package/src/server/storage/memory-backend.ts +123 -123
  48. package/src/server/storage/redis-backend.ts +276 -276
  49. package/src/server/storage/redis.ts +160 -160
  50. package/src/server/storage/sqlite-backend.ts +182 -182
  51. package/src/server/storage/supabase-backend.ts +228 -228
  52. package/src/server/storage/types.ts +116 -116
  53. package/src/shared/constants.ts +29 -29
  54. package/src/shared/errors.ts +133 -133
  55. package/src/shared/event-routing.ts +28 -28
  56. package/src/shared/events.ts +180 -180
  57. package/src/shared/index.ts +75 -75
  58. package/src/shared/tool-utils.ts +61 -61
  59. package/src/shared/types.ts +282 -282
  60. package/src/shared/utils.ts +38 -38
  61. package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
@@ -1,214 +1,247 @@
1
- /**
2
- * MCP Apps Hook
3
- *
4
- * Provides utilities for rendering interactive UI components from MCP servers.
5
- */
6
-
7
- import React, { useState, useEffect, useCallback, useRef, memo } from 'react';
8
- import { useAppHost } from './use-app-host.js';
9
- import type { SSEClient } from '../core/sse-client.js';
10
-
11
- export interface McpClient {
12
- connections: Array<{
13
- sessionId: string;
14
- tools: Array<{
15
- name: string;
16
- mcpApp?: {
17
- resourceUri: string;
18
- };
19
- _meta?: {
20
- ui?: {
21
- resourceUri?: string;
22
- };
23
- 'ui/resourceUri'?: string;
24
- };
25
- }>;
26
- }>;
27
- sseClient?: SSEClient | null;
28
- }
29
-
30
- export interface McpAppMetadata {
31
- toolName: string;
32
- resourceUri: string;
33
- sessionId: string;
34
- }
35
-
36
- interface McpAppRendererProps {
37
- metadata: McpAppMetadata;
38
- input?: Record<string, unknown>;
39
- result?: unknown;
40
- status: 'executing' | 'inProgress' | 'complete' | 'idle';
41
- sseClient?: SSEClient | null;
42
- /** Custom CSS class for the container */
43
- className?: string;
44
- }
45
-
46
- /**
47
- * Internal component that renders the MCP app in a sandboxed iframe
48
- */
49
- const McpAppRenderer = memo(function McpAppRenderer({
50
- metadata,
51
- input,
52
- result,
53
- status,
54
- sseClient,
55
- className,
56
- }: McpAppRendererProps) {
57
- const iframeRef = useRef<HTMLIFrameElement>(null);
58
- const { host, error: hostError } = useAppHost(sseClient as SSEClient, iframeRef);
59
- const [isLaunched, setIsLaunched] = useState(false);
60
- const [error, setError] = useState<Error | null>(null);
61
-
62
- // Track which data has been sent to prevent duplicates
63
- const sentInputRef = useRef(false);
64
- const sentResultRef = useRef(false);
65
- const lastInputRef = useRef(input);
66
- const lastResultRef = useRef(result);
67
- const lastStatusRef = useRef(status);
68
-
69
- // Launch the app when host is ready
70
- useEffect(() => {
71
- if (!host || !metadata.resourceUri || !metadata.sessionId) return;
72
-
73
- host
74
- .launch(metadata.resourceUri, metadata.sessionId)
75
- .then(() => setIsLaunched(true))
76
- .catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
77
- }, [host, metadata.resourceUri, metadata.sessionId]);
78
-
79
- // Send tool input when available or when it changes
80
- useEffect(() => {
81
- if (!host || !isLaunched || !input) return;
82
-
83
- // Send if never sent, or if input changed
84
- if (!sentInputRef.current || JSON.stringify(input) !== JSON.stringify(lastInputRef.current)) {
85
- sentInputRef.current = true;
86
- lastInputRef.current = input;
87
- host.sendToolInput(input);
88
- }
89
- }, [host, isLaunched, input]);
90
-
91
- // Send tool result when complete or when it changes
92
- useEffect(() => {
93
- if (!host || !isLaunched || result === undefined) return;
94
- if (status !== 'complete') return;
95
-
96
- // Send if never sent, or if result changed
97
- if (!sentResultRef.current || JSON.stringify(result) !== JSON.stringify(lastResultRef.current)) {
98
- sentResultRef.current = true;
99
- lastResultRef.current = result;
100
- const formattedResult =
101
- typeof result === 'string'
102
- ? { content: [{ type: 'text', text: result }] }
103
- : result;
104
- host.sendToolResult(formattedResult);
105
- }
106
- }, [host, isLaunched, result, status]);
107
-
108
- // Reset sent flags when tool status resets to executing (new tool call)
109
- useEffect(() => {
110
- if (status === 'executing' && lastStatusRef.current !== 'executing') {
111
- sentInputRef.current = false;
112
- sentResultRef.current = false;
113
- }
114
- lastStatusRef.current = status;
115
- }, [status]);
116
-
117
- // Display errors
118
- const displayError = error || hostError;
119
- if (displayError) {
120
- return (
121
- <div className={`p-4 bg-red-900/20 border border-red-700 rounded text-red-200 ${className || ''}`}>
122
- Error: {displayError.message || String(displayError)}
123
- </div>
124
- );
125
- }
126
-
127
- return (
128
- <div className={`w-full border border-gray-700 rounded overflow-hidden bg-white min-h-96 my-2 relative ${className || ''}`}>
129
- <iframe
130
- ref={iframeRef}
131
- sandbox="allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads"
132
- className="w-full h-full min-h-96"
133
- style={{ height: 'auto' }}
134
- title="MCP App"
135
- />
136
- {!isLaunched && (
137
- <div className="absolute inset-0 bg-gray-900/50 flex items-center justify-center pointer-events-none">
138
- <div className="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
139
- </div>
140
- )}
141
- </div>
142
- );
143
- });
144
-
145
- /**
146
- * Simple hook to get MCP app metadata
147
- *
148
- * @param mcpClient - The MCP client from useMcp() or context
149
- * @returns Object with getAppMetadata function and McpAppRenderer component
150
- *
151
- * @example
152
- * ```tsx
153
- * function ToolRenderer(props) {
154
- * const { getAppMetadata, McpAppRenderer } = useMcpApps(mcpClient);
155
- * const metadata = getAppMetadata(props.name);
156
- *
157
- * if (!metadata) return null;
158
- * return (
159
- * <McpAppRenderer
160
- * metadata={metadata}
161
- * input={props.args}
162
- * result={props.result}
163
- * status={props.status}
164
- * />
165
- * );
166
- * }
167
- * ```
168
- */
169
- export function useMcpApps(mcpClient: McpClient | null) {
170
- /**
171
- * Get MCP app metadata for a tool name
172
- * This is fast and can be called on every render
173
- */
174
- const getAppMetadata = useCallback(
175
- (toolName: string): McpAppMetadata | undefined => {
176
- if (!mcpClient) return undefined;
177
-
178
- const extractedName = extractToolName(toolName);
179
-
180
- for (const conn of mcpClient.connections) {
181
- for (const tool of conn.tools) {
182
- const candidateName = extractToolName(tool.name);
183
- // Check both locations: direct mcpApp or _meta.ui
184
- const resourceUri =
185
- tool.mcpApp?.resourceUri ??
186
- tool._meta?.ui?.resourceUri ??
187
- tool._meta?.['ui/resourceUri'];
188
-
189
- if (resourceUri && candidateName === extractedName) {
190
- return {
191
- toolName: candidateName,
192
- resourceUri,
193
- sessionId: conn.sessionId,
194
- };
195
- }
196
- }
197
- }
198
-
199
- return undefined;
200
- },
201
- [mcpClient]
202
- );
203
-
204
- return { getAppMetadata, McpAppRenderer };
205
- }
206
-
207
- /**
208
- * Extract the base tool name, removing any prefixes
209
- */
210
- function extractToolName(fullName: string): string {
211
- // Handle patterns like "tool_abc123_get-time" -> "get-time"
212
- const match = fullName.match(/(?:tool_[^_]+_)?(.+)$/);
213
- return match?.[1] || fullName;
214
- }
1
+ /**
2
+ * MCP Apps Hook
3
+ *
4
+ * Provides utilities for rendering interactive UI components from MCP servers.
5
+ */
6
+
7
+ import React, { useState, useEffect, useCallback, useRef, memo } from 'react';
8
+ import { useAppHost } from './use-app-host.js';
9
+ import type { SSEClient } from '../core/sse-client.js';
10
+
11
+ export interface McpClient {
12
+ connections: Array<{
13
+ sessionId: string;
14
+ tools: Array<{
15
+ name: string;
16
+ mcpApp?: {
17
+ resourceUri: string;
18
+ };
19
+ _meta?: {
20
+ ui?: {
21
+ resourceUri?: string;
22
+ };
23
+ 'ui/resourceUri'?: string;
24
+ };
25
+ }>;
26
+ }>;
27
+ sseClient?: SSEClient | null;
28
+ }
29
+
30
+ export interface McpAppMetadata {
31
+ toolName: string;
32
+ resourceUri: string;
33
+ sessionId: string;
34
+ }
35
+
36
+ interface McpAppRendererProps {
37
+ mcpClient: McpClient | null;
38
+ name: string;
39
+ input?: Record<string, unknown>;
40
+ result?: unknown;
41
+ status: 'executing' | 'inProgress' | 'complete' | 'idle';
42
+ /** Custom CSS class for the container */
43
+ className?: string;
44
+ }
45
+
46
+ /**
47
+ * Simplified MCP App renderer - users just pass tool name and data
48
+ * Internal hook handles metadata lookup and SSE client retrieval
49
+ */
50
+ const McpAppRenderer = memo(function McpAppRenderer({
51
+ mcpClient,
52
+ name,
53
+ input,
54
+ result,
55
+ status,
56
+ className,
57
+ }: McpAppRendererProps) {
58
+ const getAppMetadata = useCallback((): McpAppMetadata | undefined => {
59
+ if (!mcpClient) return undefined;
60
+
61
+ const extractedName = extractToolName(name);
62
+
63
+ for (const conn of mcpClient.connections) {
64
+ for (const tool of conn.tools) {
65
+ const candidateName = extractToolName(tool.name);
66
+ const resourceUri =
67
+ tool.mcpApp?.resourceUri ??
68
+ tool._meta?.ui?.resourceUri ??
69
+ tool._meta?.['ui/resourceUri'];
70
+
71
+ if (resourceUri && candidateName === extractedName) {
72
+ return {
73
+ toolName: candidateName,
74
+ resourceUri,
75
+ sessionId: conn.sessionId,
76
+ };
77
+ }
78
+ }
79
+ }
80
+
81
+ return undefined;
82
+ }, [mcpClient, name]);
83
+
84
+ const metadata = getAppMetadata();
85
+ const sseClient = mcpClient?.sseClient ?? null;
86
+
87
+ if (!metadata || !sseClient) {
88
+ return null;
89
+ }
90
+ const iframeRef = useRef<HTMLIFrameElement>(null);
91
+ const { host, error: hostError } = useAppHost(sseClient as SSEClient, iframeRef);
92
+ const [isLaunched, setIsLaunched] = useState(false);
93
+ const [error, setError] = useState<Error | null>(null);
94
+
95
+ // Track which data has been sent to prevent duplicates
96
+ const sentInputRef = useRef(false);
97
+ const sentResultRef = useRef(false);
98
+ const lastInputRef = useRef(input);
99
+ const lastResultRef = useRef(result);
100
+ const lastStatusRef = useRef(status);
101
+
102
+ // Launch the app when host is ready
103
+ useEffect(() => {
104
+ if (!host || !metadata.resourceUri || !metadata.sessionId) return;
105
+
106
+ host
107
+ .launch(metadata.resourceUri, metadata.sessionId)
108
+ .then(() => setIsLaunched(true))
109
+ .catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
110
+ }, [host, metadata.resourceUri, metadata.sessionId]);
111
+
112
+ // Send tool input when available or when it changes
113
+ useEffect(() => {
114
+ if (!host || !isLaunched || !input) return;
115
+
116
+ // Send if never sent, or if input changed
117
+ if (!sentInputRef.current || JSON.stringify(input) !== JSON.stringify(lastInputRef.current)) {
118
+ sentInputRef.current = true;
119
+ lastInputRef.current = input;
120
+ host.sendToolInput(input);
121
+ }
122
+ }, [host, isLaunched, input]);
123
+
124
+ // Send tool result when complete or when it changes
125
+ useEffect(() => {
126
+ if (!host || !isLaunched || result === undefined) return;
127
+ if (status !== 'complete') return;
128
+
129
+ // Send if never sent, or if result changed
130
+ if (!sentResultRef.current || JSON.stringify(result) !== JSON.stringify(lastResultRef.current)) {
131
+ sentResultRef.current = true;
132
+ lastResultRef.current = result;
133
+ const formattedResult =
134
+ typeof result === 'string'
135
+ ? { content: [{ type: 'text', text: result }] }
136
+ : result;
137
+ host.sendToolResult(formattedResult);
138
+ }
139
+ }, [host, isLaunched, result, status]);
140
+
141
+ // Reset sent flags when tool status resets to executing (new tool call)
142
+ useEffect(() => {
143
+ if (status === 'executing' && lastStatusRef.current !== 'executing') {
144
+ sentInputRef.current = false;
145
+ sentResultRef.current = false;
146
+ }
147
+ lastStatusRef.current = status;
148
+ }, [status]);
149
+
150
+ // Display errors
151
+ const displayError = error || hostError;
152
+ if (displayError) {
153
+ return (
154
+ <div className={`p-4 bg-red-900/20 border border-red-700 rounded text-red-200 ${className || ''}`}>
155
+ Error: {displayError.message || String(displayError)}
156
+ </div>
157
+ );
158
+ }
159
+
160
+ return (
161
+ <div className={`w-full border border-gray-700 rounded overflow-hidden bg-white min-h-96 my-2 relative ${className || ''}`}>
162
+ <iframe
163
+ ref={iframeRef}
164
+ sandbox="allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads"
165
+ className="w-full h-full min-h-96"
166
+ style={{ height: 'auto' }}
167
+ title="MCP App"
168
+ />
169
+ {!isLaunched && (
170
+ <div className="absolute inset-0 bg-gray-900/50 flex items-center justify-center pointer-events-none">
171
+ <div className="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
172
+ </div>
173
+ )}
174
+ </div>
175
+ );
176
+ });
177
+
178
+ /**
179
+ * Simple hook to get MCP app metadata
180
+ *
181
+ * @param mcpClient - The MCP client from useMcp() or context
182
+ * @returns Object with getAppMetadata function and McpAppRenderer component
183
+ *
184
+ * @example
185
+ * ```tsx
186
+ * function ToolRenderer(props) {
187
+ * const { getAppMetadata, McpAppRenderer } = useMcpApps(mcpClient);
188
+ * const metadata = getAppMetadata(props.name);
189
+ *
190
+ * if (!metadata) return null;
191
+ * return (
192
+ * <McpAppRenderer
193
+ * metadata={metadata}
194
+ * input={props.args}
195
+ * result={props.result}
196
+ * status={props.status}
197
+ * />
198
+ * );
199
+ * }
200
+ * ```
201
+ */
202
+ export function useMcpApps(mcpClient: McpClient | null) {
203
+ /**
204
+ * Get MCP app metadata for a tool name
205
+ * This is fast and can be called on every render
206
+ */
207
+ const getAppMetadata = useCallback(
208
+ (toolName: string): McpAppMetadata | undefined => {
209
+ if (!mcpClient) return undefined;
210
+
211
+ const extractedName = extractToolName(toolName);
212
+
213
+ for (const conn of mcpClient.connections) {
214
+ for (const tool of conn.tools) {
215
+ const candidateName = extractToolName(tool.name);
216
+ // Check both locations: direct mcpApp or _meta.ui
217
+ const resourceUri =
218
+ tool.mcpApp?.resourceUri ??
219
+ tool._meta?.ui?.resourceUri ??
220
+ tool._meta?.['ui/resourceUri'];
221
+
222
+ if (resourceUri && candidateName === extractedName) {
223
+ return {
224
+ toolName: candidateName,
225
+ resourceUri,
226
+ sessionId: conn.sessionId,
227
+ };
228
+ }
229
+ }
230
+ }
231
+
232
+ return undefined;
233
+ },
234
+ [mcpClient]
235
+ );
236
+
237
+ return { getAppMetadata, McpAppRenderer };
238
+ }
239
+
240
+ /**
241
+ * Extract the base tool name, removing any prefixes
242
+ */
243
+ function extractToolName(fullName: string): string {
244
+ // Handle patterns like "tool_abc123_get-time" -> "get-time"
245
+ const match = fullName.match(/(?:tool_[^_]+_)?(.+)$/);
246
+ return match?.[1] || fullName;
247
+ }