@toolgrain/mcp 0.2.0 → 0.3.0-alpha.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/CHANGELOG.md +14 -0
- package/README.md +6 -3
- package/dist/abort.d.ts.map +1 -0
- package/dist/abort.js.map +1 -0
- package/dist/auth/authorization.js +1 -1
- package/dist/auth/authorization.js.map +1 -1
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +5 -1
- package/dist/connection.js.map +1 -1
- package/dist/dispatcher.d.ts +4 -2
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/dispatcher.js.map +1 -1
- package/dist/execute-request.d.ts.map +1 -1
- package/dist/execute-request.js +9 -1
- package/dist/execute-request.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy/dispatcher.d.ts.map +1 -1
- package/dist/legacy/dispatcher.js +1 -1
- package/dist/legacy/dispatcher.js.map +1 -1
- package/dist/legacy/messages.d.ts +12 -4
- package/dist/legacy/messages.d.ts.map +1 -1
- package/dist/legacy/messages.js +2 -2
- package/dist/legacy/messages.js.map +1 -1
- package/dist/modern/dispatcher.d.ts.map +1 -1
- package/dist/modern/dispatcher.js +1 -1
- package/dist/modern/dispatcher.js.map +1 -1
- package/dist/modern/metadata.d.ts.map +1 -1
- package/dist/modern/metadata.js +1 -2
- package/dist/modern/metadata.js.map +1 -1
- package/dist/server.d.ts +8 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +49 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/context.d.ts +10 -1
- package/dist/tools/context.d.ts.map +1 -1
- package/dist/tools/context.js +2 -1
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/progress.d.ts +17 -0
- package/dist/tools/progress.d.ts.map +1 -0
- package/dist/tools/progress.js +62 -0
- package/dist/tools/progress.js.map +1 -0
- package/dist/transport.d.ts +4 -4
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js.map +1 -1
- package/package.json +3 -3
- package/src/auth/authorization.ts +1 -1
- package/src/connection.ts +7 -2
- package/src/dispatcher.ts +4 -1
- package/src/execute-request.ts +16 -1
- package/src/index.ts +1 -0
- package/src/legacy/dispatcher.ts +1 -0
- package/src/legacy/messages.ts +2 -2
- package/src/modern/dispatcher.ts +1 -0
- package/src/modern/metadata.ts +1 -2
- package/src/server.ts +76 -10
- package/src/tools/context.ts +11 -0
- package/src/tools/progress.ts +81 -0
- package/src/transport.ts +7 -4
- package/dist/auth/abort.d.ts.map +0 -1
- package/dist/auth/abort.js.map +0 -1
- /package/dist/{auth/abort.d.ts → abort.d.ts} +0 -0
- /package/dist/{auth/abort.js → abort.js} +0 -0
- /package/src/{auth/abort.ts → abort.ts} +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ProgressNotification } from "@toolgrain/protocol";
|
|
2
|
+
/** One progress update; values are finite numbers and progress must strictly increase. */
|
|
3
|
+
export interface ToolProgress {
|
|
4
|
+
/** Work completed so far, in application-defined units; the first value may be zero. */
|
|
5
|
+
readonly progress: number;
|
|
6
|
+
/** Optional total work, in the same units; omit when unknown. */
|
|
7
|
+
readonly total?: number;
|
|
8
|
+
/** Optional public status message sent to the client. */
|
|
9
|
+
readonly message?: string;
|
|
10
|
+
}
|
|
11
|
+
/** One invocation owns its reporter, ordering, and completion boundary. */
|
|
12
|
+
export declare function createProgressReporter(signal: AbortSignal, token: unknown, send?: (message: ProgressNotification) => void | Promise<void>): {
|
|
13
|
+
report: (value: ToolProgress) => Promise<void>;
|
|
14
|
+
finish(): Promise<void>;
|
|
15
|
+
close(): void;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/tools/progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAiB,MAAM,qBAAqB,CAAC;AAI/E,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAQD,2EAA2E;AAC3E,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;oBAUvC,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC;;;EA8CpD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { abortable } from "../abort.js";
|
|
3
|
+
const update = z.object({
|
|
4
|
+
progress: z.number().finite(),
|
|
5
|
+
total: z.number().finite().optional(),
|
|
6
|
+
message: z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
/** One invocation owns its reporter, ordering, and completion boundary. */
|
|
9
|
+
export function createProgressReporter(signal, token, send) {
|
|
10
|
+
const validToken = typeof token === "string" ||
|
|
11
|
+
(typeof token === "number" &&
|
|
12
|
+
Number.isFinite(token) &&
|
|
13
|
+
Number.isInteger(token));
|
|
14
|
+
let open = true;
|
|
15
|
+
let previous = Number.NEGATIVE_INFINITY;
|
|
16
|
+
let writes = Promise.resolve();
|
|
17
|
+
const report = (value) => {
|
|
18
|
+
if (!open || signal.aborted || !validToken || !send)
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
const parsed = update.safeParse(value);
|
|
21
|
+
if (!parsed.success)
|
|
22
|
+
return Promise.reject(new TypeError("Invalid progress update"));
|
|
23
|
+
if (parsed.data.progress <= previous)
|
|
24
|
+
return Promise.reject(new TypeError("Progress must strictly increase"));
|
|
25
|
+
previous = parsed.data.progress;
|
|
26
|
+
const message = {
|
|
27
|
+
jsonrpc: "2.0",
|
|
28
|
+
method: "notifications/progress",
|
|
29
|
+
params: {
|
|
30
|
+
progress: parsed.data.progress,
|
|
31
|
+
...(parsed.data.total === undefined
|
|
32
|
+
? {}
|
|
33
|
+
: { total: parsed.data.total }),
|
|
34
|
+
...(parsed.data.message === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: { message: parsed.data.message }),
|
|
37
|
+
progressToken: token,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const write = writes.then(async () => {
|
|
41
|
+
if (!signal.aborted)
|
|
42
|
+
await abortable(Promise.resolve().then(() => {
|
|
43
|
+
if (!signal.aborted)
|
|
44
|
+
return send(message);
|
|
45
|
+
}), signal);
|
|
46
|
+
});
|
|
47
|
+
// The awaiting handler observes failures; keep the queue usable for cleanup.
|
|
48
|
+
writes = write.catch(() => { });
|
|
49
|
+
return write;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
report,
|
|
53
|
+
async finish() {
|
|
54
|
+
open = false;
|
|
55
|
+
await writes;
|
|
56
|
+
},
|
|
57
|
+
close() {
|
|
58
|
+
open = false;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/tools/progress.ts"],"names":[],"mappings":"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"}
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProtocolResponse } from "@toolgrain/protocol";
|
|
1
|
+
import type { ProgressNotification, ProtocolResponse } from "@toolgrain/protocol";
|
|
2
2
|
import type { Server } from "./server.js";
|
|
3
3
|
/** Callbacks are synchronous; message processing runs independently. */
|
|
4
4
|
export interface TransportCallbacks {
|
|
@@ -21,10 +21,10 @@ export interface Transport {
|
|
|
21
21
|
*/
|
|
22
22
|
start(callbacks: TransportCallbacks): Promise<void>;
|
|
23
23
|
/**
|
|
24
|
-
* Send one
|
|
25
|
-
* @param message -
|
|
24
|
+
* Send one response or progress notification, resolving after write/backpressure handling.
|
|
25
|
+
* @param message - Envelope already converted for the client's protocol revision.
|
|
26
26
|
*/
|
|
27
|
-
send(message: ProtocolResponse): Promise<void>;
|
|
27
|
+
send(message: ProtocolResponse | ProgressNotification): Promise<void>;
|
|
28
28
|
/** Release adapter-owned resources; closing is terminal and must tolerate repeated calls. */
|
|
29
29
|
close(): Promise<void>;
|
|
30
30
|
}
|
package/dist/transport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IACjC,6FAA6F;IAC7F,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,oFAAoF;IACpF,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,yFAAyF;IACzF,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,4FAA4F;IAC5F,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,gFAAgF;AAChF,MAAM,WAAW,SAAS;IACxB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,SAAS,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,6FAA6F;IAC7F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB;;;;OAIG;IACH,KAAK,CACH,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,GAAG,SAAS,CAAC,GACzD,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,0FAA0F;IAC1F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,GAAG,gBAAgB,GACtC,SAAS,IAAI,gBAAgB,CAE/B"}
|
package/dist/transport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAqDA,MAAM,UAAU,kBAAkB,CAChC,SAAuC;IAEvC,OAAO,MAAM,IAAI,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC;AAC7D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toolgrain/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-alpha.0",
|
|
4
4
|
"description": "Define reusable, typed MCP tools and servers with Zod validation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public",
|
|
25
25
|
"registry": "https://registry.npmjs.org/",
|
|
26
|
-
"tag": "
|
|
26
|
+
"tag": "alpha"
|
|
27
27
|
},
|
|
28
28
|
"type": "module",
|
|
29
29
|
"exports": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@toolgrain/repo-tools": "0.0.0"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@toolgrain/protocol": "0.
|
|
62
|
+
"@toolgrain/protocol": "0.3.0-alpha.0"
|
|
63
63
|
},
|
|
64
64
|
"engines": {
|
|
65
65
|
"node": "^24.20.0",
|
package/src/connection.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
INVALID_REQUEST,
|
|
3
3
|
PARSE_ERROR,
|
|
4
|
+
type ProgressNotification,
|
|
4
5
|
type ProtocolResponse,
|
|
5
6
|
} from "@toolgrain/protocol";
|
|
6
7
|
import { type ErrorHandler, reportError } from "./diagnostics.js";
|
|
@@ -134,7 +135,7 @@ class Connection implements ConnectionLifecycle {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
private send(
|
|
137
|
-
message: ProtocolResponse,
|
|
138
|
+
message: ProtocolResponse | ProgressNotification,
|
|
138
139
|
isActive: () => boolean = () => this.isOpen(),
|
|
139
140
|
): Promise<void> {
|
|
140
141
|
const write = this.writes.then(async () => {
|
|
@@ -193,7 +194,11 @@ class Connection implements ConnectionLifecycle {
|
|
|
193
194
|
const response = await executeRequest(
|
|
194
195
|
this.dispatch,
|
|
195
196
|
request,
|
|
196
|
-
{
|
|
197
|
+
{
|
|
198
|
+
signal: pending.signal,
|
|
199
|
+
client: this.legacy.client(),
|
|
200
|
+
notify: (message) => this.send(message, pending.isActive),
|
|
201
|
+
},
|
|
197
202
|
(error) => this.report(error),
|
|
198
203
|
prepared.protocolVersion,
|
|
199
204
|
);
|
package/src/dispatcher.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type Implementation,
|
|
3
3
|
type JSONRPCErrorResponse,
|
|
4
4
|
LEGACY_PROTOCOL_VERSION,
|
|
5
|
+
type ProgressNotification,
|
|
5
6
|
type ProtocolResponse,
|
|
6
7
|
type SupportedProtocolVersion,
|
|
7
8
|
} from "@toolgrain/protocol";
|
|
@@ -16,7 +17,7 @@ import { createModernDispatcher } from "./modern/dispatcher.js";
|
|
|
16
17
|
import type { Request } from "./protocol/messages.js";
|
|
17
18
|
import { validateVersionContext } from "./protocol/version-metadata.js";
|
|
18
19
|
import type { ToolAdapter } from "./tools/adapter.js";
|
|
19
|
-
import type { ToolClient } from "./tools/context.js";
|
|
20
|
+
import type { ToolClient, ToolContext } from "./tools/context.js";
|
|
20
21
|
|
|
21
22
|
/** Per-request execution state supplied by the connection or independent caller. */
|
|
22
23
|
export interface RequestScope {
|
|
@@ -24,6 +25,8 @@ export interface RequestScope {
|
|
|
24
25
|
/** Client details negotiated by legacy initialization, when the connection has them. */
|
|
25
26
|
client?: ToolClient | undefined;
|
|
26
27
|
auth?: AuthIdentity;
|
|
28
|
+
notify?: (message: ProgressNotification) => void | Promise<void>;
|
|
29
|
+
reportProgress?: ToolContext["reportProgress"];
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export type Dispatcher = (
|
package/src/execute-request.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type ErrorHandler, reportError } from "./diagnostics.js";
|
|
|
7
7
|
import type { Dispatcher, RequestScope } from "./dispatcher.js";
|
|
8
8
|
import type { Request } from "./protocol/messages.js";
|
|
9
9
|
import { ProtocolError, protocolError } from "./protocol/responses.js";
|
|
10
|
+
import { createProgressReporter } from "./tools/progress.js";
|
|
10
11
|
|
|
11
12
|
/** Shared dispatch boundary for connection-scoped and independent requests. */
|
|
12
13
|
export async function executeRequest(
|
|
@@ -17,8 +18,20 @@ export async function executeRequest(
|
|
|
17
18
|
protocolVersion?: SupportedProtocolVersion,
|
|
18
19
|
): Promise<ProtocolResponse | undefined> {
|
|
19
20
|
const isActive = () => !scope.signal.aborted;
|
|
21
|
+
let progress: ReturnType<typeof createProgressReporter> | undefined;
|
|
20
22
|
try {
|
|
21
|
-
|
|
23
|
+
progress = createProgressReporter(
|
|
24
|
+
scope.signal,
|
|
25
|
+
(request.params?._meta as Record<string, unknown> | undefined)
|
|
26
|
+
?.progressToken,
|
|
27
|
+
scope.notify,
|
|
28
|
+
);
|
|
29
|
+
const response = await dispatch(
|
|
30
|
+
request,
|
|
31
|
+
{ ...scope, reportProgress: progress.report },
|
|
32
|
+
protocolVersion,
|
|
33
|
+
);
|
|
34
|
+
await progress.finish();
|
|
22
35
|
return isActive() ? response : undefined;
|
|
23
36
|
} catch (error) {
|
|
24
37
|
if (!isActive()) return;
|
|
@@ -31,5 +44,7 @@ export async function executeRequest(
|
|
|
31
44
|
request.id,
|
|
32
45
|
undefined,
|
|
33
46
|
);
|
|
47
|
+
} finally {
|
|
48
|
+
progress?.close();
|
|
34
49
|
}
|
|
35
50
|
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export { toolContext } from "./tools/context.js";
|
|
|
26
26
|
export type { Tool, ToolDefinition, ToolMetadata } from "./tools/definition.js";
|
|
27
27
|
export { tool } from "./tools/definition.js";
|
|
28
28
|
export { ToolNameError } from "./tools/name.js";
|
|
29
|
+
export type { ToolProgress } from "./tools/progress.js";
|
|
29
30
|
export type { ToolMap } from "./tools/registry.js";
|
|
30
31
|
export type {
|
|
31
32
|
McpErrorResult,
|
package/src/legacy/dispatcher.ts
CHANGED
package/src/legacy/messages.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { object } from "../protocol/messages.js";
|
|
2
|
+
import { object, requestId } from "../protocol/messages.js";
|
|
3
3
|
|
|
4
4
|
export const commonParams = z.looseObject({
|
|
5
|
-
_meta:
|
|
5
|
+
_meta: z.looseObject({ progressToken: requestId.optional() }).optional(),
|
|
6
6
|
});
|
|
7
7
|
export const initializeParams = commonParams.extend({
|
|
8
8
|
protocolVersion: z.string(),
|
package/src/modern/dispatcher.ts
CHANGED
package/src/modern/metadata.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { icon } from "../protocol/icon.js";
|
|
3
|
-
import { object } from "../protocol/messages.js";
|
|
3
|
+
import { object, requestId } from "../protocol/messages.js";
|
|
4
4
|
|
|
5
5
|
const capabilityObject = z.record(z.string(), z.json());
|
|
6
|
-
const requestId = z.union([z.string(), z.number().finite()]);
|
|
7
6
|
const capabilities = z.looseObject({
|
|
8
7
|
experimental: z.record(z.string(), capabilityObject).optional(),
|
|
9
8
|
roots: object.optional(),
|
package/src/server.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ProgressNotification,
|
|
2
3
|
ProtocolResponse,
|
|
3
4
|
Tool as ProtocolTool,
|
|
4
5
|
SupportedProtocolVersion,
|
|
5
6
|
} from "@toolgrain/protocol";
|
|
7
|
+
import { abortable } from "./abort.js";
|
|
6
8
|
import { createAuthorization } from "./auth/authorization.js";
|
|
7
9
|
import { AuthError, isAuthError } from "./auth/errors.js";
|
|
8
10
|
import type { AuthMetadata, AuthProvider, AuthScope } from "./auth/types.js";
|
|
@@ -21,6 +23,13 @@ import {
|
|
|
21
23
|
type Transport,
|
|
22
24
|
} from "./transport.js";
|
|
23
25
|
|
|
26
|
+
// Internal adapter contract: final delivery remains part of the active request.
|
|
27
|
+
// A global symbol preserves the hook across compatible package copies.
|
|
28
|
+
const responseDelivery = Symbol.for("@toolgrain/mcp/response-delivery/v1");
|
|
29
|
+
interface ResponseDeliveryOptions {
|
|
30
|
+
[responseDelivery]?: (response: ProtocolResponse) => void | Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
/** Tool registrations, identity, and shared configuration for an MCP server. */
|
|
25
34
|
export interface ServerOptions<Tools extends ToolMap = ToolMap> {
|
|
26
35
|
/** Server implementation name advertised to clients. */
|
|
@@ -54,6 +63,13 @@ export interface HandleOptions {
|
|
|
54
63
|
protocolVersion?: SupportedProtocolVersion;
|
|
55
64
|
/** Single-use scope issued by this server's `authorize()`; dispose it in `finally`. */
|
|
56
65
|
auth?: AuthScope;
|
|
66
|
+
/**
|
|
67
|
+
* Deliver progress notifications for this request, honoring asynchronous
|
|
68
|
+
* backpressure. Omit for a one-response caller; progress then becomes a no-op.
|
|
69
|
+
* The caller owns framing and must abort `signal` when its response channel closes.
|
|
70
|
+
* Callback failure cancels this invocation and reaches `onError(error)` without tool details.
|
|
71
|
+
*/
|
|
72
|
+
onNotification?: (message: ProgressNotification) => void | Promise<void>;
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
/** An MCP dispatcher with one optional transport attachment and terminal shutdown. */
|
|
@@ -147,6 +163,9 @@ export function createServer<const Tools extends ToolMap>(
|
|
|
147
163
|
running.add(controller);
|
|
148
164
|
return {
|
|
149
165
|
signal: controller.signal,
|
|
166
|
+
abort(reason: unknown) {
|
|
167
|
+
controller.abort(reason);
|
|
168
|
+
},
|
|
150
169
|
finish() {
|
|
151
170
|
signal?.removeEventListener("abort", abort);
|
|
152
171
|
running.delete(controller);
|
|
@@ -185,10 +204,8 @@ export function createServer<const Tools extends ToolMap>(
|
|
|
185
204
|
throw error;
|
|
186
205
|
}
|
|
187
206
|
},
|
|
188
|
-
async handle(
|
|
189
|
-
|
|
190
|
-
{ signal, protocolVersion, auth }: HandleOptions = {},
|
|
191
|
-
) {
|
|
207
|
+
async handle(message, handleOptions: HandleOptions = {}) {
|
|
208
|
+
const { signal, protocolVersion, auth, onNotification } = handleOptions;
|
|
192
209
|
if (closed) throw new Error("Server is closed");
|
|
193
210
|
const identity = authorization.consume(auth);
|
|
194
211
|
if (auth?.signal.aborted) return;
|
|
@@ -204,13 +221,62 @@ export function createServer<const Tools extends ToolMap>(
|
|
|
204
221
|
? AbortSignal.any([pending.signal, auth.signal])
|
|
205
222
|
: pending.signal;
|
|
206
223
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
224
|
+
const response = await abortable(
|
|
225
|
+
executeRequest(
|
|
226
|
+
dispatch,
|
|
227
|
+
incoming.request,
|
|
228
|
+
{
|
|
229
|
+
signal: combined,
|
|
230
|
+
...(identity ? { auth: identity } : {}),
|
|
231
|
+
...(onNotification
|
|
232
|
+
? {
|
|
233
|
+
notify: async (message: ProgressNotification) => {
|
|
234
|
+
try {
|
|
235
|
+
await onNotification(message);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (combined.aborted) return;
|
|
238
|
+
reportError(options.onError, error);
|
|
239
|
+
pending.abort(
|
|
240
|
+
new DOMException(
|
|
241
|
+
"Progress delivery failed",
|
|
242
|
+
"AbortError",
|
|
243
|
+
),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
: {}),
|
|
249
|
+
},
|
|
250
|
+
options.onError,
|
|
251
|
+
protocolVersion,
|
|
252
|
+
),
|
|
253
|
+
combined,
|
|
213
254
|
);
|
|
255
|
+
const deliver = (handleOptions as ResponseDeliveryOptions)[
|
|
256
|
+
responseDelivery
|
|
257
|
+
];
|
|
258
|
+
if (response && deliver) {
|
|
259
|
+
try {
|
|
260
|
+
await abortable(
|
|
261
|
+
Promise.resolve().then(() => {
|
|
262
|
+
combined.throwIfAborted();
|
|
263
|
+
return deliver(response);
|
|
264
|
+
}),
|
|
265
|
+
combined,
|
|
266
|
+
);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (!combined.aborted) {
|
|
269
|
+
reportError(options.onError, error);
|
|
270
|
+
pending.abort(
|
|
271
|
+
new DOMException("Response delivery failed", "AbortError"),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return response;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (!combined.aborted) throw error;
|
|
214
280
|
} finally {
|
|
215
281
|
pending.finish();
|
|
216
282
|
}
|
package/src/tools/context.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "../context/registry.js";
|
|
19
19
|
import { createContextScope } from "../context/scope.js";
|
|
20
20
|
import type { DeepReadonly } from "./json-value.js";
|
|
21
|
+
import type { ToolProgress } from "./progress.js";
|
|
21
22
|
|
|
22
23
|
/** What the client reported about itself; unverified, so not a basis for security decisions. */
|
|
23
24
|
export interface ToolClient {
|
|
@@ -47,6 +48,14 @@ export interface ToolContext {
|
|
|
47
48
|
readonly signal: AbortSignal;
|
|
48
49
|
/** Frozen request metadata; contains no verified authorization identity. */
|
|
49
50
|
readonly request: ToolRequest;
|
|
51
|
+
/**
|
|
52
|
+
* Report request-scoped progress; safe to pass to helpers without binding.
|
|
53
|
+
* Await delivery/backpressure. Resolves without sending when no progress token
|
|
54
|
+
* or delivery channel exists, or after cancellation/completion. Active updates
|
|
55
|
+
* require finite values and strictly increasing progress; invalid updates reject.
|
|
56
|
+
* Delivery failure cancels the request and reaches transport diagnostics.
|
|
57
|
+
*/
|
|
58
|
+
readonly reportProgress: (update: ToolProgress) => Promise<void>;
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
/** Optional metadata, cancellation, and provider bindings for a direct-call context. */
|
|
@@ -94,10 +103,12 @@ export function createToolContext(
|
|
|
94
103
|
{ client, ...request }: ToolRequest,
|
|
95
104
|
auth?: AuthIdentity,
|
|
96
105
|
providers: ContextRegistry = createContextRegistry(),
|
|
106
|
+
reportProgress: ToolContext["reportProgress"] = () => Promise.resolve(),
|
|
97
107
|
): ToolContext {
|
|
98
108
|
const ctx = attachAuth(
|
|
99
109
|
{
|
|
100
110
|
signal,
|
|
111
|
+
reportProgress,
|
|
101
112
|
request: Object.freeze({
|
|
102
113
|
...request,
|
|
103
114
|
...(client === undefined
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { ProgressNotification, ProgressToken } from "@toolgrain/protocol";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { abortable } from "../abort.js";
|
|
4
|
+
|
|
5
|
+
/** One progress update; values are finite numbers and progress must strictly increase. */
|
|
6
|
+
export interface ToolProgress {
|
|
7
|
+
/** Work completed so far, in application-defined units; the first value may be zero. */
|
|
8
|
+
readonly progress: number;
|
|
9
|
+
/** Optional total work, in the same units; omit when unknown. */
|
|
10
|
+
readonly total?: number;
|
|
11
|
+
/** Optional public status message sent to the client. */
|
|
12
|
+
readonly message?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const update = z.object({
|
|
16
|
+
progress: z.number().finite(),
|
|
17
|
+
total: z.number().finite().optional(),
|
|
18
|
+
message: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/** One invocation owns its reporter, ordering, and completion boundary. */
|
|
22
|
+
export function createProgressReporter(
|
|
23
|
+
signal: AbortSignal,
|
|
24
|
+
token: unknown,
|
|
25
|
+
send?: (message: ProgressNotification) => void | Promise<void>,
|
|
26
|
+
) {
|
|
27
|
+
const validToken =
|
|
28
|
+
typeof token === "string" ||
|
|
29
|
+
(typeof token === "number" &&
|
|
30
|
+
Number.isFinite(token) &&
|
|
31
|
+
Number.isInteger(token));
|
|
32
|
+
let open = true;
|
|
33
|
+
let previous = Number.NEGATIVE_INFINITY;
|
|
34
|
+
let writes = Promise.resolve();
|
|
35
|
+
const report = (value: ToolProgress): Promise<void> => {
|
|
36
|
+
if (!open || signal.aborted || !validToken || !send)
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
const parsed = update.safeParse(value);
|
|
39
|
+
if (!parsed.success)
|
|
40
|
+
return Promise.reject(new TypeError("Invalid progress update"));
|
|
41
|
+
if (parsed.data.progress <= previous)
|
|
42
|
+
return Promise.reject(new TypeError("Progress must strictly increase"));
|
|
43
|
+
previous = parsed.data.progress;
|
|
44
|
+
const message: ProgressNotification = {
|
|
45
|
+
jsonrpc: "2.0",
|
|
46
|
+
method: "notifications/progress",
|
|
47
|
+
params: {
|
|
48
|
+
progress: parsed.data.progress,
|
|
49
|
+
...(parsed.data.total === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { total: parsed.data.total }),
|
|
52
|
+
...(parsed.data.message === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { message: parsed.data.message }),
|
|
55
|
+
progressToken: token as ProgressToken,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const write = writes.then(async () => {
|
|
59
|
+
if (!signal.aborted)
|
|
60
|
+
await abortable(
|
|
61
|
+
Promise.resolve().then(() => {
|
|
62
|
+
if (!signal.aborted) return send(message);
|
|
63
|
+
}),
|
|
64
|
+
signal,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
// The awaiting handler observes failures; keep the queue usable for cleanup.
|
|
68
|
+
writes = write.catch(() => {});
|
|
69
|
+
return write;
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
report,
|
|
73
|
+
async finish() {
|
|
74
|
+
open = false;
|
|
75
|
+
await writes;
|
|
76
|
+
},
|
|
77
|
+
close() {
|
|
78
|
+
open = false;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
package/src/transport.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ProgressNotification,
|
|
3
|
+
ProtocolResponse,
|
|
4
|
+
} from "@toolgrain/protocol";
|
|
2
5
|
import type { Server } from "./server.js";
|
|
3
6
|
|
|
4
7
|
/** Callbacks are synchronous; message processing runs independently. */
|
|
@@ -23,10 +26,10 @@ export interface Transport {
|
|
|
23
26
|
*/
|
|
24
27
|
start(callbacks: TransportCallbacks): Promise<void>;
|
|
25
28
|
/**
|
|
26
|
-
* Send one
|
|
27
|
-
* @param message -
|
|
29
|
+
* Send one response or progress notification, resolving after write/backpressure handling.
|
|
30
|
+
* @param message - Envelope already converted for the client's protocol revision.
|
|
28
31
|
*/
|
|
29
|
-
send(message: ProtocolResponse): Promise<void>;
|
|
32
|
+
send(message: ProtocolResponse | ProgressNotification): Promise<void>;
|
|
30
33
|
/** Release adapter-owned resources; closing is terminal and must tolerate repeated calls. */
|
|
31
34
|
close(): Promise<void>;
|
|
32
35
|
}
|
package/dist/auth/abort.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../src/auth/abort.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,CAAC,EACzB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAChB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,CAAC,CAAC,CASZ"}
|
package/dist/auth/abort.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/auth/abort.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,UAAU,SAAS,CACvB,IAAgB,EAChB,MAAmB;IAEnB,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,MAAM,CAAC,OAAO;YAAE,KAAK,EAAE,CAAC;;YACvB,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,IAAI;aACD,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC;aACrB,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|