@mcp-ts/sdk 1.0.1 → 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 +42 -19
- package/dist/adapters/agui-adapter.d.mts +6 -6
- package/dist/adapters/agui-adapter.d.ts +6 -6
- package/dist/adapters/agui-adapter.js +50 -25
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +50 -25
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +23 -8
- package/dist/adapters/agui-middleware.d.ts +23 -8
- package/dist/adapters/agui-middleware.js +71 -43
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs +71 -44
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +2 -2
- package/dist/adapters/ai-adapter.d.ts +2 -2
- package/dist/adapters/langchain-adapter.d.mts +2 -2
- package/dist/adapters/langchain-adapter.d.ts +2 -2
- package/dist/adapters/mastra-adapter.d.mts +2 -2
- package/dist/adapters/mastra-adapter.d.ts +2 -2
- package/dist/client/index.d.mts +182 -55
- package/dist/client/index.d.ts +182 -55
- package/dist/client/index.js +535 -130
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +535 -131
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +386 -4
- package/dist/client/react.d.ts +386 -4
- package/dist/client/react.js +890 -143
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +883 -145
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +3 -3
- package/dist/client/vue.d.ts +3 -3
- package/dist/client/vue.js +546 -141
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +546 -142
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{events-BP6WyRNh.d.mts → events-BgeztGYZ.d.mts} +12 -1
- package/dist/{events-BP6WyRNh.d.ts → events-BgeztGYZ.d.ts} +12 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +797 -248
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +791 -245
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-DMF3ED2O.d.mts → multi-session-client-CxogNckF.d.mts} +1 -1
- package/dist/{multi-session-client-BOFgPypS.d.ts → multi-session-client-cox_WXUj.d.ts} +1 -1
- package/dist/server/index.d.mts +41 -37
- package/dist/server/index.d.ts +41 -37
- package/dist/server/index.js +241 -116
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +237 -112
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +39 -3
- package/dist/shared/index.d.ts +39 -3
- 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 +9 -2
- package/src/adapters/agui-adapter.ts +58 -35
- package/src/adapters/agui-middleware.ts +83 -45
- package/src/client/core/app-host.ts +417 -0
- package/src/client/core/sse-client.ts +365 -212
- package/src/client/core/types.ts +31 -0
- package/src/client/index.ts +1 -0
- package/src/client/react/agui-subscriber.ts +275 -0
- package/src/client/react/index.ts +23 -3
- package/src/client/react/use-agui-subscriber.ts +270 -0
- package/src/client/react/use-app-host.ts +73 -0
- package/src/client/react/use-mcp-app-iframe.ts +164 -0
- package/src/client/react/{useMcp.ts → use-mcp.ts} +18 -0
- package/src/client/vue/index.ts +1 -1
- package/src/server/handlers/nextjs-handler.ts +8 -7
- package/src/server/handlers/sse-handler.ts +129 -165
- package/src/server/mcp/oauth-client.ts +32 -2
- package/src/server/storage/index.ts +17 -1
- package/src/server/storage/sqlite-backend.ts +185 -0
- package/src/shared/events.ts +12 -0
- package/src/shared/index.ts +6 -1
- package/src/shared/tool-utils.ts +61 -0
- package/src/shared/types.ts +3 -1
- /package/src/client/vue/{useMcp.ts → use-mcp.ts} +0 -0
package/dist/client/index.js
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var nanoid = require('nanoid');
|
|
4
|
+
var appBridge = require('@modelcontextprotocol/ext-apps/app-bridge');
|
|
4
5
|
|
|
5
6
|
var __defProp = Object.defineProperty;
|
|
6
7
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
8
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
9
|
+
var DEFAULT_REQUEST_TIMEOUT = 6e4;
|
|
10
|
+
var MAX_RECONNECT_ATTEMPTS = 5;
|
|
11
|
+
var BASE_RECONNECT_DELAY = 1e3;
|
|
8
12
|
var SSEClient = class {
|
|
9
13
|
constructor(options) {
|
|
10
14
|
this.options = options;
|
|
11
15
|
__publicField(this, "eventSource", null);
|
|
12
16
|
__publicField(this, "pendingRequests", /* @__PURE__ */ new Map());
|
|
17
|
+
__publicField(this, "resourceCache", /* @__PURE__ */ new Map());
|
|
13
18
|
__publicField(this, "reconnectAttempts", 0);
|
|
14
|
-
__publicField(this, "maxReconnectAttempts", 5);
|
|
15
|
-
__publicField(this, "reconnectDelay", 1e3);
|
|
16
19
|
__publicField(this, "isManuallyDisconnected", false);
|
|
17
20
|
__publicField(this, "connectionPromise", null);
|
|
18
21
|
__publicField(this, "connectionResolver", null);
|
|
19
22
|
}
|
|
23
|
+
// ============================================
|
|
24
|
+
// Connection Management
|
|
25
|
+
// ============================================
|
|
20
26
|
/**
|
|
21
|
-
* Connect to SSE endpoint
|
|
27
|
+
* Connect to the SSE endpoint
|
|
22
28
|
*/
|
|
23
29
|
connect() {
|
|
24
30
|
if (this.eventSource) {
|
|
@@ -29,52 +35,12 @@ var SSEClient = class {
|
|
|
29
35
|
this.connectionPromise = new Promise((resolve) => {
|
|
30
36
|
this.connectionResolver = resolve;
|
|
31
37
|
});
|
|
32
|
-
const url =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
url.searchParams.set("token", this.options.authToken);
|
|
36
|
-
}
|
|
37
|
-
this.eventSource = new EventSource(url.toString());
|
|
38
|
-
this.eventSource.addEventListener("open", () => {
|
|
39
|
-
console.log("[SSEClient] Connected");
|
|
40
|
-
this.reconnectAttempts = 0;
|
|
41
|
-
this.options.onStatusChange?.("connected");
|
|
42
|
-
});
|
|
43
|
-
this.eventSource.addEventListener("connected", (e) => {
|
|
44
|
-
const data = JSON.parse(e.data);
|
|
45
|
-
console.log("[SSEClient] Server ready:", data);
|
|
46
|
-
if (this.connectionResolver) {
|
|
47
|
-
this.connectionResolver();
|
|
48
|
-
this.connectionResolver = null;
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
this.eventSource.addEventListener("connection", (e) => {
|
|
52
|
-
const event = JSON.parse(e.data);
|
|
53
|
-
this.options.onConnectionEvent?.(event);
|
|
54
|
-
});
|
|
55
|
-
this.eventSource.addEventListener("observability", (e) => {
|
|
56
|
-
const event = JSON.parse(e.data);
|
|
57
|
-
this.options.onObservabilityEvent?.(event);
|
|
58
|
-
});
|
|
59
|
-
this.eventSource.addEventListener("rpc-response", (e) => {
|
|
60
|
-
const response = JSON.parse(e.data);
|
|
61
|
-
this.handleRpcResponse(response);
|
|
62
|
-
});
|
|
63
|
-
this.eventSource.addEventListener("error", () => {
|
|
64
|
-
console.error("[SSEClient] Connection error");
|
|
65
|
-
this.options.onStatusChange?.("error");
|
|
66
|
-
if (!this.isManuallyDisconnected && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
67
|
-
this.reconnectAttempts++;
|
|
68
|
-
console.log(`[SSEClient] Reconnecting (attempt ${this.reconnectAttempts})...`);
|
|
69
|
-
setTimeout(() => {
|
|
70
|
-
this.disconnect();
|
|
71
|
-
this.connect();
|
|
72
|
-
}, this.reconnectDelay * this.reconnectAttempts);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
38
|
+
const url = this.buildUrl();
|
|
39
|
+
this.eventSource = new EventSource(url);
|
|
40
|
+
this.setupEventListeners();
|
|
75
41
|
}
|
|
76
42
|
/**
|
|
77
|
-
* Disconnect from SSE endpoint
|
|
43
|
+
* Disconnect from the SSE endpoint
|
|
78
44
|
*/
|
|
79
45
|
disconnect() {
|
|
80
46
|
this.isManuallyDisconnected = true;
|
|
@@ -84,142 +50,581 @@ var SSEClient = class {
|
|
|
84
50
|
}
|
|
85
51
|
this.connectionPromise = null;
|
|
86
52
|
this.connectionResolver = null;
|
|
87
|
-
|
|
88
|
-
const error = new Error("Connection closed");
|
|
89
|
-
error.name = "ConnectionClosedError";
|
|
90
|
-
reject(error);
|
|
91
|
-
}
|
|
92
|
-
this.pendingRequests.clear();
|
|
53
|
+
this.rejectAllPendingRequests(new Error("Connection closed"));
|
|
93
54
|
this.options.onStatusChange?.("disconnected");
|
|
94
55
|
}
|
|
95
56
|
/**
|
|
96
|
-
*
|
|
97
|
-
|
|
57
|
+
* Check if connected to the SSE endpoint
|
|
58
|
+
*/
|
|
59
|
+
isConnected() {
|
|
60
|
+
return this.eventSource?.readyState === EventSource.OPEN;
|
|
61
|
+
}
|
|
62
|
+
// ============================================
|
|
63
|
+
// RPC Methods
|
|
64
|
+
// ============================================
|
|
65
|
+
async getSessions() {
|
|
66
|
+
return this.sendRequest("getSessions");
|
|
67
|
+
}
|
|
68
|
+
async connectToServer(params) {
|
|
69
|
+
return this.sendRequest("connect", params);
|
|
70
|
+
}
|
|
71
|
+
async disconnectFromServer(sessionId) {
|
|
72
|
+
return this.sendRequest("disconnect", { sessionId });
|
|
73
|
+
}
|
|
74
|
+
async listTools(sessionId) {
|
|
75
|
+
return this.sendRequest("listTools", { sessionId });
|
|
76
|
+
}
|
|
77
|
+
async callTool(sessionId, toolName, toolArgs) {
|
|
78
|
+
const result = await this.sendRequest("callTool", { sessionId, toolName, toolArgs });
|
|
79
|
+
this.emitUiEventIfPresent(result, sessionId, toolName);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
async restoreSession(sessionId) {
|
|
83
|
+
return this.sendRequest("restoreSession", { sessionId });
|
|
84
|
+
}
|
|
85
|
+
async finishAuth(sessionId, code) {
|
|
86
|
+
return this.sendRequest("finishAuth", { sessionId, code });
|
|
87
|
+
}
|
|
88
|
+
async listPrompts(sessionId) {
|
|
89
|
+
return this.sendRequest("listPrompts", { sessionId });
|
|
90
|
+
}
|
|
91
|
+
async getPrompt(sessionId, name, args) {
|
|
92
|
+
return this.sendRequest("getPrompt", { sessionId, name, args });
|
|
93
|
+
}
|
|
94
|
+
async listResources(sessionId) {
|
|
95
|
+
return this.sendRequest("listResources", { sessionId });
|
|
96
|
+
}
|
|
97
|
+
async readResource(sessionId, uri) {
|
|
98
|
+
return this.sendRequest("readResource", { sessionId, uri });
|
|
99
|
+
}
|
|
100
|
+
// ============================================
|
|
101
|
+
// Resource Preloading (for instant UI loading)
|
|
102
|
+
// ============================================
|
|
103
|
+
/**
|
|
104
|
+
* Preload UI resources for tools that have UI metadata.
|
|
105
|
+
* Call this when tools are discovered to enable instant MCP App UI loading.
|
|
106
|
+
*/
|
|
107
|
+
preloadToolUiResources(sessionId, tools) {
|
|
108
|
+
for (const tool of tools) {
|
|
109
|
+
const uri = this.extractUiResourceUri(tool);
|
|
110
|
+
if (!uri) continue;
|
|
111
|
+
if (this.resourceCache.has(uri)) {
|
|
112
|
+
this.log(`Resource already cached: ${uri}`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
this.log(`Preloading UI resource for tool "${tool.name}": ${uri}`);
|
|
116
|
+
const promise = this.sendRequest("readResource", { sessionId, uri }).catch((err) => {
|
|
117
|
+
this.log(`Failed to preload resource ${uri}: ${err.message}`, "warn");
|
|
118
|
+
this.resourceCache.delete(uri);
|
|
119
|
+
return null;
|
|
120
|
+
});
|
|
121
|
+
this.resourceCache.set(uri, promise);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get a preloaded resource from cache, or fetch if not cached.
|
|
126
|
+
*/
|
|
127
|
+
getOrFetchResource(sessionId, uri) {
|
|
128
|
+
const cached = this.resourceCache.get(uri);
|
|
129
|
+
if (cached) {
|
|
130
|
+
this.log(`Cache hit for resource: ${uri}`);
|
|
131
|
+
return cached;
|
|
132
|
+
}
|
|
133
|
+
this.log(`Cache miss, fetching resource: ${uri}`);
|
|
134
|
+
const promise = this.sendRequest("readResource", { sessionId, uri });
|
|
135
|
+
this.resourceCache.set(uri, promise);
|
|
136
|
+
return promise;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if a resource is already cached
|
|
140
|
+
*/
|
|
141
|
+
hasPreloadedResource(uri) {
|
|
142
|
+
return this.resourceCache.has(uri);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Clear the resource cache
|
|
146
|
+
*/
|
|
147
|
+
clearResourceCache() {
|
|
148
|
+
this.resourceCache.clear();
|
|
149
|
+
}
|
|
150
|
+
// ============================================
|
|
151
|
+
// Private: Request Handling
|
|
152
|
+
// ============================================
|
|
153
|
+
/**
|
|
154
|
+
* Send an RPC request and return the response directly from HTTP.
|
|
155
|
+
* This bypasses SSE latency by returning results in the HTTP response body.
|
|
98
156
|
*/
|
|
99
157
|
async sendRequest(method, params) {
|
|
100
158
|
if (this.connectionPromise) {
|
|
101
159
|
await this.connectionPromise;
|
|
102
160
|
}
|
|
103
|
-
const id = `rpc_${nanoid.nanoid(10)}`;
|
|
104
161
|
const request = {
|
|
105
|
-
id
|
|
162
|
+
id: `rpc_${nanoid.nanoid(10)}`,
|
|
106
163
|
method,
|
|
107
164
|
params
|
|
108
165
|
};
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.pendingRequests.delete(id);
|
|
114
|
-
reject(new Error("Request timeout"));
|
|
115
|
-
}
|
|
116
|
-
}, 3e4);
|
|
166
|
+
const response = await fetch(this.buildUrl(), {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: this.buildHeaders(),
|
|
169
|
+
body: JSON.stringify(request)
|
|
117
170
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
url.searchParams.set("identity", this.options.identity);
|
|
121
|
-
await fetch(url.toString(), {
|
|
122
|
-
method: "POST",
|
|
123
|
-
headers: {
|
|
124
|
-
"Content-Type": "application/json",
|
|
125
|
-
...this.options.authToken && { Authorization: `Bearer ${this.options.authToken}` }
|
|
126
|
-
},
|
|
127
|
-
body: JSON.stringify(request)
|
|
128
|
-
});
|
|
129
|
-
} catch (error) {
|
|
130
|
-
this.pendingRequests.delete(id);
|
|
131
|
-
throw error;
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
132
173
|
}
|
|
133
|
-
|
|
174
|
+
const data = await response.json();
|
|
175
|
+
return this.parseRpcResponse(data, request.id);
|
|
134
176
|
}
|
|
135
177
|
/**
|
|
136
|
-
*
|
|
178
|
+
* Parse RPC response and handle different response formats
|
|
137
179
|
*/
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
} else {
|
|
145
|
-
pending.resolve(response.result);
|
|
146
|
-
}
|
|
180
|
+
parseRpcResponse(data, requestId) {
|
|
181
|
+
if ("result" in data) {
|
|
182
|
+
return data.result;
|
|
183
|
+
}
|
|
184
|
+
if ("error" in data && data.error) {
|
|
185
|
+
throw new Error(data.error.message || "Unknown RPC error");
|
|
147
186
|
}
|
|
187
|
+
if ("acknowledged" in data) {
|
|
188
|
+
return this.waitForSseResponse(requestId);
|
|
189
|
+
}
|
|
190
|
+
throw new Error("Invalid RPC response format");
|
|
148
191
|
}
|
|
149
192
|
/**
|
|
150
|
-
*
|
|
193
|
+
* Wait for RPC response via SSE (legacy fallback)
|
|
151
194
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
195
|
+
waitForSseResponse(requestId) {
|
|
196
|
+
const timeoutMs = this.options.requestTimeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
const timeoutId = setTimeout(() => {
|
|
199
|
+
this.pendingRequests.delete(requestId);
|
|
200
|
+
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
201
|
+
}, timeoutMs);
|
|
202
|
+
this.pendingRequests.set(requestId, {
|
|
203
|
+
resolve,
|
|
204
|
+
reject,
|
|
205
|
+
timeoutId
|
|
206
|
+
});
|
|
207
|
+
});
|
|
154
208
|
}
|
|
155
209
|
/**
|
|
156
|
-
*
|
|
210
|
+
* Handle RPC response received via SSE (legacy)
|
|
157
211
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
handleRpcResponse(response) {
|
|
213
|
+
const pending = this.pendingRequests.get(response.id);
|
|
214
|
+
if (!pending) return;
|
|
215
|
+
clearTimeout(pending.timeoutId);
|
|
216
|
+
this.pendingRequests.delete(response.id);
|
|
217
|
+
if (response.error) {
|
|
218
|
+
pending.reject(new Error(response.error.message));
|
|
219
|
+
} else {
|
|
220
|
+
pending.resolve(response.result);
|
|
221
|
+
}
|
|
160
222
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
223
|
+
// ============================================
|
|
224
|
+
// Private: Event Handling
|
|
225
|
+
// ============================================
|
|
226
|
+
setupEventListeners() {
|
|
227
|
+
if (!this.eventSource) return;
|
|
228
|
+
this.eventSource.addEventListener("open", () => {
|
|
229
|
+
this.log("Connected");
|
|
230
|
+
this.reconnectAttempts = 0;
|
|
231
|
+
this.options.onStatusChange?.("connected");
|
|
232
|
+
});
|
|
233
|
+
this.eventSource.addEventListener("connected", () => {
|
|
234
|
+
this.log("Server ready");
|
|
235
|
+
this.connectionResolver?.();
|
|
236
|
+
this.connectionResolver = null;
|
|
237
|
+
});
|
|
238
|
+
this.eventSource.addEventListener("connection", (e) => {
|
|
239
|
+
const event = JSON.parse(e.data);
|
|
240
|
+
this.options.onConnectionEvent?.(event);
|
|
241
|
+
});
|
|
242
|
+
this.eventSource.addEventListener("observability", (e) => {
|
|
243
|
+
const event = JSON.parse(e.data);
|
|
244
|
+
this.options.onObservabilityEvent?.(event);
|
|
245
|
+
});
|
|
246
|
+
this.eventSource.addEventListener("rpc-response", (e) => {
|
|
247
|
+
const response = JSON.parse(e.data);
|
|
248
|
+
this.handleRpcResponse(response);
|
|
249
|
+
});
|
|
250
|
+
this.eventSource.addEventListener("error", () => {
|
|
251
|
+
this.log("Connection error", "error");
|
|
252
|
+
this.options.onStatusChange?.("error");
|
|
253
|
+
this.attemptReconnect();
|
|
254
|
+
});
|
|
166
255
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
256
|
+
attemptReconnect() {
|
|
257
|
+
if (this.isManuallyDisconnected || this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
this.reconnectAttempts++;
|
|
261
|
+
const delay = BASE_RECONNECT_DELAY * this.reconnectAttempts;
|
|
262
|
+
this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
this.disconnect();
|
|
265
|
+
this.connect();
|
|
266
|
+
}, delay);
|
|
267
|
+
}
|
|
268
|
+
// ============================================
|
|
269
|
+
// Private: Utilities
|
|
270
|
+
// ============================================
|
|
271
|
+
buildUrl() {
|
|
272
|
+
const url = new URL(this.options.url, globalThis.location?.origin);
|
|
273
|
+
url.searchParams.set("identity", this.options.identity);
|
|
274
|
+
if (this.options.authToken) {
|
|
275
|
+
url.searchParams.set("token", this.options.authToken);
|
|
276
|
+
}
|
|
277
|
+
return url.toString();
|
|
278
|
+
}
|
|
279
|
+
buildHeaders() {
|
|
280
|
+
const headers = {
|
|
281
|
+
"Content-Type": "application/json"
|
|
282
|
+
};
|
|
283
|
+
if (this.options.authToken) {
|
|
284
|
+
headers["Authorization"] = `Bearer ${this.options.authToken}`;
|
|
285
|
+
}
|
|
286
|
+
return headers;
|
|
287
|
+
}
|
|
288
|
+
rejectAllPendingRequests(error) {
|
|
289
|
+
for (const [, pending] of this.pendingRequests) {
|
|
290
|
+
clearTimeout(pending.timeoutId);
|
|
291
|
+
pending.reject(error);
|
|
292
|
+
}
|
|
293
|
+
this.pendingRequests.clear();
|
|
172
294
|
}
|
|
295
|
+
extractUiResourceUri(tool) {
|
|
296
|
+
const meta = tool._meta?.ui;
|
|
297
|
+
if (!meta || typeof meta !== "object") return void 0;
|
|
298
|
+
if (meta.visibility && !meta.visibility.includes("app")) return void 0;
|
|
299
|
+
return meta.resourceUri ?? meta.uri;
|
|
300
|
+
}
|
|
301
|
+
emitUiEventIfPresent(result, sessionId, toolName) {
|
|
302
|
+
const meta = result?._meta;
|
|
303
|
+
const resourceUri = meta?.ui?.resourceUri ?? meta?.["ui/resourceUri"];
|
|
304
|
+
if (resourceUri) {
|
|
305
|
+
this.options.onEvent?.({
|
|
306
|
+
type: "mcp-apps-ui",
|
|
307
|
+
sessionId,
|
|
308
|
+
resourceUri,
|
|
309
|
+
toolName,
|
|
310
|
+
result,
|
|
311
|
+
timestamp: Date.now()
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
log(message, level = "info") {
|
|
316
|
+
if (!this.options.debug && level === "info") return;
|
|
317
|
+
const prefix = "[SSEClient]";
|
|
318
|
+
switch (level) {
|
|
319
|
+
case "warn":
|
|
320
|
+
console.warn(prefix, message);
|
|
321
|
+
break;
|
|
322
|
+
case "error":
|
|
323
|
+
console.error(prefix, message);
|
|
324
|
+
break;
|
|
325
|
+
default:
|
|
326
|
+
console.log(prefix, message);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
|
|
331
|
+
var SANDBOX_PERMISSIONS = [
|
|
332
|
+
"allow-scripts",
|
|
333
|
+
// Required for app JavaScript execution
|
|
334
|
+
"allow-forms",
|
|
335
|
+
// Required for form submissions
|
|
336
|
+
"allow-same-origin",
|
|
337
|
+
// Required for Blob URL correctness
|
|
338
|
+
"allow-modals",
|
|
339
|
+
// Required for dialogs/alerts
|
|
340
|
+
"allow-popups",
|
|
341
|
+
// Required for opening links
|
|
342
|
+
"allow-downloads"
|
|
343
|
+
// Required for file downloads
|
|
344
|
+
].join(" ");
|
|
345
|
+
var MCP_URI_SCHEMES = ["ui://", "mcp-app://"];
|
|
346
|
+
var AppHost = class {
|
|
347
|
+
constructor(client, iframe, options) {
|
|
348
|
+
this.client = client;
|
|
349
|
+
this.iframe = iframe;
|
|
350
|
+
__publicField(this, "bridge");
|
|
351
|
+
__publicField(this, "sessionId");
|
|
352
|
+
__publicField(this, "resourceCache", /* @__PURE__ */ new Map());
|
|
353
|
+
__publicField(this, "debug");
|
|
354
|
+
/** Callback for app messages (e.g., chat messages from the app) */
|
|
355
|
+
__publicField(this, "onAppMessage");
|
|
356
|
+
this.debug = options?.debug ?? false;
|
|
357
|
+
this.configureSandbox();
|
|
358
|
+
this.bridge = this.initializeBridge();
|
|
359
|
+
}
|
|
360
|
+
// ============================================
|
|
361
|
+
// Public API
|
|
362
|
+
// ============================================
|
|
173
363
|
/**
|
|
174
|
-
*
|
|
364
|
+
* Start the host. This prepares the bridge handlers but doesn't connect yet.
|
|
365
|
+
* The actual connection happens in launch() after HTML is loaded.
|
|
366
|
+
* @returns Promise that resolves immediately (bridge connects during launch)
|
|
175
367
|
*/
|
|
176
|
-
async
|
|
177
|
-
|
|
368
|
+
async start() {
|
|
369
|
+
this.log("Host started, ready to launch");
|
|
178
370
|
}
|
|
179
371
|
/**
|
|
180
|
-
*
|
|
372
|
+
* Preload UI resources to enable instant app loading.
|
|
373
|
+
* Call this when tools are discovered to cache their UI resources.
|
|
181
374
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
375
|
+
preload(tools) {
|
|
376
|
+
for (const tool of tools) {
|
|
377
|
+
const uri = this.extractUiResourceUri(tool);
|
|
378
|
+
if (!uri || this.resourceCache.has(uri)) continue;
|
|
379
|
+
const promise = this.preloadResource(uri);
|
|
380
|
+
this.resourceCache.set(uri, promise);
|
|
381
|
+
}
|
|
184
382
|
}
|
|
185
383
|
/**
|
|
186
|
-
*
|
|
384
|
+
* Launch an MCP App from a URL or MCP resource URI.
|
|
385
|
+
* Loads the HTML first, then establishes bridge connection.
|
|
187
386
|
*/
|
|
188
|
-
async
|
|
189
|
-
|
|
387
|
+
async launch(url, sessionId) {
|
|
388
|
+
if (sessionId) this.sessionId = sessionId;
|
|
389
|
+
const initializedPromise = this.onAppReady();
|
|
390
|
+
if (this.isMcpUri(url)) {
|
|
391
|
+
await this.launchMcpApp(url);
|
|
392
|
+
} else {
|
|
393
|
+
this.iframe.src = url;
|
|
394
|
+
}
|
|
395
|
+
await this.onIframeReady();
|
|
396
|
+
await this.connectBridge();
|
|
397
|
+
this.log("Waiting for app initialization");
|
|
398
|
+
await Promise.race([
|
|
399
|
+
initializedPromise,
|
|
400
|
+
new Promise((resolve) => setTimeout(() => {
|
|
401
|
+
this.log("Initialization timeout - continuing anyway", "warn");
|
|
402
|
+
resolve();
|
|
403
|
+
}, 3e3))
|
|
404
|
+
]);
|
|
405
|
+
this.log("App launched and ready");
|
|
190
406
|
}
|
|
191
407
|
/**
|
|
192
|
-
*
|
|
408
|
+
* Wait for app to signal initialization complete
|
|
193
409
|
*/
|
|
194
|
-
|
|
195
|
-
return
|
|
410
|
+
onAppReady() {
|
|
411
|
+
return new Promise((resolve) => {
|
|
412
|
+
const originalHandler = this.bridge.oninitialized;
|
|
413
|
+
this.bridge.oninitialized = (...args) => {
|
|
414
|
+
this.log("App initialized");
|
|
415
|
+
resolve();
|
|
416
|
+
this.bridge.oninitialized = originalHandler;
|
|
417
|
+
originalHandler?.(...args);
|
|
418
|
+
};
|
|
419
|
+
});
|
|
196
420
|
}
|
|
197
421
|
/**
|
|
198
|
-
*
|
|
422
|
+
* Wait for iframe to finish loading
|
|
199
423
|
*/
|
|
200
|
-
|
|
201
|
-
return
|
|
424
|
+
onIframeReady() {
|
|
425
|
+
return new Promise((resolve) => {
|
|
426
|
+
if (this.iframe.contentDocument?.readyState === "complete") {
|
|
427
|
+
resolve();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
this.iframe.addEventListener("load", () => resolve(), { once: true });
|
|
431
|
+
});
|
|
202
432
|
}
|
|
203
433
|
/**
|
|
204
|
-
*
|
|
434
|
+
* Send tool input arguments to the MCP App.
|
|
435
|
+
* Call this after launch() when tool input is available.
|
|
205
436
|
*/
|
|
206
|
-
|
|
207
|
-
|
|
437
|
+
sendToolInput(args) {
|
|
438
|
+
this.log("Sending tool input to app");
|
|
439
|
+
this.bridge.sendToolInput({ arguments: args });
|
|
208
440
|
}
|
|
209
441
|
/**
|
|
210
|
-
*
|
|
442
|
+
* Send tool result to the MCP App.
|
|
443
|
+
* Call this when the tool call completes.
|
|
211
444
|
*/
|
|
212
|
-
|
|
213
|
-
|
|
445
|
+
sendToolResult(result) {
|
|
446
|
+
this.log("Sending tool result to app");
|
|
447
|
+
this.bridge.sendToolResult(result);
|
|
214
448
|
}
|
|
215
449
|
/**
|
|
216
|
-
*
|
|
450
|
+
* Send tool cancellation to the MCP App.
|
|
451
|
+
* Call this when the tool call is cancelled or fails.
|
|
217
452
|
*/
|
|
218
|
-
|
|
219
|
-
|
|
453
|
+
sendToolCancelled(reason) {
|
|
454
|
+
this.log("Sending tool cancellation to app");
|
|
455
|
+
this.bridge.sendToolCancelled({ reason });
|
|
456
|
+
}
|
|
457
|
+
// ============================================
|
|
458
|
+
// Private: Initialization
|
|
459
|
+
// ============================================
|
|
460
|
+
configureSandbox() {
|
|
461
|
+
if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
|
|
462
|
+
this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
initializeBridge() {
|
|
466
|
+
const bridge = new appBridge.AppBridge(
|
|
467
|
+
null,
|
|
468
|
+
HOST_INFO,
|
|
469
|
+
{
|
|
470
|
+
openLinks: {},
|
|
471
|
+
serverTools: {},
|
|
472
|
+
logging: {},
|
|
473
|
+
// Declare support for model context updates
|
|
474
|
+
updateModelContext: { text: {} }
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
// Initial host context
|
|
478
|
+
hostContext: {
|
|
479
|
+
theme: "dark",
|
|
480
|
+
platform: "web",
|
|
481
|
+
containerDimensions: { maxHeight: 6e3 },
|
|
482
|
+
displayMode: "inline",
|
|
483
|
+
availableDisplayModes: ["inline", "fullscreen"]
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
);
|
|
487
|
+
bridge.oncalltool = (params) => this.handleToolCall(params);
|
|
488
|
+
bridge.onopenlink = this.handleOpenLink.bind(this);
|
|
489
|
+
bridge.onmessage = this.handleMessage.bind(this);
|
|
490
|
+
bridge.onloggingmessage = (params) => this.log(`App log [${params.level}]: ${params.data}`);
|
|
491
|
+
bridge.onupdatemodelcontext = async () => ({});
|
|
492
|
+
bridge.onsizechange = async ({ width, height }) => {
|
|
493
|
+
if (height !== void 0) this.iframe.style.height = `${height}px`;
|
|
494
|
+
if (width !== void 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
|
|
495
|
+
return {};
|
|
496
|
+
};
|
|
497
|
+
bridge.onrequestdisplaymode = async (params) => ({
|
|
498
|
+
mode: params.mode === "fullscreen" ? "fullscreen" : "inline"
|
|
499
|
+
});
|
|
500
|
+
return bridge;
|
|
501
|
+
}
|
|
502
|
+
async connectBridge() {
|
|
503
|
+
this.log("Connecting bridge to iframe");
|
|
504
|
+
const transport = new appBridge.PostMessageTransport(
|
|
505
|
+
this.iframe.contentWindow,
|
|
506
|
+
this.iframe.contentWindow
|
|
507
|
+
);
|
|
508
|
+
try {
|
|
509
|
+
await this.bridge.connect(transport);
|
|
510
|
+
this.log("Bridge connected successfully");
|
|
511
|
+
} catch (error) {
|
|
512
|
+
this.log("Bridge connection failed", "error");
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// ============================================
|
|
517
|
+
// Private: Bridge Event Handlers
|
|
518
|
+
// ============================================
|
|
519
|
+
async handleToolCall(params) {
|
|
520
|
+
if (!this.client.isConnected()) {
|
|
521
|
+
throw new Error("Client disconnected");
|
|
522
|
+
}
|
|
523
|
+
const sessionId = await this.getSessionId();
|
|
524
|
+
if (!sessionId) {
|
|
525
|
+
throw new Error("No active session");
|
|
526
|
+
}
|
|
527
|
+
const result = await this.client.callTool(
|
|
528
|
+
sessionId,
|
|
529
|
+
params.name,
|
|
530
|
+
params.arguments ?? {}
|
|
531
|
+
);
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
async handleOpenLink(params) {
|
|
535
|
+
window.open(params.url, "_blank", "noopener,noreferrer");
|
|
536
|
+
return {};
|
|
537
|
+
}
|
|
538
|
+
async handleMessage(params) {
|
|
539
|
+
this.onAppMessage?.(params);
|
|
540
|
+
return {};
|
|
541
|
+
}
|
|
542
|
+
// ============================================
|
|
543
|
+
// Private: Resource Loading
|
|
544
|
+
// ============================================
|
|
545
|
+
async launchMcpApp(uri) {
|
|
546
|
+
if (!this.client.isConnected()) {
|
|
547
|
+
throw new Error("Client must be connected");
|
|
548
|
+
}
|
|
549
|
+
const sessionId = await this.getSessionId();
|
|
550
|
+
if (!sessionId) {
|
|
551
|
+
throw new Error("No active session");
|
|
552
|
+
}
|
|
553
|
+
const response = await this.fetchResourceWithCache(sessionId, uri);
|
|
554
|
+
if (!response?.contents?.length) {
|
|
555
|
+
throw new Error(`Empty resource: ${uri}`);
|
|
556
|
+
}
|
|
557
|
+
const content = response.contents[0];
|
|
558
|
+
const html = this.decodeContent(content);
|
|
559
|
+
if (!html) {
|
|
560
|
+
throw new Error(`Invalid content in resource: ${uri}`);
|
|
561
|
+
}
|
|
562
|
+
const blob = new Blob([html], { type: "text/html" });
|
|
563
|
+
this.iframe.src = URL.createObjectURL(blob);
|
|
564
|
+
}
|
|
565
|
+
async fetchResourceWithCache(sessionId, uri) {
|
|
566
|
+
if (this.hasClientCache()) {
|
|
567
|
+
return this.client.getOrFetchResource(sessionId, uri);
|
|
568
|
+
}
|
|
569
|
+
const cached = this.resourceCache.get(uri);
|
|
570
|
+
if (cached) {
|
|
571
|
+
const result = await cached;
|
|
572
|
+
if (result) return result;
|
|
573
|
+
}
|
|
574
|
+
return this.client.readResource(sessionId, uri);
|
|
575
|
+
}
|
|
576
|
+
async preloadResource(uri) {
|
|
577
|
+
try {
|
|
578
|
+
const sessionId = await this.getSessionId();
|
|
579
|
+
if (!sessionId) return null;
|
|
580
|
+
return await this.client.readResource(sessionId, uri);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
this.log(`Preload failed for ${uri}`, "warn");
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
// ============================================
|
|
587
|
+
// Private: Utilities
|
|
588
|
+
// ============================================
|
|
589
|
+
async getSessionId() {
|
|
590
|
+
if (this.sessionId) return this.sessionId;
|
|
591
|
+
const result = await this.client.getSessions();
|
|
592
|
+
return result.sessions?.[0]?.sessionId;
|
|
593
|
+
}
|
|
594
|
+
isMcpUri(url) {
|
|
595
|
+
return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
|
|
596
|
+
}
|
|
597
|
+
hasClientCache() {
|
|
598
|
+
return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
|
|
599
|
+
}
|
|
600
|
+
extractUiResourceUri(tool) {
|
|
601
|
+
const meta = tool._meta;
|
|
602
|
+
if (!meta?.ui) return void 0;
|
|
603
|
+
return meta.ui.resourceUri ?? meta.ui.uri;
|
|
604
|
+
}
|
|
605
|
+
decodeContent(content) {
|
|
606
|
+
if (content.blob) {
|
|
607
|
+
return atob(content.blob);
|
|
608
|
+
}
|
|
609
|
+
return content.text;
|
|
610
|
+
}
|
|
611
|
+
log(message, level = "info") {
|
|
612
|
+
if (!this.debug && level === "info") return;
|
|
613
|
+
const prefix = "[AppHost]";
|
|
614
|
+
switch (level) {
|
|
615
|
+
case "warn":
|
|
616
|
+
console.warn(prefix, message);
|
|
617
|
+
break;
|
|
618
|
+
case "error":
|
|
619
|
+
console.error(prefix, message);
|
|
620
|
+
break;
|
|
621
|
+
default:
|
|
622
|
+
console.log(prefix, message);
|
|
623
|
+
}
|
|
220
624
|
}
|
|
221
625
|
};
|
|
222
626
|
|
|
627
|
+
exports.AppHost = AppHost;
|
|
223
628
|
exports.SSEClient = SSEClient;
|
|
224
629
|
//# sourceMappingURL=index.js.map
|
|
225
630
|
//# sourceMappingURL=index.js.map
|