@toolgrain/mcp 0.0.0-stage → 0.1.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.
Files changed (248) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +85 -2
  3. package/dist/auth/abort.d.ts +3 -0
  4. package/dist/auth/abort.d.ts.map +1 -0
  5. package/dist/auth/abort.js +14 -0
  6. package/dist/auth/abort.js.map +1 -0
  7. package/dist/auth/audience.d.ts +3 -0
  8. package/dist/auth/audience.d.ts.map +1 -0
  9. package/dist/auth/audience.js +17 -0
  10. package/dist/auth/audience.js.map +1 -0
  11. package/dist/auth/authorization.d.ts +7 -0
  12. package/dist/auth/authorization.d.ts.map +1 -0
  13. package/dist/auth/authorization.js +58 -0
  14. package/dist/auth/authorization.js.map +1 -0
  15. package/dist/auth/context.d.ts +8 -0
  16. package/dist/auth/context.d.ts.map +1 -0
  17. package/dist/auth/context.js +37 -0
  18. package/dist/auth/context.js.map +1 -0
  19. package/dist/auth/errors.d.ts +14 -0
  20. package/dist/auth/errors.d.ts.map +1 -0
  21. package/dist/auth/errors.js +23 -0
  22. package/dist/auth/errors.js.map +1 -0
  23. package/dist/auth/types.d.ts +28 -0
  24. package/dist/auth/types.d.ts.map +1 -0
  25. package/dist/auth/types.js +2 -0
  26. package/dist/auth/types.js.map +1 -0
  27. package/dist/auth/url.d.ts +3 -0
  28. package/dist/auth/url.d.ts.map +1 -0
  29. package/dist/auth/url.js +18 -0
  30. package/dist/auth/url.js.map +1 -0
  31. package/dist/connection.d.ts +12 -0
  32. package/dist/connection.d.ts.map +1 -0
  33. package/dist/connection.js +151 -0
  34. package/dist/connection.js.map +1 -0
  35. package/dist/context/access.d.ts +7 -0
  36. package/dist/context/access.d.ts.map +1 -0
  37. package/dist/context/access.js +22 -0
  38. package/dist/context/access.js.map +1 -0
  39. package/dist/context/definition.d.ts +24 -0
  40. package/dist/context/definition.d.ts.map +1 -0
  41. package/dist/context/definition.js +57 -0
  42. package/dist/context/definition.js.map +1 -0
  43. package/dist/context/registry.d.ts +5 -0
  44. package/dist/context/registry.d.ts.map +1 -0
  45. package/dist/context/registry.js +15 -0
  46. package/dist/context/registry.js.map +1 -0
  47. package/dist/context/scope.d.ts +5 -0
  48. package/dist/context/scope.d.ts.map +1 -0
  49. package/dist/context/scope.js +56 -0
  50. package/dist/context/scope.js.map +1 -0
  51. package/dist/diagnostics.d.ts +10 -0
  52. package/dist/diagnostics.d.ts.map +1 -0
  53. package/dist/diagnostics.js +13 -0
  54. package/dist/diagnostics.js.map +1 -0
  55. package/dist/dispatcher.d.ts +19 -0
  56. package/dist/dispatcher.d.ts.map +1 -0
  57. package/dist/dispatcher.js +23 -0
  58. package/dist/dispatcher.js.map +1 -0
  59. package/dist/execute-request.d.ts +7 -0
  60. package/dist/execute-request.d.ts.map +1 -0
  61. package/dist/execute-request.js +20 -0
  62. package/dist/execute-request.js.map +1 -0
  63. package/dist/index.d.ts +22 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +12 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/legacy/connection.d.ts +26 -0
  68. package/dist/legacy/connection.d.ts.map +1 -0
  69. package/dist/legacy/connection.js +73 -0
  70. package/dist/legacy/connection.js.map +1 -0
  71. package/dist/legacy/dispatcher.d.ts +10 -0
  72. package/dist/legacy/dispatcher.d.ts.map +1 -0
  73. package/dist/legacy/dispatcher.js +41 -0
  74. package/dist/legacy/dispatcher.js.map +1 -0
  75. package/dist/legacy/handlers.d.ts +12 -0
  76. package/dist/legacy/handlers.d.ts.map +1 -0
  77. package/dist/legacy/handlers.js +44 -0
  78. package/dist/legacy/handlers.js.map +1 -0
  79. package/dist/legacy/messages.d.ts +31 -0
  80. package/dist/legacy/messages.d.ts.map +1 -0
  81. package/dist/legacy/messages.js +27 -0
  82. package/dist/legacy/messages.js.map +1 -0
  83. package/dist/legacy/responses.d.ts +5 -0
  84. package/dist/legacy/responses.d.ts.map +1 -0
  85. package/dist/legacy/responses.js +8 -0
  86. package/dist/legacy/responses.js.map +1 -0
  87. package/dist/legacy/results.d.ts +5 -0
  88. package/dist/legacy/results.d.ts.map +1 -0
  89. package/dist/legacy/results.js +19 -0
  90. package/dist/legacy/results.js.map +1 -0
  91. package/dist/modern/dispatcher.d.ts +7 -0
  92. package/dist/modern/dispatcher.d.ts.map +1 -0
  93. package/dist/modern/dispatcher.js +37 -0
  94. package/dist/modern/dispatcher.js.map +1 -0
  95. package/dist/modern/handlers.d.ts +9 -0
  96. package/dist/modern/handlers.d.ts.map +1 -0
  97. package/dist/modern/handlers.js +36 -0
  98. package/dist/modern/handlers.js.map +1 -0
  99. package/dist/modern/messages.d.ts +142 -0
  100. package/dist/modern/messages.d.ts.map +1 -0
  101. package/dist/modern/messages.js +14 -0
  102. package/dist/modern/messages.js.map +1 -0
  103. package/dist/modern/metadata.d.ts +45 -0
  104. package/dist/modern/metadata.d.ts.map +1 -0
  105. package/dist/modern/metadata.js +49 -0
  106. package/dist/modern/metadata.js.map +1 -0
  107. package/dist/modern/responses.d.ts +3 -0
  108. package/dist/modern/responses.d.ts.map +1 -0
  109. package/dist/modern/responses.js +15 -0
  110. package/dist/modern/responses.js.map +1 -0
  111. package/dist/modern/results.d.ts +4 -0
  112. package/dist/modern/results.d.ts.map +1 -0
  113. package/dist/modern/results.js +4 -0
  114. package/dist/modern/results.js.map +1 -0
  115. package/dist/pending-requests.d.ts +12 -0
  116. package/dist/pending-requests.d.ts.map +1 -0
  117. package/dist/pending-requests.js +30 -0
  118. package/dist/pending-requests.js.map +1 -0
  119. package/dist/protocol/decode.d.ts +21 -0
  120. package/dist/protocol/decode.d.ts.map +1 -0
  121. package/dist/protocol/decode.js +40 -0
  122. package/dist/protocol/decode.js.map +1 -0
  123. package/dist/protocol/icon.d.ts +11 -0
  124. package/dist/protocol/icon.d.ts.map +1 -0
  125. package/dist/protocol/icon.js +8 -0
  126. package/dist/protocol/icon.js.map +1 -0
  127. package/dist/protocol/messages.d.ts +19 -0
  128. package/dist/protocol/messages.d.ts.map +1 -0
  129. package/dist/protocol/messages.js +21 -0
  130. package/dist/protocol/messages.js.map +1 -0
  131. package/dist/protocol/responses.d.ts +9 -0
  132. package/dist/protocol/responses.d.ts.map +1 -0
  133. package/dist/protocol/responses.js +20 -0
  134. package/dist/protocol/responses.js.map +1 -0
  135. package/dist/protocol/version-metadata.d.ts +7 -0
  136. package/dist/protocol/version-metadata.d.ts.map +1 -0
  137. package/dist/protocol/version-metadata.js +21 -0
  138. package/dist/protocol/version-metadata.js.map +1 -0
  139. package/dist/server.d.ts +35 -0
  140. package/dist/server.d.ts.map +1 -0
  141. package/dist/server.js +101 -0
  142. package/dist/server.js.map +1 -0
  143. package/dist/tools/adapter.d.ts +12 -0
  144. package/dist/tools/adapter.d.ts.map +1 -0
  145. package/dist/tools/adapter.js +45 -0
  146. package/dist/tools/adapter.js.map +1 -0
  147. package/dist/tools/call.d.ts +8 -0
  148. package/dist/tools/call.d.ts.map +1 -0
  149. package/dist/tools/call.js +9 -0
  150. package/dist/tools/call.js.map +1 -0
  151. package/dist/tools/content.d.ts +181 -0
  152. package/dist/tools/content.d.ts.map +1 -0
  153. package/dist/tools/content.js +54 -0
  154. package/dist/tools/content.js.map +1 -0
  155. package/dist/tools/context.d.ts +36 -0
  156. package/dist/tools/context.d.ts.map +1 -0
  157. package/dist/tools/context.js +50 -0
  158. package/dist/tools/context.js.map +1 -0
  159. package/dist/tools/definition.d.ts +55 -0
  160. package/dist/tools/definition.d.ts.map +1 -0
  161. package/dist/tools/definition.js +44 -0
  162. package/dist/tools/definition.js.map +1 -0
  163. package/dist/tools/errors.d.ts +11 -0
  164. package/dist/tools/errors.d.ts.map +1 -0
  165. package/dist/tools/errors.js +19 -0
  166. package/dist/tools/errors.js.map +1 -0
  167. package/dist/tools/json-value.d.ts +15 -0
  168. package/dist/tools/json-value.d.ts.map +1 -0
  169. package/dist/tools/json-value.js +42 -0
  170. package/dist/tools/json-value.js.map +1 -0
  171. package/dist/tools/name.d.ts +6 -0
  172. package/dist/tools/name.d.ts.map +1 -0
  173. package/dist/tools/name.js +13 -0
  174. package/dist/tools/name.js.map +1 -0
  175. package/dist/tools/registry.d.ts +14 -0
  176. package/dist/tools/registry.d.ts.map +1 -0
  177. package/dist/tools/registry.js +31 -0
  178. package/dist/tools/registry.js.map +1 -0
  179. package/dist/tools/results.d.ts +14 -0
  180. package/dist/tools/results.d.ts.map +1 -0
  181. package/dist/tools/results.js +71 -0
  182. package/dist/tools/results.js.map +1 -0
  183. package/dist/tools/rich-result.d.ts +44 -0
  184. package/dist/tools/rich-result.d.ts.map +1 -0
  185. package/dist/tools/rich-result.js +24 -0
  186. package/dist/tools/rich-result.js.map +1 -0
  187. package/dist/tools/schema.d.ts +18 -0
  188. package/dist/tools/schema.d.ts.map +1 -0
  189. package/dist/tools/schema.js +70 -0
  190. package/dist/tools/schema.js.map +1 -0
  191. package/dist/transport-ownership.d.ts +4 -0
  192. package/dist/transport-ownership.d.ts.map +1 -0
  193. package/dist/transport-ownership.js +13 -0
  194. package/dist/transport-ownership.js.map +1 -0
  195. package/dist/transport.d.ts +24 -0
  196. package/dist/transport.d.ts.map +1 -0
  197. package/dist/transport.js +4 -0
  198. package/dist/transport.js.map +1 -0
  199. package/package.json +65 -4
  200. package/src/auth/abort.ts +14 -0
  201. package/src/auth/audience.ts +22 -0
  202. package/src/auth/authorization.ts +81 -0
  203. package/src/auth/context.ts +43 -0
  204. package/src/auth/errors.ts +35 -0
  205. package/src/auth/types.ts +34 -0
  206. package/src/auth/url.ts +22 -0
  207. package/src/connection.ts +207 -0
  208. package/src/context/access.ts +36 -0
  209. package/src/context/definition.ts +100 -0
  210. package/src/context/registry.ts +25 -0
  211. package/src/context/scope.ts +82 -0
  212. package/src/diagnostics.ts +25 -0
  213. package/src/dispatcher.ts +70 -0
  214. package/src/execute-request.ts +35 -0
  215. package/src/index.ts +41 -0
  216. package/src/legacy/connection.ts +101 -0
  217. package/src/legacy/dispatcher.ts +72 -0
  218. package/src/legacy/handlers.ts +76 -0
  219. package/src/legacy/messages.ts +27 -0
  220. package/src/legacy/responses.ts +20 -0
  221. package/src/legacy/results.ts +20 -0
  222. package/src/modern/dispatcher.ts +77 -0
  223. package/src/modern/handlers.ts +55 -0
  224. package/src/modern/messages.ts +14 -0
  225. package/src/modern/metadata.ts +49 -0
  226. package/src/modern/responses.ts +25 -0
  227. package/src/modern/results.ts +6 -0
  228. package/src/pending-requests.ts +30 -0
  229. package/src/protocol/decode.ts +69 -0
  230. package/src/protocol/icon.ts +8 -0
  231. package/src/protocol/messages.ts +29 -0
  232. package/src/protocol/responses.ts +27 -0
  233. package/src/protocol/version-metadata.ts +42 -0
  234. package/src/server.ts +164 -0
  235. package/src/tools/adapter.ts +76 -0
  236. package/src/tools/call.ts +18 -0
  237. package/src/tools/content.ts +57 -0
  238. package/src/tools/context.ts +119 -0
  239. package/src/tools/definition.ts +144 -0
  240. package/src/tools/errors.ts +21 -0
  241. package/src/tools/json-value.ts +81 -0
  242. package/src/tools/name.ts +13 -0
  243. package/src/tools/registry.ts +52 -0
  244. package/src/tools/results.ts +100 -0
  245. package/src/tools/rich-result.ts +70 -0
  246. package/src/tools/schema.ts +86 -0
  247. package/src/transport-ownership.ts +17 -0
  248. package/src/transport.ts +34 -0
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ export { validateTokenAudience } from "./auth/audience.js";
2
+ export { createAuthAccessor } from "./auth/context.js";
3
+ export type { AuthErrorCode } from "./auth/errors.js";
4
+ export { AuthError, isAuthError } from "./auth/errors.js";
5
+ export type {
6
+ AuthMetadata,
7
+ AuthProvider,
8
+ AuthReadonly,
9
+ AuthScope,
10
+ AuthVerifyOptions,
11
+ } from "./auth/types.js";
12
+ export { authUrl as validateAuthUrl } from "./auth/url.js";
13
+ export type { ContextProvider } from "./context/definition.js";
14
+ export { createContext } from "./context/definition.js";
15
+ export type { ToolErrorDetails } from "./diagnostics.js";
16
+ export type { HandleOptions, Server, ServerOptions } from "./server.js";
17
+ export { createServer } from "./server.js";
18
+ export type {
19
+ ToolClient,
20
+ ToolContext,
21
+ ToolContextOptions,
22
+ ToolRequest,
23
+ } from "./tools/context.js";
24
+ export { toolContext } from "./tools/context.js";
25
+ export type { Tool, ToolDefinition, ToolMetadata } from "./tools/definition.js";
26
+ export { tool } from "./tools/definition.js";
27
+ export { ToolNameError } from "./tools/name.js";
28
+ export type { ToolMap } from "./tools/registry.js";
29
+ export type {
30
+ McpErrorResult,
31
+ McpResult,
32
+ McpResultOptions,
33
+ McpStructuredResult,
34
+ } from "./tools/rich-result.js";
35
+ export { mcpResult } from "./tools/rich-result.js";
36
+ export { ToolSchemaError } from "./tools/schema.js";
37
+ export type {
38
+ RequestTransport,
39
+ Transport,
40
+ TransportCallbacks,
41
+ } from "./transport.js";
@@ -0,0 +1,101 @@
1
+ import {
2
+ INVALID_REQUEST,
3
+ type JSONRPCErrorResponse,
4
+ LEGACY_PROTOCOL_VERSION,
5
+ type ProtocolResponse,
6
+ type RequestId,
7
+ type SupportedProtocolVersion,
8
+ } from "@toolgrain/protocol";
9
+ import type {
10
+ ClientCapabilities,
11
+ Implementation,
12
+ } from "@toolgrain/protocol/2025-06-18";
13
+ import type { Notification, Request } from "../protocol/messages.js";
14
+ import { protocolError } from "../protocol/responses.js";
15
+ import {
16
+ hasProtocolVersionMetadata,
17
+ metadataMatchesVersion,
18
+ } from "../protocol/version-metadata.js";
19
+ import { type ToolClient, toolClient } from "../tools/context.js";
20
+ import { commonParams, initializeParams } from "./messages.js";
21
+ import { errorResponse } from "./responses.js";
22
+
23
+ /** Owns the initialization lifecycle for one attached transport. */
24
+ export function createLegacyConnection() {
25
+ let state: "initializing" | "awaiting-initialized" | "ready" | undefined;
26
+ let initializeId: RequestId | undefined;
27
+ let client: ToolClient | undefined;
28
+ return {
29
+ prepare(request: Request) {
30
+ const legacy =
31
+ (state !== undefined &&
32
+ metadataMatchesVersion(request, LEGACY_PROTOCOL_VERSION)) ||
33
+ (!hasProtocolVersionMetadata(request) &&
34
+ (state !== undefined ||
35
+ request.method === "initialize" ||
36
+ request.method === "ping"));
37
+ if (!legacy) return {};
38
+ const initialize = request.method === "initialize";
39
+ if (
40
+ initialize
41
+ ? state !== undefined
42
+ : state !== "ready" && request.method !== "ping"
43
+ )
44
+ return {
45
+ error: protocolError(
46
+ INVALID_REQUEST,
47
+ initialize
48
+ ? "Server is already initialized"
49
+ : "Server is not initialized",
50
+ request.id,
51
+ ),
52
+ };
53
+ return { protocolVersion: LEGACY_PROTOCOL_VERSION } as const;
54
+ },
55
+ begin(request: Request, protocolVersion?: SupportedProtocolVersion) {
56
+ if (
57
+ protocolVersion !== LEGACY_PROTOCOL_VERSION ||
58
+ request.method !== "initialize"
59
+ )
60
+ return;
61
+ state = "initializing";
62
+ initializeId = request.id;
63
+ return {
64
+ respond(response: ProtocolResponse | undefined) {
65
+ state =
66
+ response && "result" in response
67
+ ? "awaiting-initialized"
68
+ : undefined;
69
+ // The response succeeded only if these parameters already validated.
70
+ const params = initializeParams.safeParse(request.params);
71
+ if (state && params.success)
72
+ client = toolClient(
73
+ params.data.clientInfo as Implementation,
74
+ params.data.capabilities as ClientCapabilities,
75
+ );
76
+ },
77
+ finish() {
78
+ initializeId = undefined;
79
+ },
80
+ };
81
+ },
82
+ notification(notification: Notification) {
83
+ if (
84
+ notification.method === "notifications/initialized" &&
85
+ commonParams.safeParse(notification.params ?? {}).success &&
86
+ state === "awaiting-initialized"
87
+ )
88
+ state = "ready";
89
+ },
90
+ /** Details from a completed legacy initialization on this connection. */
91
+ client() {
92
+ return client;
93
+ },
94
+ canCancel(id: RequestId) {
95
+ return id !== initializeId;
96
+ },
97
+ errorResponse(response: JSONRPCErrorResponse) {
98
+ return state === undefined ? response : errorResponse(response);
99
+ },
100
+ };
101
+ }
@@ -0,0 +1,72 @@
1
+ import {
2
+ type Implementation,
3
+ LEGACY_PROTOCOL_VERSION,
4
+ METHOD_NOT_FOUND,
5
+ type SupportedProtocolVersion,
6
+ } from "@toolgrain/protocol";
7
+ import type { JSONRPCResponse, Result } from "@toolgrain/protocol/2025-06-18";
8
+ import type { ContextRegistry } from "../context/registry.js";
9
+ import type { RequestScope } from "../dispatcher.js";
10
+ import type { Request } from "../protocol/messages.js";
11
+ import { ProtocolError } from "../protocol/responses.js";
12
+ import { hasProtocolVersionMetadata } from "../protocol/version-metadata.js";
13
+ import type { ToolAdapter } from "../tools/adapter.js";
14
+ import { createToolContext } from "../tools/context.js";
15
+ import { callTool, initialize, listTools, ping } from "./handlers.js";
16
+ import { successResponse } from "./responses.js";
17
+
18
+ /** Initialization selects this adapter only without an explicit modern context. */
19
+ export function usesLegacyProtocol(
20
+ request: Request,
21
+ protocolVersion?: SupportedProtocolVersion,
22
+ ): boolean {
23
+ return (
24
+ protocolVersion === LEGACY_PROTOCOL_VERSION ||
25
+ (protocolVersion === undefined &&
26
+ !hasProtocolVersionMetadata(request) &&
27
+ request.method === "initialize")
28
+ );
29
+ }
30
+
31
+ export function createLegacyDispatcher(
32
+ tools: ToolAdapter,
33
+ identity: Implementation,
34
+ instructions?: string,
35
+ providers?: ContextRegistry,
36
+ ) {
37
+ return async (
38
+ request: Request,
39
+ scope: RequestScope,
40
+ ): Promise<JSONRPCResponse | undefined> => {
41
+ let result: Result | undefined;
42
+ switch (request.method) {
43
+ case "initialize":
44
+ result = initialize(request.params, identity, instructions);
45
+ break;
46
+ case "ping":
47
+ result = ping(request.params);
48
+ break;
49
+ case "tools/list":
50
+ result = listTools(request.params, tools);
51
+ break;
52
+ case "tools/call":
53
+ result = await callTool(request.params, tools, (params) =>
54
+ createToolContext(
55
+ scope.signal,
56
+ {
57
+ id: request.id,
58
+ protocolVersion: LEGACY_PROTOCOL_VERSION,
59
+ ...(scope.client === undefined ? {} : { client: scope.client }),
60
+ meta: params._meta ?? {},
61
+ },
62
+ scope.auth,
63
+ providers,
64
+ ),
65
+ );
66
+ break;
67
+ default:
68
+ throw new ProtocolError(METHOD_NOT_FOUND, "Method not found");
69
+ }
70
+ return result ? successResponse(request.id, result) : undefined;
71
+ };
72
+ }
@@ -0,0 +1,76 @@
1
+ import {
2
+ type Implementation,
3
+ INVALID_PARAMS,
4
+ LEGACY_PROTOCOL_VERSION,
5
+ } from "@toolgrain/protocol";
6
+ import type {
7
+ InitializeResult,
8
+ ListToolsResult,
9
+ } from "@toolgrain/protocol/2025-06-18";
10
+ import { ProtocolError } from "../protocol/responses.js";
11
+ import type { ToolAdapter } from "../tools/adapter.js";
12
+ import { callTool as executeToolCall } from "../tools/call.js";
13
+ import type { ToolContext } from "../tools/context.js";
14
+ import {
15
+ callParams,
16
+ commonParams,
17
+ initializeParams,
18
+ listParams,
19
+ } from "./messages.js";
20
+ import { toolResult } from "./results.js";
21
+
22
+ /** Initialization can only negotiate the initialization-based revision. */
23
+ export function initialize(
24
+ params: unknown,
25
+ identity: Implementation,
26
+ instructions?: string,
27
+ ): InitializeResult {
28
+ if (!initializeParams.safeParse(params).success)
29
+ throw new ProtocolError(
30
+ INVALID_PARAMS,
31
+ "Invalid initialization parameters",
32
+ );
33
+ return {
34
+ protocolVersion: LEGACY_PROTOCOL_VERSION,
35
+ capabilities: { tools: {} },
36
+ serverInfo: { ...identity },
37
+ ...(instructions === undefined ? {} : { instructions }),
38
+ };
39
+ }
40
+
41
+ export function ping(params: unknown): Record<string, never> {
42
+ if (!commonParams.safeParse(params ?? {}).success)
43
+ throw new ProtocolError(INVALID_PARAMS, "Invalid ping parameters");
44
+ return {};
45
+ }
46
+
47
+ export function listTools(
48
+ params: unknown,
49
+ tools: ToolAdapter,
50
+ ): ListToolsResult {
51
+ const parsed = listParams.safeParse(params ?? {});
52
+ if (!parsed.success || parsed.data.cursor)
53
+ throw new ProtocolError(INVALID_PARAMS, "Invalid tool list parameters");
54
+ return {
55
+ tools: tools
56
+ .list()
57
+ .map(({ icons: _icons, outputSchema, ...definition }) => ({
58
+ ...definition,
59
+ ...(outputSchema?.type === "object"
60
+ ? { outputSchema: { ...outputSchema, type: "object" as const } }
61
+ : {}),
62
+ })),
63
+ };
64
+ }
65
+
66
+ export async function callTool(
67
+ params: unknown,
68
+ tools: ToolAdapter,
69
+ ctx: (params: { _meta?: Record<string, unknown> | undefined }) => ToolContext,
70
+ ) {
71
+ const parsed = callParams.safeParse(params);
72
+ if (!parsed.success)
73
+ throw new ProtocolError(INVALID_PARAMS, "Invalid tool call parameters");
74
+ const result = await executeToolCall(parsed.data, tools, ctx(parsed.data));
75
+ return result ? toolResult(result) : undefined;
76
+ }
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ import { object } from "../protocol/messages.js";
3
+
4
+ export const commonParams = z.looseObject({
5
+ _meta: object.optional(),
6
+ });
7
+ export const initializeParams = commonParams.extend({
8
+ protocolVersion: z.string(),
9
+ capabilities: z.looseObject({
10
+ experimental: z.record(z.string(), object).optional(),
11
+ roots: z.looseObject({ listChanged: z.boolean().optional() }).optional(),
12
+ sampling: object.optional(),
13
+ elicitation: object.optional(),
14
+ }),
15
+ clientInfo: z.looseObject({
16
+ name: z.string(),
17
+ version: z.string(),
18
+ title: z.string().optional(),
19
+ }),
20
+ });
21
+ export const listParams = commonParams.extend({
22
+ cursor: z.string().optional(),
23
+ });
24
+ export const callParams = commonParams.extend({
25
+ name: z.string(),
26
+ arguments: object.optional(),
27
+ });
@@ -0,0 +1,20 @@
1
+ import {
2
+ JSONRPC_VERSION,
3
+ type JSONRPCErrorResponse,
4
+ type LegacyJSONRPCErrorResponse,
5
+ type RequestId,
6
+ } from "@toolgrain/protocol";
7
+ import type { JSONRPCResponse, Result } from "@toolgrain/protocol/2025-06-18";
8
+
9
+ export function successResponse(
10
+ id: RequestId,
11
+ result: Result,
12
+ ): JSONRPCResponse {
13
+ return { jsonrpc: JSONRPC_VERSION, id, result };
14
+ }
15
+
16
+ export function errorResponse(
17
+ response: JSONRPCErrorResponse,
18
+ ): LegacyJSONRPCErrorResponse {
19
+ return { ...response, id: response.id ?? null };
20
+ }
@@ -0,0 +1,20 @@
1
+ import type { CallToolResult } from "@toolgrain/protocol/2025-06-18";
2
+ import type { ToolExecutionResult } from "../tools/results.js";
3
+
4
+ /** The initialization-based format permits structured content only at an object root. */
5
+ export function toolResult(result: ToolExecutionResult): CallToolResult {
6
+ const { structuredContent, ...content } = result;
7
+ return {
8
+ ...content,
9
+ content: content.content.map((block) => {
10
+ if (block.type !== "resource_link") return block;
11
+ const { icons: _icons, ...resource } = block;
12
+ return resource;
13
+ }),
14
+ ...(structuredContent !== null &&
15
+ typeof structuredContent === "object" &&
16
+ !Array.isArray(structuredContent)
17
+ ? { structuredContent: structuredContent as Record<string, unknown> }
18
+ : {}),
19
+ };
20
+ }
@@ -0,0 +1,77 @@
1
+ import {
2
+ type ClientCapabilities,
3
+ type Implementation,
4
+ INVALID_PARAMS,
5
+ type JSONRPCResultResponse,
6
+ METHOD_NOT_FOUND,
7
+ PROTOCOL_VERSION,
8
+ type Result,
9
+ SUPPORTED_PROTOCOL_VERSIONS,
10
+ UNSUPPORTED_PROTOCOL_VERSION,
11
+ } from "@toolgrain/protocol";
12
+ import type { ContextRegistry } from "../context/registry.js";
13
+ import type { RequestScope } from "../dispatcher.js";
14
+ import type { Request } from "../protocol/messages.js";
15
+ import { ProtocolError } from "../protocol/responses.js";
16
+ import type { ToolAdapter } from "../tools/adapter.js";
17
+ import { createToolContext, toolClient } from "../tools/context.js";
18
+ import { callTool, discover, listTools } from "./handlers.js";
19
+ import { metadata } from "./metadata.js";
20
+ import { successResponse } from "./responses.js";
21
+
22
+ export function createModernDispatcher(
23
+ tools: ToolAdapter,
24
+ identity: Implementation,
25
+ instructions?: string,
26
+ providers?: ContextRegistry,
27
+ ) {
28
+ return async (
29
+ request: Request,
30
+ scope: RequestScope,
31
+ ): Promise<JSONRPCResultResponse | undefined> => {
32
+ const parsed = metadata.safeParse(request.params?._meta);
33
+ if (!parsed.success)
34
+ throw new ProtocolError(INVALID_PARAMS, "Invalid request metadata");
35
+ const revision = parsed.data["io.modelcontextprotocol/protocolVersion"];
36
+ if (revision !== PROTOCOL_VERSION)
37
+ throw new ProtocolError(
38
+ UNSUPPORTED_PROTOCOL_VERSION,
39
+ "Unsupported protocol version",
40
+ { supported: [...SUPPORTED_PROTOCOL_VERSIONS], requested: revision },
41
+ );
42
+ let result: Result | undefined;
43
+ switch (request.method) {
44
+ case "server/discover":
45
+ result = discover(request.params, instructions);
46
+ break;
47
+ case "tools/list":
48
+ result = listTools(request.params, tools);
49
+ break;
50
+ case "tools/call":
51
+ result = await callTool(request.params, tools, (params) =>
52
+ createToolContext(
53
+ scope.signal,
54
+ {
55
+ id: request.id,
56
+ protocolVersion: PROTOCOL_VERSION,
57
+ client: toolClient(
58
+ parsed.data["io.modelcontextprotocol/clientInfo"] as
59
+ | Implementation
60
+ | undefined,
61
+ parsed.data[
62
+ "io.modelcontextprotocol/clientCapabilities"
63
+ ] as ClientCapabilities,
64
+ ),
65
+ meta: params._meta ?? {},
66
+ },
67
+ scope.auth,
68
+ providers,
69
+ ),
70
+ );
71
+ break;
72
+ default:
73
+ throw new ProtocolError(METHOD_NOT_FOUND, "Method not found");
74
+ }
75
+ return result ? successResponse(request.id, result, identity) : undefined;
76
+ };
77
+ }
@@ -0,0 +1,55 @@
1
+ import {
2
+ type DiscoverResult,
3
+ INVALID_PARAMS,
4
+ type ListToolsResult,
5
+ SUPPORTED_PROTOCOL_VERSIONS,
6
+ } from "@toolgrain/protocol";
7
+ import { ProtocolError } from "../protocol/responses.js";
8
+ import type { ToolAdapter } from "../tools/adapter.js";
9
+ import { callTool as executeToolCall } from "../tools/call.js";
10
+ import type { ToolContext } from "../tools/context.js";
11
+ import { callParams, commonParams, listParams } from "./messages.js";
12
+ import { toolResult } from "./results.js";
13
+
14
+ export function discover(
15
+ params: unknown,
16
+ instructions?: string,
17
+ ): DiscoverResult {
18
+ if (!commonParams.safeParse(params).success)
19
+ throw new ProtocolError(INVALID_PARAMS, "Invalid discovery parameters");
20
+ return {
21
+ resultType: "complete",
22
+ ttlMs: 0,
23
+ cacheScope: "private",
24
+ supportedVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
25
+ capabilities: { tools: {} },
26
+ ...(instructions === undefined ? {} : { instructions }),
27
+ };
28
+ }
29
+
30
+ export function listTools(
31
+ params: unknown,
32
+ tools: ToolAdapter,
33
+ ): ListToolsResult {
34
+ const parsed = listParams.safeParse(params);
35
+ if (!parsed.success || parsed.data.cursor)
36
+ throw new ProtocolError(INVALID_PARAMS, "Invalid tool list parameters");
37
+ return {
38
+ resultType: "complete",
39
+ ttlMs: 0,
40
+ cacheScope: "private",
41
+ tools: tools.list(),
42
+ };
43
+ }
44
+
45
+ export async function callTool(
46
+ params: unknown,
47
+ tools: ToolAdapter,
48
+ ctx: (params: { _meta?: Record<string, unknown> | undefined }) => ToolContext,
49
+ ) {
50
+ const parsed = callParams.safeParse(params);
51
+ if (!parsed.success)
52
+ throw new ProtocolError(INVALID_PARAMS, "Invalid tool call parameters");
53
+ const result = await executeToolCall(parsed.data, tools, ctx(parsed.data));
54
+ return result ? toolResult(result) : undefined;
55
+ }
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ import { object } from "../protocol/messages.js";
3
+ import { metadata } from "./metadata.js";
4
+
5
+ export const commonParams = z.looseObject({ _meta: metadata });
6
+ export const listParams = commonParams.extend({
7
+ cursor: z.string().optional(),
8
+ });
9
+ export const callParams = commonParams.extend({
10
+ name: z.string(),
11
+ arguments: object.optional(),
12
+ requestState: z.string().optional(),
13
+ inputResponses: object.optional(),
14
+ });
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { icon } from "../protocol/icon.js";
3
+ import { object } from "../protocol/messages.js";
4
+
5
+ const capabilityObject = z.record(z.string(), z.json());
6
+ const requestId = z.union([z.string(), z.number().finite()]);
7
+ const capabilities = z.looseObject({
8
+ experimental: z.record(z.string(), capabilityObject).optional(),
9
+ roots: object.optional(),
10
+ sampling: z
11
+ .looseObject({
12
+ context: capabilityObject.optional(),
13
+ tools: capabilityObject.optional(),
14
+ })
15
+ .optional(),
16
+ elicitation: z
17
+ .looseObject({
18
+ form: capabilityObject.optional(),
19
+ url: capabilityObject.optional(),
20
+ })
21
+ .optional(),
22
+ extensions: z.record(z.string(), capabilityObject).optional(),
23
+ });
24
+ const implementation = z.looseObject({
25
+ name: z.string(),
26
+ version: z.string(),
27
+ title: z.string().optional(),
28
+ description: z.string().optional(),
29
+ websiteUrl: z.string().optional(),
30
+ icons: z.array(icon).optional(),
31
+ });
32
+ export const metadata = z.looseObject({
33
+ "io.modelcontextprotocol/protocolVersion": z.string(),
34
+ "io.modelcontextprotocol/clientCapabilities": capabilities,
35
+ "io.modelcontextprotocol/clientInfo": implementation.optional(),
36
+ "io.modelcontextprotocol/logLevel": z
37
+ .enum([
38
+ "debug",
39
+ "info",
40
+ "notice",
41
+ "warning",
42
+ "error",
43
+ "critical",
44
+ "alert",
45
+ "emergency",
46
+ ])
47
+ .optional(),
48
+ progressToken: requestId.optional(),
49
+ });
@@ -0,0 +1,25 @@
1
+ import type {
2
+ Implementation,
3
+ JSONRPCResultResponse,
4
+ RequestId,
5
+ Result,
6
+ } from "@toolgrain/protocol";
7
+ import { JSONRPC_VERSION } from "@toolgrain/protocol";
8
+
9
+ export function successResponse(
10
+ id: RequestId,
11
+ result: Result,
12
+ identity: Implementation,
13
+ ): JSONRPCResultResponse {
14
+ return {
15
+ jsonrpc: JSONRPC_VERSION,
16
+ id,
17
+ result: {
18
+ ...result,
19
+ _meta: {
20
+ ...result._meta,
21
+ "io.modelcontextprotocol/serverInfo": { ...identity },
22
+ },
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,6 @@
1
+ import type { CallToolResult } from "@toolgrain/protocol";
2
+ import type { ToolExecutionResult } from "../tools/results.js";
3
+
4
+ export function toolResult(result: ToolExecutionResult): CallToolResult {
5
+ return { resultType: "complete", ...result };
6
+ }
@@ -0,0 +1,30 @@
1
+ import type { RequestId } from "@toolgrain/protocol";
2
+
3
+ /** Owns duplicate detection and cancellation signals for in-flight requests. */
4
+ export function createPendingRequests(isOpen: () => boolean) {
5
+ const pending = new Map<RequestId, AbortController>();
6
+ return {
7
+ begin(id: RequestId) {
8
+ if (pending.has(id)) return;
9
+ const controller = new AbortController();
10
+ pending.set(id, controller);
11
+ return {
12
+ signal: controller.signal,
13
+ isActive: () => isOpen() && !controller.signal.aborted,
14
+ finish: () => {
15
+ pending.delete(id);
16
+ },
17
+ };
18
+ },
19
+ cancel(id: RequestId) {
20
+ pending
21
+ .get(id)
22
+ ?.abort(new DOMException("Request cancelled by client", "AbortError"));
23
+ },
24
+ clear() {
25
+ for (const controller of pending.values())
26
+ controller.abort(new DOMException("Connection closed", "AbortError"));
27
+ pending.clear();
28
+ },
29
+ };
30
+ }