wiki-formant 0.10.0 → 0.12.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 +61 -5
- package/bin/check-classes.mjs +119 -0
- package/dist/conformance.d.ts +57 -2
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +183 -9
- package/dist/conformance.js.map +1 -1
- package/dist/crawlers.d.ts.map +1 -1
- package/dist/crawlers.js +9 -2
- package/dist/crawlers.js.map +1 -1
- package/dist/http.d.ts +41 -2
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +67 -4
- package/dist/http.js.map +1 -1
- package/dist/mcp.d.ts +77 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +111 -18
- package/dist/mcp.js.map +1 -1
- package/dist/pagination.d.ts +20 -0
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +16 -0
- package/dist/pagination.js.map +1 -1
- package/dist/rate-limit.d.ts +11 -0
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +16 -0
- package/dist/rate-limit.js.map +1 -1
- package/dist/react-server.d.ts +78 -0
- package/dist/react-server.d.ts.map +1 -0
- package/dist/react-server.js +62 -0
- package/dist/react-server.js.map +1 -0
- package/dist/react.d.ts +57 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +27 -0
- package/dist/react.js.map +1 -1
- package/dist/well-known.d.ts +9 -1
- package/dist/well-known.d.ts.map +1 -1
- package/dist/well-known.js +21 -4
- package/dist/well-known.js.map +1 -1
- package/dist/x402.d.ts +104 -0
- package/dist/x402.d.ts.map +1 -0
- package/dist/x402.js +224 -0
- package/dist/x402.js.map +1 -0
- package/package.json +39 -5
package/dist/crawlers.js
CHANGED
|
@@ -68,11 +68,18 @@ export function aiCrawlerTokens() {
|
|
|
68
68
|
* agent everything, because it stops matching `*` the moment it matches itself.
|
|
69
69
|
*/
|
|
70
70
|
export function aiCrawlerRules(opts) {
|
|
71
|
+
const aiAllow = [opts.aiAllow].flat();
|
|
71
72
|
return [
|
|
72
|
-
|
|
73
|
+
// `aiAllow` rides on the default group too, not only on the named roster.
|
|
74
|
+
// The agent surface an origin advertises has to be reachable by a caller it
|
|
75
|
+
// has never heard of: two origins here disallowed `/api/` under `*` while
|
|
76
|
+
// their own agent card named `/api/mcp`, so an MCP client that identified
|
|
77
|
+
// honestly as itself was told the endpoint was off limits. The roster is
|
|
78
|
+
// for indexing policy, not for hiding a documented endpoint.
|
|
79
|
+
{ userAgent: '*', allow: [...new Set([...[opts.allow].flat(), ...aiAllow])], disallow: opts.disallow },
|
|
73
80
|
...aiCrawlerTokens().map(userAgent => ({
|
|
74
81
|
userAgent,
|
|
75
|
-
allow:
|
|
82
|
+
allow: aiAllow,
|
|
76
83
|
disallow: opts.disallow,
|
|
77
84
|
})),
|
|
78
85
|
];
|
package/dist/crawlers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crawlers.js","sourceRoot":"","sources":["../src/crawlers.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"crawlers.js","sourceRoot":"","sources":["../src/crawlers.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
/** A stable ETag from whatever the corpus revision is (count + newest stamp). */
|
|
2
2
|
export declare function corpusEtag(parts: Array<string | number | Date | null | undefined>): string;
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* 304 when the client already holds this revision, else `null` to render.
|
|
5
|
+
*
|
|
6
|
+
* Both validators are matched the way RFC 9110 defines them rather than by
|
|
7
|
+
* string equality, because equality is wrong in the cases that actually occur:
|
|
8
|
+
* a client sends every tag it holds as a list, a proxy adds or strips the weak
|
|
9
|
+
* prefix, and a crawler reformats the date. Each of those took a full render
|
|
10
|
+
* from a response that was already fresh.
|
|
11
|
+
*/
|
|
12
|
+
export declare function notModified(request: Request, etag: string, lastModified?: string | null): Response | null;
|
|
5
13
|
/**
|
|
6
14
|
* Headers for a plain-text export. `maxAge` is the edge window — a curated
|
|
7
15
|
* corpus can sit on hours, a projection of live data should pass a short one.
|
|
@@ -16,11 +24,42 @@ export declare function textHeaders(etag: string, lastModified: string, maxAge?:
|
|
|
16
24
|
* offer, and a twin that stamps the epoch is worse than one that stamps
|
|
17
25
|
* nothing. `extra` carries whatever the mount needs on top — an `X-Robots-Tag`
|
|
18
26
|
* where the twin is a second public URL with no canonical of its own.
|
|
27
|
+
*
|
|
28
|
+
* Pass `etag`. The twin is the single most recrawled URL a page has, and a
|
|
29
|
+
* response with no validator is a full render on every pass forever — the same
|
|
30
|
+
* arithmetic that justifies the corpus ETag, applied per page. Three wikis
|
|
31
|
+
* shipped twins with neither validator and only 304'd where a proxy happened to
|
|
32
|
+
* synthesise one; that is why it is spelled out here rather than left optional
|
|
33
|
+
* in spirit.
|
|
19
34
|
*/
|
|
20
35
|
export declare function markdownHeaders(lastModified?: string | null, opts?: {
|
|
36
|
+
maxAge?: number;
|
|
37
|
+
etag?: string;
|
|
38
|
+
extra?: Record<string, string>;
|
|
39
|
+
}): Record<string, string>;
|
|
40
|
+
/**
|
|
41
|
+
* Headers for a JSON descriptor — an agent card, an OpenAPI document, a
|
|
42
|
+
* registry manifest. These are the documents a client refetches most and the
|
|
43
|
+
* ones that had no validator at all: served as `Cache-Control: public` with no
|
|
44
|
+
* `max-age`, a caller falls back to heuristic freshness and can never
|
|
45
|
+
* revalidate, so a corrected card takes an unbounded time to reach anyone.
|
|
46
|
+
*/
|
|
47
|
+
export declare function descriptorHeaders(etag: string, opts?: {
|
|
21
48
|
maxAge?: number;
|
|
22
49
|
extra?: Record<string, string>;
|
|
23
50
|
}): Record<string, string>;
|
|
51
|
+
/**
|
|
52
|
+
* A JSON descriptor served with a validator, answering a conditional GET.
|
|
53
|
+
*
|
|
54
|
+
* The body is its own ETag source, so the tag moves exactly when the document
|
|
55
|
+
* does and never otherwise. Every descriptor route in this workspace was a
|
|
56
|
+
* hand-rolled `NextResponse.json` with a Cache-Control and no validator at all,
|
|
57
|
+
* which is why a corrected agent card took an unbounded time to reach anyone.
|
|
58
|
+
*/
|
|
59
|
+
export declare function descriptorResponse(request: Request, body: unknown, opts?: {
|
|
60
|
+
maxAge?: number;
|
|
61
|
+
extra?: Record<string, string>;
|
|
62
|
+
}): Response;
|
|
24
63
|
/** Strip URLs and collapse whitespace so an excerpt stays one readable line. */
|
|
25
64
|
export declare function cleanSnippet(text: string, max?: number): string;
|
|
26
65
|
/** One markdown bullet: linked title, excerpt, and the date an agent diffs on. */
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAOA,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAY1F;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAOA,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAY1F;AAKD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAC3B,QAAQ,GAAG,IAAI,CAuBjB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,MAAM,SAAO,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,EAC5B,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,GAC5E,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,GAC7D,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,OAAO,EACb,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAO,GAC7D,QAAQ,CAIV;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,MAAM,CAQ5D;AAED,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;CAChC,GAAG,MAAM,CAMT"}
|
package/dist/http.js
CHANGED
|
@@ -18,14 +18,40 @@ export function corpusEtag(parts) {
|
|
|
18
18
|
}
|
|
19
19
|
return `W/"${hash.toString(36)}-${seed.length.toString(36)}"`;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
21
|
+
/** Weak comparison per RFC 9110 8.8.3.2: the `W/` prefix never affects a match. */
|
|
22
|
+
const bareTag = (tag) => tag.trim().replace(/^W\//, '');
|
|
23
|
+
/**
|
|
24
|
+
* 304 when the client already holds this revision, else `null` to render.
|
|
25
|
+
*
|
|
26
|
+
* Both validators are matched the way RFC 9110 defines them rather than by
|
|
27
|
+
* string equality, because equality is wrong in the cases that actually occur:
|
|
28
|
+
* a client sends every tag it holds as a list, a proxy adds or strips the weak
|
|
29
|
+
* prefix, and a crawler reformats the date. Each of those took a full render
|
|
30
|
+
* from a response that was already fresh.
|
|
31
|
+
*/
|
|
22
32
|
export function notModified(request, etag, lastModified) {
|
|
33
|
+
const headers = {
|
|
34
|
+
ETag: etag,
|
|
35
|
+
...(lastModified ? { 'Last-Modified': lastModified } : {}),
|
|
36
|
+
};
|
|
23
37
|
const inm = request.headers.get('if-none-match');
|
|
38
|
+
if (inm) {
|
|
39
|
+
const fresh = inm.trim() === '*' || inm.split(',').some(t => bareTag(t) === bareTag(etag));
|
|
40
|
+
// When If-None-Match is present If-Modified-Since must be ignored entirely.
|
|
41
|
+
return fresh ? new Response(null, { status: 304, headers }) : null;
|
|
42
|
+
}
|
|
24
43
|
const ims = request.headers.get('if-modified-since');
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
if (!ims || !lastModified)
|
|
45
|
+
return null;
|
|
46
|
+
const held = Date.parse(ims);
|
|
47
|
+
const current = Date.parse(lastModified);
|
|
48
|
+
if (!Number.isFinite(held) || !Number.isFinite(current))
|
|
27
49
|
return null;
|
|
28
|
-
|
|
50
|
+
// Second precision: the header carries no sub-second part, so a stamp that
|
|
51
|
+
// rounds down would otherwise read as newer than the copy it was sent for.
|
|
52
|
+
return Math.floor(current / 1000) <= Math.floor(held / 1000)
|
|
53
|
+
? new Response(null, { status: 304, headers })
|
|
54
|
+
: null;
|
|
29
55
|
}
|
|
30
56
|
/**
|
|
31
57
|
* Headers for a plain-text export. `maxAge` is the edge window — a curated
|
|
@@ -48,16 +74,53 @@ export function textHeaders(etag, lastModified, maxAge = 3600) {
|
|
|
48
74
|
* offer, and a twin that stamps the epoch is worse than one that stamps
|
|
49
75
|
* nothing. `extra` carries whatever the mount needs on top — an `X-Robots-Tag`
|
|
50
76
|
* where the twin is a second public URL with no canonical of its own.
|
|
77
|
+
*
|
|
78
|
+
* Pass `etag`. The twin is the single most recrawled URL a page has, and a
|
|
79
|
+
* response with no validator is a full render on every pass forever — the same
|
|
80
|
+
* arithmetic that justifies the corpus ETag, applied per page. Three wikis
|
|
81
|
+
* shipped twins with neither validator and only 304'd where a proxy happened to
|
|
82
|
+
* synthesise one; that is why it is spelled out here rather than left optional
|
|
83
|
+
* in spirit.
|
|
51
84
|
*/
|
|
52
85
|
export function markdownHeaders(lastModified, opts = {}) {
|
|
53
86
|
const maxAge = opts.maxAge ?? 3600;
|
|
54
87
|
return {
|
|
55
88
|
'Content-Type': 'text/markdown; charset=utf-8',
|
|
56
89
|
'Cache-Control': `public, s-maxage=${maxAge}, stale-while-revalidate=${maxAge * 24}`,
|
|
90
|
+
...(opts.etag ? { ETag: opts.etag } : {}),
|
|
57
91
|
...(lastModified ? { 'Last-Modified': lastModified } : {}),
|
|
58
92
|
...opts.extra,
|
|
59
93
|
};
|
|
60
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Headers for a JSON descriptor — an agent card, an OpenAPI document, a
|
|
97
|
+
* registry manifest. These are the documents a client refetches most and the
|
|
98
|
+
* ones that had no validator at all: served as `Cache-Control: public` with no
|
|
99
|
+
* `max-age`, a caller falls back to heuristic freshness and can never
|
|
100
|
+
* revalidate, so a corrected card takes an unbounded time to reach anyone.
|
|
101
|
+
*/
|
|
102
|
+
export function descriptorHeaders(etag, opts = {}) {
|
|
103
|
+
const maxAge = opts.maxAge ?? 86400;
|
|
104
|
+
return {
|
|
105
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
106
|
+
'Cache-Control': `public, max-age=300, s-maxage=${maxAge}, stale-while-revalidate=${maxAge * 7}`,
|
|
107
|
+
ETag: etag,
|
|
108
|
+
...opts.extra,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A JSON descriptor served with a validator, answering a conditional GET.
|
|
113
|
+
*
|
|
114
|
+
* The body is its own ETag source, so the tag moves exactly when the document
|
|
115
|
+
* does and never otherwise. Every descriptor route in this workspace was a
|
|
116
|
+
* hand-rolled `NextResponse.json` with a Cache-Control and no validator at all,
|
|
117
|
+
* which is why a corrected agent card took an unbounded time to reach anyone.
|
|
118
|
+
*/
|
|
119
|
+
export function descriptorResponse(request, body, opts = {}) {
|
|
120
|
+
const text = JSON.stringify(body);
|
|
121
|
+
const etag = corpusEtag([text]);
|
|
122
|
+
return notModified(request, etag) ?? new Response(text, { headers: descriptorHeaders(etag, opts) });
|
|
123
|
+
}
|
|
61
124
|
/** Strip URLs and collapse whitespace so an excerpt stays one readable line. */
|
|
62
125
|
export function cleanSnippet(text, max = 160) {
|
|
63
126
|
return text
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,4EAA4E;AAC5E,6EAA6E;AAC7E,4EAA4E;AAC5E,wEAAwE;AAExE,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAAC,KAAuD;IAChF,MAAM,IAAI,GAAG,KAAK;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;SACjE,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,2EAA2E;IAC3E,6EAA6E;IAC7E,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC;AAChE,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The versions this transport speaks, newest first.
|
|
3
|
+
*
|
|
4
|
+
* This was a constant answered unconditionally, which is legal and still wrong:
|
|
5
|
+
* a client asking for 2025-06-18 was told 2025-03-26 and silently gave up tool
|
|
6
|
+
* titles, structured output and `_meta`. The last one is where a payment
|
|
7
|
+
* receipt rides, so the one surface that needed it rebuilt this module's HTTP
|
|
8
|
+
* shell by hand to reach it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MCP_PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
|
|
11
|
+
export type McpProtocolVersion = (typeof MCP_PROTOCOL_VERSIONS)[number];
|
|
12
|
+
/** The newest version spoken here, and what an unrecognised ask falls back to. */
|
|
13
|
+
export declare const MCP_PROTOCOL_VERSION: McpProtocolVersion;
|
|
14
|
+
/** Echo the client's version when it is one we speak, else offer the newest. */
|
|
15
|
+
export declare function negotiateProtocol(requested: unknown): McpProtocolVersion;
|
|
16
|
+
/** The version a post-initialize request is operating under. */
|
|
17
|
+
export declare function requestProtocol(request: Request): McpProtocolVersion;
|
|
2
18
|
export type ToolParam = {
|
|
3
19
|
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
4
20
|
description: string;
|
|
@@ -29,10 +45,33 @@ export type ToolAnnotations = {
|
|
|
29
45
|
/** Touches systems beyond this server. */
|
|
30
46
|
openWorldHint?: boolean;
|
|
31
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* What a handler can see of the call beyond its arguments, and its one channel
|
|
50
|
+
* back out. `_meta` carries everything that is *about* a call rather than in it
|
|
51
|
+
* — a payment receipt, a progress token — and a handler returning a bare value
|
|
52
|
+
* could reach none of it.
|
|
53
|
+
*/
|
|
54
|
+
export interface ToolContext {
|
|
55
|
+
/** `params._meta` as it arrived. */
|
|
56
|
+
meta: Record<string, unknown>;
|
|
57
|
+
/** Set a key on this result's `_meta`. */
|
|
58
|
+
setMeta: (key: string, value: unknown) => void;
|
|
59
|
+
/** The version negotiated for this request. */
|
|
60
|
+
protocolVersion: McpProtocolVersion;
|
|
61
|
+
/** The HTTP request, where the mount had one to give. */
|
|
62
|
+
request?: Request;
|
|
63
|
+
}
|
|
32
64
|
export interface McpTool {
|
|
33
65
|
name: string;
|
|
66
|
+
/** Human-facing label. 2025-06-18 promoted this out of `annotations`. */
|
|
67
|
+
title?: string;
|
|
34
68
|
description: string;
|
|
35
69
|
inputSchema: ToolSchema;
|
|
70
|
+
/**
|
|
71
|
+
* Declare it and the result carries `structuredContent` beside the text, so a
|
|
72
|
+
* client reads the answer instead of scraping prose for it.
|
|
73
|
+
*/
|
|
74
|
+
outputSchema?: ToolSchema;
|
|
36
75
|
annotations?: ToolAnnotations;
|
|
37
76
|
/** Surfaced as an A2A skill on the agent card. Not sent over MCP. */
|
|
38
77
|
skill?: {
|
|
@@ -40,7 +79,7 @@ export interface McpTool {
|
|
|
40
79
|
tags: string[];
|
|
41
80
|
examples?: string[];
|
|
42
81
|
};
|
|
43
|
-
handler: (args: Record<string, unknown
|
|
82
|
+
handler: (args: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>;
|
|
44
83
|
}
|
|
45
84
|
export interface McpResource {
|
|
46
85
|
uri: string;
|
|
@@ -90,6 +129,21 @@ export interface McpServerConfig {
|
|
|
90
129
|
maxBatch?: number;
|
|
91
130
|
/** Per-request analytics hook. Runs before dispatch; never blocks the response. */
|
|
92
131
|
onCall?: (request: Request, body: unknown) => void;
|
|
132
|
+
/**
|
|
133
|
+
* Envelope-level middleware: it may withhold entries before dispatch and
|
|
134
|
+
* merge its own responses back in afterwards. A payment gate has to sit here
|
|
135
|
+
* rather than in a handler, because the demand *replaces* the call and the
|
|
136
|
+
* receipt rides on the envelope. Without this hook one surface had rebuilt
|
|
137
|
+
* the whole of `mcpResponse` by hand to wrap it.
|
|
138
|
+
*/
|
|
139
|
+
gate?: (body: unknown) => Promise<EnvelopeGate>;
|
|
140
|
+
}
|
|
141
|
+
/** What a `gate` hands back: the body to dispatch, and how to finish. */
|
|
142
|
+
export interface EnvelopeGate {
|
|
143
|
+
body: RpcRequest | RpcRequest[];
|
|
144
|
+
/** Every entry withheld — dispatch nothing rather than an empty batch. */
|
|
145
|
+
empty: boolean;
|
|
146
|
+
finish(dispatched: object | object[] | null): Promise<object | object[] | null>;
|
|
93
147
|
}
|
|
94
148
|
/**
|
|
95
149
|
* A caller-fixable failure inside a handler (page not found, empty input).
|
|
@@ -100,14 +154,21 @@ export declare class McpToolError extends Error {
|
|
|
100
154
|
readonly details?: Record<string, unknown> | undefined;
|
|
101
155
|
constructor(message: string, details?: Record<string, unknown> | undefined);
|
|
102
156
|
}
|
|
103
|
-
|
|
157
|
+
/** One entry in a JSON-RPC envelope. Exported because `wiki-formant/x402`
|
|
158
|
+
* gates the envelope before `handleMcp` ever sees it. */
|
|
159
|
+
export type RpcRequest = {
|
|
104
160
|
jsonrpc: '2.0';
|
|
105
161
|
id: string | number | null;
|
|
106
162
|
method: string;
|
|
107
163
|
params?: unknown;
|
|
108
164
|
};
|
|
165
|
+
/** Everything the dispatcher needs that is not in the JSON-RPC entry itself. */
|
|
166
|
+
interface Dispatch {
|
|
167
|
+
protocolVersion: McpProtocolVersion;
|
|
168
|
+
request?: Request;
|
|
169
|
+
}
|
|
109
170
|
/** Dispatch a parsed body. `null` means notification-only — answer 202, not 200. */
|
|
110
|
-
export declare function handleMcp(body: RpcRequest | RpcRequest[], config: McpServerConfig): Promise<object | object[] | null>;
|
|
171
|
+
export declare function handleMcp(body: RpcRequest | RpcRequest[], config: McpServerConfig, dispatch?: Dispatch): Promise<object | object[] | null>;
|
|
111
172
|
export declare const MCP_CORS: Record<string, string>;
|
|
112
173
|
export declare function withMcpCors<T extends Response>(res: T): T;
|
|
113
174
|
export declare function mcpOptions(): Response;
|
|
@@ -117,7 +178,18 @@ export declare function mcpOptions(): Response;
|
|
|
117
178
|
* could not even read the refusal — answer it here, and say what to do instead.
|
|
118
179
|
*/
|
|
119
180
|
export declare function mcpGet(docsUrl?: string): Response;
|
|
120
|
-
/**
|
|
181
|
+
/**
|
|
182
|
+
* The 429 an MCP endpoint owes a caller that is over budget.
|
|
183
|
+
*
|
|
184
|
+
* Every surface here answered with a bare `{error: "..."}` — a string where the
|
|
185
|
+
* client's parser expects `{code, message}` — on the one response an agent
|
|
186
|
+
* meets precisely when it is working hard, and the one it most needs to read to
|
|
187
|
+
* back off correctly. It needs the CORS headers for the same reason the GET
|
|
188
|
+
* refusal does. The id is null because the limiter runs before the body is
|
|
189
|
+
* parsed, which is deliberate: an unparsed body cannot cost a query.
|
|
190
|
+
*/
|
|
191
|
+
export declare function mcpRateLimited(retryAfterSec: number, message?: string): Response;
|
|
192
|
+
/** The whole POST leg: parse, gate, track, dispatch, and answer with the right status. */
|
|
121
193
|
export declare function mcpResponse(request: Request, config: McpServerConfig): Promise<Response>;
|
|
122
194
|
export {};
|
|
123
195
|
//# sourceMappingURL=mcp.d.ts.map
|
package/dist/mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,oBAAoB,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAeA;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,qDAAsD,CAAC;AACzF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,EAAE,kBAA6C,CAAC;AAQjF,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,OAAO,GAAG,kBAAkB,CAExE;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,CAGpE;AAKD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,0CAA0C;IAC1C,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,+CAA+C;IAC/C,eAAe,EAAE,kBAAkB,CAAC;IACpC,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,qEAAqE;IACrE,KAAK,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC5D,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CACjD;AAED,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,GAAG,UAAU,EAAE,CAAC;IAChC,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;CACjF;AAED;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,KAAK;IAGnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAD1C,OAAO,EAAE,MAAM,EACN,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;CAK7C;AAED;0DAC0D;AAC1D,MAAM,MAAM,UAAU,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAoH1G,gFAAgF;AAChF,UAAU,QAAQ;IAChB,eAAe,EAAE,kBAAkB,CAAC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAuMD,oFAAoF;AACpF,wBAAsB,SAAS,CAC7B,IAAI,EAAE,UAAU,GAAG,UAAU,EAAE,EAC/B,MAAM,EAAE,eAAe,EACvB,QAAQ,GAAE,QAA+C,GACxD,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,CAiBnC;AAYD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAW3C,CAAC;AAEF,wBAAgB,WAAW,CAAC,CAAC,SAAS,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAGzD;AAED,wBAAgB,UAAU,IAAI,QAAQ,CAErC;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAQjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAUhF;AAED,0FAA0F;AAC1F,wBAAsB,WAAW,CAC/B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,QAAQ,CAAC,CA4CnB"}
|
package/dist/mcp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// mcp.ts — a minimal Model Context Protocol server over Streamable HTTP
|
|
2
|
-
// (JSON-RPC). Spec: https://modelcontextprotocol.io/specification/2025-
|
|
2
|
+
// (JSON-RPC). Spec: https://modelcontextprotocol.io/specification/2025-06-18
|
|
3
3
|
//
|
|
4
4
|
// Web-standard `Request`/`Response` only, so this runs unchanged on Next route
|
|
5
5
|
// handlers (NextResponse extends Response), Hono, Bun, Deno and workers.
|
|
@@ -12,7 +12,32 @@
|
|
|
12
12
|
//
|
|
13
13
|
// A caller-fixable mistake is a tool result with `isError`, never a -32603.
|
|
14
14
|
// That split is the one most implementations get wrong.
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The versions this transport speaks, newest first.
|
|
17
|
+
*
|
|
18
|
+
* This was a constant answered unconditionally, which is legal and still wrong:
|
|
19
|
+
* a client asking for 2025-06-18 was told 2025-03-26 and silently gave up tool
|
|
20
|
+
* titles, structured output and `_meta`. The last one is where a payment
|
|
21
|
+
* receipt rides, so the one surface that needed it rebuilt this module's HTTP
|
|
22
|
+
* shell by hand to reach it.
|
|
23
|
+
*/
|
|
24
|
+
export const MCP_PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'];
|
|
25
|
+
/** The newest version spoken here, and what an unrecognised ask falls back to. */
|
|
26
|
+
export const MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSIONS[0];
|
|
27
|
+
/** What a request carrying no `MCP-Protocol-Version` header means, per the spec. */
|
|
28
|
+
const ASSUMED_VERSION = '2025-03-26';
|
|
29
|
+
const speaks = (v) => MCP_PROTOCOL_VERSIONS.includes(v);
|
|
30
|
+
/** Echo the client's version when it is one we speak, else offer the newest. */
|
|
31
|
+
export function negotiateProtocol(requested) {
|
|
32
|
+
return speaks(requested) ? requested : MCP_PROTOCOL_VERSION;
|
|
33
|
+
}
|
|
34
|
+
/** The version a post-initialize request is operating under. */
|
|
35
|
+
export function requestProtocol(request) {
|
|
36
|
+
const header = request.headers.get('mcp-protocol-version');
|
|
37
|
+
return speaks(header) ? header : ASSUMED_VERSION;
|
|
38
|
+
}
|
|
39
|
+
/** JSON-RPC batching was removed in 2025-06-18; it stays legal below that. */
|
|
40
|
+
const allowsBatch = (v) => v !== '2025-06-18';
|
|
16
41
|
/**
|
|
17
42
|
* A caller-fixable failure inside a handler (page not found, empty input).
|
|
18
43
|
* Reported as a tool result with `isError` so the model sees the text and can
|
|
@@ -40,10 +65,10 @@ const rpcError = (id, code, message, data) => ({
|
|
|
40
65
|
id,
|
|
41
66
|
error: { code, message, ...(data ? { data } : {}) },
|
|
42
67
|
});
|
|
43
|
-
const toolText = (id, text, isError = false) => ({
|
|
68
|
+
const toolText = (id, text, isError = false, extra = {}) => ({
|
|
44
69
|
jsonrpc: '2.0',
|
|
45
70
|
id,
|
|
46
|
-
result: { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) },
|
|
71
|
+
result: { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}), ...extra },
|
|
47
72
|
});
|
|
48
73
|
/**
|
|
49
74
|
* Every problem with the call at once, each naming the field and its legal
|
|
@@ -107,11 +132,19 @@ const BASE_METHODS = ['initialize', 'ping', 'tools/list', 'tools/call'];
|
|
|
107
132
|
function methodsFor(config) {
|
|
108
133
|
return [
|
|
109
134
|
...BASE_METHODS,
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
// The list methods answer whether or not anything is registered: an empty
|
|
136
|
+
// list is a better answer to a client that asked than a -32601 it has to
|
|
137
|
+
// interpret. This enumeration used to omit them while the dispatch below
|
|
138
|
+
// answered them, so the error message contradicted the server describing
|
|
139
|
+
// itself.
|
|
140
|
+
'resources/list',
|
|
141
|
+
'resources/templates/list',
|
|
142
|
+
'prompts/list',
|
|
143
|
+
...(config.resources?.length ? ['resources/read'] : []),
|
|
144
|
+
...(config.prompts?.length ? ['prompts/get'] : []),
|
|
112
145
|
];
|
|
113
146
|
}
|
|
114
|
-
async function handleRpc(req, config) {
|
|
147
|
+
async function handleRpc(req, config, dispatch) {
|
|
115
148
|
const { id, method, params } = req;
|
|
116
149
|
const p = (params ?? {});
|
|
117
150
|
const resources = config.resources ?? [];
|
|
@@ -123,7 +156,9 @@ async function handleRpc(req, config) {
|
|
|
123
156
|
jsonrpc: '2.0',
|
|
124
157
|
id,
|
|
125
158
|
result: {
|
|
126
|
-
|
|
159
|
+
// Echo what the client asked for when we speak it. Answering a
|
|
160
|
+
// constant is what silently held every caller at 2025-03-26.
|
|
161
|
+
protocolVersion: negotiateProtocol(p.protocolVersion),
|
|
127
162
|
// Only advertise capabilities the config actually populates — an
|
|
128
163
|
// advertised `resources` whose list comes back empty reads as a bug
|
|
129
164
|
// to a client, not as honesty.
|
|
@@ -147,10 +182,12 @@ async function handleRpc(req, config) {
|
|
|
147
182
|
jsonrpc: '2.0',
|
|
148
183
|
id,
|
|
149
184
|
result: {
|
|
150
|
-
tools: config.tools.map(({ name, description, inputSchema, annotations }) => ({
|
|
185
|
+
tools: config.tools.map(({ name, title, description, inputSchema, outputSchema, annotations }) => ({
|
|
151
186
|
name,
|
|
187
|
+
...(title ? { title } : {}),
|
|
152
188
|
description,
|
|
153
189
|
inputSchema,
|
|
190
|
+
...(outputSchema ? { outputSchema } : {}),
|
|
154
191
|
...(annotations ? { annotations } : {}),
|
|
155
192
|
})),
|
|
156
193
|
},
|
|
@@ -168,6 +205,11 @@ async function handleRpc(req, config) {
|
|
|
168
205
|
})),
|
|
169
206
|
},
|
|
170
207
|
};
|
|
208
|
+
// Declared `resources` makes a client ask for templates too. There are
|
|
209
|
+
// none to give — every resource here is a fixed URI — but an empty list
|
|
210
|
+
// is the answer to that question, where -32601 reads as a broken server.
|
|
211
|
+
case 'resources/templates/list':
|
|
212
|
+
return { jsonrpc: '2.0', id, result: { resourceTemplates: [] } };
|
|
171
213
|
case 'resources/read': {
|
|
172
214
|
const { uri } = p;
|
|
173
215
|
const resource = uri ? resources.find(r => r.uri === uri) : undefined;
|
|
@@ -223,9 +265,28 @@ async function handleRpc(req, config) {
|
|
|
223
265
|
const invalid = validateArgs(tool, args);
|
|
224
266
|
if (invalid)
|
|
225
267
|
return toolText(id, invalid, true);
|
|
268
|
+
const out = {};
|
|
269
|
+
const ctx = {
|
|
270
|
+
meta: (p._meta ?? {}),
|
|
271
|
+
setMeta: (key, value) => {
|
|
272
|
+
out[key] = value;
|
|
273
|
+
},
|
|
274
|
+
protocolVersion: dispatch.protocolVersion,
|
|
275
|
+
request: dispatch.request,
|
|
276
|
+
};
|
|
226
277
|
try {
|
|
227
|
-
const data = await tool.handler(args);
|
|
228
|
-
|
|
278
|
+
const data = await tool.handler(args, ctx);
|
|
279
|
+
const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
280
|
+
// `structuredContent` only where a schema says what its shape is; the
|
|
281
|
+
// text block stays either way, because a client that never looked at
|
|
282
|
+
// outputSchema still has to be able to read the answer.
|
|
283
|
+
const structured = tool.outputSchema && data !== null && typeof data === 'object' && !Array.isArray(data)
|
|
284
|
+
? { structuredContent: data }
|
|
285
|
+
: {};
|
|
286
|
+
return toolText(id, text, false, {
|
|
287
|
+
...structured,
|
|
288
|
+
...(Object.keys(out).length ? { _meta: out } : {}),
|
|
289
|
+
});
|
|
229
290
|
}
|
|
230
291
|
catch (err) {
|
|
231
292
|
if (!(err instanceof McpToolError))
|
|
@@ -245,7 +306,7 @@ async function handleRpc(req, config) {
|
|
|
245
306
|
}
|
|
246
307
|
}
|
|
247
308
|
/** Dispatch a parsed body. `null` means notification-only — answer 202, not 200. */
|
|
248
|
-
export async function handleMcp(body, config) {
|
|
309
|
+
export async function handleMcp(body, config, dispatch = { protocolVersion: ASSUMED_VERSION }) {
|
|
249
310
|
const maxBatch = config.maxBatch ?? DEFAULT_MAX_BATCH;
|
|
250
311
|
const isBatch = Array.isArray(body);
|
|
251
312
|
if (isBatch && body.length > maxBatch) {
|
|
@@ -254,7 +315,7 @@ export async function handleMcp(body, config) {
|
|
|
254
315
|
if (isBatch && body.length === 0) {
|
|
255
316
|
return rpcError(null, -32600, 'Batch is empty. Send at least one JSON-RPC request.');
|
|
256
317
|
}
|
|
257
|
-
const responses = (await Promise.all((isBatch ? body : [body]).map(r => handleRpc(r, config)))).filter(Boolean);
|
|
318
|
+
const responses = (await Promise.all((isBatch ? body : [body]).map(r => handleRpc(r, config, dispatch)))).filter(Boolean);
|
|
258
319
|
return isBatch ? responses : (responses[0] ?? null);
|
|
259
320
|
}
|
|
260
321
|
// ---------------------------------------------------------------------------
|
|
@@ -270,6 +331,11 @@ export const MCP_CORS = {
|
|
|
270
331
|
'Access-Control-Allow-Origin': '*',
|
|
271
332
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
272
333
|
'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID',
|
|
334
|
+
// Allow-Headers governs what a browser may send; without Expose-Headers it
|
|
335
|
+
// may read none of what comes back. A browser client could not see the
|
|
336
|
+
// negotiated version, and could not see `Retry-After` on the 429 telling it
|
|
337
|
+
// how long to wait — which reads as a hang, not as a limit.
|
|
338
|
+
'Access-Control-Expose-Headers': 'Mcp-Session-Id, Mcp-Protocol-Version, Retry-After',
|
|
273
339
|
'Access-Control-Max-Age': '86400',
|
|
274
340
|
};
|
|
275
341
|
export function withMcpCors(res) {
|
|
@@ -293,8 +359,26 @@ export function mcpGet(docsUrl) {
|
|
|
293
359
|
headers: { ...MCP_CORS, Allow: 'POST, OPTIONS' },
|
|
294
360
|
});
|
|
295
361
|
}
|
|
296
|
-
/**
|
|
362
|
+
/**
|
|
363
|
+
* The 429 an MCP endpoint owes a caller that is over budget.
|
|
364
|
+
*
|
|
365
|
+
* Every surface here answered with a bare `{error: "..."}` — a string where the
|
|
366
|
+
* client's parser expects `{code, message}` — on the one response an agent
|
|
367
|
+
* meets precisely when it is working hard, and the one it most needs to read to
|
|
368
|
+
* back off correctly. It needs the CORS headers for the same reason the GET
|
|
369
|
+
* refusal does. The id is null because the limiter runs before the body is
|
|
370
|
+
* parsed, which is deliberate: an unparsed body cannot cost a query.
|
|
371
|
+
*/
|
|
372
|
+
export function mcpRateLimited(retryAfterSec, message) {
|
|
373
|
+
return Response.json(rpcError(null, -32000, message ?? `Rate limit exceeded. Retry in ${retryAfterSec} seconds.`, { retryAfterSec }), { status: 429, headers: { ...MCP_CORS, 'Retry-After': String(retryAfterSec) } });
|
|
374
|
+
}
|
|
375
|
+
/** The whole POST leg: parse, gate, track, dispatch, and answer with the right status. */
|
|
297
376
|
export async function mcpResponse(request, config) {
|
|
377
|
+
const protocolVersion = requestProtocol(request);
|
|
378
|
+
// Echoed on every response so a client can see which version it is actually
|
|
379
|
+
// being answered under, rather than inferring it from the initialize it sent
|
|
380
|
+
// some requests ago.
|
|
381
|
+
const headers = { ...MCP_CORS, 'MCP-Protocol-Version': protocolVersion };
|
|
298
382
|
let body;
|
|
299
383
|
try {
|
|
300
384
|
body = await request.json();
|
|
@@ -302,16 +386,25 @@ export async function mcpResponse(request, config) {
|
|
|
302
386
|
catch {
|
|
303
387
|
return Response.json(rpcError(null, -32700, 'Parse error: request body is not valid JSON.'), {
|
|
304
388
|
status: 400,
|
|
305
|
-
headers
|
|
389
|
+
headers,
|
|
306
390
|
});
|
|
307
391
|
}
|
|
392
|
+
if (Array.isArray(body) && !allowsBatch(protocolVersion)) {
|
|
393
|
+
return Response.json(rpcError(null, -32600, `JSON-RPC batching was removed in MCP ${protocolVersion}. Send one request per POST, or negotiate ${ASSUMED_VERSION} to keep batching.`), { status: 400, headers });
|
|
394
|
+
}
|
|
395
|
+
// Tracked on the original body, before a gate withholds anything: a call an
|
|
396
|
+
// agent walked away from is exactly the one worth counting.
|
|
308
397
|
config.onCall?.(request, body);
|
|
309
|
-
const
|
|
398
|
+
const gate = await config.gate?.(body);
|
|
399
|
+
const dispatched = gate?.empty
|
|
400
|
+
? null
|
|
401
|
+
: await handleMcp(gate ? gate.body : body, config, { protocolVersion, request });
|
|
402
|
+
const result = gate ? await gate.finish(dispatched) : dispatched;
|
|
310
403
|
// Notification-only input produces no response bodies; the spec requires a
|
|
311
404
|
// bare 202 there, not a 200 carrying a JSON `null`.
|
|
312
405
|
if (result == null || (Array.isArray(result) && !result.length)) {
|
|
313
|
-
return new Response(null, { status: 202, headers
|
|
406
|
+
return new Response(null, { status: 202, headers });
|
|
314
407
|
}
|
|
315
|
-
return Response.json(result, { headers
|
|
408
|
+
return Response.json(result, { headers });
|
|
316
409
|
}
|
|
317
410
|
//# sourceMappingURL=mcp.js.map
|