@mcp-ts/sdk 1.3.7 → 1.3.10

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 (66) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +397 -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 +10 -28
  12. package/dist/client/react.d.ts +10 -28
  13. package/dist/client/react.js +101 -52
  14. package/dist/client/react.js.map +1 -1
  15. package/dist/client/react.mjs +102 -53
  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 +78 -6
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.mjs +73 -6
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/server/index.js +78 -6
  24. package/dist/server/index.js.map +1 -1
  25. package/dist/server/index.mjs +73 -6
  26. package/dist/server/index.mjs.map +1 -1
  27. package/dist/shared/index.js.map +1 -1
  28. package/dist/shared/index.mjs.map +1 -1
  29. package/package.json +185 -185
  30. package/src/adapters/agui-middleware.ts +382 -382
  31. package/src/bin/mcp-ts.ts +102 -102
  32. package/src/client/core/app-host.ts +417 -417
  33. package/src/client/core/sse-client.ts +371 -371
  34. package/src/client/core/types.ts +31 -31
  35. package/src/client/index.ts +27 -27
  36. package/src/client/react/index.ts +20 -16
  37. package/src/client/react/use-app-host.ts +74 -73
  38. package/src/client/react/use-mcp-apps.tsx +224 -214
  39. package/src/client/react/use-mcp.ts +669 -641
  40. package/src/client/vue/index.ts +10 -10
  41. package/src/client/vue/use-mcp.ts +617 -617
  42. package/src/index.ts +11 -11
  43. package/src/server/handlers/nextjs-handler.ts +204 -204
  44. package/src/server/handlers/sse-handler.ts +631 -631
  45. package/src/server/index.ts +57 -57
  46. package/src/server/mcp/multi-session-client.ts +228 -228
  47. package/src/server/mcp/oauth-client.ts +1188 -1188
  48. package/src/server/mcp/storage-oauth-provider.ts +272 -272
  49. package/src/server/storage/crypto.ts +92 -0
  50. package/src/server/storage/file-backend.ts +157 -157
  51. package/src/server/storage/index.ts +176 -176
  52. package/src/server/storage/memory-backend.ts +123 -123
  53. package/src/server/storage/redis-backend.ts +276 -276
  54. package/src/server/storage/redis.ts +160 -160
  55. package/src/server/storage/sqlite-backend.ts +182 -182
  56. package/src/server/storage/supabase-backend.ts +229 -228
  57. package/src/server/storage/types.ts +116 -116
  58. package/src/shared/constants.ts +29 -29
  59. package/src/shared/errors.ts +133 -133
  60. package/src/shared/event-routing.ts +28 -28
  61. package/src/shared/events.ts +180 -180
  62. package/src/shared/index.ts +75 -75
  63. package/src/shared/tool-utils.ts +61 -61
  64. package/src/shared/types.ts +282 -282
  65. package/src/shared/utils.ts +38 -38
  66. package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
