@toolgrain/mcp 0.2.0 → 0.3.0-alpha.1
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 +21 -0
- package/README.md +12 -5
- 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 +14 -2
- 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/pending-requests.d.ts +2 -1
- package/dist/pending-requests.d.ts.map +1 -1
- package/dist/pending-requests.js +17 -0
- package/dist/pending-requests.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 +11 -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 +19 -0
- package/dist/tools/progress.d.ts.map +1 -0
- package/dist/tools/progress.js +96 -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 +22 -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/pending-requests.ts +17 -1
- package/src/server.ts +76 -10
- package/src/tools/context.ts +12 -0
- package/src/tools/progress.ts +119 -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
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,15 @@ 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; updates are spaced at least 100 ms per invocation.
|
|
54
|
+
* Resolves without sending when no progress token or delivery channel exists,
|
|
55
|
+
* or after cancellation/completion. Active updates
|
|
56
|
+
* require finite values and strictly increasing progress; invalid updates reject.
|
|
57
|
+
* Delivery failure cancels the request and reaches transport diagnostics.
|
|
58
|
+
*/
|
|
59
|
+
readonly reportProgress: (update: ToolProgress) => Promise<void>;
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
/** Optional metadata, cancellation, and provider bindings for a direct-call context. */
|
|
@@ -94,10 +104,12 @@ export function createToolContext(
|
|
|
94
104
|
{ client, ...request }: ToolRequest,
|
|
95
105
|
auth?: AuthIdentity,
|
|
96
106
|
providers: ContextRegistry = createContextRegistry(),
|
|
107
|
+
reportProgress: ToolContext["reportProgress"] = () => Promise.resolve(),
|
|
97
108
|
): ToolContext {
|
|
98
109
|
const ctx = attachAuth(
|
|
99
110
|
{
|
|
100
111
|
signal,
|
|
112
|
+
reportProgress,
|
|
101
113
|
request: Object.freeze({
|
|
102
114
|
...request,
|
|
103
115
|
...(client === undefined
|
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
/** Tokens share the request-ID wire format, but have a separate lifetime. */
|
|
22
|
+
export function isProgressToken(token: unknown): token is ProgressToken {
|
|
23
|
+
return (
|
|
24
|
+
typeof token === "string" ||
|
|
25
|
+
(typeof token === "number" &&
|
|
26
|
+
Number.isFinite(token) &&
|
|
27
|
+
Number.isInteger(token))
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Cancellation must release the timer as well as the awaiting handler. */
|
|
32
|
+
function waitForProgress(milliseconds: number, signal: AbortSignal) {
|
|
33
|
+
return new Promise<void>((resolve, reject) => {
|
|
34
|
+
const abort = () => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
reject(signal.reason);
|
|
37
|
+
};
|
|
38
|
+
const timer = setTimeout(() => {
|
|
39
|
+
signal.removeEventListener("abort", abort);
|
|
40
|
+
resolve();
|
|
41
|
+
}, milliseconds);
|
|
42
|
+
if (signal.aborted) abort();
|
|
43
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** One invocation owns its reporter, ordering, and completion boundary. */
|
|
48
|
+
export function createProgressReporter(
|
|
49
|
+
requestSignal: AbortSignal,
|
|
50
|
+
token: unknown,
|
|
51
|
+
send?: (message: ProgressNotification) => void | Promise<void>,
|
|
52
|
+
) {
|
|
53
|
+
const lifetime = new AbortController();
|
|
54
|
+
const signal = AbortSignal.any([requestSignal, lifetime.signal]);
|
|
55
|
+
const validToken = isProgressToken(token);
|
|
56
|
+
let open = true;
|
|
57
|
+
let previous = Number.NEGATIVE_INFINITY;
|
|
58
|
+
let writes = Promise.resolve();
|
|
59
|
+
let lastSent = Number.NEGATIVE_INFINITY;
|
|
60
|
+
const report = (value: ToolProgress): Promise<void> => {
|
|
61
|
+
if (!open || signal.aborted || !validToken || !send)
|
|
62
|
+
return Promise.resolve();
|
|
63
|
+
const parsed = update.safeParse(value);
|
|
64
|
+
if (!parsed.success)
|
|
65
|
+
return Promise.reject(new TypeError("Invalid progress update"));
|
|
66
|
+
if (parsed.data.progress <= previous)
|
|
67
|
+
return Promise.reject(new TypeError("Progress must strictly increase"));
|
|
68
|
+
previous = parsed.data.progress;
|
|
69
|
+
const message: ProgressNotification = {
|
|
70
|
+
jsonrpc: "2.0",
|
|
71
|
+
method: "notifications/progress",
|
|
72
|
+
params: {
|
|
73
|
+
progress: parsed.data.progress,
|
|
74
|
+
...(parsed.data.total === undefined
|
|
75
|
+
? {}
|
|
76
|
+
: { total: parsed.data.total }),
|
|
77
|
+
...(parsed.data.message === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { message: parsed.data.message }),
|
|
80
|
+
progressToken: token as ProgressToken,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
const write = writes.then(async () => {
|
|
84
|
+
if (!signal.aborted) {
|
|
85
|
+
// Pace each operation without dropping updates or bypassing backpressure.
|
|
86
|
+
let remaining = 100 - (performance.now() - lastSent);
|
|
87
|
+
while (remaining > 0) {
|
|
88
|
+
await waitForProgress(Math.ceil(remaining), signal);
|
|
89
|
+
remaining = 100 - (performance.now() - lastSent);
|
|
90
|
+
}
|
|
91
|
+
await abortable(
|
|
92
|
+
Promise.resolve().then(() => {
|
|
93
|
+
if (!signal.aborted) {
|
|
94
|
+
lastSent = performance.now();
|
|
95
|
+
return send(message);
|
|
96
|
+
}
|
|
97
|
+
}),
|
|
98
|
+
signal,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
// The awaiting handler observes failures; keep the queue usable for cleanup.
|
|
103
|
+
writes = write.catch(() => {});
|
|
104
|
+
return write;
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
report,
|
|
108
|
+
async finish() {
|
|
109
|
+
open = false;
|
|
110
|
+
await writes;
|
|
111
|
+
},
|
|
112
|
+
close() {
|
|
113
|
+
open = false;
|
|
114
|
+
lifetime.abort(
|
|
115
|
+
new DOMException("Progress reporting closed", "AbortError"),
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
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
|