@cat-factory/server 0.217.0 → 0.218.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/dist/agents/capabilityCredentialResolver.d.ts +23 -0
- package/dist/agents/capabilityCredentialResolver.d.ts.map +1 -1
- package/dist/agents/capabilityCredentialResolver.js +18 -0
- package/dist/agents/capabilityCredentialResolver.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +2 -0
- package/dist/app.js.map +1 -1
- package/dist/http/env.d.ts +16 -1
- package/dist/http/env.d.ts.map +1 -1
- package/dist/http/workspaceAccess.d.ts +42 -0
- package/dist/http/workspaceAccess.d.ts.map +1 -1
- package/dist/http/workspaceAccess.js +48 -0
- package/dist/http/workspaceAccess.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/capabilityCredentials/CapabilityCredentialsController.d.ts +5 -0
- package/dist/modules/capabilityCredentials/CapabilityCredentialsController.d.ts.map +1 -1
- package/dist/modules/capabilityCredentials/CapabilityCredentialsController.js +14 -2
- package/dist/modules/capabilityCredentials/CapabilityCredentialsController.js.map +1 -1
- package/dist/modules/capabilityCredentials/declaredCredentials.d.ts.map +1 -1
- package/dist/modules/capabilityCredentials/declaredCredentials.js +6 -0
- package/dist/modules/capabilityCredentials/declaredCredentials.js.map +1 -1
- package/dist/modules/publicApi/PublicApiController.js +13 -8
- package/dist/modules/publicApi/PublicApiController.js.map +1 -1
- package/dist/modules/toolServers/ToolServerController.d.ts +22 -0
- package/dist/modules/toolServers/ToolServerController.d.ts.map +1 -0
- package/dist/modules/toolServers/ToolServerController.js +57 -0
- package/dist/modules/toolServers/ToolServerController.js.map +1 -0
- package/dist/modules/toolServers/declaredToolServers.d.ts +45 -0
- package/dist/modules/toolServers/declaredToolServers.d.ts.map +1 -0
- package/dist/modules/toolServers/declaredToolServers.js +108 -0
- package/dist/modules/toolServers/declaredToolServers.js.map +1 -0
- package/dist/modules/toolServers/mcpProbe.d.ts +50 -0
- package/dist/modules/toolServers/mcpProbe.d.ts.map +1 -0
- package/dist/modules/toolServers/mcpProbe.js +611 -0
- package/dist/modules/toolServers/mcpProbe.js.map +1 -0
- package/dist/modules/toolServers/probeToolServer.d.ts +30 -0
- package/dist/modules/toolServers/probeToolServer.d.ts.map +1 -0
- package/dist/modules/toolServers/probeToolServer.js +170 -0
- package/dist/modules/toolServers/probeToolServer.js.map +1 -0
- package/dist/persistence/rpc-allowlist.d.ts.map +1 -1
- package/dist/persistence/rpc-allowlist.js +5 -0
- package/dist/persistence/rpc-allowlist.js.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import { isAllowedMcpHttpUrl, redactSecrets } from '@cat-factory/kernel';
|
|
2
|
+
import { MCP_PROBE_MAX_PAGES, MCP_PROBE_TIMEOUT_MS } from '@cat-factory/contracts';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// A minimal Streamable-HTTP MCP CLIENT, for one purpose: ask a declared `http` tool server whether
|
|
5
|
+
// it is there. `initialize`, `notifications/initialized`, then `tools/list` until the pages run out.
|
|
6
|
+
//
|
|
7
|
+
// Hand-rolled over `fetch` rather than built on `@modelcontextprotocol/sdk`'s client, and the reason
|
|
8
|
+
// is the one slice 3 already recorded about the SERVING side: the backend's HTTP layer is typed
|
|
9
|
+
// against the Web platform alone so it cannot break on workerd, and the SDK's client transport
|
|
10
|
+
// carries an OAuth provider, a reconnecting SSE stream, resumption tokens and a zod-validated
|
|
11
|
+
// message layer — every one of which this needs to NOT do. Three POSTs and a JSON reader is smaller
|
|
12
|
+
// than the adapter that would hold that machinery back, and it keeps the SDK out of a module every
|
|
13
|
+
// facade bundles.
|
|
14
|
+
//
|
|
15
|
+
// What it deliberately keeps from the real client, because a probe that behaves differently from the
|
|
16
|
+
// agent's own CLI answers about the wrong thing:
|
|
17
|
+
//
|
|
18
|
+
// - `accept: application/json, text/event-stream` and a reader for BOTH. A stateless server
|
|
19
|
+
// answers with JSON; a stream-first one answers the same frame as one SSE event. A client that
|
|
20
|
+
// accepted only JSON would report a working server as a protocol error.
|
|
21
|
+
// - the `mcp-session-id` a server may mint on `initialize`, echoed on every later request, and the
|
|
22
|
+
// negotiated `mcp-protocol-version` beside it. Without them a session-keeping server answers
|
|
23
|
+
// the handshake and then 400s the `tools/list` that follows.
|
|
24
|
+
// - redirects, followed by hand up to {@link MAX_REDIRECTS} hops with every hop re-validated
|
|
25
|
+
// against `isAllowedMcpHttpUrl` (the same rule the declared url is held to, re-checked per hop
|
|
26
|
+
// exactly as the local-runner fetch does) — and, when the request carries a credential, held to
|
|
27
|
+
// the SAME ORIGIN as well. That is not extra caution beyond what a real client does, it is what
|
|
28
|
+
// a real client does: the Web platform REMOVES `Authorization` when a redirect crosses origins
|
|
29
|
+
// (fetch's CORS non-wildcard request-header rule), so an agent's own MCP client reaches such a
|
|
30
|
+
// hop unauthenticated. Forwarding a resolved credential there would make the probe the one path
|
|
31
|
+
// that hands a workspace's token to whatever a hijacked or expired vendor host redirects to,
|
|
32
|
+
// and it would answer about a request no run will ever make. Refused BY NAME instead, because
|
|
33
|
+
// "your declared url redirects off its origin" is the fix, where a silently credential-less
|
|
34
|
+
// 401 is a wrong cause.
|
|
35
|
+
// - a POST on every hop, deliberately NOT the spec's 301/302/303 method rewrite. A GET to an MCP
|
|
36
|
+
// endpoint means "open the SSE stream", so degrading to one asks a different question.
|
|
37
|
+
//
|
|
38
|
+
// It never throws. Every failure is a discriminated outcome, because the whole point is to hand the
|
|
39
|
+
// operator a CAUSE, and "the probe blew up" is not one.
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/**
|
|
42
|
+
* The protocol version the probe asks for.
|
|
43
|
+
*
|
|
44
|
+
* A literal rather than a read of the SDK's `LATEST_PROTOCOL_VERSION`, and drift is benign by the
|
|
45
|
+
* protocol's own design: negotiation belongs to the SERVER, which answers with the requested version
|
|
46
|
+
* when it speaks it and with one of its own when it does not. So a probe advertising something older
|
|
47
|
+
* than the spec's latest still completes a handshake with a newer server, and the version this
|
|
48
|
+
* reports is always the one the server chose rather than the one we asked for.
|
|
49
|
+
*/
|
|
50
|
+
const PROBE_PROTOCOL_VERSION = '2025-11-25';
|
|
51
|
+
/** Redirect hops one probe follows. Bounded because a redirect loop is the server's to author. */
|
|
52
|
+
const MAX_REDIRECTS = 3;
|
|
53
|
+
/**
|
|
54
|
+
* Bytes read from one response before the reader gives up.
|
|
55
|
+
*
|
|
56
|
+
* An SSE body has no length and a server decides when it ends, so a reader with no ceiling hands a
|
|
57
|
+
* remote server the ability to hold this request open until the timeout with a stream of padding.
|
|
58
|
+
* Sized well past any real `tools/list` page (the biggest tool tables in the wild are tens of KB).
|
|
59
|
+
*/
|
|
60
|
+
const MAX_RESPONSE_BYTES = 4_000_000;
|
|
61
|
+
/**
|
|
62
|
+
* Speak MCP to `target` and report what happened.
|
|
63
|
+
*
|
|
64
|
+
* The caller owns the DECISION to probe at all (a stdio server has no url; a loopback one is not
|
|
65
|
+
* ours to reach) and the credential resolution that fills `headers`. This owns only the protocol.
|
|
66
|
+
*/
|
|
67
|
+
export async function probeMcpHttpServer(target, deps = {}) {
|
|
68
|
+
// ONE deadline for the whole exchange rather than a timeout per request. Three round trips each
|
|
69
|
+
// allowed the full budget is three times the wait an operator was told to expect, and the thing
|
|
70
|
+
// being bounded is how long this endpoint holds a request open.
|
|
71
|
+
const timeoutMs = deps.timeoutMs ?? MCP_PROBE_TIMEOUT_MS;
|
|
72
|
+
const ctx = {
|
|
73
|
+
doFetch: deps.fetch ?? fetch,
|
|
74
|
+
target,
|
|
75
|
+
session: new SessionHeaders(),
|
|
76
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
77
|
+
timeoutMs,
|
|
78
|
+
};
|
|
79
|
+
const outcome = await handshakeAndList(ctx, deps.maxPages ?? MCP_PROBE_MAX_PAGES);
|
|
80
|
+
// A session the server minted is ENDED before answering, whatever the outcome was. The spec's own
|
|
81
|
+
// termination path, and without it every press of the Test button leaves a session on the server
|
|
82
|
+
// to expire on its own clock. Best effort by construction: it cannot change the verdict, so a
|
|
83
|
+
// server that refuses or ignores the DELETE costs nothing.
|
|
84
|
+
await endSession(ctx);
|
|
85
|
+
return outcome;
|
|
86
|
+
}
|
|
87
|
+
/** The exchange itself: handshake, the required notification, then every page of `tools/list`. */
|
|
88
|
+
async function handshakeAndList(ctx, maxPages) {
|
|
89
|
+
const handshake = await exchange(ctx, {
|
|
90
|
+
id: 1,
|
|
91
|
+
method: 'initialize',
|
|
92
|
+
params: initializeParams(),
|
|
93
|
+
});
|
|
94
|
+
if (!handshake.ok)
|
|
95
|
+
return handshake.failure;
|
|
96
|
+
const negotiated = readHandshake(handshake.result);
|
|
97
|
+
if (!negotiated) {
|
|
98
|
+
return {
|
|
99
|
+
status: 'protocol_error',
|
|
100
|
+
error: 'the endpoint answered the handshake without a protocolVersion or serverInfo, so it is ' +
|
|
101
|
+
'answering JSON-RPC but is not an MCP server',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
ctx.session.protocolVersion = negotiated.protocolVersion;
|
|
105
|
+
// The spec REQUIRES this notification before any other request, and a strict server rejects
|
|
106
|
+
// `tools/list` without it. It carries no id and expects no body (a `202` is the normal answer), so
|
|
107
|
+
// its outcome is only interesting when the transport itself failed.
|
|
108
|
+
const notified = await notify(ctx, 'notifications/initialized');
|
|
109
|
+
if (notified)
|
|
110
|
+
return notified;
|
|
111
|
+
const listed = await listTools(ctx, maxPages);
|
|
112
|
+
if (!listed.ok)
|
|
113
|
+
return listed.failure;
|
|
114
|
+
return { status: 'ok', ...negotiated, tools: listed.tools, toolsComplete: listed.complete };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* What the probe says it is.
|
|
118
|
+
*
|
|
119
|
+
* `capabilities: {}` is honest and load-bearing: this client implements no roots, no sampling and no
|
|
120
|
+
* elicitation, and a server that saw a capability declared here could legitimately try to use it
|
|
121
|
+
* mid-handshake. `tools/list` needs none of them.
|
|
122
|
+
*/
|
|
123
|
+
function initializeParams() {
|
|
124
|
+
return {
|
|
125
|
+
protocolVersion: PROBE_PROTOCOL_VERSION,
|
|
126
|
+
capabilities: {},
|
|
127
|
+
clientInfo: { name: 'cat-factory-tool-server-probe', version: '1' },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** The negotiated handshake facts, or undefined when the answer was not an MCP one. */
|
|
131
|
+
function readHandshake(result) {
|
|
132
|
+
const protocolVersion = typeof result.protocolVersion === 'string' ? result.protocolVersion : '';
|
|
133
|
+
const info = isRecord(result.serverInfo) ? result.serverInfo : undefined;
|
|
134
|
+
if (!protocolVersion || !info)
|
|
135
|
+
return undefined;
|
|
136
|
+
return {
|
|
137
|
+
protocolVersion,
|
|
138
|
+
serverName: typeof info.name === 'string' ? info.name : '',
|
|
139
|
+
// A server that omits its version is odd, not broken: the field is what the surface renders
|
|
140
|
+
// beside the name, so an empty string there beats refusing an otherwise complete handshake.
|
|
141
|
+
serverVersion: typeof info.version === 'string' ? info.version : '',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The per-session headers a Streamable-HTTP client must echo once it has them.
|
|
146
|
+
*
|
|
147
|
+
* A class rather than two locals because both are minted by the FIRST response and must reach every
|
|
148
|
+
* later request; threading them by hand is exactly how a session-keeping server ends up 400ing the
|
|
149
|
+
* request after the handshake with nothing naming why.
|
|
150
|
+
*/
|
|
151
|
+
class SessionHeaders {
|
|
152
|
+
sessionId;
|
|
153
|
+
protocolVersion;
|
|
154
|
+
/**
|
|
155
|
+
* The url the session was minted at, which a redirect may have moved off the declared one. The
|
|
156
|
+
* DELETE that ends the session has to reach the endpoint that owns it, not the url we asked for.
|
|
157
|
+
*/
|
|
158
|
+
endpoint;
|
|
159
|
+
apply(headers) {
|
|
160
|
+
if (this.sessionId)
|
|
161
|
+
headers['mcp-session-id'] = this.sessionId;
|
|
162
|
+
if (this.protocolVersion)
|
|
163
|
+
headers['mcp-protocol-version'] = this.protocolVersion;
|
|
164
|
+
}
|
|
165
|
+
adopt(response, url) {
|
|
166
|
+
const minted = response.headers.get('mcp-session-id');
|
|
167
|
+
if (!minted)
|
|
168
|
+
return;
|
|
169
|
+
this.sessionId = minted;
|
|
170
|
+
this.endpoint = url;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Read every page of `tools/list`, up to `maxPages`. */
|
|
174
|
+
async function listTools(exchangeCtx, maxPages) {
|
|
175
|
+
const tools = [];
|
|
176
|
+
let cursor;
|
|
177
|
+
for (let page = 0; page < maxPages; page++) {
|
|
178
|
+
const answer = await exchange(exchangeCtx, {
|
|
179
|
+
id: page + 2,
|
|
180
|
+
method: 'tools/list',
|
|
181
|
+
...(cursor ? { params: { cursor } } : {}),
|
|
182
|
+
});
|
|
183
|
+
if (!answer.ok)
|
|
184
|
+
return { ok: false, failure: answer.failure };
|
|
185
|
+
const listed = answer.result.tools;
|
|
186
|
+
if (!Array.isArray(listed)) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
failure: {
|
|
190
|
+
status: 'protocol_error',
|
|
191
|
+
error: 'the server answered tools/list without a tools array',
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
for (const tool of listed) {
|
|
196
|
+
if (isRecord(tool) && typeof tool.name === 'string' && tool.name)
|
|
197
|
+
tools.push(tool.name);
|
|
198
|
+
}
|
|
199
|
+
const next = answer.result.nextCursor;
|
|
200
|
+
cursor = typeof next === 'string' && next ? next : undefined;
|
|
201
|
+
if (!cursor)
|
|
202
|
+
return { ok: true, tools, complete: true };
|
|
203
|
+
}
|
|
204
|
+
// A cursor survived the page bound: the list is a PREFIX, and saying so is what stops the caller
|
|
205
|
+
// reporting a narrowed `allowedTools` entry as missing on the strength of a partial read.
|
|
206
|
+
return { ok: true, tools, complete: false };
|
|
207
|
+
}
|
|
208
|
+
/** One JSON-RPC REQUEST and its response. */
|
|
209
|
+
async function exchange(ctx, message) {
|
|
210
|
+
const sent = await post(ctx, { jsonrpc: '2.0', ...message });
|
|
211
|
+
if (!sent.ok)
|
|
212
|
+
return { ok: false, failure: sent.failure };
|
|
213
|
+
const body = await readFrame(ctx, sent.response);
|
|
214
|
+
if (!body.ok)
|
|
215
|
+
return { ok: false, failure: body.failure };
|
|
216
|
+
const frame = body.frame;
|
|
217
|
+
if (isRecord(frame.error)) {
|
|
218
|
+
const detail = typeof frame.error.message === 'string' ? frame.error.message : 'no message';
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
failure: {
|
|
222
|
+
status: 'protocol_error',
|
|
223
|
+
error: `${message.method} was refused by the server: ${redact(detail)}`,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (!isRecord(frame.result)) {
|
|
228
|
+
return {
|
|
229
|
+
ok: false,
|
|
230
|
+
failure: {
|
|
231
|
+
status: 'protocol_error',
|
|
232
|
+
error: `${message.method} answered without a JSON-RPC result object`,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return { ok: true, result: frame.result };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* One JSON-RPC NOTIFICATION. Returns a failure only when the transport itself did — a notification
|
|
240
|
+
* has no response to inspect, and a server that answers it with a body is within its rights.
|
|
241
|
+
*/
|
|
242
|
+
async function notify(ctx, method) {
|
|
243
|
+
const sent = await post(ctx, { jsonrpc: '2.0', method });
|
|
244
|
+
if (!sent.ok)
|
|
245
|
+
return sent.failure;
|
|
246
|
+
// Drain rather than parse: an unread body on a keep-alive connection is a leaked stream on Node,
|
|
247
|
+
// and there is nothing in it this cares about.
|
|
248
|
+
// silent-catch-ok: cancelling a body we never read has no failure worth a line, and this module
|
|
249
|
+
// holds no logger on purpose (it is a pure protocol client the caller reports for).
|
|
250
|
+
await sent.response.body?.cancel().catch(() => { });
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* End the session the server minted, if it minted one. The spec's own termination path.
|
|
255
|
+
*
|
|
256
|
+
* Best effort in the strict sense: it runs AFTER the verdict is decided and cannot change it, so
|
|
257
|
+
* every failure is dropped here rather than reported. A server may answer `405` (termination not
|
|
258
|
+
* allowed) and be entirely correct, the deadline may already have passed, and neither is news. What
|
|
259
|
+
* it buys is that pressing Test repeatedly does not leave a session per press on the server to age
|
|
260
|
+
* out on its own clock.
|
|
261
|
+
*/
|
|
262
|
+
async function endSession(ctx) {
|
|
263
|
+
const { sessionId, endpoint } = ctx.session;
|
|
264
|
+
if (!sessionId || !endpoint)
|
|
265
|
+
return;
|
|
266
|
+
try {
|
|
267
|
+
const response = await ctx.doFetch(endpoint, {
|
|
268
|
+
method: 'DELETE',
|
|
269
|
+
headers: requestHeaders(ctx),
|
|
270
|
+
redirect: 'manual',
|
|
271
|
+
signal: ctx.signal,
|
|
272
|
+
});
|
|
273
|
+
// silent-catch-ok: as above — a body we never read, on a request whose outcome is already
|
|
274
|
+
// irrelevant to the caller.
|
|
275
|
+
await response.body?.cancel().catch(() => { });
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// Nothing to report and nowhere to report it: the verdict is already computed, and a failed
|
|
279
|
+
// courtesy DELETE says nothing about the server an operator asked about.
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** POST one frame, following redirects by hand and turning transport faults into outcomes. */
|
|
283
|
+
async function post(ctx, frame) {
|
|
284
|
+
let url = ctx.target.url;
|
|
285
|
+
const carriesCredential = Object.keys(ctx.target.credentialHeaders).length > 0;
|
|
286
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
287
|
+
const sent = await send(ctx, url, frame);
|
|
288
|
+
if (!sent.ok)
|
|
289
|
+
return sent;
|
|
290
|
+
ctx.session.adopt(sent.response, url);
|
|
291
|
+
if (!isRedirect(sent.response))
|
|
292
|
+
return sent;
|
|
293
|
+
const hopTo = nextHop(ctx, url, sent.response, carriesCredential);
|
|
294
|
+
if (!hopTo.ok)
|
|
295
|
+
return hopTo;
|
|
296
|
+
url = hopTo.url;
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
ok: false,
|
|
300
|
+
failure: {
|
|
301
|
+
status: 'protocol_error',
|
|
302
|
+
error: `the endpoint redirected more than ${MAX_REDIRECTS} times`,
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/** One request, with every transport fault turned into an outcome rather than a throw. */
|
|
307
|
+
async function send(ctx, url, frame) {
|
|
308
|
+
try {
|
|
309
|
+
return {
|
|
310
|
+
ok: true,
|
|
311
|
+
response: await ctx.doFetch(url, {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
headers: requestHeaders(ctx),
|
|
314
|
+
body: JSON.stringify(frame),
|
|
315
|
+
redirect: 'manual',
|
|
316
|
+
signal: ctx.signal,
|
|
317
|
+
}),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
// Everything that never produced a status: DNS, TLS, connection refused, and the deadline. One
|
|
322
|
+
// outcome for all of them because they share a fix (the endpoint, or the network) and the prose
|
|
323
|
+
// carries which — while a 4xx/5xx is a DIFFERENT fix and gets its own member.
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
failure: { status: 'unreachable', error: redact(errorText(error, ctx.timeoutMs)) },
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* The headers one request carries: the declaration's own, the resolved credential, the session pair,
|
|
332
|
+
* and this client's content negotiation.
|
|
333
|
+
*
|
|
334
|
+
* Lower-cased before the probe's own values are written, which is load-bearing rather than tidy:
|
|
335
|
+
* `Headers` APPENDS as it fills from a record, so a declaration spelling `Accept` plus this
|
|
336
|
+
* function's `accept` arrives at the server as ONE combined value, and a stream-first server then
|
|
337
|
+
* negotiates against something neither side offered.
|
|
338
|
+
*/
|
|
339
|
+
function requestHeaders(ctx) {
|
|
340
|
+
const headers = lowerCasedKeys({ ...ctx.target.headers, ...ctx.target.credentialHeaders });
|
|
341
|
+
headers['content-type'] = 'application/json';
|
|
342
|
+
headers.accept = 'application/json, text/event-stream';
|
|
343
|
+
ctx.session.apply(headers);
|
|
344
|
+
return headers;
|
|
345
|
+
}
|
|
346
|
+
function lowerCasedKeys(headers) {
|
|
347
|
+
const out = {};
|
|
348
|
+
for (const [name, value] of Object.entries(headers))
|
|
349
|
+
out[name.toLowerCase()] = value;
|
|
350
|
+
return out;
|
|
351
|
+
}
|
|
352
|
+
/** Where a redirect points, or the reason the probe will not follow it. */
|
|
353
|
+
function nextHop(ctx, from, response, carriesCredential) {
|
|
354
|
+
const location = response.headers.get('location');
|
|
355
|
+
const next = location ? resolveHop(from, location) : undefined;
|
|
356
|
+
if (!next) {
|
|
357
|
+
return {
|
|
358
|
+
ok: false,
|
|
359
|
+
failure: {
|
|
360
|
+
status: 'protocol_error',
|
|
361
|
+
error: `the endpoint answered ${response.status} with no usable Location header`,
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
// The hop is held to the SAME rule the declared url is: an https endpoint that redirects a
|
|
366
|
+
// credential-bearing request onto cleartext is the one thing that rule exists to stop, and a
|
|
367
|
+
// redirect is the way a declaration that passed boot validation still gets there.
|
|
368
|
+
if (!isAllowedMcpHttpUrl(next)) {
|
|
369
|
+
return {
|
|
370
|
+
ok: false,
|
|
371
|
+
failure: {
|
|
372
|
+
status: 'protocol_error',
|
|
373
|
+
error: `the endpoint redirected to ${redact(next)}, which an MCP tool server may not be ` +
|
|
374
|
+
`reached at (https, or plain http on loopback)`,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
// A credential-bearing request stops at its own ORIGIN, matching the Web platform rather than
|
|
379
|
+
// exceeding it: fetch removes `Authorization` when a redirect crosses origins, so the agent's own
|
|
380
|
+
// MCP client reaches this hop unauthenticated and a probe that forwarded the token would both
|
|
381
|
+
// answer about a request no run makes and hand a workspace's credential to whatever the redirect
|
|
382
|
+
// names. Refused rather than followed credential-less, because the FIX is the declaration naming
|
|
383
|
+
// the final url, and a 401 from a stripped hop would name the token instead.
|
|
384
|
+
if (carriesCredential && !sameOrigin(ctx.target.url, next)) {
|
|
385
|
+
return {
|
|
386
|
+
ok: false,
|
|
387
|
+
failure: {
|
|
388
|
+
status: 'protocol_error',
|
|
389
|
+
error: `the endpoint redirected to ${redact(next)}, a different origin from the declared url, ` +
|
|
390
|
+
`and the request carries a credential — declare the final url, because an agent's own ` +
|
|
391
|
+
`MCP client reaches a cross-origin hop with the credential removed`,
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
return { ok: true, url: next };
|
|
396
|
+
}
|
|
397
|
+
function isRedirect(response) {
|
|
398
|
+
return response.status >= 300 && response.status < 400;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Whether two urls share an origin. An UNPARSEABLE url is never same-origin with anything, so the
|
|
402
|
+
* caller's refusal is what an unreadable hop reaches rather than a comparison of two undefineds.
|
|
403
|
+
*/
|
|
404
|
+
function sameOrigin(a, b) {
|
|
405
|
+
const left = originOf(a);
|
|
406
|
+
return left !== undefined && left === originOf(b);
|
|
407
|
+
}
|
|
408
|
+
function originOf(raw) {
|
|
409
|
+
try {
|
|
410
|
+
return new URL(raw).origin;
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/** Absolutise a `Location` against the url it came from, or undefined when it is unparseable. */
|
|
417
|
+
function resolveHop(from, location) {
|
|
418
|
+
try {
|
|
419
|
+
return new URL(location, from).toString();
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Read one JSON-RPC response frame out of whichever body shape arrived.
|
|
427
|
+
*
|
|
428
|
+
* A non-2xx is settled FIRST and as its own outcome. An MCP server refusing a call answers 200 with
|
|
429
|
+
* a JSON-RPC error, so a status outside 2xx means something other than the MCP layer answered — a
|
|
430
|
+
* gateway, an auth proxy, a wrong path — and reporting it as a protocol error would send the
|
|
431
|
+
* operator to look at the server's tool table instead of at the status.
|
|
432
|
+
*/
|
|
433
|
+
async function readFrame(ctx, response) {
|
|
434
|
+
if (!response.ok) {
|
|
435
|
+
const body = await bodyText(response);
|
|
436
|
+
return {
|
|
437
|
+
ok: false,
|
|
438
|
+
failure: {
|
|
439
|
+
status: 'http_error',
|
|
440
|
+
httpStatus: response.status,
|
|
441
|
+
// PREVIEWED, like every other quoted body: an auth proxy or a load balancer answers a 4xx
|
|
442
|
+
// with an HTML page, and this string is both rendered in a browser and logged. The status is
|
|
443
|
+
// the diagnosis; the body is a hint about which proxy answered.
|
|
444
|
+
error: redact(body ? `HTTP ${response.status}: ${preview(body)}` : `HTTP ${response.status}`),
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
449
|
+
const raw = contentType.includes('text/event-stream')
|
|
450
|
+
? await firstSseData(response)
|
|
451
|
+
: await bodyText(response);
|
|
452
|
+
// An EMPTY body counts as unreadable rather than as JSON that fails to parse, so the prose names
|
|
453
|
+
// the missing body instead of quoting nothing.
|
|
454
|
+
if (raw === undefined || !raw.trim()) {
|
|
455
|
+
return {
|
|
456
|
+
ok: false,
|
|
457
|
+
failure: bodyFailure(ctx, 'the endpoint answered 200 with no readable body'),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
let parsed;
|
|
461
|
+
try {
|
|
462
|
+
parsed = JSON.parse(raw);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return {
|
|
466
|
+
ok: false,
|
|
467
|
+
failure: bodyFailure(ctx, `the endpoint answered 200 with a body that is not JSON: ${redact(preview(raw))}`),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
// A BATCH response is a legal JSON-RPC shape and every request here is single, so a server that
|
|
471
|
+
// wraps its answer in an array is answering the one call we made. Unwrapped rather than refused.
|
|
472
|
+
const frame = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
473
|
+
if (!isRecord(frame)) {
|
|
474
|
+
return {
|
|
475
|
+
ok: false,
|
|
476
|
+
failure: {
|
|
477
|
+
status: 'protocol_error',
|
|
478
|
+
error: 'the endpoint answered JSON that is not a frame',
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return { ok: true, frame };
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* The failure to report for a body that yielded no frame.
|
|
486
|
+
*
|
|
487
|
+
* The DEADLINE is checked FIRST, because the one signal aborts the response stream as well as the
|
|
488
|
+
* request: a server that answers 200 and then stalls (an SSE stream that never completes an event, a
|
|
489
|
+
* body that stops mid-way) leaves a partial buffer behind, and calling that a `protocol_error` tells
|
|
490
|
+
* the operator "the url names something else" for what is a slow endpoint — the exact cause
|
|
491
|
+
* `unreachable` is documented to cover. A cause is the whole product here, so the two are kept
|
|
492
|
+
* apart even though the code reaches them through the same branch.
|
|
493
|
+
*/
|
|
494
|
+
function bodyFailure(ctx, protocolError) {
|
|
495
|
+
if (ctx.signal.aborted)
|
|
496
|
+
return { status: 'unreachable', error: timeoutText(ctx.timeoutMs) };
|
|
497
|
+
return { status: 'protocol_error', error: protocolError };
|
|
498
|
+
}
|
|
499
|
+
/** A whole body as text, bounded, or undefined when there was no body to read at all. */
|
|
500
|
+
async function bodyText(response) {
|
|
501
|
+
const stream = response.body;
|
|
502
|
+
if (!stream) {
|
|
503
|
+
// A bodyless response is legitimate for a notification and a protocol fault for a request; the
|
|
504
|
+
// caller distinguishes, so this stays apart from an EMPTY body, which read fine and said nothing.
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
return readBounded(stream, () => false);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* The first SSE `data:` payload in the stream.
|
|
511
|
+
*
|
|
512
|
+
* Read INCREMENTALLY and stopped at the first complete event rather than draining to the end,
|
|
513
|
+
* because a stream-first server keeps the stream open after answering: `.text()` on it waits for the
|
|
514
|
+
* deadline to close a connection that has already delivered what was asked for.
|
|
515
|
+
*/
|
|
516
|
+
async function firstSseData(response) {
|
|
517
|
+
const stream = response.body;
|
|
518
|
+
if (!stream)
|
|
519
|
+
return undefined;
|
|
520
|
+
return extractSseData(await readBounded(stream, (buffered) => extractSseData(buffered) !== undefined));
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Read `stream` until `done(buffered)` says enough arrived, the stream ends, or the character ceiling
|
|
524
|
+
* is hit. Cancels what it did not read, so a stream held open costs no connection past this point.
|
|
525
|
+
*
|
|
526
|
+
* Always a string, never undefined: a read that failed part-way still returns what arrived, and
|
|
527
|
+
* whether that is usable is the caller's question (`bodyFailure` turns an empty one into the right
|
|
528
|
+
* cause, deadline included).
|
|
529
|
+
*/
|
|
530
|
+
async function readBounded(stream, done) {
|
|
531
|
+
const reader = stream.getReader();
|
|
532
|
+
const decoder = new TextDecoder();
|
|
533
|
+
let buffered = '';
|
|
534
|
+
try {
|
|
535
|
+
for (;;) {
|
|
536
|
+
const chunk = await reader.read();
|
|
537
|
+
if (chunk.done)
|
|
538
|
+
break;
|
|
539
|
+
buffered += decoder.decode(chunk.value, { stream: true });
|
|
540
|
+
if (buffered.length > MAX_RESPONSE_BYTES || done(buffered))
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
// A body that failed mid-read: whatever arrived is what the caller gets to parse, and an empty
|
|
546
|
+
// buffer becomes its own "no readable body" outcome above.
|
|
547
|
+
}
|
|
548
|
+
finally {
|
|
549
|
+
// silent-catch-ok: cancelling a reader that has already ended or errored is the normal path
|
|
550
|
+
// here, and a throw out of `finally` would replace the outcome the caller needs with noise.
|
|
551
|
+
await reader.cancel().catch(() => { });
|
|
552
|
+
}
|
|
553
|
+
// Flush the decoder's pending bytes, so a multi-byte character split across the LAST chunk lands
|
|
554
|
+
// in the buffer (as itself, or as the replacement character a truncated sequence deserves) rather
|
|
555
|
+
// than being dropped silently.
|
|
556
|
+
return buffered + decoder.decode();
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* The `data:` payload of the first COMPLETE event in an SSE buffer, or undefined while none has
|
|
560
|
+
* arrived yet. An event ends at a blank line, which is what makes "complete" decidable mid-stream;
|
|
561
|
+
* a `data:` line with nothing after it may still be half a payload.
|
|
562
|
+
*/
|
|
563
|
+
function extractSseData(buffered) {
|
|
564
|
+
const normalised = buffered.replaceAll('\r\n', '\n');
|
|
565
|
+
const terminator = /\n\n/.exec(normalised);
|
|
566
|
+
if (!terminator)
|
|
567
|
+
return undefined;
|
|
568
|
+
const event = normalised.slice(0, terminator.index);
|
|
569
|
+
// Multi-line `data:` fields concatenate with newlines, per the SSE spec.
|
|
570
|
+
const data = event
|
|
571
|
+
.split('\n')
|
|
572
|
+
.filter((line) => line.startsWith('data:'))
|
|
573
|
+
.map((line) => line.slice('data:'.length).replace(/^ /, ''));
|
|
574
|
+
return data.length ? data.join('\n') : undefined;
|
|
575
|
+
}
|
|
576
|
+
function isRecord(value) {
|
|
577
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
578
|
+
}
|
|
579
|
+
/** A thrown value as prose. `AbortSignal.timeout` rejects with a bare `TimeoutError`, so name it. */
|
|
580
|
+
function errorText(error, timeoutMs) {
|
|
581
|
+
if (error instanceof Error) {
|
|
582
|
+
return error.name === 'TimeoutError'
|
|
583
|
+
? timeoutText(timeoutMs)
|
|
584
|
+
: `${error.name}: ${error.message}`;
|
|
585
|
+
}
|
|
586
|
+
return String(error);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* The deadline in prose, authored ONCE: the same expiry is reachable as a rejected request and as an
|
|
590
|
+
* aborted body read, and two spellings of one cause read like two causes.
|
|
591
|
+
*/
|
|
592
|
+
function timeoutText(timeoutMs) {
|
|
593
|
+
return `the endpoint did not answer within ${timeoutMs / 1000}s`;
|
|
594
|
+
}
|
|
595
|
+
/** First line and a bounded prefix, for prose that quotes an unexpected body. */
|
|
596
|
+
function preview(raw) {
|
|
597
|
+
const oneLine = raw.replaceAll(/\s+/g, ' ').trim();
|
|
598
|
+
return oneLine.length > 200 ? `${oneLine.slice(0, 200)}…` : oneLine;
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Every string this module returns goes through here.
|
|
602
|
+
*
|
|
603
|
+
* A fetch failure routinely echoes the request url, and this request's url may carry userinfo while
|
|
604
|
+
* its headers carry a resolved credential; a 4xx body from an auth proxy echoes tokens as a matter
|
|
605
|
+
* of routine. The result is rendered in a browser and logged, so the scrub happens at the emit site
|
|
606
|
+
* rather than being left to whoever consumes it.
|
|
607
|
+
*/
|
|
608
|
+
function redact(text) {
|
|
609
|
+
return redactSecrets(text) ?? '';
|
|
610
|
+
}
|
|
611
|
+
//# sourceMappingURL=mcpProbe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpProbe.js","sourceRoot":"","sources":["../../../src/modules/toolServers/mcpProbe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Logger, ToolSecretResolver } from '@cat-factory/kernel';
|
|
2
|
+
import type { ToolServerProbeResult } from '@cat-factory/contracts';
|
|
3
|
+
import type { AgentKindRegistry } from '@cat-factory/agents';
|
|
4
|
+
import { type McpProbeDeps } from './mcpProbe.js';
|
|
5
|
+
export interface ProbeToolServerInput {
|
|
6
|
+
agentKindRegistry: AgentKindRegistry;
|
|
7
|
+
workspaceId: string;
|
|
8
|
+
/** The server id, from the request path. */
|
|
9
|
+
serverId: string;
|
|
10
|
+
/**
|
|
11
|
+
* The facade's composed capability-credential chain — the SAME resolver the container executor
|
|
12
|
+
* dispatches with. Absent ⇒ the facade wired none, so a server declaring a required credential is
|
|
13
|
+
* reported as `credentials_missing` rather than probed blind, which is the disposition the dispatch
|
|
14
|
+
* path already applies to the same state.
|
|
15
|
+
*/
|
|
16
|
+
resolveToolSecrets?: ToolSecretResolver;
|
|
17
|
+
logger?: Logger;
|
|
18
|
+
/** Injected for tests; forwarded to the protocol client. */
|
|
19
|
+
probe?: McpProbeDeps;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Probe one server. Never throws for a PROBE outcome — a failure is data, because the operator is
|
|
23
|
+
* asking a diagnostic question and an exception answers it with nothing.
|
|
24
|
+
*
|
|
25
|
+
* It does throw for a REFUSAL: an id no kind declares and nothing registered is a 404, not a status.
|
|
26
|
+
* A verdict would be a lie there ("unreachable" for a server that does not exist), and the SPA's
|
|
27
|
+
* list came from the same registry, so a 404 means the deployment changed under the operator.
|
|
28
|
+
*/
|
|
29
|
+
export declare function probeToolServer(input: ProbeToolServerInput): Promise<ToolServerProbeResult>;
|
|
30
|
+
//# sourceMappingURL=probeToolServer.d.ts.map
|