@@ -1,214 +1,224 @@
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, {
8
+ useState,
9
+ useEffect,
10
+ useCallback,
11
+ useRef,
12
+ memo,
13
+ useMemo,
14
+ type MutableRefObject,
15
+ } from 'react';
16
+ import { useAppHost } from './use-app-host.js';
17
+ import type { SSEClient } from '../core/sse-client.js';
18
+
19
+ export interface McpClient {
20
+ connections: Array<{
21
+ sessionId: string;
22
+ tools: Array<{
23
+ name: string;
24
+ mcpApp?: {
25
+ resourceUri: string;
26
+ };
27
+ _meta?: {
28
+ ui?: {
29
+ resourceUri?: string;
30
+ };
31
+ 'ui/resourceUri'?: string;
32
+ };
33
+ }>;
34
+ }>;
35
+ sseClient?: SSEClient | null;
36
+ }
37
+
38
+ export interface McpAppMetadata {
39
+ toolName: string;
40
+ resourceUri: string;
41
+ sessionId: string;
42
+ }
43
+
44
+ /** Props for {@link useMcpApps}'s `McpAppRenderer` (client is supplied via the hook). */
45
+ export interface McpAppRendererProps {
46
+ name: string;
47
+ input?: Record<string, unknown>;
48
+ result?: unknown;
49
+ status: 'executing' | 'inProgress' | 'complete' | 'idle';
50
+ /** Custom CSS class for the container */
51
+ className?: string;
52
+ }
53
+
54
+ type McpAppViewProps = McpAppRendererProps & {
55
+ /**
56
+ * Ref avoids tying `McpAppRenderer` identity to `mcpClient`: when `connections` updates, `useMcp()` still
57
+ * returns a new object (correct for `useEffect` deps), but the iframe must not remount.
58
+ */
59
+ clientRef: MutableRefObject<McpClient | null>;
60
+ };
61
+
62
+ /** Renders one MCP App in a sandboxed iframe; reads the latest client from `clientRef` each render. */
63
+ const McpAppView = memo(function McpAppView({
64
+ clientRef,
65
+ name,
66
+ input,
67
+ result,
68
+ status,
69
+ className,
70
+ }: McpAppViewProps) {
71
+ const mcpClient = clientRef.current;
72
+ const metadata = getMcpAppMetadata(mcpClient, name);
73
+ const sseClient = mcpClient?.sseClient ?? null;
74
+ const resourceUri = metadata?.resourceUri;
75
+ const appSessionId = metadata?.sessionId;
76
+
77
+ const iframeRef = useRef<HTMLIFrameElement>(null);
78
+ const { host, error: hostError } = useAppHost(sseClient as SSEClient, iframeRef);
79
+ const [isLaunched, setIsLaunched] = useState(false);
80
+ const [error, setError] = useState<Error | null>(null);
81
+
82
+ const sentInputRef = useRef(false);
83
+ const sentResultRef = useRef(false);
84
+ const lastInputRef = useRef(input);
85
+ const lastResultRef = useRef(result);
86
+ const lastStatusRef = useRef(status);
87
+
88
+ useEffect(() => {
89
+ setIsLaunched(false);
90
+ setError(null);
91
+ }, [resourceUri, appSessionId]);
92
+
93
+ useEffect(() => {
94
+ if (!host || !resourceUri || !appSessionId) return;
95
+
96
+ host
97
+ .launch(resourceUri, appSessionId)
98
+ .then(() => setIsLaunched(true))
99
+ .catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
100
+ }, [host, resourceUri, appSessionId]);
101
+
102
+ useEffect(() => {
103
+ if (!host || !isLaunched || !resourceUri || !appSessionId || !input) return;
104
+
105
+ if (!sentInputRef.current || JSON.stringify(input) !== JSON.stringify(lastInputRef.current)) {
106
+ sentInputRef.current = true;
107
+ lastInputRef.current = input;
108
+ host.sendToolInput(input);
109
+ }
110
+ }, [host, isLaunched, input, resourceUri, appSessionId, name]);
111
+
112
+ useEffect(() => {
113
+ if (!host || !isLaunched || !resourceUri || !appSessionId || result === undefined) return;
114
+ if (status !== 'complete') return;
115
+
116
+ if (!sentResultRef.current || JSON.stringify(result) !== JSON.stringify(lastResultRef.current)) {
117
+ sentResultRef.current = true;
118
+ lastResultRef.current = result;
119
+ const formattedResult =
120
+ typeof result === 'string'
121
+ ? { content: [{ type: 'text', text: result }] }
122
+ : result;
123
+ host.sendToolResult(formattedResult);
124
+ }
125
+ }, [host, isLaunched, result, status, resourceUri, appSessionId, name]);
126
+
127
+ useEffect(() => {
128
+ if (status === 'executing' && lastStatusRef.current !== 'executing') {
129
+ sentInputRef.current = false;
130
+ sentResultRef.current = false;
131
+ }
132
+ lastStatusRef.current = status;
133
+ }, [status]);
134
+
135
+ if (!metadata || !sseClient) {
136
+ return null;
137
+ }
138
+
139
+ const displayError = error || hostError;
140
+ if (displayError) {
141
+ return (
142
+ <div className={`p-4 bg-red-900/20 border border-red-700 rounded text-red-200 ${className || ''}`}>
143
+ Error: {displayError.message || String(displayError)}
144
+ </div>
145
+ );
146
+ }
147
+
148
+ return (
149
+ <div className={`w-full border border-gray-700 rounded overflow-hidden bg-white min-h-96 my-2 relative ${className || ''}`}>
150
+ <iframe
151
+ ref={iframeRef}
152
+ sandbox="allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads"
153
+ className="w-full h-full min-h-96"
154
+ style={{ height: 'auto' }}
155
+ title="MCP App"
156
+ />
157
+ {!isLaunched && (
158
+ <div className="absolute inset-0 bg-gray-900/50 flex items-center justify-center pointer-events-none">
159
+ <div className="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
160
+ </div>
161
+ )}
162
+ </div>
163
+ );
164
+ });
165
+
166
+ /**
167
+ * Helpers scoped to one `mcpClient`. Pass the client here once; `McpAppRenderer` only needs per-tool props (`name`, `input`, `result`, `status`).
168
+ *
169
+ * @param mcpClient - From `useMcp()` or context (for example `useMcpContext()`).
170
+ */
171
+ export function useMcpApps(mcpClient: McpClient | null) {
172
+ // Stable `McpAppRenderer` type: parent re-renders and `connections` updates must not remount the iframe.
173
+ const clientRef = useRef(mcpClient);
174
+ clientRef.current = mcpClient;
175
+
176
+ const getAppMetadata = useCallback(
177
+ (toolName: string) => getMcpAppMetadata(clientRef.current, toolName),
178
+ []
179
+ );
180
+
181
+ const McpAppRenderer = useMemo(() => {
182
+ const Renderer = memo(function McpAppRenderer(props: McpAppRendererProps) {
183
+ return <McpAppView clientRef={clientRef} {...props} />;
184
+ });
185
+ Renderer.displayName = 'McpAppRenderer';
186
+ return Renderer;
187
+ }, []);
188
+
189
+ return { getAppMetadata, McpAppRenderer };
190
+ }
191
+
192
+ function extractToolName(fullName: string): string {
193
+ const match = fullName.match(/(?:tool_[^_]+_)?(.+)$/);
194
+ return match?.[1] || fullName;
195
+ }
196
+
197
+ function getMcpAppMetadata(
198
+ mcpClient: McpClient | null,
199
+ toolName: string
200
+ ): McpAppMetadata | undefined {
201
+ if (!mcpClient) return undefined;
202
+
203
+ const extractedName = extractToolName(toolName);
204
+
205
+ for (const conn of mcpClient.connections) {
206
+ for (const tool of conn.tools) {
207
+ const candidateName = extractToolName(tool.name);
208
+ const resourceUri =
209
+ tool.mcpApp?.resourceUri ??
210
+ tool._meta?.ui?.resourceUri ??
211
+ tool._meta?.['ui/resourceUri'];
212
+
213
+ if (resourceUri && candidateName === extractedName) {
214
+ return {
215
+ toolName: candidateName,
216
+ resourceUri,
217
+ sessionId: conn.sessionId,
218
+ };
219
+ }
220
+ }
221
+ }
222
+
223
+ return undefined;
224
+ }