@softure-ai/mcp-access 0.3.0 → 0.5.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 (51) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +32 -1
  3. package/dist/index.js +1 -1
  4. package/dist/jobs/index.d.ts +1 -1
  5. package/dist/jobs/index.d.ts.map +1 -1
  6. package/dist/jobs/index.js +1 -1
  7. package/dist/jobs/index.js.map +1 -1
  8. package/dist/jobs/operation-tasks.d.ts +11 -4
  9. package/dist/jobs/operation-tasks.d.ts.map +1 -1
  10. package/dist/jobs/operation-tasks.js +5 -3
  11. package/dist/jobs/operation-tasks.js.map +1 -1
  12. package/dist/jobs/operations.d.ts +28 -7
  13. package/dist/jobs/operations.d.ts.map +1 -1
  14. package/dist/jobs/operations.js +27 -6
  15. package/dist/jobs/operations.js.map +1 -1
  16. package/dist/server/endpoint.d.ts +13 -2
  17. package/dist/server/endpoint.d.ts.map +1 -1
  18. package/dist/server/endpoint.js +35 -2
  19. package/dist/server/endpoint.js.map +1 -1
  20. package/dist/server/index.d.ts +2 -1
  21. package/dist/server/index.d.ts.map +1 -1
  22. package/dist/server/index.js +1 -0
  23. package/dist/server/index.js.map +1 -1
  24. package/dist/server/server-factory.d.ts +7 -3
  25. package/dist/server/server-factory.d.ts.map +1 -1
  26. package/dist/server/server-factory.js +9 -3
  27. package/dist/server/server-factory.js.map +1 -1
  28. package/dist/server/tool-list-session.d.ts +22 -0
  29. package/dist/server/tool-list-session.d.ts.map +1 -0
  30. package/dist/server/tool-list-session.js +97 -0
  31. package/dist/server/tool-list-session.js.map +1 -0
  32. package/dist/testing/index.d.ts +1 -0
  33. package/dist/testing/index.d.ts.map +1 -1
  34. package/dist/testing/index.js +1 -0
  35. package/dist/testing/index.js.map +1 -1
  36. package/dist/testing/sessions.d.ts +8 -0
  37. package/dist/testing/sessions.d.ts.map +1 -0
  38. package/dist/testing/sessions.js +40 -0
  39. package/dist/testing/sessions.js.map +1 -0
  40. package/module.json +1 -1
  41. package/package.json +1 -1
  42. package/src/index.ts +1 -1
  43. package/src/jobs/index.ts +2 -0
  44. package/src/jobs/operation-tasks.ts +12 -7
  45. package/src/jobs/operations.ts +62 -17
  46. package/src/server/endpoint.ts +57 -3
  47. package/src/server/index.ts +8 -0
  48. package/src/server/server-factory.ts +11 -5
  49. package/src/server/tool-list-session.ts +103 -0
  50. package/src/testing/index.ts +1 -0
  51. package/src/testing/sessions.ts +41 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { McpServer, type McpServerOptions } from "@modelcontextprotocol/server";
2
2
  import type { McpServerIdentity } from "../contract.js";
3
- import type { McpServerFactory } from "./endpoint.js";
3
+ import type { VersionedMcpServerFactory } from "./endpoint.js";
4
4
  import { type StrictToolInputOptions } from "./strict-input.js";
5
5
  import { type ServerInfoToolOptions } from "./server-info.js";
