@softure-ai/mcp-access 0.5.0 → 0.7.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 +18 -0
- package/README.md +78 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/messages/en.d.ts +4 -0
- package/dist/messages/en.d.ts.map +1 -1
- package/dist/messages/en.js +4 -0
- package/dist/messages/en.js.map +1 -1
- package/dist/messages/index.d.ts +4 -0
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/server/endpoint.d.ts +8 -1
- package/dist/server/endpoint.d.ts.map +1 -1
- package/dist/server/endpoint.js +54 -7
- package/dist/server/endpoint.js.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/request-diagnostics.d.ts +59 -0
- package/dist/server/request-diagnostics.d.ts.map +1 -0
- package/dist/server/request-diagnostics.js +84 -0
- package/dist/server/request-diagnostics.js.map +1 -0
- package/dist/server/server-factory.d.ts +2 -1
- package/dist/server/server-factory.d.ts.map +1 -1
- package/dist/server/server-factory.js +16 -10
- package/dist/server/server-factory.js.map +1 -1
- package/dist/server/server-info.d.ts +30 -2
- package/dist/server/server-info.d.ts.map +1 -1
- package/dist/server/server-info.js +28 -6
- package/dist/server/server-info.js.map +1 -1
- package/dist/server/tool-list.d.ts +2 -0
- package/dist/server/tool-list.d.ts.map +1 -1
- package/dist/server/tool-list.js +6 -2
- package/dist/server/tool-list.js.map +1 -1
- package/module.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/messages/en.ts +6 -0
- package/src/server/endpoint.ts +73 -6
- package/src/server/index.ts +3 -0
- package/src/server/request-diagnostics.ts +130 -0
- package/src/server/server-factory.ts +32 -12
- package/src/server/server-info.ts +60 -8
- package/src/server/tool-list.ts +7 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-diagnostics.js","sourceRoot":"","sources":["../../src/server/request-diagnostics.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,7 +22,8 @@ export interface McpServerFactoryOptions {
|
|
|
22
22
|
readonly strictToolInput?: boolean | StrictToolInputOptions;
|
|
23
23
|
/**
|
|
24
24
|
* Adds the read-only `get_server_info` tool for every access level: the build info (pass `readBuildInfo` of
|
|
25
|
-
* `@softure-ai/ops/server`) with the served name and version. Off by default.
|
|
25
|
+
* `@softure-ai/ops/server`) with the served name and version. Off by default. With `tools: true` it also answers the
|
|
26
|
+
* served tool contract of the caller's access level, taken from the measured list (#1078).
|
|
26
27
|
*/
|
|
27
28
|
readonly serverInfoTool?: ServerInfoToolOptions;
|
|
28
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-factory.d.ts","sourceRoot":"","sources":["../../src/server/server-factory.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAuB,KAAK,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,MAAM,WAAW,uBAAuB;IACtC,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,gBAAgB,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,KAAK,OAAO,CAAC;IACpF,yGAAyG;IACzG,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC5D
|
|
1
|
+
{"version":3,"file":"server-factory.d.ts","sourceRoot":"","sources":["../../src/server/server-factory.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAuB,KAAK,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,MAAM,WAAW,uBAAuB;IACtC,yBAAyB;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,gBAAgB,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,KAAK,OAAO,CAAC;IACpF,yGAAyG;IACzG,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,sBAAsB,CAAC;IAC5D;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACjD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,yBAAyB,CA4ClG"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
12
12
|
import { withStrictToolInput } from "./strict-input.js";
|
|
13
13
|
import { registerServerInfoTool } from "./server-info.js";
|
|
14
|
-
import {
|
|
14
|
+
import { describeToolList, listRecordedTools } from "./tool-list.js";
|
|
15
15
|
/**
|
|
16
16
|
* An `McpServerFactory` whose servers carry the tool list's version for the identity's access level. Its
|
|
17
17
|
* `toolListVersion(identity)` reads that version without building a server, so `createMcpEndpoint` ties each session
|
|
@@ -19,13 +19,18 @@ import { measureToolList } from "./tool-list.js";
|
|
|
19
19
|
*/
|
|
20
20
|
export function createMcpServerFactory(options) {
|
|
21
21
|
const strictOptions = options.strictToolInput === true ? {} : options.strictToolInput === false ? undefined : options.strictToolInput;
|
|
22
|
-
// The served version
|
|
23
|
-
// its handler never runs there and
|
|
24
|
-
const registerTools = async (server, identity,
|
|
22
|
+
// The served version and the measured tools reach `get_server_info` here; the recording server registers it with a
|
|
23
|
+
// placeholder version and no tools, since its handler never runs there and neither is part of the listed tool.
|
|
24
|
+
const registerTools = async (server, identity, served) => {
|
|
25
25
|
const target = strictOptions === undefined ? server : withStrictToolInput(server, strictOptions);
|
|
26
26
|
await options.registerTools(target, identity);
|
|
27
|
-
if (options.serverInfoTool !== undefined)
|
|
28
|
-
registerServerInfoTool(target, {
|
|
27
|
+
if (options.serverInfoTool !== undefined) {
|
|
28
|
+
registerServerInfoTool(target, {
|
|
29
|
+
...options.serverInfoTool,
|
|
30
|
+
server: { name: options.name, version: served?.version ?? "" },
|
|
31
|
+
servedTools: async () => (served === undefined ? [] : (await served.measure()).tools),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
29
34
|
};
|
|
30
35
|
const measurements = new Map();
|
|
31
36
|
// The promise is cached, so parallel first requests measure once; a failed measurement is dropped so
|
|
@@ -34,11 +39,12 @@ export function createMcpServerFactory(options) {
|
|
|
34
39
|
const cached = measurements.get(identity.canWrite);
|
|
35
40
|
if (cached !== undefined)
|
|
36
41
|
return cached;
|
|
37
|
-
const
|
|
38
|
-
|
|
42
|
+
const release = options.release;
|
|
43
|
+
const measurement = listRecordedTools((server) => registerTools(server, identity), {
|
|
44
|
+
...(release === undefined ? {} : { release }),
|
|
39
45
|
name: options.name,
|
|
40
46
|
...(options.serverOptions === undefined ? {} : { serverOptions: options.serverOptions }),
|
|
41
|
-
});
|
|
47
|
+
}).then((tools) => ({ ...describeToolList(tools, release), tools }));
|
|
42
48
|
measurements.set(identity.canWrite, measurement);
|
|
43
49
|
void measurement.catch(() => measurements.delete(identity.canWrite));
|
|
44
50
|
return measurement;
|
|
@@ -47,7 +53,7 @@ export function createMcpServerFactory(options) {
|
|
|
47
53
|
const factory = async (identity) => {
|
|
48
54
|
const version = await toolListVersion(identity);
|
|
49
55
|
const server = new McpServer({ name: options.name, version, ...(options.title === undefined ? {} : { title: options.title }) }, options.serverOptions);
|
|
50
|
-
await registerTools(server, identity, version);
|
|
56
|
+
await registerTools(server, identity, { version, measure: () => measure(identity) });
|
|
51
57
|
return server;
|
|
52
58
|
};
|
|
53
59
|
return Object.assign(factory, { toolListVersion });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-factory.js","sourceRoot":"","sources":["../../src/server/server-factory.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,kGAAkG;AAClG,mGAAmG;AACnG,qGAAqG;AACrG,6CAA6C;AAC7C,EAAE;AACF,yDAAyD;AACzD,oBAAoB;AACpB,gFAAgF;AAChF,QAAQ;AACR,OAAO,EAAE,SAAS,EAAyB,MAAM,8BAA8B,CAAC;AAGhF,OAAO,EAAE,mBAAmB,EAA+B,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,sBAAsB,EAA8B,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"server-factory.js","sourceRoot":"","sources":["../../src/server/server-factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { ServedTool } from "./tool-list.js";
|
|
2
3
|
export declare const SERVER_INFO_TOOL_NAME = "get_server_info";
|
|
3
4
|
/** The build info of `@softure-ai/ops` (`readBuildInfo()`), typed by shape so mcp-access does not import ops. */
|
|
4
5
|
export interface McpBuildInfo {
|
|
@@ -12,9 +13,20 @@ export interface ServerInfoToolOptions {
|
|
|
12
13
|
readonly buildInfo: () => McpBuildInfo | Promise<McpBuildInfo>;
|
|
13
14
|
/** The changelog the build info route answers, when the app passes one there. Only what is public. */
|
|
14
15
|
readonly changelog?: () => unknown;
|
|
16
|
+
/**
|
|
17
|
+
* Also answers the served tool contract (`tools`), with an optional `name` input that narrows it to one tool. Turning
|
|
18
|
+
* it on changes the tool's input schema, so the version changes once. Default: off.
|
|
19
|
+
*/
|
|
20
|
+
readonly tools?: boolean;
|
|
15
21
|
/** Default: `serverInfoTool.description` of the module dictionary. */
|
|
16
22
|
readonly description?: string;
|
|
17
23
|
}
|
|
24
|
+
/** One served tool's contract, exactly as `tools/list` answers it. */
|
|
25
|
+
export interface ServedToolContract {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly description?: unknown;
|
|
28
|
+
readonly inputSchema: unknown;
|
|
29
|
+
}
|
|
18
30
|
/** What `initialize` answers as `serverInfo`. */
|
|
19
31
|
export interface ServedServerInfo {
|
|
20
32
|
readonly name: string;
|
|
@@ -23,12 +35,28 @@ export interface ServedServerInfo {
|
|
|
23
35
|
export interface RegisterServerInfoToolOptions extends ServerInfoToolOptions {
|
|
24
36
|
/** The name and version the server answers at `initialize`; `createMcpServerFactory` passes them in. */
|
|
25
37
|
readonly server: ServedServerInfo;
|
|
38
|
+
/**
|
|
39
|
+
* The tools the caller's `tools/list` answers, read when `tools` is on. `createMcpServerFactory` passes its measured
|
|
40
|
+
* list; a hand-built server passes `() => listServedTools(createServer)` for the caller's access level.
|
|
41
|
+
*/
|
|
42
|
+
readonly servedTools?: () => readonly ServedTool[] | Promise<readonly ServedTool[]>;
|
|
43
|
+
}
|
|
44
|
+
/** The contract part of the answer, with `tools` on. */
|
|
45
|
+
export interface ServerInfoToolsAnswer {
|
|
46
|
+
/** Tells the model that the server's contract wins over its own copy. */
|
|
47
|
+
readonly toolsHint: string;
|
|
48
|
+
readonly tools: readonly ServedToolContract[];
|
|
49
|
+
/** Every served tool's name, when `name` matched none. */
|
|
50
|
+
readonly toolNames?: readonly string[];
|
|
26
51
|
}
|
|
27
52
|
/** The tool's answer: the served name and version, the build info and, with a provider, the changelog. */
|
|
28
53
|
export type ServerInfoAnswer = McpBuildInfo & {
|
|
29
54
|
readonly server: ServedServerInfo;
|
|
30
55
|
readonly changelog?: unknown;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
56
|
+
} & Partial<ServerInfoToolsAnswer>;
|
|
57
|
+
/**
|
|
58
|
+
* Registers `get_server_info`, read only: no input fields, or an optional `name` with `tools` on. A failing `buildInfo`
|
|
59
|
+
* reaches the SDK, which answers a tool error.
|
|
60
|
+
*/
|
|
33
61
|
export declare function registerServerInfoTool(server: McpServer, options: RegisterServerInfoToolOptions): void;
|
|
34
62
|
//# sourceMappingURL=server-info.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-info.d.ts","sourceRoot":"","sources":["../../src/server/server-info.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-info.d.ts","sourceRoot":"","sources":["../../src/server/server-info.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjD,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AAEvD,iHAAiH;AACjH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,2GAA2G;IAC3G,QAAQ,CAAC,SAAS,EAAE,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/D,sGAAsG;IACtG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,6BAA8B,SAAQ,qBAAqB;IAC1E,wGAAwG;IACxG,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CAAC;CACrF;AAED,wDAAwD;AACxD,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC9C,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,0GAA0G;AAC1G,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAEnJ;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,6BAA6B,GAAG,IAAI,CAoBtG"}
|
|
@@ -3,20 +3,42 @@ import { z } from "zod";
|
|
|
3
3
|
import { en } from "../messages/en.js";
|
|
4
4
|
import { toolResult } from "./tools.js";
|
|
5
5
|
export const SERVER_INFO_TOOL_NAME = "get_server_info";
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Registers `get_server_info`, read only: no input fields, or an optional `name` with `tools` on. A failing `buildInfo`
|
|
8
|
+
* reaches the SDK, which answers a tool error.
|
|
9
|
+
*/
|
|
7
10
|
export function registerServerInfoTool(server, options) {
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
const annotations = { readOnlyHint: true, idempotentHint: true, openWorldHint: false };
|
|
12
|
+
const description = options.description ?? (options.tools === true ? en.serverInfoTool.descriptionWithTools : en.serverInfoTool.description);
|
|
13
|
+
if (options.tools !== true) {
|
|
10
14
|
// An empty object rather than no schema, so strict input refuses a field sent to it.
|
|
11
|
-
inputSchema: z.object({}),
|
|
12
|
-
|
|
13
|
-
}
|
|
15
|
+
server.registerTool(SERVER_INFO_TOOL_NAME, { description, inputSchema: z.object({}), annotations }, async () => toolResult(await readServerInfo(options)));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const readServedTools = options.servedTools;
|
|
19
|
+
if (readServedTools === undefined) {
|
|
20
|
+
throw new Error("@softure-ai/mcp-access: get_server_info with tools: true needs servedTools, e.g. () => listServedTools(createServer)");
|
|
21
|
+
}
|
|
22
|
+
server.registerTool(SERVER_INFO_TOOL_NAME, { description, inputSchema: z.object({ name: z.string().optional().describe(en.serverInfoTool.nameInputDescription) }), annotations }, async ({ name }) => {
|
|
23
|
+
const answer = await readServerInfo(options);
|
|
24
|
+
return toolResult({ ...answer, ...describeToolContract(await readServedTools(), name) });
|
|
25
|
+
});
|
|
14
26
|
}
|
|
15
27
|
async function readServerInfo(options) {
|
|
16
28
|
const { release, commit, builtAt, startedAt } = await options.buildInfo();
|
|
17
29
|
const answer = { server: { name: options.server.name, version: options.server.version }, release, commit, builtAt, startedAt };
|
|
18
30
|
return options.changelog === undefined ? answer : { ...answer, changelog: await readChangelog(options.changelog) };
|
|
19
31
|
}
|
|
32
|
+
/** The contract of every served tool, or of the one named; an unknown name answers no tool and every name. */
|
|
33
|
+
function describeToolContract(served, name) {
|
|
34
|
+
const tools = served.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema }));
|
|
35
|
+
if (name === undefined)
|
|
36
|
+
return { toolsHint: en.serverInfoTool.toolsHint, tools };
|
|
37
|
+
const named = tools.filter((tool) => tool.name === name);
|
|
38
|
+
if (named.length > 0)
|
|
39
|
+
return { toolsHint: en.serverInfoTool.toolsHint, tools: named };
|
|
40
|
+
return { toolsHint: en.serverInfoTool.unknownToolHint, tools: [], toolNames: tools.map((tool) => tool.name).sort() };
|
|
41
|
+
}
|
|
20
42
|
/** As the build info route: a failing provider answers `null` and logs the error class, so the build info still answers. */
|
|
21
43
|
async function readChangelog(provider) {
|
|
22
44
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-info.js","sourceRoot":"","sources":["../../src/server/server-info.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-info.js","sourceRoot":"","sources":["../../src/server/server-info.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,6 +26,8 @@ export interface MeasureToolListOptions {
|
|
|
26
26
|
* description, annotations or input or output schema changes the version; the order of registration does not.
|
|
27
27
|
*/
|
|
28
28
|
export declare function measureToolList(registerTools: RegisterTools, options?: MeasureToolListOptions): Promise<ToolListMeasurement>;
|
|
29
|
+
/** The tools as `tools/list` answers them, registered on a recording server shaped like the served one. */
|
|
30
|
+
export declare function listRecordedTools(registerTools: RegisterTools, options?: MeasureToolListOptions): Promise<ServedTool[]>;
|
|
29
31
|
/** The version and count for a listed tool set. */
|
|
30
32
|
export declare function describeToolList(tools: readonly ServedTool[], release?: string): ToolListMeasurement;
|
|
31
33
|
/** The first 12 hex digits of a sha256 over the tools in name order, each as canonical JSON (keys sorted at every level). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-list.d.ts","sourceRoot":"","sources":["../../src/server/tool-list.ts"],"names":[],"mappings":"AAMA,OAAO,EAAoB,SAAS,EAAoB,KAAK,gBAAgB,EAAE,KAAK,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAEjI,2CAA2C;AAC3C,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAMjD,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvF,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,mHAAmH;AACnH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,iGAAiG;IACjG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,QAAQ,CAAC,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,aAAa,EAAE,aAAa,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,mBAAmB,CAAC,
|
|
1
|
+
{"version":3,"file":"tool-list.d.ts","sourceRoot":"","sources":["../../src/server/tool-list.ts"],"names":[],"mappings":"AAMA,OAAO,EAAoB,SAAS,EAAoB,KAAK,gBAAgB,EAAE,KAAK,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAEjI,2CAA2C;AAC3C,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAMjD,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvF,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,mHAAmH;AACnH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,iGAAiG;IACjG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,QAAQ,CAAC,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,aAAa,EAAE,aAAa,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAItI;AAED,2GAA2G;AAC3G,wBAAsB,iBAAiB,CAAC,aAAa,EAAE,aAAa,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAIjI;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,OAAO,GAAE,MAAkC,GAAG,mBAAmB,CAG/H;AAED,6HAA6H;AAC7H,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAGxE;AAED,wJAAwJ;AACxJ,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAgBjI"}
|
package/dist/server/tool-list.js
CHANGED
|
@@ -18,9 +18,13 @@ const RELEASE_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
|
18
18
|
export async function measureToolList(registerTools, options = {}) {
|
|
19
19
|
const release = options.release ?? DEFAULT_TOOL_LIST_RELEASE;
|
|
20
20
|
assertRelease(release);
|
|
21
|
-
|
|
21
|
+
return describeToolList(await listRecordedTools(registerTools, options), release);
|
|
22
|
+
}
|
|
23
|
+
/** The tools as `tools/list` answers them, registered on a recording server shaped like the served one. */
|
|
24
|
+
export async function listRecordedTools(registerTools, options = {}) {
|
|
25
|
+
const server = new McpServer({ name: options.name ?? "tool-list", version: options.release ?? DEFAULT_TOOL_LIST_RELEASE }, options.serverOptions);
|
|
22
26
|
await registerTools(server);
|
|
23
|
-
return
|
|
27
|
+
return listServedTools(() => server);
|
|
24
28
|
}
|
|
25
29
|
/** The version and count for a listed tool set. */
|
|
26
30
|
export function describeToolList(tools, release = DEFAULT_TOOL_LIST_RELEASE) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-list.js","sourceRoot":"","sources":["../../src/server/tool-list.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,qGAAqG;AACrG,oGAAoG;AACpG,oGAAoG;AACpG,uEAAuE;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,gBAAgB,EAAsC,MAAM,8BAA8B,CAAC;AAEjI,2CAA2C;AAC3C,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAC;AAEjD,oDAAoD;AACpD,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,eAAe,GAAG,4CAA4C,CAAC;AAuBrE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,aAA4B,EAAE,UAAkC,EAAE;IACtG,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,yBAAyB,CAAC;IAC7D,aAAa,CAAC,OAAO,CAAC,CAAC;IACvB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-list.js","sourceRoot":"","sources":["../../src/server/tool-list.ts"],"names":[],"mappings":"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"}
|
package/module.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "mcp-access",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"dependsOn": { "security": "^0.2.0", "auth": "^0.2.0", "jobs": "^0.1.1?", "ops": "^0.2.0?" },
|
|
5
5
|
"dbSchema": "mcp",
|
|
6
6
|
"tables": ["access_tokens", "oauth_clients", "oauth_authorization_codes", "oauth_grants", "oauth_registration_window", "used_confirmations", "operation_tasks"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softure-ai/mcp-access",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "SOFTURE AI MCP access module: hashed, scoped, expiring access tokens, a rate-limited Bearer endpoint around the app's MCP server factory, and a token page with client setup instructions for Next.js; tool result helpers with a safe error boundary, a stdio entry, a catalog parity check and the softure-mcp prune command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/index.ts
CHANGED
|
@@ -51,7 +51,7 @@ export const API_TOKEN_RATE_LIMIT_BUCKETS = {
|
|
|
51
51
|
export const mcpAccess = defineModule({
|
|
52
52
|
manifest: {
|
|
53
53
|
id: MODULE_ID,
|
|
54
|
-
version: "0.
|
|
54
|
+
version: "0.7.0",
|
|
55
55
|
dependsOn: { security: "^0.2.0", auth: "^0.2.0", jobs: "^0.1.1?", ops: "^0.2.0?" },
|
|
56
56
|
dbSchema: "mcp",
|
|
57
57
|
tables: ["access_tokens", "oauth_clients", "oauth_authorization_codes", "oauth_grants", "oauth_registration_window", "used_confirmations", "operation_tasks"],
|
package/src/messages/en.ts
CHANGED
|
@@ -129,6 +129,12 @@ export const en = {
|
|
|
129
129
|
serverInfoTool: {
|
|
130
130
|
description:
|
|
131
131
|
"Tells which build of this server answers: its name and version (the version changes whenever a tool changes), the release, the commit, when it was built and started, and the changelog when the app publishes one. Use it to check that a change is live.",
|
|
132
|
+
descriptionWithTools:
|
|
133
|
+
"Tells which build of this server answers (its name and version, the release, the commit, when it was built and started, the changelog when the app publishes one) and the current description and input schema of every tool it serves you. Use it to check that a change is live, and read a tool's contract here when a call is refused or your copy may be out of date.",
|
|
134
|
+
nameInputDescription: "Optional: one tool's name, to answer only that tool's current description and input schema.",
|
|
135
|
+
toolsHint:
|
|
136
|
+
"If your copy of a tool's schema or description differs from `tools` here, the server's is current: build calls from it.",
|
|
137
|
+
unknownToolHint: "No served tool has that name; `toolNames` lists every tool this server serves to you.",
|
|
132
138
|
},
|
|
133
139
|
operations: {
|
|
134
140
|
statusToolDescription:
|
package/src/server/endpoint.ts
CHANGED
|
@@ -25,9 +25,19 @@ import {
|
|
|
25
25
|
isInitializeRequest,
|
|
26
26
|
isToolListSessionCurrent,
|
|
27
27
|
MCP_SESSION_ID_HEADER,
|
|
28
|
+
readToolListSessionVersion,
|
|
28
29
|
rewriteInitializeAnswer,
|
|
29
30
|
} from "./tool-list-session.js";
|
|
30
31
|
import { isWritingEnabled } from "./writes-switch.js";
|
|
32
|
+
import {
|
|
33
|
+
createDiagnosticsSink,
|
|
34
|
+
readJsonRpcSummary,
|
|
35
|
+
sanitizeDiagnosticText,
|
|
36
|
+
type JsonRpcSummary,
|
|
37
|
+
type McpRequestDiagnostic,
|
|
38
|
+
type McpRequestDiagnostics,
|
|
39
|
+
type McpSessionState,
|
|
40
|
+
} from "./request-diagnostics.js";
|
|
31
41
|
|
|
32
42
|
export { MCP_READ_SCOPE, MCP_WRITE_SCOPE };
|
|
33
43
|
|
|
@@ -60,6 +70,20 @@ export interface McpEndpointOptions {
|
|
|
60
70
|
* `toolListVersion` (`createMcpServerFactory`). Without either, the endpoint issues no session id.
|
|
61
71
|
*/
|
|
62
72
|
readonly toolListVersion?: ToolListVersionReader;
|
|
73
|
+
/**
|
|
74
|
+
* Request diagnostics (#1066), off by default: `true` logs one `console.info` line per request, a function receives
|
|
75
|
+
* each entry. An entry holds the JSON-RPC method, the tool name, the session state, the served tool list version,
|
|
76
|
+
* the status and the client's `clientInfo` on `initialize`; never a token, body, argument value or identity.
|
|
77
|
+
*/
|
|
78
|
+
readonly diagnostics?: McpRequestDiagnostics;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** What serving a request found out, for the diagnostics. */
|
|
82
|
+
interface SessionTrace {
|
|
83
|
+
session: McpSessionState;
|
|
84
|
+
sessionVersion: string | null;
|
|
85
|
+
toolListVersion: string | null;
|
|
86
|
+
sessionIssued: boolean;
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
/** Serves one request to the MCP endpoint. */
|
|
@@ -71,8 +95,9 @@ export type McpEndpoint = (ctx: McpAccessContext, request: Request) => Promise<R
|
|
|
71
95
|
* (`responseMode: "json"`): a stream would add proxy buffering and timeouts for no tool that needs
|
|
72
96
|
* it, and mid-call notifications are dropped.
|
|
73
97
|
*/
|
|
74
|
-
export function createMcpEndpoint({ createServer, toolListVersion }: McpEndpointOptions): McpEndpoint {
|
|
98
|
+
export function createMcpEndpoint({ createServer, toolListVersion, diagnostics }: McpEndpointOptions): McpEndpoint {
|
|
75
99
|
const readToolListVersion = toolListVersion ?? getFactoryToolListVersion(createServer);
|
|
100
|
+
const report = createDiagnosticsSink(diagnostics);
|
|
76
101
|
const handler = createMcpHandler(
|
|
77
102
|
(requestContext) => createServer(readIdentity(requestContext.authInfo)),
|
|
78
103
|
{
|
|
@@ -83,6 +108,17 @@ export function createMcpEndpoint({ createServer, toolListVersion }: McpEndpoint
|
|
|
83
108
|
);
|
|
84
109
|
|
|
85
110
|
return async (ctx, request) => {
|
|
111
|
+
if (report === undefined) return serve(ctx, request, null);
|
|
112
|
+
const trace: SessionTrace = { session: "unchecked", sessionVersion: null, toolListVersion: null, sessionIssued: false };
|
|
113
|
+
// Read before serving: the SDK consumes the body. Only the method, tool name and clientInfo are kept.
|
|
114
|
+
const summary = await readJsonRpcSummary(request, MAX_MCP_REQUEST_BYTES);
|
|
115
|
+
const response = await serve(ctx, request, trace);
|
|
116
|
+
report(createDiagnostic(request, summary, trace, response.status));
|
|
117
|
+
return response;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
async function serve(ctx: McpAccessContext, request: Request, trace: SessionTrace | null): Promise<Response> {
|
|
121
|
+
if (trace !== null && !request.headers.has(MCP_SESSION_ID_HEADER)) trace.session = "none";
|
|
86
122
|
assertRateLimitBucket(ctx);
|
|
87
123
|
const client = identifyClient(ctx, request.headers);
|
|
88
124
|
if (!client.ok) return Response.json({ error: "client_unidentified" }, { status: 400, headers: NO_STORE });
|
|
@@ -105,15 +141,15 @@ export function createMcpEndpoint({ createServer, toolListVersion }: McpEndpoint
|
|
|
105
141
|
|
|
106
142
|
const authInfo = await authenticate(ctx, request);
|
|
107
143
|
if (authInfo instanceof Response) return authInfo;
|
|
108
|
-
return serveInSession(request, authInfo);
|
|
109
|
-
}
|
|
144
|
+
return serveInSession(request, authInfo, trace);
|
|
145
|
+
}
|
|
110
146
|
|
|
111
147
|
/**
|
|
112
148
|
* Sessions tied to the tool list version (#1055): a session from another version (a release changed a tool, or the
|
|
113
149
|
* access level changed) answers 404, so the client initializes again and lists the tools again; `initialize` gets a
|
|
114
150
|
* new session id. A request without a session id is served as it always was.
|
|
115
151
|
*/
|
|
116
|
-
async function serveInSession(request: Request, authInfo: AuthInfo): Promise<Response> {
|
|
152
|
+
async function serveInSession(request: Request, authInfo: AuthInfo, trace: SessionTrace | null): Promise<Response> {
|
|
117
153
|
const sessionId = request.headers.get(MCP_SESSION_ID_HEADER);
|
|
118
154
|
const isInitialize = sessionId === null && (await isInitializeRequest(request, MAX_MCP_REQUEST_BYTES));
|
|
119
155
|
if (readToolListVersion === undefined || (sessionId === null && !isInitialize)) {
|
|
@@ -129,13 +165,44 @@ export function createMcpEndpoint({ createServer, toolListVersion }: McpEndpoint
|
|
|
129
165
|
const response = await handler.fetch(request, { authInfo });
|
|
130
166
|
return isInitialize ? rewriteInitializeAnswer(response, null) : response;
|
|
131
167
|
}
|
|
168
|
+
if (trace !== null) trace.toolListVersion = version;
|
|
132
169
|
if (sessionId !== null) {
|
|
133
|
-
|
|
170
|
+
if (isToolListSessionCurrent(sessionId, version)) {
|
|
171
|
+
if (trace !== null) trace.session = "current";
|
|
172
|
+
return handler.fetch(request, { authInfo });
|
|
173
|
+
}
|
|
174
|
+
if (trace !== null) recordRenewedSession(trace, sessionId);
|
|
175
|
+
return createSessionRenewalResponse();
|
|
134
176
|
}
|
|
135
|
-
|
|
177
|
+
const response = await rewriteInitializeAnswer(await handler.fetch(request, { authInfo }), createToolListSessionId(version));
|
|
178
|
+
if (trace !== null) trace.sessionIssued = response.headers.has(MCP_SESSION_ID_HEADER);
|
|
179
|
+
return response;
|
|
136
180
|
}
|
|
137
181
|
}
|
|
138
182
|
|
|
183
|
+
/** A session answered 404: stale when the id carries a version, not issued by the endpoint otherwise. */
|
|
184
|
+
function recordRenewedSession(trace: SessionTrace, sessionId: string): void {
|
|
185
|
+
const sessionVersion = readToolListSessionVersion(sessionId);
|
|
186
|
+
trace.session = sessionVersion === null ? "not_issued" : "stale";
|
|
187
|
+
trace.sessionVersion = sessionVersion === null ? null : sanitizeDiagnosticText(sessionVersion);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The diagnostics entry of one served request. */
|
|
191
|
+
function createDiagnostic(request: Request, summary: JsonRpcSummary, trace: SessionTrace, status: number): McpRequestDiagnostic {
|
|
192
|
+
return {
|
|
193
|
+
httpMethod: request.method,
|
|
194
|
+
method: summary.method,
|
|
195
|
+
tool: summary.tool,
|
|
196
|
+
session: trace.session,
|
|
197
|
+
sessionVersion: trace.sessionVersion,
|
|
198
|
+
toolListVersion: trace.toolListVersion,
|
|
199
|
+
sessionIssued: trace.sessionIssued,
|
|
200
|
+
status,
|
|
201
|
+
client: summary.client,
|
|
202
|
+
protocolVersion: summary.protocolVersion,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
139
206
|
/** The factory's own version reader, when it has one (`createMcpServerFactory`). */
|
|
140
207
|
function getFactoryToolListVersion(factory: McpServerFactory | VersionedMcpServerFactory): ToolListVersionReader | undefined {
|
|
141
208
|
return "toolListVersion" in factory && typeof factory.toolListVersion === "function" ? factory.toolListVersion : undefined;
|
package/src/server/index.ts
CHANGED
|
@@ -143,6 +143,8 @@ export {
|
|
|
143
143
|
type RegisterServerInfoToolOptions,
|
|
144
144
|
type ServedServerInfo,
|
|
145
145
|
type ServerInfoAnswer,
|
|
146
|
+
type ServerInfoToolsAnswer,
|
|
147
|
+
type ServedToolContract,
|
|
146
148
|
type ServerInfoToolOptions,
|
|
147
149
|
} from "./server-info.js";
|
|
148
150
|
export { toStrictToolInput, withStrictToolInput, type RenamedFields, type StrictToolInputMessages, type StrictToolInputOptions } from "./strict-input.js";
|
|
@@ -163,6 +165,7 @@ export {
|
|
|
163
165
|
MCP_SESSION_ID_HEADER,
|
|
164
166
|
readToolListSessionVersion,
|
|
165
167
|
} from "./tool-list-session.js";
|
|
168
|
+
export { type McpRequestDiagnostic, type McpRequestDiagnostics, type McpSessionState } from "./request-diagnostics.js";
|
|
166
169
|
export {
|
|
167
170
|
toolError,
|
|
168
171
|
toolResult,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Opt-in request diagnostics for the MCP endpoint (#1066). A hosted client can keep a stale tool list although the
|
|
2
|
+
// server serves the current one, and nothing showed what the client actually sends. One entry per request says which
|
|
3
|
+
// JSON-RPC method came, in which session state, with which answer. No token, body, argument value, session id or
|
|
4
|
+
// user identity is ever part of an entry; client-supplied strings are cut and stripped of control characters.
|
|
5
|
+
import { errorLogLabel } from "@softure-ai/core";
|
|
6
|
+
|
|
7
|
+
/** How a request's `Mcp-Session-Id` relates to the tool list version (#1055). */
|
|
8
|
+
export type McpSessionState =
|
|
9
|
+
/** No session id came with the request. */
|
|
10
|
+
| "none"
|
|
11
|
+
/** The session carries the current version and was served. */
|
|
12
|
+
| "current"
|
|
13
|
+
/** The session carries another version and was answered 404, so the client should initialize again. */
|
|
14
|
+
| "stale"
|
|
15
|
+
/** The session id is not one the endpoint issues; answered 404 like a stale one. */
|
|
16
|
+
| "not_issued"
|
|
17
|
+
/** A session id came, but it was not checked (refused before authentication, or no version to check against). */
|
|
18
|
+
| "unchecked";
|
|
19
|
+
|
|
20
|
+
/** One request as the diagnostics see it. */
|
|
21
|
+
export interface McpRequestDiagnostic {
|
|
22
|
+
/** The HTTP method. */
|
|
23
|
+
readonly httpMethod: string;
|
|
24
|
+
/** The JSON-RPC method, `"batch"` for an array, `null` when the body was not read or is not JSON-RPC. */
|
|
25
|
+
readonly method: string | null;
|
|
26
|
+
/** The tool name of a `tools/call`, otherwise `null`. */
|
|
27
|
+
readonly tool: string | null;
|
|
28
|
+
readonly session: McpSessionState;
|
|
29
|
+
/** The version a stale session carries, otherwise `null`. */
|
|
30
|
+
readonly sessionVersion: string | null;
|
|
31
|
+
/** The tool list version served to the identity, `null` when unknown (refused before authentication, no reader). */
|
|
32
|
+
readonly toolListVersion: string | null;
|
|
33
|
+
/** Whether the answer issued a new session id (a successful `initialize`). */
|
|
34
|
+
readonly sessionIssued: boolean;
|
|
35
|
+
/** The HTTP status of the answer. */
|
|
36
|
+
readonly status: number;
|
|
37
|
+
/** The client's `clientInfo` on `initialize`, otherwise `null`. */
|
|
38
|
+
readonly client: { readonly name: string; readonly version: string } | null;
|
|
39
|
+
/** The protocol version the client asked for on `initialize`, otherwise the `MCP-Protocol-Version` header, or `null`. */
|
|
40
|
+
readonly protocolVersion: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** `true` logs one `console.info` line per request; a function receives each entry instead. */
|
|
44
|
+
export type McpRequestDiagnostics = boolean | ((entry: McpRequestDiagnostic) => void);
|
|
45
|
+
|
|
46
|
+
/** What the diagnostics read from a request body. */
|
|
47
|
+
export interface JsonRpcSummary {
|
|
48
|
+
readonly method: string | null;
|
|
49
|
+
readonly tool: string | null;
|
|
50
|
+
readonly client: { readonly name: string; readonly version: string } | null;
|
|
51
|
+
readonly protocolVersion: string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const MAX_TEXT_LENGTH = 128;
|
|
55
|
+
const PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
56
|
+
const EMPTY_SUMMARY: JsonRpcSummary = { method: null, tool: null, client: null, protocolVersion: null };
|
|
57
|
+
|
|
58
|
+
/** Client-supplied text as it may be logged: control characters replaced, at most 128 characters. */
|
|
59
|
+
export function sanitizeDiagnosticText(value: string): string {
|
|
60
|
+
const clean = Array.from(value, (character) => (isControlCharacter(character) ? "?" : character)).join("");
|
|
61
|
+
return clean.length > MAX_TEXT_LENGTH ? `${clean.slice(0, MAX_TEXT_LENGTH)}…` : clean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The method, tool name, `clientInfo` and protocol version of a request, read from a clone of its body. A body over
|
|
66
|
+
* `maxBytes` or one that is not JSON gives no method (the SDK answers it itself). Nothing else of the body is kept.
|
|
67
|
+
*/
|
|
68
|
+
export async function readJsonRpcSummary(request: Request, maxBytes: number): Promise<JsonRpcSummary> {
|
|
69
|
+
const header = request.headers.get(PROTOCOL_VERSION_HEADER);
|
|
70
|
+
const headerVersion = header === null ? null : sanitizeDiagnosticText(header);
|
|
71
|
+
const withHeader = { ...EMPTY_SUMMARY, protocolVersion: headerVersion };
|
|
72
|
+
if (request.method !== "POST") return withHeader;
|
|
73
|
+
const length = Number(request.headers.get("content-length") ?? "0");
|
|
74
|
+
if (!Number.isFinite(length) || length > maxBytes) return withHeader;
|
|
75
|
+
let body: unknown;
|
|
76
|
+
try {
|
|
77
|
+
const text = await request.clone().text();
|
|
78
|
+
if (text.length > maxBytes) return withHeader;
|
|
79
|
+
body = JSON.parse(text);
|
|
80
|
+
} catch {
|
|
81
|
+
// Not JSON or no readable body: the SDK answers that itself; the entry just has no method.
|
|
82
|
+
return withHeader;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(body)) return { ...withHeader, method: "batch" };
|
|
85
|
+
if (!isRecord(body) || typeof body.method !== "string") return withHeader;
|
|
86
|
+
|
|
87
|
+
const method = sanitizeDiagnosticText(body.method);
|
|
88
|
+
const params = isRecord(body.params) ? body.params : {};
|
|
89
|
+
if (body.method === "tools/call") {
|
|
90
|
+
return { ...withHeader, method, tool: typeof params.name === "string" ? sanitizeDiagnosticText(params.name) : null };
|
|
91
|
+
}
|
|
92
|
+
if (body.method !== "initialize") return { ...withHeader, method };
|
|
93
|
+
const clientInfo = isRecord(params.clientInfo) ? params.clientInfo : null;
|
|
94
|
+
const client =
|
|
95
|
+
clientInfo === null
|
|
96
|
+
? null
|
|
97
|
+
: {
|
|
98
|
+
name: typeof clientInfo.name === "string" ? sanitizeDiagnosticText(clientInfo.name) : "",
|
|
99
|
+
version: typeof clientInfo.version === "string" ? sanitizeDiagnosticText(clientInfo.version) : "",
|
|
100
|
+
};
|
|
101
|
+
const protocolVersion = typeof params.protocolVersion === "string" ? sanitizeDiagnosticText(params.protocolVersion) : headerVersion;
|
|
102
|
+
return { method, tool: null, client, protocolVersion };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The sink for the option, or `undefined` when diagnostics are off. A throwing sink is logged, never rethrown. */
|
|
106
|
+
export function createDiagnosticsSink(diagnostics: McpRequestDiagnostics | undefined): ((entry: McpRequestDiagnostic) => void) | undefined {
|
|
107
|
+
if (diagnostics === undefined || diagnostics === false) return undefined;
|
|
108
|
+
const write = diagnostics === true ? logDiagnostic : diagnostics;
|
|
109
|
+
return (entry) => {
|
|
110
|
+
try {
|
|
111
|
+
write(entry);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(`@softure-ai/mcp-access: the request diagnostics sink failed: ${errorLogLabel(error)}`);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function logDiagnostic(entry: McpRequestDiagnostic): void {
|
|
119
|
+
console.info(`@softure-ai/mcp-access: mcp request ${JSON.stringify(entry)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** C0 controls, DEL and C1 controls: anything that could break or forge a log line. */
|
|
123
|
+
function isControlCharacter(character: string): boolean {
|
|
124
|
+
const code = character.codePointAt(0) ?? 0;
|
|
125
|
+
return code < 0x20 || (code >= 0x7f && code <= 0x9f);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
129
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
130
|
+
}
|