@plurnk/plurnk-mcp 1.6.1 → 1.7.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/.env.defaults +6 -4
- package/README.md +110 -45
- package/SPEC.md +274 -30
- package/dist/McpExecutor.d.ts +25 -4
- package/dist/McpExecutor.d.ts.map +1 -1
- package/dist/McpExecutor.js +105 -31
- package/dist/McpExecutor.js.map +1 -1
- package/dist/McpResources.d.ts +2 -2
- package/dist/McpResources.d.ts.map +1 -1
- package/dist/McpResources.js +114 -24
- package/dist/McpResources.js.map +1 -1
- package/dist/Module.d.ts +30 -5
- package/dist/Module.d.ts.map +1 -1
- package/dist/Module.js +649 -35
- package/dist/Module.js.map +1 -1
- package/dist/ToolPresentation.d.ts +5 -0
- package/dist/ToolPresentation.d.ts.map +1 -0
- package/dist/ToolPresentation.js +134 -0
- package/dist/ToolPresentation.js.map +1 -0
- package/dist/client.d.ts +25 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +478 -85
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +7 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +114 -45
- package/dist/config.js.map +1 -1
- package/dist/extensionChannel.d.ts +25 -0
- package/dist/extensionChannel.d.ts.map +1 -0
- package/dist/extensionChannel.js +195 -0
- package/dist/extensionChannel.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/inputRequired.d.ts +36 -0
- package/dist/inputRequired.d.ts.map +1 -0
- package/dist/inputRequired.js +171 -0
- package/dist/inputRequired.js.map +1 -0
- package/dist/oauth.d.ts +28 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +149 -0
- package/dist/oauth.js.map +1 -0
- package/dist/protocol.d.ts +7 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +7 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocolHeaders.d.ts +4 -0
- package/dist/protocolHeaders.d.ts.map +1 -0
- package/dist/protocolHeaders.js +87 -0
- package/dist/protocolHeaders.js.map +1 -0
- package/dist/subscriptions.d.ts +15 -0
- package/dist/subscriptions.d.ts.map +1 -0
- package/dist/subscriptions.js +188 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/tasks.d.ts +19 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +334 -0
- package/dist/tasks.js.map +1 -0
- package/package.json +10 -6
package/dist/client.js
CHANGED
|
@@ -1,71 +1,312 @@
|
|
|
1
|
-
import { Client, StreamableHTTPClientTransport, } from "@modelcontextprotocol/client";
|
|
1
|
+
import { Client, ClientCredentialsProvider, StreamableHTTPClientTransport, } from "@modelcontextprotocol/client";
|
|
2
2
|
import { StdioClientTransport, getDefaultEnvironment, } from "@modelcontextprotocol/client/stdio";
|
|
3
|
+
import { Validator, } from "@plurnk/plurnk-contracts";
|
|
3
4
|
import packageJson from "../package.json" with { type: "json" };
|
|
4
|
-
import { connectTimeoutMs, requestTimeoutMs, } from "./config.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import { connectTimeoutMs, expandReferences, requestTimeoutMs, } from "./config.js";
|
|
6
|
+
import { INPUT_REQUIRED_MAX_ROUNDS, runInputRequiredRequest, } from "./inputRequired.js";
|
|
7
|
+
import InteractiveOAuthProvider from "./oauth.js";
|
|
8
|
+
import ExtensionChannel from "./extensionChannel.js";
|
|
9
|
+
import { mcpRoutingHeaderValue } from "./protocolHeaders.js";
|
|
10
|
+
import { MCP_PROTOCOL_VERSION, MCP_TASKS_EXTENSION_ID, } from "./protocol.js";
|
|
11
|
+
import Subscriptions from "./subscriptions.js";
|
|
12
|
+
import { callToolWithTasks, serverSupportsTasks, } from "./tasks.js";
|
|
13
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
14
|
+
const expandedRecord = (source, environ, field) => source === undefined
|
|
15
|
+
? undefined
|
|
16
|
+
: Object.fromEntries(Object.entries(source).map(([key, value]) => [
|
|
17
|
+
key,
|
|
18
|
+
expandReferences(value, environ, `${field}.${key}`),
|
|
19
|
+
]));
|
|
20
|
+
const requireString = (value, field) => {
|
|
21
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
22
|
+
throw new Error(`${field} must be a non-empty string.`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
const resolveDefinition = (source, environ) => {
|
|
27
|
+
const definition = Validator.assertMcpServerDefinition(source);
|
|
28
|
+
if (definition.transport === "stdio") {
|
|
29
|
+
return {
|
|
30
|
+
transport: "stdio",
|
|
31
|
+
command: expandReferences(requireString(definition.command, `${definition.name}.command`), environ, `${definition.name}.command`),
|
|
32
|
+
args: (definition.args ?? []).map((argument, index) => expandReferences(argument, environ, `${definition.name}.args[${index}]`)),
|
|
33
|
+
...(definition.cwd === undefined
|
|
34
|
+
? {}
|
|
35
|
+
: { cwd: expandReferences(definition.cwd, environ, `${definition.name}.cwd`) }),
|
|
36
|
+
...(definition.env === undefined
|
|
37
|
+
? {}
|
|
38
|
+
: { env: expandedRecord(definition.env, environ, `${definition.name}.env`) }),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const url = requireString(definition.url, `${definition.name}.url`);
|
|
42
|
+
const headers = expandedRecord(definition.headers, environ, `${definition.name}.headers`);
|
|
43
|
+
const authorizationHeader = Object.keys(headers ?? {}).find((name) => name.toLowerCase() === "authorization");
|
|
44
|
+
if (definition.authorization !== undefined && authorizationHeader !== undefined) {
|
|
45
|
+
throw new Error(`${definition.name}.authorization conflicts with the Authorization header.`);
|
|
46
|
+
}
|
|
47
|
+
if (definition.authorization === undefined) {
|
|
48
|
+
return {
|
|
49
|
+
transport: "http",
|
|
50
|
+
url,
|
|
51
|
+
...(headers === undefined ? {} : { headers }),
|
|
52
|
+
cachePartition: "anonymous",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (definition.authorization.type === "bearer") {
|
|
56
|
+
const token = expandReferences(definition.authorization.token, environ, `${definition.name}.authorization.token`);
|
|
57
|
+
if (token.length === 0)
|
|
58
|
+
throw new Error(`${definition.name}.authorization.token resolved empty.`);
|
|
59
|
+
return {
|
|
60
|
+
transport: "http",
|
|
61
|
+
url,
|
|
62
|
+
...(headers === undefined ? {} : { headers }),
|
|
63
|
+
authProvider: { token: async () => token },
|
|
64
|
+
cachePartition: `bearer:${definition.authorization.token}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (definition.authorization.type === "client-credentials") {
|
|
68
|
+
const secret = expandReferences(definition.authorization.clientSecret, environ, `${definition.name}.authorization.clientSecret`);
|
|
69
|
+
if (secret.length === 0) {
|
|
70
|
+
throw new Error(`${definition.name}.authorization.clientSecret resolved empty.`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
transport: "http",
|
|
74
|
+
url,
|
|
75
|
+
...(headers === undefined ? {} : { headers }),
|
|
76
|
+
authProvider: new ClientCredentialsProvider({
|
|
77
|
+
clientId: definition.authorization.clientId,
|
|
78
|
+
clientSecret: secret,
|
|
79
|
+
...(definition.authorization.scope === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: { scope: definition.authorization.scope }),
|
|
82
|
+
}),
|
|
83
|
+
cachePartition: `client:${definition.authorization.clientId}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const oauthAuthorization = definition.authorization;
|
|
87
|
+
const oauthProvider = new InteractiveOAuthProvider({
|
|
88
|
+
redirectUrl: oauthAuthorization.redirectUrl,
|
|
89
|
+
...(oauthAuthorization.scope === undefined ? {} : { scope: oauthAuthorization.scope }),
|
|
90
|
+
...("clientMetadataUrl" in oauthAuthorization
|
|
91
|
+
? { clientMetadataUrl: oauthAuthorization.clientMetadataUrl }
|
|
92
|
+
: {}),
|
|
93
|
+
...("clientId" in oauthAuthorization
|
|
94
|
+
? {
|
|
95
|
+
clientId: oauthAuthorization.clientId,
|
|
96
|
+
clientSecret: expandReferences(oauthAuthorization.clientSecret, environ, `${definition.name}.authorization.clientSecret`),
|
|
97
|
+
}
|
|
98
|
+
: {}),
|
|
99
|
+
});
|
|
100
|
+
const cachePartition = "clientMetadataUrl" in oauthAuthorization
|
|
101
|
+
? `oauth:cimd:${oauthAuthorization.clientMetadataUrl}`
|
|
102
|
+
: "clientId" in oauthAuthorization
|
|
103
|
+
? `oauth:client:${oauthAuthorization.clientId}`
|
|
104
|
+
: "oauth:dynamic";
|
|
105
|
+
return {
|
|
106
|
+
transport: "http",
|
|
107
|
+
url,
|
|
108
|
+
...(headers === undefined ? {} : { headers }),
|
|
109
|
+
authProvider: oauthProvider,
|
|
110
|
+
oauthProvider,
|
|
111
|
+
cachePartition,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
const openTransport = (definition) => {
|
|
115
|
+
if (definition.transport === "http") {
|
|
116
|
+
return new StreamableHTTPClientTransport(new URL(definition.url), {
|
|
117
|
+
...(definition.headers === undefined
|
|
118
|
+
? {}
|
|
119
|
+
: { requestInit: { headers: definition.headers } }),
|
|
120
|
+
...(definition.authProvider === undefined
|
|
121
|
+
? {}
|
|
122
|
+
: { authProvider: definition.authProvider }),
|
|
123
|
+
fetch: async (url, init) => {
|
|
124
|
+
const body = typeof init?.body === "string"
|
|
125
|
+
? (() => {
|
|
126
|
+
try {
|
|
127
|
+
return JSON.parse(init.body);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
})()
|
|
133
|
+
: undefined;
|
|
134
|
+
const request = body !== null && typeof body === "object" && !Array.isArray(body)
|
|
135
|
+
? body
|
|
136
|
+
: undefined;
|
|
137
|
+
if (["tasks/get", "tasks/update", "tasks/cancel"].includes(String(request?.method))
|
|
138
|
+
&& typeof request?.params?.taskId === "string") {
|
|
139
|
+
const headers = new Headers(init?.headers);
|
|
140
|
+
headers.set("Mcp-Name", mcpRoutingHeaderValue(request.params.taskId));
|
|
141
|
+
return fetch(url, { ...init, headers });
|
|
142
|
+
}
|
|
143
|
+
return fetch(url, init);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
11
146
|
}
|
|
12
147
|
return new StdioClientTransport({
|
|
13
|
-
command:
|
|
14
|
-
args:
|
|
15
|
-
cwd:
|
|
148
|
+
command: definition.command,
|
|
149
|
+
args: definition.args,
|
|
150
|
+
cwd: definition.cwd,
|
|
16
151
|
env: {
|
|
17
152
|
...getDefaultEnvironment(),
|
|
18
|
-
...
|
|
153
|
+
...definition.env,
|
|
19
154
|
},
|
|
20
155
|
});
|
|
21
156
|
};
|
|
22
|
-
|
|
23
|
-
|
|
157
|
+
export class AuthorizationRequiredError extends Error {
|
|
158
|
+
authorizationUrl;
|
|
159
|
+
constructor(authorizationUrl, cause) {
|
|
160
|
+
super("MCP server requires interactive OAuth authorization.", { cause });
|
|
161
|
+
this.name = "AuthorizationRequiredError";
|
|
162
|
+
this.authorizationUrl = authorizationUrl;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const openClient = async (definition, environ, options, transport) => {
|
|
166
|
+
const changed = (error) => options.onCatalogChanged?.(error ?? null);
|
|
167
|
+
const clientInfo = {
|
|
24
168
|
name: packageJson.name,
|
|
25
169
|
version: packageJson.version,
|
|
26
|
-
}
|
|
170
|
+
};
|
|
171
|
+
const clientCapabilities = {
|
|
172
|
+
elicitation: {
|
|
173
|
+
form: {},
|
|
174
|
+
url: {},
|
|
175
|
+
},
|
|
176
|
+
extensions: {
|
|
177
|
+
[MCP_TASKS_EXTENSION_ID]: {},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
const client = new Client(clientInfo, {
|
|
181
|
+
capabilities: clientCapabilities,
|
|
27
182
|
versionNegotiation: {
|
|
28
183
|
mode: { pin: MCP_PROTOCOL_VERSION },
|
|
29
184
|
},
|
|
30
185
|
inputRequired: {
|
|
31
186
|
autoFulfill: false,
|
|
187
|
+
maxRounds: INPUT_REQUIRED_MAX_ROUNDS,
|
|
188
|
+
},
|
|
189
|
+
listChanged: {
|
|
190
|
+
tools: {
|
|
191
|
+
autoRefresh: false,
|
|
192
|
+
onChanged: (error) => changed(error ?? undefined),
|
|
193
|
+
},
|
|
194
|
+
resources: {
|
|
195
|
+
autoRefresh: false,
|
|
196
|
+
onChanged: (error) => changed(error ?? undefined),
|
|
197
|
+
},
|
|
198
|
+
prompts: {
|
|
199
|
+
autoRefresh: false,
|
|
200
|
+
onChanged: (error) => changed(error ?? undefined),
|
|
201
|
+
},
|
|
32
202
|
},
|
|
203
|
+
...(definition.transport === "http"
|
|
204
|
+
? { cachePartition: definition.cachePartition }
|
|
205
|
+
: {}),
|
|
33
206
|
});
|
|
207
|
+
client.onerror = (error) => options.onInfrastructureError?.(error);
|
|
34
208
|
try {
|
|
35
|
-
await client.connect(
|
|
209
|
+
await client.connect(transport, {
|
|
36
210
|
timeout: connectTimeoutMs(environ),
|
|
37
211
|
});
|
|
38
212
|
}
|
|
39
213
|
catch (cause) {
|
|
214
|
+
const authorizationUrl = definition.transport === "http"
|
|
215
|
+
? definition.oauthProvider?.takeAuthorizationUrl()
|
|
216
|
+
: undefined;
|
|
217
|
+
let closeFailure;
|
|
40
218
|
try {
|
|
41
219
|
await client.close();
|
|
42
220
|
}
|
|
43
|
-
catch (
|
|
44
|
-
|
|
221
|
+
catch (error) {
|
|
222
|
+
closeFailure = error;
|
|
223
|
+
}
|
|
224
|
+
if (authorizationUrl !== undefined) {
|
|
225
|
+
throw new AuthorizationRequiredError(authorizationUrl.href, closeFailure === undefined ? cause : new AggregateError([cause, closeFailure]));
|
|
226
|
+
}
|
|
227
|
+
if (closeFailure !== undefined) {
|
|
228
|
+
throw new AggregateError([cause, closeFailure], `MCP ${MCP_PROTOCOL_VERSION} connection and cleanup failed.`);
|
|
45
229
|
}
|
|
46
230
|
throw new Error(`MCP ${MCP_PROTOCOL_VERSION} connection failed.`, { cause });
|
|
47
231
|
}
|
|
232
|
+
const discover = client.getDiscoverResult();
|
|
48
233
|
if (client.getProtocolEra() !== "modern"
|
|
49
234
|
|| client.getNegotiatedProtocolVersion() !== MCP_PROTOCOL_VERSION
|
|
50
|
-
||
|
|
235
|
+
|| discover === undefined) {
|
|
51
236
|
await client.close();
|
|
52
237
|
throw new Error(`MCP server did not negotiate required revision ${MCP_PROTOCOL_VERSION}.`);
|
|
53
238
|
}
|
|
54
|
-
|
|
239
|
+
const extensions = new ExtensionChannel(transport, {
|
|
240
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
241
|
+
clientInfo,
|
|
242
|
+
clientCapabilities,
|
|
243
|
+
cancelRequest: async (requestId) => client.notification({
|
|
244
|
+
method: "notifications/cancelled",
|
|
245
|
+
params: { requestId },
|
|
246
|
+
}),
|
|
247
|
+
onError: options.onInfrastructureError,
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
client,
|
|
251
|
+
transport,
|
|
252
|
+
extensions,
|
|
253
|
+
subscriptions: new Subscriptions(client, {
|
|
254
|
+
timeout: requestTimeoutMs(environ),
|
|
255
|
+
tasks: serverSupportsTasks(discover.capabilities),
|
|
256
|
+
onError: options.onInfrastructureError,
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
55
259
|
};
|
|
56
260
|
export default class ServerConnection {
|
|
57
|
-
#
|
|
261
|
+
#definition;
|
|
262
|
+
#resolved;
|
|
58
263
|
#environ;
|
|
264
|
+
#options;
|
|
59
265
|
#client;
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
266
|
+
#pendingAuthorization;
|
|
267
|
+
#activeRequests = 0;
|
|
268
|
+
#closed = false;
|
|
269
|
+
constructor(definition, environ = process.env, options = {}) {
|
|
270
|
+
this.#definition = structuredClone(Validator.assertMcpServerDefinition(definition));
|
|
271
|
+
this.#resolved = resolveDefinition(this.#definition, environ);
|
|
63
272
|
this.#environ = environ;
|
|
273
|
+
this.#options = options;
|
|
274
|
+
}
|
|
275
|
+
get definition() {
|
|
276
|
+
return structuredClone(this.#definition);
|
|
277
|
+
}
|
|
278
|
+
get activeRequests() {
|
|
279
|
+
return this.#activeRequests;
|
|
280
|
+
}
|
|
281
|
+
get authorizationUrl() {
|
|
282
|
+
return this.#pendingAuthorization?.authorizationUrl ?? null;
|
|
64
283
|
}
|
|
65
|
-
|
|
284
|
+
assertReplaceable() {
|
|
285
|
+
if (this.#activeRequests !== 0) {
|
|
286
|
+
throw new Error(`MCP server '${this.#definition.name}' has ${this.#activeRequests} active user request(s).`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async #open() {
|
|
290
|
+
if (this.#closed)
|
|
291
|
+
throw new Error(`MCP server '${this.#definition.name}' connection is closed.`);
|
|
292
|
+
if (this.#pendingAuthorization !== undefined) {
|
|
293
|
+
throw new AuthorizationRequiredError(this.#pendingAuthorization.authorizationUrl);
|
|
294
|
+
}
|
|
66
295
|
if (this.#client !== undefined)
|
|
67
296
|
return this.#client;
|
|
68
|
-
const
|
|
297
|
+
const transport = openTransport(this.#resolved);
|
|
298
|
+
const pending = openClient(this.#resolved, this.#environ, this.#options, transport).catch((cause) => {
|
|
299
|
+
if (cause instanceof AuthorizationRequiredError
|
|
300
|
+
&& this.#resolved.transport === "http"
|
|
301
|
+
&& this.#resolved.oauthProvider !== undefined
|
|
302
|
+
&& transport instanceof StreamableHTTPClientTransport) {
|
|
303
|
+
this.#pendingAuthorization = {
|
|
304
|
+
transport,
|
|
305
|
+
provider: this.#resolved.oauthProvider,
|
|
306
|
+
authorizationUrl: cause.authorizationUrl,
|
|
307
|
+
standaloneTransport: true,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
69
310
|
if (this.#client === pending)
|
|
70
311
|
this.#client = undefined;
|
|
71
312
|
throw cause;
|
|
@@ -73,81 +314,233 @@ export default class ServerConnection {
|
|
|
73
314
|
this.#client = pending;
|
|
74
315
|
return pending;
|
|
75
316
|
}
|
|
76
|
-
|
|
77
|
-
return this.#
|
|
317
|
+
async connect() {
|
|
318
|
+
return (await this.#open()).client;
|
|
78
319
|
}
|
|
79
|
-
async
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
320
|
+
async finishAuthorization(callbackUrl) {
|
|
321
|
+
const pending = this.#pendingAuthorization;
|
|
322
|
+
if (pending === undefined) {
|
|
323
|
+
throw new Error(`MCP server '${this.#definition.name}' has no pending OAuth authorization.`);
|
|
324
|
+
}
|
|
325
|
+
const callback = new URL(callbackUrl);
|
|
326
|
+
const expected = new URL(pending.provider.redirectUrl);
|
|
327
|
+
if (callback.protocol !== expected.protocol
|
|
328
|
+
|| callback.host !== expected.host
|
|
329
|
+
|| callback.pathname !== expected.pathname) {
|
|
330
|
+
throw new Error("OAuth callback URL does not match the configured redirect URL.");
|
|
331
|
+
}
|
|
332
|
+
pending.provider.assertCallbackState(callback);
|
|
333
|
+
await pending.transport.finishAuth(callback.searchParams);
|
|
334
|
+
this.#pendingAuthorization = undefined;
|
|
335
|
+
await this.connect();
|
|
84
336
|
}
|
|
85
|
-
async
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
337
|
+
async #request(run) {
|
|
338
|
+
this.#activeRequests += 1;
|
|
339
|
+
try {
|
|
340
|
+
const opened = await this.#open();
|
|
341
|
+
try {
|
|
342
|
+
return await run(opened.client, opened.subscriptions, opened.extensions);
|
|
343
|
+
}
|
|
344
|
+
catch (cause) {
|
|
345
|
+
const authorization = this.#takeAuthorization(opened, cause);
|
|
346
|
+
if (authorization !== null)
|
|
347
|
+
throw authorization;
|
|
348
|
+
throw cause;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
this.#activeRequests -= 1;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
#takeAuthorization(opened, cause) {
|
|
356
|
+
if (this.#resolved.transport !== "http"
|
|
357
|
+
|| this.#resolved.oauthProvider === undefined
|
|
358
|
+
|| !(opened.transport instanceof StreamableHTTPClientTransport)) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const authorizationUrl = this.#resolved.oauthProvider.takeAuthorizationUrl();
|
|
362
|
+
if (authorizationUrl === undefined)
|
|
363
|
+
return null;
|
|
364
|
+
this.#pendingAuthorization = {
|
|
365
|
+
transport: opened.transport,
|
|
366
|
+
provider: this.#resolved.oauthProvider,
|
|
367
|
+
authorizationUrl: authorizationUrl.href,
|
|
368
|
+
standaloneTransport: false,
|
|
112
369
|
};
|
|
370
|
+
return new AuthorizationRequiredError(authorizationUrl.href, cause);
|
|
113
371
|
}
|
|
114
|
-
async
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
372
|
+
async tools(signal) {
|
|
373
|
+
return this.#request(async (client) => {
|
|
374
|
+
if (client.getDiscoverResult()?.capabilities.tools === undefined)
|
|
375
|
+
return [];
|
|
376
|
+
const { tools } = await client.listTools(undefined, this.#requestOptions(signal));
|
|
377
|
+
return tools;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
async catalog(signal) {
|
|
381
|
+
return this.#request(async (client) => {
|
|
382
|
+
const discover = client.getDiscoverResult();
|
|
383
|
+
if (discover === undefined)
|
|
384
|
+
throw new Error("Modern MCP connection omitted its discovery result.");
|
|
385
|
+
const [tools, resources, resourceTemplates, prompts] = await Promise.all([
|
|
386
|
+
discover.capabilities.tools === undefined
|
|
387
|
+
? Promise.resolve([])
|
|
388
|
+
: client.listTools(undefined, this.#requestOptions(signal)).then((result) => result.tools),
|
|
389
|
+
discover.capabilities.resources === undefined
|
|
390
|
+
? Promise.resolve([])
|
|
391
|
+
: client.listResources(undefined, this.#requestOptions(signal)).then((result) => result.resources),
|
|
392
|
+
discover.capabilities.resources === undefined
|
|
393
|
+
? Promise.resolve([])
|
|
394
|
+
: client.listResourceTemplates(undefined, this.#requestOptions(signal))
|
|
395
|
+
.then((result) => result.resourceTemplates),
|
|
396
|
+
discover.capabilities.prompts === undefined
|
|
397
|
+
? Promise.resolve([])
|
|
398
|
+
: client.listPrompts(undefined, this.#requestOptions(signal)).then((result) => result.prompts),
|
|
399
|
+
]);
|
|
118
400
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
401
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
402
|
+
server: client.getServerVersion(),
|
|
403
|
+
capabilities: discover.capabilities,
|
|
404
|
+
tools,
|
|
405
|
+
resources,
|
|
406
|
+
resourceTemplates,
|
|
407
|
+
prompts,
|
|
121
408
|
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
client.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
async resources(signal) {
|
|
412
|
+
return this.#request(async (client) => {
|
|
413
|
+
if (client.getDiscoverResult()?.capabilities.resources === undefined) {
|
|
414
|
+
return { resources: [], resourceTemplates: [] };
|
|
415
|
+
}
|
|
416
|
+
const [resources, resourceTemplates] = await Promise.all([
|
|
417
|
+
client.listResources(undefined, this.#requestOptions(signal))
|
|
418
|
+
.then((result) => result.resources),
|
|
419
|
+
client.listResourceTemplates(undefined, this.#requestOptions(signal))
|
|
420
|
+
.then((result) => result.resourceTemplates),
|
|
421
|
+
]);
|
|
422
|
+
return { resources, resourceTemplates };
|
|
423
|
+
});
|
|
133
424
|
}
|
|
134
|
-
async
|
|
135
|
-
return (
|
|
425
|
+
async prompts(signal) {
|
|
426
|
+
return this.#request(async (client) => {
|
|
427
|
+
if (client.getDiscoverResult()?.capabilities.prompts === undefined)
|
|
428
|
+
return [];
|
|
429
|
+
return (await client.listPrompts(undefined, this.#requestOptions(signal))).prompts;
|
|
430
|
+
});
|
|
136
431
|
}
|
|
137
|
-
async
|
|
138
|
-
return (
|
|
432
|
+
async callTool(name, args, signal, onProgress, interact, toolDefinition) {
|
|
433
|
+
return this.#request(async (client, subscriptions, extensions) => {
|
|
434
|
+
const timeout = requestTimeoutMs(this.#environ);
|
|
435
|
+
if (serverSupportsTasks(client.getDiscoverResult()?.capabilities)) {
|
|
436
|
+
const tool = toolDefinition ?? (await client.listTools(undefined, this.#requestOptions(signal))).tools.find((candidate) => candidate.name === name);
|
|
437
|
+
if (tool === undefined) {
|
|
438
|
+
throw new Error(`MCP server '${this.#definition.name}' did not list tool '${name}'.`);
|
|
439
|
+
}
|
|
440
|
+
return callToolWithTasks({
|
|
441
|
+
server: this.#definition.name,
|
|
442
|
+
name,
|
|
443
|
+
args,
|
|
444
|
+
tool,
|
|
445
|
+
signal,
|
|
446
|
+
onProgress,
|
|
447
|
+
interact,
|
|
448
|
+
timeout,
|
|
449
|
+
channel: extensions,
|
|
450
|
+
subscriptions,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
return runInputRequiredRequest({
|
|
454
|
+
server: this.#definition.name,
|
|
455
|
+
operation: "tools/call",
|
|
456
|
+
originalParams: { name, arguments: args },
|
|
457
|
+
signal,
|
|
458
|
+
interact,
|
|
459
|
+
onProgress,
|
|
460
|
+
timeout,
|
|
461
|
+
requestLeg: (params, options) => client.callTool(params, {
|
|
462
|
+
...options,
|
|
463
|
+
...(toolDefinition === undefined ? {} : { toolDefinition }),
|
|
464
|
+
}),
|
|
465
|
+
});
|
|
466
|
+
});
|
|
139
467
|
}
|
|
140
|
-
|
|
141
|
-
return {
|
|
468
|
+
async readResource(uri, signal, interact) {
|
|
469
|
+
return this.#request(async (client, subscriptions) => {
|
|
470
|
+
await subscriptions.selectResource(uri);
|
|
471
|
+
return runInputRequiredRequest({
|
|
472
|
+
server: this.#definition.name,
|
|
473
|
+
operation: "resources/read",
|
|
474
|
+
originalParams: { uri },
|
|
475
|
+
signal,
|
|
476
|
+
interact,
|
|
477
|
+
timeout: requestTimeoutMs(this.#environ),
|
|
478
|
+
requestLeg: (params, options, retry) => client.readResource(params, retry ? { ...options, cacheMode: "refresh" } : options),
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
async getPrompt(name, args, signal, interact) {
|
|
483
|
+
return this.#request(async (client) => runInputRequiredRequest({
|
|
484
|
+
server: this.#definition.name,
|
|
485
|
+
operation: "prompts/get",
|
|
486
|
+
originalParams: { name, ...(args === undefined ? {} : { arguments: args }) },
|
|
142
487
|
signal,
|
|
488
|
+
interact,
|
|
143
489
|
timeout: requestTimeoutMs(this.#environ),
|
|
490
|
+
requestLeg: (params, options) => client.getPrompt(params, options),
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
async complete(params, signal) {
|
|
494
|
+
return this.#request(async (client) => client.complete(params, this.#requestOptions(signal)));
|
|
495
|
+
}
|
|
496
|
+
#requestOptions(signal, onProgress) {
|
|
497
|
+
const timeout = requestTimeoutMs(this.#environ);
|
|
498
|
+
return {
|
|
499
|
+
signal,
|
|
500
|
+
timeout,
|
|
501
|
+
maxTotalTimeout: timeout,
|
|
502
|
+
...(onProgress === undefined ? {} : { onprogress: onProgress }),
|
|
144
503
|
};
|
|
145
504
|
}
|
|
146
505
|
async close() {
|
|
506
|
+
if (this.#closed)
|
|
507
|
+
return;
|
|
508
|
+
this.#closed = true;
|
|
147
509
|
const client = this.#client;
|
|
148
510
|
this.#client = undefined;
|
|
149
|
-
|
|
150
|
-
|
|
511
|
+
const pending = this.#pendingAuthorization;
|
|
512
|
+
this.#pendingAuthorization = undefined;
|
|
513
|
+
const closures = [];
|
|
514
|
+
if (client !== undefined) {
|
|
515
|
+
closures.push(client.then(async ({ client: connected, extensions, subscriptions }) => {
|
|
516
|
+
const failures = [];
|
|
517
|
+
extensions.close();
|
|
518
|
+
// A full connection close terminates its listen request. Retiring first
|
|
519
|
+
// avoids a redundant cancellation racing the SDK's removed listen ID.
|
|
520
|
+
const settled = await Promise.allSettled([
|
|
521
|
+
subscriptions.retire(),
|
|
522
|
+
connected.close(),
|
|
523
|
+
]);
|
|
524
|
+
failures.push(...settled.flatMap((result) => result.status === "rejected" ? [result.reason] : []));
|
|
525
|
+
if (failures.length === 1)
|
|
526
|
+
throw failures[0];
|
|
527
|
+
if (failures.length > 1) {
|
|
528
|
+
throw new AggregateError(failures, `MCP server '${this.#definition.name}' connection shutdown failed.`);
|
|
529
|
+
}
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
if (pending?.standaloneTransport === true)
|
|
533
|
+
closures.push(pending.transport.close());
|
|
534
|
+
const settled = await Promise.allSettled(closures);
|
|
535
|
+
const failures = settled.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
536
|
+
if (failures.length === 1)
|
|
537
|
+
throw failures[0];
|
|
538
|
+
if (failures.length > 1) {
|
|
539
|
+
throw new AggregateError(failures, `MCP server '${this.#definition.name}' shutdown failed.`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
describeError(error) {
|
|
543
|
+
return message(error);
|
|
151
544
|
}
|
|
152
545
|
}
|
|
153
546
|
//# sourceMappingURL=client.js.map
|