6
6
  export interface McpServerFactoryOptions {
@@ -26,6 +26,10 @@ export interface McpServerFactoryOptions {
26
26
  */
27
27
  readonly serverInfoTool?: ServerInfoToolOptions;
28
28
  }
29
- /** An `McpServerFactory` whose servers carry the tool list's version for the identity's access level. */
30
- export declare function createMcpServerFactory(options: McpServerFactoryOptions): McpServerFactory;
29
+ /**
30
+ * An `McpServerFactory` whose servers carry the tool list's version for the identity's access level. Its
31
+ * `toolListVersion(identity)` reads that version without building a server, so `createMcpEndpoint` ties each session
32
+ * to it (#1055).
33
+ */
34
+ export declare function createMcpServerFactory(options: McpServerFactoryOptions): VersionedMcpServerFactory;
31
35
  //# sourceMappingURL=server-factory.d.ts.map
@@ -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,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,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;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACjD;AAED,yGAAyG;AACzG,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CAmCzF"}
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;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACjD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,yBAAyB,CAqClG"}
@@ -12,7 +12,11 @@ import { McpServer } from "@modelcontextprotocol/server";
12
12
  import { withStrictToolInput } from "./strict-input.js";
13
13
  import { registerServerInfoTool } from "./server-info.js";
14
14
  import { measureToolList } from "./tool-list.js";
15
- /** An `McpServerFactory` whose servers carry the tool list's version for the identity's access level. */
15
+ /**
16
+ * An `McpServerFactory` whose servers carry the tool list's version for the identity's access level. Its
17
+ * `toolListVersion(identity)` reads that version without building a server, so `createMcpEndpoint` ties each session
18
+ * to it (#1055).
19
+ */
16
20
  export function createMcpServerFactory(options) {
17
21
  const strictOptions = options.strictToolInput === true ? {} : options.strictToolInput === false ? undefined : options.strictToolInput;
18
22
  // The served version reaches `get_server_info` here; the recording server registers it with a placeholder, since
@@ -39,11 +43,13 @@ export function createMcpServerFactory(options) {
39
43
  void measurement.catch(() => measurements.delete(identity.canWrite));
40
44
  return measurement;
41
45
  };
42
- return async (identity) => {
43
- const version = options.version ?? (await measure(identity)).toolListVersion;
46
+ const toolListVersion = async (identity) => options.version ?? (await measure(identity)).toolListVersion;
47
+ const factory = async (identity) => {
48
+ const version = await toolListVersion(identity);
44
49
  const server = new McpServer({ name: options.name, version, ...(options.title === undefined ? {} : { title: options.title }) }, options.serverOptions);
45
50
  await registerTools(server, identity, version);
46
51
  return server;
47
52
  };
53
+ return Object.assign(factory, { toolListVersion });
48
54
  }
49
55
  //# sourceMappingURL=server-factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-factory.js","sourceRoot":"","sources":["../../src/server/server-factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server-factory.js","sourceRoot":"","sources":["../../src/server/server-factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /** The header that carries the session id, both ways. */
2
+ export declare const MCP_SESSION_ID_HEADER = "mcp-session-id";
3
+ /** A new session id for the version: `<base64url(version)>.<32 hex>`, visible ASCII whatever the version holds. */
4
+ export declare function createToolListSessionId(toolListVersion: string): string;
5
+ /** The tool list version a session id carries, or `null` when the id is not one the endpoint issues. */
6
+ export declare function readToolListSessionVersion(sessionId: string): string | null;
7
+ /** Whether a request in this session may be served: its version is the current one. */
8
+ export declare function isToolListSessionCurrent(sessionId: string, toolListVersion: string): boolean;
9
+ /** The answer to a request in a session from another tool list version: the client starts a new session. */
10
+ export declare function createSessionRenewalResponse(): Response;
11
+ /**
12
+ * Whether the request is a JSON-RPC `initialize`: a POST without a session id whose JSON body asks for it. The body
13
+ * is read from a clone, and a body over `maxBytes` is not read (the SDK refuses it with 413 anyway).
14
+ */
15
+ export declare function isInitializeRequest(request: Request, maxBytes: number): Promise<boolean>;
16
+ /**
17
+ * The `initialize` answer with `capabilities.tools.listChanged: false`, since no answer of the endpoint ever carries
18
+ * the notification, and with the session id when there is one. The body keeps its format (JSON or one SSE event);
19
+ * an answer that is not a successful `InitializeResult` is returned as it is.
20
+ */
21
+ export declare function rewriteInitializeAnswer(response: Response, sessionId: string | null): Promise<Response>;
22
+ //# sourceMappingURL=tool-list-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-list-session.d.ts","sourceRoot":"","sources":["../../src/server/tool-list-session.ts"],"names":[],"mappings":"AAQA,yDAAyD;AACzD,eAAO,MAAM,qBAAqB,mBAAmB,CAAC;AAOtD,mHAAmH;AACnH,wBAAgB,uBAAuB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,wGAAwG;AACxG,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3E;AAED,uFAAuF;AACvF,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAE5F;AAED,4GAA4G;AAC5G,wBAAgB,4BAA4B,IAAI,QAAQ,CAKvD;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc9F;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsB7G"}
@@ -0,0 +1,97 @@
1
+ // Sessions tied to the tool list version (#1055). A hosted client keeps its cached `tools/list` for as long as its
2
+ // connection lives, even after a release changed a tool, and the endpoint has no channel to send
3
+ // `notifications/tools/list_changed`. So the endpoint answers `initialize` with an `Mcp-Session-Id` that carries the
4
+ // tool list version of the identity's access level, and a later request whose session carries another version gets
5
+ // `404 Not Found`: Streamable HTTP defines that as "start a new session", so the client initializes again and lists
6
+ // the tools again. Nothing is stored: the id itself holds the version, so any instance can check it.
7
+ import { randomBytes } from "node:crypto";
8
+ /** The header that carries the session id, both ways. */
9
+ export const MCP_SESSION_ID_HEADER = "mcp-session-id";
10
+ /** Random bytes after the version, as hex. */
11
+ const RANDOM_BYTES = 16;
12
+ const SESSION_ID_PATTERN = /^([A-Za-z0-9_-]+)\.[0-9a-f]+$/;
13
+ const SSE_DATA_PREFIX = "data: ";
14
+ /** A new session id for the version: `<base64url(version)>.<32 hex>`, visible ASCII whatever the version holds. */
15
+ export function createToolListSessionId(toolListVersion) {
16
+ return `${Buffer.from(toolListVersion, "utf8").toString("base64url")}.${randomBytes(RANDOM_BYTES).toString("hex")}`;
17
+ }
18
+ /** The tool list version a session id carries, or `null` when the id is not one the endpoint issues. */
19
+ export function readToolListSessionVersion(sessionId) {
20
+ const match = SESSION_ID_PATTERN.exec(sessionId);
21
+ if (match?.[1] === undefined)
22
+ return null;
23
+ return Buffer.from(match[1], "base64url").toString("utf8");
24
+ }
25
+ /** Whether a request in this session may be served: its version is the current one. */
26
+ export function isToolListSessionCurrent(sessionId, toolListVersion) {
27
+ return readToolListSessionVersion(sessionId) === toolListVersion;
28
+ }
29
+ /** The answer to a request in a session from another tool list version: the client starts a new session. */
30
+ export function createSessionRenewalResponse() {
31
+ return Response.json({ jsonrpc: "2.0", error: { code: -32001, message: "Session not found: the tool list changed, initialize again" }, id: null }, { status: 404, headers: { "cache-control": "no-store" } });
32
+ }
33
+ /**
34
+ * Whether the request is a JSON-RPC `initialize`: a POST without a session id whose JSON body asks for it. The body
35
+ * is read from a clone, and a body over `maxBytes` is not read (the SDK refuses it with 413 anyway).
36
+ */
37
+ export async function isInitializeRequest(request, maxBytes) {
38
+ if (request.method !== "POST" || request.headers.has(MCP_SESSION_ID_HEADER))
39
+ return false;
40
+ const length = Number(request.headers.get("content-length") ?? "0");
41
+ if (!Number.isFinite(length) || length > maxBytes)
42
+ return false;
43
+ let body;
44
+ try {
45
+ const text = await request.clone().text();
46
+ if (text.length > maxBytes)
47
+ return false;
48
+ body = JSON.parse(text);
49
+ }
50
+ catch {
51
+ // Not JSON: the SDK answers the parse error itself.
52
+ return false;
53
+ }
54
+ return typeof body === "object" && body !== null && !Array.isArray(body) && body.method === "initialize";
55
+ }
56
+ /**
57
+ * The `initialize` answer with `capabilities.tools.listChanged: false`, since no answer of the endpoint ever carries
58
+ * the notification, and with the session id when there is one. The body keeps its format (JSON or one SSE event);
59
+ * an answer that is not a successful `InitializeResult` is returned as it is.
60
+ */
61
+ export async function rewriteInitializeAnswer(response, sessionId) {
62
+ if (response.status !== 200)
63
+ return response;
64
+ const text = await response.text();
65
+ const isEventStream = (response.headers.get("content-type") ?? "").includes("text/event-stream");
66
+ const lines = text.split("\n");
67
+ const dataIndex = isEventStream ? lines.findIndex((line) => line.startsWith(SSE_DATA_PREFIX)) : -1;
68
+ const json = isEventStream ? (dataIndex === -1 ? null : (lines[dataIndex] ?? "").slice(SSE_DATA_PREFIX.length)) : text;
69
+ const answer = parseInitializeAnswer(json);
70
+ const headers = new Headers(response.headers);
71
+ // The body changes length, so the old one would be wrong.
72
+ headers.delete("content-length");
73
+ const tools = answer?.result?.capabilities?.tools;
74
+ if (answer === null || answer.result?.protocolVersion === undefined) {
75
+ return new Response(text, { status: response.status, statusText: response.statusText, headers });
76
+ }
77
+ if (tools !== undefined && tools !== null && typeof tools === "object")
78
+ tools.listChanged = false;
79
+ if (sessionId !== null)
80
+ headers.set(MCP_SESSION_ID_HEADER, sessionId);
81
+ const rewritten = JSON.stringify(answer);
82
+ if (isEventStream)
83
+ lines[dataIndex] = `${SSE_DATA_PREFIX}${rewritten}`;
84
+ return new Response(isEventStream ? lines.join("\n") : rewritten, { status: response.status, statusText: response.statusText, headers });
85
+ }
86
+ /** The answer as JSON, or `null` when there is none or it is not JSON (the SDK's own error text). */
87
+ function parseInitializeAnswer(json) {
88
+ if (json === null)
89
+ return null;
90
+ try {
91
+ return JSON.parse(json);
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+ //# sourceMappingURL=tool-list-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-list-session.js","sourceRoot":"","sources":["../../src/server/tool-list-session.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
1
  export { expectToolCatalogMatchesServer, getToolCatalogDifferences, measureServedToolLists, type ServedToolLists, type ToolCatalogDifferences, } from "./catalog.js";
2
+ export { expectToolListSessionRenewed } from "./sessions.js";
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,sBAAsB,GAC5B,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EACzB,sBAAsB,EACtB,KAAK,eAAe,EACpB,KAAK,sBAAsB,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC"}
@@ -1,3 +1,4 @@
1
1
  // `@softure-ai/mcp-access/testing`: checks for an app's own tests.
2
2
  export { expectToolCatalogMatchesServer, getToolCatalogDifferences, measureServedToolLists, } from "./catalog.js";
3
+ export { expectToolListSessionRenewed } from "./sessions.js";
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EACzB,sBAAsB,GAGvB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EACzB,sBAAsB,GAGvB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { McpServerFactory } from "../server/endpoint.js";
2
+ /**
3
+ * Throws unless a session opened under `before` (the previous release's factory) is renewed under `after` for every
4
+ * access level whose served tool list changed, and kept for every level whose list did not. A pair where no level
5
+ * changed throws too: the check would prove nothing.
6
+ */
7
+ export declare function expectToolListSessionRenewed(before: McpServerFactory, after: McpServerFactory): Promise<void>;
8
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/testing/sessions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAA6B,MAAM,uBAAuB,CAAC;AAWzF;;;;GAIG;AACH,wBAAsB,4BAA4B,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAanH"}
@@ -0,0 +1,40 @@
1
+ import { createToolListSessionId, isToolListSessionCurrent } from "../server/tool-list-session.js";
2
+ import { fingerprintToolList, listServedTools } from "../server/tool-list.js";
3
+ const READER = { userId: "00000000-0000-4000-8000-000000000001", canWrite: false, tokenId: "session-check" };
4
+ const WRITER = { ...READER, canWrite: true };
5
+ const LEVELS = [
6
+ ["read", READER],
7
+ ["write", WRITER],
8
+ ];
9
+ /**
10
+ * Throws unless a session opened under `before` (the previous release's factory) is renewed under `after` for every
11
+ * access level whose served tool list changed, and kept for every level whose list did not. A pair where no level
12
+ * changed throws too: the check would prove nothing.
13
+ */
14
+ export async function expectToolListSessionRenewed(before, after) {
15
+ const problems = [];
16
+ const unchanged = [];
17
+ for (const [level, identity] of LEVELS) {
18
+ const sessionId = createToolListSessionId(await readVersion(before, identity));
19
+ const isCurrent = isToolListSessionCurrent(sessionId, await readVersion(after, identity));
20
+ const hasChanged = fingerprintToolList(await listServedTools(() => before(identity))) !== fingerprintToolList(await listServedTools(() => after(identity)));
21
+ if (!hasChanged)
22
+ unchanged.push(level);
23
+ if (hasChanged && isCurrent)
24
+ problems.push(`the ${level} tool list changed but a session opened before is kept`);
25
+ if (!hasChanged && !isCurrent)
26
+ problems.push(`the ${level} tool list did not change but a session opened before is renewed`);
27
+ }
28
+ if (unchanged.length === LEVELS.length)
29
+ problems.push(`the tool list did not change for any access level (${unchanged.join(", ")})`);
30
+ if (problems.length > 0)
31
+ throw new Error(`@softure-ai/mcp-access: sessions do not follow the tool list: ${problems.join("; ")}`);
32
+ }
33
+ async function readVersion(factory, identity) {
34
+ const reader = factory.toolListVersion;
35
+ if (typeof reader !== "function") {
36
+ throw new Error("@softure-ai/mcp-access: the factory has no toolListVersion; build it with createMcpServerFactory or pass toolListVersion to createMcpEndpoint");
37
+ }
38
+ return reader(identity);
39
+ }
40
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/testing/sessions.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9E,MAAM,MAAM,GAAsB,EAAE,MAAM,EAAE,sCAAsC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;AAChI,MAAM,MAAM,GAAsB,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAChE,MAAM,MAAM,GAAG;IACb,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,OAAO,EAAE,MAAM,CAAC;CACT,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,MAAwB,EAAE,KAAuB;IAClG,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,uBAAuB,CAAC,MAAM,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC/E,MAAM,SAAS,GAAG,wBAAwB,CAAC,SAAS,EAAE,MAAM,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;QAC1F,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,eAAe,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,mBAAmB,CAAC,MAAM,eAAe,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC5J,IAAI,CAAC,UAAU;YAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,UAAU,IAAI,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,KAAK,wDAAwD,CAAC,CAAC;QACjH,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,KAAK,kEAAkE,CAAC,CAAC;IAC/H,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,QAAQ,CAAC,IAAI,CAAC,sDAAsD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrI,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,iEAAiE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACnI,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,OAAyB,EAAE,QAA2B;IAC/E,MAAM,MAAM,GAAI,OAA8C,CAAC,eAAe,CAAC;IAC/E,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,+IAA+I,CAAC,CAAC;IACnK,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1B,CAAC"}
package/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "mcp-access",
3
- "version": "0.3.0",
3
+ "version": "0.5.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.0",
3
+ "version": "0.5.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.3.0",
54
+ version: "0.5.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/jobs/index.ts CHANGED
@@ -26,6 +26,7 @@ export const mcpAccessJobSchedules = {
26
26
  export {
27
27
  cancelOperation,
28
28
  getOperation,
29
+ getOperationErrorHints,
29
30
  getOperationStatus,
30
31
  getPollAfterSeconds,
31
32
  MAX_OPERATION_DEDUP_KEY_LENGTH,
@@ -44,6 +45,7 @@ export {
44
45
  type OperationPollOptions,
45
46
  type OperationStatus,
46
47
  type OperationStatusToolOptions,
48
+ type OperationToolContext,
47
49
  type ReadOperationResult,
48
50
  type StartOperationInput,
49
51
  } from "./operations.js";
@@ -70,14 +70,17 @@ export interface OperationTask {
70
70
  readonly result?: CallToolResult;
71
71
  }
72
72
 
73
- export interface OperationTasksOptions {
74
- /** `{ db, clock, config }` of the request. */
75
- readonly ctx: JobsContext;
73
+ /** `ctx`, or `getContext` resolved inside each task request (see `OperationToolContext`). */
74
+ export type OperationTasksOptions = OperationTaskContext & {
76
75
  /** The request's identity: a task is found only for its owner, and only a writer may cancel it. */
77
76
  readonly identity: McpServerIdentity;
78
77
  readonly readResult?: ReadOperationResult;
79
78
  readonly poll?: OperationPollOptions;
80
- }
79
+ };
80
+
81
+ type OperationTaskContext =
82
+ | { readonly ctx: JobsContext; readonly getContext?: never }
83
+ | { readonly ctx?: never; readonly getContext: () => Promise<JobsContext> };
81
84
 
82
85
  export interface AnswerOperationTaskInput {
83
86
  readonly jobId: string;
@@ -133,24 +136,26 @@ export async function getOperationTask(
133
136
  * `tasks/cancel` for the request's account. Register it on every server whose tools pass `taskRequest`.
134
137
  */
135
138
  export function registerOperationTasks(server: McpServer, options: OperationTasksOptions): void {
136
- const { ctx, identity } = options;
139
+ const { identity } = options;
140
+ const resolveContext = (): Promise<JobsContext> => (options.getContext === undefined ? Promise.resolve(options.ctx) : options.getContext());
137
141
  const describeOptions = { ...(options.readResult === undefined ? {} : { readResult: options.readResult }), ...(options.poll === undefined ? {} : { poll: options.poll }) };
138
142
  server.server.registerCapabilities({ extensions: { [TASKS_EXTENSION]: {} } });
139
143
 
140
144
  server.server.setRequestHandler("tasks/get", { params: taskIdParams, result: taskResult }, async ({ taskId }, request) => {
141
145
  requireTasksCapability(request);
142
- return { ...(await requireTask(ctx, identity, taskId, describeOptions)) };
146
+ return { ...(await requireTask(await resolveContext(), identity, taskId, describeOptions)) };
143
147
  });
144
148
 
145
149
  // Operations never ask for input, so there is nothing to update; an owned task is acknowledged, as the spec asks.
146
150
  server.server.setRequestHandler("tasks/update", { params: taskIdParams, result: taskResult }, async ({ taskId }, request) => {
147
151
  requireTasksCapability(request);
148
- await requireTask(ctx, identity, taskId, describeOptions);
152
+ await requireTask(await resolveContext(), identity, taskId, describeOptions);
149
153
  return {};
150
154
  });
151
155
 
152
156
  server.server.setRequestHandler("tasks/cancel", { params: taskIdParams, result: taskResult }, async ({ taskId }, request) => {
153
157
  requireTasksCapability(request);
158
+ const ctx = await resolveContext();
154
159
  const found = await findTask(ctx, identity, taskId);
155
160
  if (found === null) throw new ProtocolError(ProtocolErrorCode.InvalidParams, TASK_NOT_FOUND);
156
161
  if (found === "expired") throw new ProtocolError(ProtocolErrorCode.InvalidParams, TASK_EXPIRED);
@@ -11,13 +11,13 @@
11
11
  // keeps after the job ends. Any other job, or another account's, is "not found".
12
12
  import { randomUUID } from "node:crypto";
13
13
  import type { CallToolResult, McpServer } from "@modelcontextprotocol/server";
14
- import { err, ok, type Err, type Ok } from "@softure-ai/core";
14
+ import { err, ok, type Err, type Ok, type SoftureConfig } from "@softure-ai/core";
15
15
  import { enqueueJob, requestJobCancel, type JobsContext } from "@softure-ai/jobs/server";
16
16
  import { z } from "zod";
17
17
  import type { McpServerIdentity } from "../contract.js";
18
- import type { McpAccessMessages } from "../messages/index.js";
18
+ import { mcpAccessMessages, type McpAccessMessages } from "../messages/index.js";
19
19
  import { getMcpAccessMessages } from "../server/options.js";
20
- import { toolResult, withToolErrors, type ToolErrorHints } from "../server/tools.js";
20
+ import { answerValue, toolResult, withToolErrors, type ToolErrorHints } from "../server/tools.js";
21
21
  import { answerOperationTask, isTasksRequest, isTaskTtlValid, MAX_TASK_TTL_SECONDS, type OperationTaskRequest } from "./operation-tasks.js";
22
22
  import {
23
23
  describeOperation,
@@ -76,29 +76,47 @@ export interface StartOperationInput {
76
76
  readonly taskTtlSeconds?: number;
77
77
  }
78
78
 
79
- interface OperationToolOptions {
80
- /** `{ db, clock, config }` of the request. */
81
- readonly ctx: JobsContext;
79
+ /**
80
+ * Where an operation tool finds its `{ db, clock, config }`: the request's context as a value, or a getter resolved
81
+ * inside each call (for a server factory that opens the database per call, so registering and listing the tools
82
+ * opens nothing).
83
+ */
84
+ export type OperationToolContext =
85
+ | {
86
+ /** `{ db, clock, config }` of the request. */
87
+ readonly ctx: JobsContext;
88
+ readonly getContext?: never;
89
+ readonly config?: never;
90
+ }
91
+ | {
92
+ readonly ctx?: never;
93
+ /** Resolves `{ db, clock, config }` inside each call, once per call. */
94
+ readonly getContext: () => Promise<JobsContext>;
95
+ /** The app's config for the tool description at registration. Default: the built-in English dictionary. */
96
+ readonly config?: SoftureConfig;
97
+ };
98
+
99
+ type OperationToolOptions = OperationToolContext & {
82
100
  /** The request's identity: operations are scoped to `identity.userId`. */
83
101
  readonly identity: McpServerIdentity;
84
102
  /** Default: the module dictionary's description. */
85
103
  readonly description?: string;
86
- }
104
+ };
87
105
 
88
- export interface OperationStatusToolOptions extends OperationToolOptions {
106
+ export type OperationStatusToolOptions = OperationToolOptions & {
89
107
  /** Default `get_operation`. */
90
108
  readonly name?: string;
91
109
  readonly readResult?: ReadOperationResult;
92
110
  readonly poll?: OperationPollOptions;
93
- }
111
+ };
94
112
 
95
- export interface OperationCancelToolOptions extends OperationToolOptions {
113
+ export type OperationCancelToolOptions = OperationToolOptions & {
96
114
  /** Default `cancel_operation`. */
97
115
  readonly name?: string;
98
116
  /** The status tool the answer points to. Default `get_operation`. */
99
117
  readonly statusToolName?: string;
100
118
  readonly poll?: OperationPollOptions;
101
- }
119
+ };
102
120
 
103
121
  const operationInput = z.object({ operationId: z.string().min(1).max(100) });
104
122
 
@@ -171,7 +189,7 @@ export async function cancelOperation(
171
189
  /** Registers the read-only status tool (`get_operation`) for the request's account. */
172
190
  export function registerOperationStatusTool(server: McpServer, options: OperationStatusToolOptions): void {
173
191
  const name = options.name ?? OPERATION_STATUS_TOOL_NAME;
174
- const messages = getMcpAccessMessages(options.ctx.config).operations;
192
+ const messages = getRegistrationMessages(options);
175
193
  const getOptions: GetOperationOptions = {
176
194
  statusToolName: name,
177
195
  ...(options.readResult === undefined ? {} : { readResult: options.readResult }),
@@ -185,7 +203,13 @@ export function registerOperationStatusTool(server: McpServer, options: Operatio
185
203
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
186
204
  },
187
205
  ({ operationId }) =>
188
- withToolErrors(() => getOperation(options.ctx, options.identity, operationId, getOptions), { hints: getOperationHints(messages), label: name }),
206
+ withToolErrors(
207
+ async () => {
208
+ const ctx = await resolveOperationContext(options);
209
+ return answerValue(await getOperation(ctx, options.identity, operationId, getOptions), getOperationErrorHints(ctx.config));
210
+ },
211
+ { label: name },
212
+ ),
189
213
  );
190
214
  }
191
215
 
@@ -193,7 +217,7 @@ export function registerOperationStatusTool(server: McpServer, options: Operatio
193
217
  export function registerOperationCancelTool(server: McpServer, options: OperationCancelToolOptions): void {
194
218
  if (!options.identity.canWrite) return;
195
219
  const name = options.name ?? OPERATION_CANCEL_TOOL_NAME;
196
- const messages = getMcpAccessMessages(options.ctx.config).operations;
220
+ const messages = getRegistrationMessages(options);
197
221
  const cancelOptions = { statusToolName: options.statusToolName ?? OPERATION_STATUS_TOOL_NAME, ...(options.poll === undefined ? {} : { poll: options.poll }) };
198
222
  server.registerTool(
199
223
  name,
@@ -203,11 +227,32 @@ export function registerOperationCancelTool(server: McpServer, options: Operatio
203
227
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
204
228
  },
205
229
  ({ operationId }) =>
206
- withToolErrors(() => cancelOperation(options.ctx, options.identity, operationId, cancelOptions), { hints: getOperationHints(messages), label: name }),
230
+ withToolErrors(
231
+ async () => {
232
+ const ctx = await resolveOperationContext(options);
233
+ return answerValue(await cancelOperation(ctx, options.identity, operationId, cancelOptions), getOperationErrorHints(ctx.config));
234
+ },
235
+ { label: name },
236
+ ),
207
237
  );
208
238
  }
209
239
 
210
-
211
- function getOperationHints(messages: McpAccessMessages["operations"]): ToolErrorHints {
240
+ /**
241
+ * The module's sentences for the operation error codes, as `withToolErrors({ hints })` takes them: for an app that
242
+ * wraps `getOperation` or `cancelOperation` in tools of its own.
243
+ */
244
+ export function getOperationErrorHints(config: SoftureConfig): ToolErrorHints {
245
+ const messages = getMcpAccessMessages(config).operations;
212
246
  return { "mcp-access.operation_not_found": messages.notFound, "mcp-access.operation_finished": messages.finished };
213
247
  }
248
+
249
+ /** The context of this call: the value given, or the getter resolved now. */
250
+ function resolveOperationContext(source: OperationToolContext): Promise<JobsContext> {
251
+ return source.getContext === undefined ? Promise.resolve(source.ctx) : source.getContext();
252
+ }
253
+
254
+ /** The texts a registration needs before any call: from the context or config given, else the English dictionary. */
255
+ function getRegistrationMessages(source: OperationToolContext): McpAccessMessages["operations"] {
256
+ const config = source.getContext === undefined ? source.ctx.config : source.config;
257
+ return config === undefined ? mcpAccessMessages.en.operations : getMcpAccessMessages(config).operations;
258
+ }