@copilotkit/aimock 1.43.0 → 1.45.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +46 -0
- package/README.md +14 -2
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +31 -5
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +31 -5
- package/dist/aimock-cli.js.map +1 -1
- package/dist/bedrock-converse.cjs +344 -43
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +345 -44
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +195 -42
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.d.cts.map +1 -1
- package/dist/bedrock.d.ts.map +1 -1
- package/dist/bedrock.js +196 -43
- package/dist/bedrock.js.map +1 -1
- package/dist/chaos.cjs +9 -5
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +10 -7
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +238 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +242 -13
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +159 -72
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.d.cts.map +1 -1
- package/dist/cohere.d.ts.map +1 -1
- package/dist/cohere.js +160 -73
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +63 -5
- package/dist/config-loader.cjs.map +1 -1
- package/dist/config-loader.d.cts +37 -4
- package/dist/config-loader.d.cts.map +1 -1
- package/dist/config-loader.d.ts +37 -4
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +63 -5
- package/dist/config-loader.js.map +1 -1
- package/dist/constants.cjs +8 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +6 -2
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/echo-text.cjs +65 -0
- package/dist/echo-text.cjs.map +1 -0
- package/dist/echo-text.d.cts +22 -0
- package/dist/echo-text.d.cts.map +1 -0
- package/dist/echo-text.d.ts +22 -0
- package/dist/echo-text.d.ts.map +1 -0
- package/dist/echo-text.js +65 -0
- package/dist/echo-text.js.map +1 -0
- package/dist/embeddings.cjs +18 -1
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +18 -1
- package/dist/embeddings.js.map +1 -1
- package/dist/fakes-plugin.cjs +131 -0
- package/dist/fakes-plugin.cjs.map +1 -0
- package/dist/fakes-plugin.d.cts +23 -0
- package/dist/fakes-plugin.d.cts.map +1 -0
- package/dist/fakes-plugin.d.ts +23 -0
- package/dist/fakes-plugin.d.ts.map +1 -0
- package/dist/fakes-plugin.js +127 -0
- package/dist/fakes-plugin.js.map +1 -0
- package/dist/files.cjs +9 -11
- package/dist/files.cjs.map +1 -1
- package/dist/files.d.cts.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +10 -13
- package/dist/files.js.map +1 -1
- package/dist/fine-tuning.cjs +1 -19
- package/dist/fine-tuning.cjs.map +1 -1
- package/dist/fine-tuning.d.cts.map +1 -1
- package/dist/fine-tuning.d.ts.map +1 -1
- package/dist/fine-tuning.js +2 -20
- package/dist/fine-tuning.js.map +1 -1
- package/dist/fixture-loader-services.cjs +116 -0
- package/dist/fixture-loader-services.cjs.map +1 -0
- package/dist/fixture-loader-services.d.cts +48 -0
- package/dist/fixture-loader-services.d.cts.map +1 -0
- package/dist/fixture-loader-services.d.ts +48 -0
- package/dist/fixture-loader-services.d.ts.map +1 -0
- package/dist/fixture-loader-services.js +114 -0
- package/dist/fixture-loader-services.js.map +1 -0
- package/dist/fixture-loader.cjs +552 -29
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +140 -2
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +140 -2
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +542 -31
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +42 -2
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +43 -3
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +288 -52
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +289 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +234 -38
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +235 -39
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +1157 -12
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +85 -4
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +85 -4
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +1113 -14
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +3 -2
- package/dist/images.cjs.map +1 -1
- package/dist/images.js +3 -2
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +39 -1
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +11 -4
- package/dist/jest.cjs +43 -14
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +47 -5
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +47 -5
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +45 -16
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +155 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +54 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +54 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +155 -13
- package/dist/journal.js.map +1 -1
- package/dist/live-sanitize.cjs +4 -32
- package/dist/live-sanitize.cjs.map +1 -1
- package/dist/live-sanitize.js +4 -32
- package/dist/live-sanitize.js.map +1 -1
- package/dist/llmock.cjs +204 -39
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +51 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +51 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +206 -41
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +217 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +211 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- package/dist/mcp-fakes-report.cjs +111 -0
- package/dist/mcp-fakes-report.cjs.map +1 -0
- package/dist/mcp-fakes-report.d.cts +40 -0
- package/dist/mcp-fakes-report.d.cts.map +1 -0
- package/dist/mcp-fakes-report.d.ts +40 -0
- package/dist/mcp-fakes-report.d.ts.map +1 -0
- package/dist/mcp-fakes-report.js +107 -0
- package/dist/mcp-fakes-report.js.map +1 -0
- package/dist/mcp-fakes.cjs +1942 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +504 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +504 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1937 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +534 -27
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.d.cts +49 -0
- package/dist/mcp-handler.d.cts.map +1 -0
- package/dist/mcp-handler.d.ts +49 -0
- package/dist/mcp-handler.d.ts.map +1 -0
- package/dist/mcp-handler.js +530 -28
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +326 -47
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +97 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +97 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +327 -48
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-recorder.cjs +1224 -0
- package/dist/mcp-recorder.cjs.map +1 -0
- package/dist/mcp-recorder.d.cts +108 -0
- package/dist/mcp-recorder.d.cts.map +1 -0
- package/dist/mcp-recorder.d.ts +108 -0
- package/dist/mcp-recorder.d.ts.map +1 -0
- package/dist/mcp-recorder.js +1215 -0
- package/dist/mcp-recorder.js.map +1 -0
- package/dist/mcp-stub.cjs +5 -1
- package/dist/mcp-stub.d.cts +3 -1
- package/dist/mcp-stub.d.ts +3 -1
- package/dist/mcp-stub.js +3 -1
- package/dist/mcp-types.d.cts +6 -0
- package/dist/mcp-types.d.ts +6 -0
- package/dist/message-text.cjs +192 -0
- package/dist/message-text.cjs.map +1 -0
- package/dist/message-text.d.cts +31 -0
- package/dist/message-text.d.cts.map +1 -0
- package/dist/message-text.d.ts +31 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +184 -0
- package/dist/message-text.js.map +1 -0
- package/dist/messages.cjs +400 -56
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.d.cts.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +401 -58
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +46 -66
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +14 -6
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +14 -6
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +32 -40
- package/dist/metrics.js.map +1 -1
- package/dist/misbehavior.cjs +1523 -0
- package/dist/misbehavior.cjs.map +1 -0
- package/dist/misbehavior.d.cts +43 -0
- package/dist/misbehavior.d.cts.map +1 -0
- package/dist/misbehavior.d.ts +43 -0
- package/dist/misbehavior.d.ts.map +1 -0
- package/dist/misbehavior.js +1511 -0
- package/dist/misbehavior.js.map +1 -0
- package/dist/ollama.cjs +315 -113
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +316 -114
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +3405 -0
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +30 -0
- package/dist/openapi.d.cts.map +1 -0
- package/dist/openapi.d.ts +30 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +3404 -0
- package/dist/openapi.js.map +1 -0
- package/dist/openrouter-video.cjs +4 -4
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +4 -4
- package/dist/openrouter-video.js.map +1 -1
- package/dist/record-sanitize.cjs +231 -0
- package/dist/record-sanitize.cjs.map +1 -0
- package/dist/record-sanitize.js +224 -0
- package/dist/record-sanitize.js.map +1 -0
- package/dist/recorder.cjs +115 -6
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts +2 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts +2 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +116 -8
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +701 -166
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts +5 -13
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts +5 -13
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +699 -169
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +951 -0
- package/dist/route-registry.cjs.map +1 -0
- package/dist/route-registry.d.cts +33 -0
- package/dist/route-registry.d.cts.map +1 -0
- package/dist/route-registry.d.ts +33 -0
- package/dist/route-registry.d.ts.map +1 -0
- package/dist/route-registry.js +866 -0
- package/dist/route-registry.js.map +1 -0
- package/dist/router.cjs +24 -2
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +25 -4
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +1159 -264
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +4 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1067 -172
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +4 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +4 -2
- package/dist/speech.js.map +1 -1
- package/dist/stream-collapse.cjs +236 -34
- package/dist/stream-collapse.cjs.map +1 -1
- package/dist/stream-collapse.d.cts +39 -4
- package/dist/stream-collapse.d.cts.map +1 -1
- package/dist/stream-collapse.d.ts +39 -4
- package/dist/stream-collapse.d.ts.map +1 -1
- package/dist/stream-collapse.js +235 -35
- package/dist/stream-collapse.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +2 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs +18 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +516 -5
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +516 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-cli.cjs +123 -33
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +125 -35
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-stores.cjs +1180 -0
- package/dist/vector-stores.cjs.map +1 -0
- package/dist/vector-stores.d.cts +106 -0
- package/dist/vector-stores.d.cts.map +1 -0
- package/dist/vector-stores.d.ts +106 -0
- package/dist/vector-stores.d.ts.map +1 -0
- package/dist/vector-stores.js +1164 -0
- package/dist/vector-stores.js.map +1 -0
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/version.cjs +45 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.js +44 -0
- package/dist/version.js.map +1 -0
- package/dist/video.cjs +19 -0
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +19 -0
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +77 -16
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +38 -5
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +38 -5
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +78 -17
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +6 -2
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +6 -2
- package/dist/watcher.js.map +1 -1
- package/dist/ws-gemini-live.cjs +210 -101
- package/dist/ws-gemini-live.cjs.map +1 -1
- package/dist/ws-gemini-live.d.cts +9 -3
- package/dist/ws-gemini-live.d.cts.map +1 -1
- package/dist/ws-gemini-live.d.ts +9 -3
- package/dist/ws-gemini-live.d.ts.map +1 -1
- package/dist/ws-gemini-live.js +211 -102
- package/dist/ws-gemini-live.js.map +1 -1
- package/dist/ws-realtime.cjs +175 -62
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts +8 -3
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts +8 -3
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +176 -63
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +121 -16
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.d.cts +8 -12
- package/dist/ws-responses.d.cts.map +1 -1
- package/dist/ws-responses.d.ts +8 -12
- package/dist/ws-responses.d.ts.map +1 -1
- package/dist/ws-responses.js +123 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +13 -2
- package/skills/write-fixtures/SKILL.md +179 -21
package/dist/mcp-handler.js
CHANGED
|
@@ -1,7 +1,46 @@
|
|
|
1
|
+
import { build, fixed, jsonText, msg, plainText, quote } from "./message-text.js";
|
|
2
|
+
import { decodeMcpHeaderValue, resolveMcpIdentity } from "./helpers.js";
|
|
1
3
|
import { createJsonRpcDispatcher } from "./jsonrpc.js";
|
|
4
|
+
import { MCP_FAKE_ERROR_CODES } from "./types.js";
|
|
2
5
|
import { randomUUID } from "node:crypto";
|
|
3
6
|
|
|
4
7
|
//#region src/mcp-handler.ts
|
|
8
|
+
/**
|
|
9
|
+
* `data.aimock.code` of a `tools/call` whose fake claim threw: an aimock
|
|
10
|
+
* defect (a validated entry whose result could not be built or sent), not a
|
|
11
|
+
* scenario failure. Answered with the JSON-RPC internal-error code `-32603`;
|
|
12
|
+
* the entry stays unconsumed.
|
|
13
|
+
*/
|
|
14
|
+
const MCP_FAKE_INTERNAL_ERROR = "MCP_FAKE_INTERNAL_ERROR";
|
|
15
|
+
const MCP_FAKE_INTERNAL_ERROR_RPC_CODE = -32603;
|
|
16
|
+
/** An empty record, for the caller to pass in and read even if the handler throws. */
|
|
17
|
+
function newMCPRequestRecord() {
|
|
18
|
+
return {
|
|
19
|
+
identity: null,
|
|
20
|
+
messages: []
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* C8: the `onToolCall` handlers that answer a config `mcp.tools[].result`.
|
|
25
|
+
* `tools/call` reports such an answer as `answeredBy: "config"`. Internal:
|
|
26
|
+
* not exported from the package.
|
|
27
|
+
*/
|
|
28
|
+
const configToolHandlers = /* @__PURE__ */ new WeakSet();
|
|
29
|
+
/** C7: the syslog levels of `logging/setLevel`, lowest first. */
|
|
30
|
+
const LOG_LEVELS = [
|
|
31
|
+
"debug",
|
|
32
|
+
"info",
|
|
33
|
+
"notice",
|
|
34
|
+
"warning",
|
|
35
|
+
"error",
|
|
36
|
+
"critical",
|
|
37
|
+
"alert",
|
|
38
|
+
"emergency"
|
|
39
|
+
];
|
|
40
|
+
/** C7: the rank of a syslog level name, or -1 when it is not one. */
|
|
41
|
+
function logLevelRank(level) {
|
|
42
|
+
return typeof level === "string" ? LOG_LEVELS.indexOf(level) : -1;
|
|
43
|
+
}
|
|
5
44
|
function jsonRpcResult(id, result) {
|
|
6
45
|
return {
|
|
7
46
|
jsonrpc: "2.0",
|
|
@@ -9,26 +48,195 @@ function jsonRpcResult(id, result) {
|
|
|
9
48
|
result
|
|
10
49
|
};
|
|
11
50
|
}
|
|
12
|
-
function jsonRpcError(id, code, message) {
|
|
51
|
+
function jsonRpcError(id, code, message, data) {
|
|
13
52
|
return {
|
|
14
53
|
jsonrpc: "2.0",
|
|
15
54
|
id,
|
|
16
|
-
error: {
|
|
55
|
+
error: data === void 0 ? {
|
|
17
56
|
code,
|
|
18
57
|
message
|
|
58
|
+
} : {
|
|
59
|
+
code,
|
|
60
|
+
message,
|
|
61
|
+
data
|
|
19
62
|
}
|
|
20
63
|
};
|
|
21
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The identity part of a fake message or log line (I9): `testId "<id>"`,
|
|
67
|
+
* `context "<ctx>"`, both, or `no testId or context`.
|
|
68
|
+
*/
|
|
69
|
+
function describeMcpIdentity(identity) {
|
|
70
|
+
const { testId, context } = identity;
|
|
71
|
+
if (testId !== null && context !== null) return msg`testId ${quote(testId)}, context ${quote(context)}`;
|
|
72
|
+
if (testId !== null) return msg`testId ${quote(testId)}`;
|
|
73
|
+
if (context !== null) return msg`context ${quote(context)}`;
|
|
74
|
+
return msg`no testId or context`;
|
|
75
|
+
}
|
|
76
|
+
/** A value as JSON text for a message part. */
|
|
77
|
+
function jsonPart(value) {
|
|
78
|
+
return jsonText(JSON.stringify(value) ?? "null");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The 9.2 error for a failed claim (`mismatch`, `exhausted`, `evicted`), or
|
|
82
|
+
* for an undeclared tool under `deny` (`not_declared`).
|
|
83
|
+
*/
|
|
84
|
+
function fakeFailure(claim, tool, ctx, declaredTools) {
|
|
85
|
+
const who = describeMcpIdentity(ctx.identity);
|
|
86
|
+
const base = {
|
|
87
|
+
tool,
|
|
88
|
+
testId: ctx.identity.testId,
|
|
89
|
+
context: ctx.identity.context,
|
|
90
|
+
mount: ctx.mount
|
|
91
|
+
};
|
|
92
|
+
switch (claim.kind) {
|
|
93
|
+
case "mismatch": {
|
|
94
|
+
const list = claim.declared.map((d) => d.args !== void 0 ? msg`${plainText(d.id)} ${jsonPart(d.args)}` : msg`${plainText(d.id)} anyArgs`).reduce((acc, part, i) => i === 0 ? part : msg`${acc}; ${part}`, msg`none`);
|
|
95
|
+
return {
|
|
96
|
+
code: "MCP_FAKE_MISMATCH",
|
|
97
|
+
outcome: "mismatch",
|
|
98
|
+
message: build(msg`aimock MCP fake mismatch: tool ${quote(tool)} was called with arguments that match no declared fake (${who}). Received ${jsonPart(claim.received)}. Declared: ${list}.`),
|
|
99
|
+
aimock: {
|
|
100
|
+
code: "MCP_FAKE_MISMATCH",
|
|
101
|
+
...base,
|
|
102
|
+
received: claim.received,
|
|
103
|
+
declared: claim.declared,
|
|
104
|
+
firstDifference: claim.firstDifference
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
case "exhausted": {
|
|
109
|
+
const used = claim.matchingDeclared === 1 ? msg`the 1 matching fake is` : msg`all ${claim.matchingDeclared} matching fakes are`;
|
|
110
|
+
return {
|
|
111
|
+
code: "MCP_FAKE_EXHAUSTED",
|
|
112
|
+
outcome: "exhausted",
|
|
113
|
+
message: build(msg`aimock MCP fake exhausted: tool ${quote(tool)} with ${jsonPart(claim.received)} was called again, but ${used} already used (${who}).`),
|
|
114
|
+
aimock: {
|
|
115
|
+
code: "MCP_FAKE_EXHAUSTED",
|
|
116
|
+
...base,
|
|
117
|
+
received: claim.received,
|
|
118
|
+
matchingDeclared: claim.matchingDeclared,
|
|
119
|
+
matchingConsumed: claim.matchingConsumed,
|
|
120
|
+
matchingIds: claim.matchingIds
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
case "evicted": return {
|
|
125
|
+
code: "MCP_FAKE_EVICTED",
|
|
126
|
+
outcome: "evicted",
|
|
127
|
+
evicted: {
|
|
128
|
+
testId: claim.testId,
|
|
129
|
+
cap: claim.maxTestIds
|
|
130
|
+
},
|
|
131
|
+
message: build(msg`aimock MCP fake evicted: consumption state for testId ${quote(claim.testId)} was evicted by the per-mount test-id cap (${claim.maxTestIds}); reset before reusing this test id (${who}).`),
|
|
132
|
+
aimock: {
|
|
133
|
+
code: "MCP_FAKE_EVICTED",
|
|
134
|
+
...base,
|
|
135
|
+
testId: claim.testId,
|
|
136
|
+
cap: claim.maxTestIds
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
case "not_declared": return {
|
|
140
|
+
code: "MCP_FAKE_NOT_DECLARED",
|
|
141
|
+
outcome: "not_declared",
|
|
142
|
+
message: build(msg`Unknown tool: ${plainText(tool)} (aimock MCP fake not declared: this scenario denies undeclared tools; ${who}).`),
|
|
143
|
+
aimock: {
|
|
144
|
+
code: "MCP_FAKE_NOT_DECLARED",
|
|
145
|
+
...base,
|
|
146
|
+
declaredTools: declaredTools()
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
22
151
|
function createMCPRequestHandler(state) {
|
|
152
|
+
/** Per-request fake context, set by the outer handler for the dispatcher's methods. */
|
|
153
|
+
const contexts = /* @__PURE__ */ new WeakMap();
|
|
154
|
+
/** C7: each session's `logging/setLevel` level; dropped on DELETE. */
|
|
155
|
+
const logLevels = /* @__PURE__ */ new Map();
|
|
156
|
+
/** The initialize capabilities: `logging` only on a mount with recorded logs (C7). */
|
|
157
|
+
const capabilities = () => state.fakes.hasRecordedLogs() ? {
|
|
158
|
+
tools: {},
|
|
159
|
+
resources: {},
|
|
160
|
+
prompts: {},
|
|
161
|
+
logging: {}
|
|
162
|
+
} : {
|
|
163
|
+
tools: {},
|
|
164
|
+
resources: {},
|
|
165
|
+
prompts: {}
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* Spec 10 steps 1-2 for `tools/call`: a fake answer or fake failure, or
|
|
169
|
+
* `null` to fall through to today's behavior (step 3).
|
|
170
|
+
*/
|
|
171
|
+
const callFake = (ctx, outcome, tool, args, id) => {
|
|
172
|
+
const event = {
|
|
173
|
+
identity: ctx.identity,
|
|
174
|
+
tool,
|
|
175
|
+
mount: ctx.mount,
|
|
176
|
+
args
|
|
177
|
+
};
|
|
178
|
+
let claim;
|
|
179
|
+
try {
|
|
180
|
+
claim = state.fakes.claim(tool, args, ctx.identity);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const thrown = err instanceof Error ? err.message : String(err);
|
|
183
|
+
const message = build(msg`aimock MCP fake internal error: tool ${quote(tool)} could not be answered (${describeMcpIdentity(ctx.identity)}): ${plainText(thrown)}`);
|
|
184
|
+
outcome.error = message;
|
|
185
|
+
state.onFakeEvent({
|
|
186
|
+
...event,
|
|
187
|
+
kind: "internal_error",
|
|
188
|
+
code: MCP_FAKE_INTERNAL_ERROR,
|
|
189
|
+
message
|
|
190
|
+
});
|
|
191
|
+
return jsonRpcError(id, MCP_FAKE_INTERNAL_ERROR_RPC_CODE, message, { aimock: {
|
|
192
|
+
code: MCP_FAKE_INTERNAL_ERROR,
|
|
193
|
+
tool,
|
|
194
|
+
testId: ctx.identity.testId,
|
|
195
|
+
context: ctx.identity.context,
|
|
196
|
+
mount: ctx.mount,
|
|
197
|
+
error: thrown
|
|
198
|
+
} });
|
|
199
|
+
}
|
|
200
|
+
if (claim.kind === "answer") {
|
|
201
|
+
outcome.mcpFake = {
|
|
202
|
+
id: claim.entry.id,
|
|
203
|
+
outcome: "answered"
|
|
204
|
+
};
|
|
205
|
+
state.onFakeEvent({
|
|
206
|
+
...event,
|
|
207
|
+
kind: "answered",
|
|
208
|
+
entryId: claim.entry.id
|
|
209
|
+
});
|
|
210
|
+
return jsonRpcResult(id, claim.result);
|
|
211
|
+
}
|
|
212
|
+
let failure;
|
|
213
|
+
if (claim.kind === "none") {
|
|
214
|
+
if (state.fakes.policy(ctx.identity) !== "deny") return null;
|
|
215
|
+
failure = fakeFailure({ kind: "not_declared" }, tool, ctx, () => state.fakes.declaredTools(ctx.identity));
|
|
216
|
+
} else failure = fakeFailure(claim, tool, ctx, () => []);
|
|
217
|
+
outcome.mcpFake = {
|
|
218
|
+
id: null,
|
|
219
|
+
outcome: failure.outcome
|
|
220
|
+
};
|
|
221
|
+
state.onFakeEvent(failure.outcome === "evicted" ? {
|
|
222
|
+
...event,
|
|
223
|
+
kind: "evicted",
|
|
224
|
+
code: failure.code,
|
|
225
|
+
message: failure.message,
|
|
226
|
+
...failure.evicted
|
|
227
|
+
} : {
|
|
228
|
+
...event,
|
|
229
|
+
kind: failure.outcome,
|
|
230
|
+
code: failure.code,
|
|
231
|
+
message: failure.message
|
|
232
|
+
});
|
|
233
|
+
return jsonRpcError(id, MCP_FAKE_ERROR_CODES[failure.code], failure.message, { aimock: failure.aimock });
|
|
234
|
+
};
|
|
23
235
|
const dispatcher = createJsonRpcDispatcher({ methods: {
|
|
24
236
|
initialize: async (_params, id) => {
|
|
25
237
|
return jsonRpcResult(id, {
|
|
26
238
|
protocolVersion: "2025-03-26",
|
|
27
|
-
capabilities:
|
|
28
|
-
tools: {},
|
|
29
|
-
resources: {},
|
|
30
|
-
prompts: {}
|
|
31
|
-
},
|
|
239
|
+
capabilities: capabilities(),
|
|
32
240
|
serverInfo: state.serverInfo
|
|
33
241
|
});
|
|
34
242
|
},
|
|
@@ -41,15 +249,68 @@ function createMCPRequestHandler(state) {
|
|
|
41
249
|
ping: async (_params, id) => {
|
|
42
250
|
return jsonRpcResult(id, {});
|
|
43
251
|
},
|
|
44
|
-
"
|
|
252
|
+
"logging/setLevel": async (params, id, req) => {
|
|
253
|
+
if (!state.fakes.hasRecordedLogs()) return jsonRpcError(id, -32601, "Method not found");
|
|
254
|
+
const level = params ?? {};
|
|
255
|
+
if (logLevelRank(level.level) === -1) return jsonRpcError(id, -32602, "Invalid log level");
|
|
256
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
257
|
+
if (typeof sessionId === "string") logLevels.set(sessionId, String(level.level));
|
|
258
|
+
return jsonRpcResult(id, {});
|
|
259
|
+
},
|
|
260
|
+
"tools/list": async (_params, id, req) => {
|
|
45
261
|
const tools = [];
|
|
46
262
|
for (const { def } of state.tools.values()) tools.push(def);
|
|
263
|
+
const ctx = contexts.get(req);
|
|
264
|
+
const recorded = ctx ? state.fakes.recordedList(ctx.identity) : null;
|
|
265
|
+
if (ctx && recorded) {
|
|
266
|
+
const listed = [...recorded];
|
|
267
|
+
const names = new Set(recorded.map((t) => t.name));
|
|
268
|
+
for (const def of tools) if (!names.has(def.name)) {
|
|
269
|
+
names.add(def.name);
|
|
270
|
+
listed.push(def);
|
|
271
|
+
}
|
|
272
|
+
for (const fake of state.fakes.listTools(ctx.identity)) if (!names.has(fake.name)) {
|
|
273
|
+
names.add(fake.name);
|
|
274
|
+
listed.push(fake);
|
|
275
|
+
}
|
|
276
|
+
return jsonRpcResult(id, { tools: listed });
|
|
277
|
+
}
|
|
278
|
+
if (ctx) {
|
|
279
|
+
const registered = new Set(tools.map((t) => t.name));
|
|
280
|
+
for (const fake of state.fakes.listTools(ctx.identity)) if (!registered.has(fake.name)) tools.push(fake);
|
|
281
|
+
}
|
|
47
282
|
return jsonRpcResult(id, { tools });
|
|
48
283
|
},
|
|
49
|
-
"tools/call": async (params, id) => {
|
|
50
|
-
const
|
|
51
|
-
|
|
284
|
+
"tools/call": async (params, id, req) => {
|
|
285
|
+
const ctx = contexts.get(req);
|
|
286
|
+
const precomputed = ctx?.precomputed;
|
|
287
|
+
if (ctx) ctx.precomputed = void 0;
|
|
288
|
+
const isNotification = !precomputed && ctx?.notifications[ctx.calls.length] === true;
|
|
289
|
+
if (!precomputed) ctx?.calls.push({
|
|
290
|
+
mcpFake: null,
|
|
291
|
+
error: null
|
|
292
|
+
});
|
|
293
|
+
const outcome = ctx?.calls[ctx.calls.length - 1] ?? {
|
|
294
|
+
mcpFake: null,
|
|
295
|
+
error: null
|
|
296
|
+
};
|
|
297
|
+
const { name: rawName, arguments: args } = params ?? {};
|
|
298
|
+
if (!rawName) return jsonRpcError(id, -32602, "Missing tool name");
|
|
299
|
+
if (typeof rawName !== "string") return jsonRpcError(id, -32602, build(msg`Invalid tool name: expected a string, got ${fixed(Array.isArray(rawName) ? "array" : typeof rawName)}`));
|
|
300
|
+
const name = rawName;
|
|
301
|
+
if (precomputed?.kind === "reply") return precomputed.value;
|
|
302
|
+
if (ctx && !isNotification && !precomputed) {
|
|
303
|
+
const fake = callFake(ctx, outcome, name, args, id);
|
|
304
|
+
if (fake) return fake;
|
|
305
|
+
}
|
|
52
306
|
const entry = state.tools.get(name);
|
|
307
|
+
if (ctx && !isNotification) state.onUnfaked({
|
|
308
|
+
identity: ctx.identity,
|
|
309
|
+
tool: name,
|
|
310
|
+
mount: ctx.mount,
|
|
311
|
+
args,
|
|
312
|
+
answeredBy: !entry ? "unknown-tool" : entry.handler && configToolHandlers.has(entry.handler) ? "config" : entry.handler ? "handler" : "empty"
|
|
313
|
+
});
|
|
53
314
|
if (!entry) return jsonRpcError(id, -32602, `Unknown tool: ${name}`);
|
|
54
315
|
if (entry.handler) try {
|
|
55
316
|
const result = await entry.handler(args);
|
|
@@ -109,50 +370,126 @@ function createMCPRequestHandler(state) {
|
|
|
109
370
|
return jsonRpcResult(id, { messages: [] });
|
|
110
371
|
}
|
|
111
372
|
} });
|
|
112
|
-
|
|
373
|
+
/** Answer a rejected identity (H4, H5) with HTTP 400, in the session-error shape. */
|
|
374
|
+
const rejectIdentity = (res, resolution) => {
|
|
375
|
+
res.writeHead(resolution.error.status, { "Content-Type": "application/json" });
|
|
376
|
+
res.end(JSON.stringify({
|
|
377
|
+
error: resolution.error.message,
|
|
378
|
+
code: resolution.error.code
|
|
379
|
+
}));
|
|
380
|
+
};
|
|
381
|
+
/**
|
|
382
|
+
* L2: report each identity value that fell back to the raw value (H1),
|
|
383
|
+
* naming its source (header or query) and the value itself, and any query
|
|
384
|
+
* name that did not decode. `fellBack` says only that some value of the
|
|
385
|
+
* field fell back; the header is checked on its own, and the query by the
|
|
386
|
+
* same resolver given the query alone, so the rules are the resolver's.
|
|
387
|
+
*/
|
|
388
|
+
const reportFallback = (sessionId, req, resolution) => {
|
|
389
|
+
if (!state.hasFakes()) return;
|
|
390
|
+
const { fellBack } = resolution;
|
|
391
|
+
if (fellBack.testId || fellBack.context) {
|
|
392
|
+
const queryOnly = resolveMcpIdentity({
|
|
393
|
+
headers: {},
|
|
394
|
+
url: req.url
|
|
395
|
+
});
|
|
396
|
+
for (const field of ["testId", "context"]) {
|
|
397
|
+
if (!fellBack[field]) continue;
|
|
398
|
+
const header = req.headers[IDENTITY_HEADERS[field]];
|
|
399
|
+
const headerSent = typeof header === "string" && header !== "";
|
|
400
|
+
if (headerSent && decodeMcpHeaderValue(header).fellBack) state.onDecodeFallback(sessionId, {
|
|
401
|
+
kind: "value",
|
|
402
|
+
field,
|
|
403
|
+
source: "header",
|
|
404
|
+
raw: header,
|
|
405
|
+
used: true
|
|
406
|
+
});
|
|
407
|
+
const fromQuery = queryOnly.ok && queryOnly.fellBack[field] ? queryOnly.identity[field] : null;
|
|
408
|
+
if (fromQuery !== null) state.onDecodeFallback(sessionId, {
|
|
409
|
+
kind: "value",
|
|
410
|
+
field,
|
|
411
|
+
source: "query",
|
|
412
|
+
raw: fromQuery,
|
|
413
|
+
used: !headerSent
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (resolution.undecodedQueryNames) state.onDecodeFallback(sessionId, {
|
|
418
|
+
kind: "names",
|
|
419
|
+
count: resolution.undecodedQueryNames
|
|
420
|
+
});
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* Handle one MCP request. `mount` is the path the mount is served at (I9;
|
|
424
|
+
* `/` for a standalone server). Resolves with what the request resolved,
|
|
425
|
+
* for the mount's journal entry.
|
|
426
|
+
*/
|
|
427
|
+
return async (req, res, body, mount = "/", record = newMCPRequestRecord()) => {
|
|
428
|
+
/** T1: recorded `atMs` / `durationMs` are measured from here. */
|
|
429
|
+
const arrivedAt = performance.now();
|
|
113
430
|
if (req.method === "DELETE") {
|
|
114
431
|
const sessionId = req.headers["mcp-session-id"];
|
|
115
432
|
if (!sessionId) {
|
|
116
433
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
117
434
|
res.end(JSON.stringify({ error: "Missing mcp-session-id header" }));
|
|
118
|
-
return;
|
|
435
|
+
return record;
|
|
119
436
|
}
|
|
120
437
|
if (!state.sessions.has(sessionId)) {
|
|
121
438
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
122
439
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
123
|
-
return;
|
|
440
|
+
return record;
|
|
124
441
|
}
|
|
442
|
+
const resolution = resolveMcpIdentity(req, state.sessions.get(sessionId));
|
|
443
|
+
if (resolution.ok) record.identity = resolution.identity;
|
|
125
444
|
state.sessions.delete(sessionId);
|
|
445
|
+
logLevels.delete(sessionId);
|
|
446
|
+
state.onSessionClosed(sessionId);
|
|
126
447
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
127
448
|
res.end(JSON.stringify({ ok: true }));
|
|
128
|
-
return;
|
|
449
|
+
return record;
|
|
129
450
|
}
|
|
451
|
+
const headerSessionId = req.headers["mcp-session-id"];
|
|
452
|
+
const knownSession = headerSessionId ? state.sessions.get(headerSessionId) : void 0;
|
|
130
453
|
let parsed;
|
|
131
454
|
try {
|
|
132
455
|
parsed = JSON.parse(body);
|
|
133
456
|
} catch {
|
|
457
|
+
const resolution = resolveMcpIdentity(req, knownSession);
|
|
458
|
+
if (!resolution.ok) {
|
|
459
|
+
rejectIdentity(res, resolution);
|
|
460
|
+
return record;
|
|
461
|
+
}
|
|
462
|
+
record.identity = resolution.identity;
|
|
463
|
+
if (headerSessionId && knownSession) reportFallback(headerSessionId, req, resolution);
|
|
134
464
|
await dispatcher(req, res, body);
|
|
135
|
-
return;
|
|
465
|
+
return record;
|
|
136
466
|
}
|
|
137
467
|
const method = typeof parsed === "object" && parsed !== null && "method" in parsed ? parsed.method : void 0;
|
|
138
468
|
if (method === "initialize") {
|
|
139
469
|
const id = typeof parsed === "object" && parsed !== null && "id" in parsed ? parsed.id : null;
|
|
470
|
+
const resolution = resolveMcpIdentity(req);
|
|
471
|
+
if (!resolution.ok) {
|
|
472
|
+
rejectIdentity(res, resolution);
|
|
473
|
+
return record;
|
|
474
|
+
}
|
|
475
|
+
record.identity = resolution.identity;
|
|
476
|
+
const { identity, supplied } = resolution;
|
|
140
477
|
const sessionId = randomUUID();
|
|
141
478
|
state.sessions.set(sessionId, {
|
|
142
479
|
id: sessionId,
|
|
143
480
|
initialized: false,
|
|
144
|
-
createdAt: Date.now()
|
|
481
|
+
createdAt: Date.now(),
|
|
482
|
+
...supplied.testId && identity.testId !== null ? { testId: identity.testId } : {},
|
|
483
|
+
...supplied.context && identity.context !== null ? { context: identity.context } : {},
|
|
484
|
+
...supplied.undeclared && identity.undeclared !== null ? { undeclared: identity.undeclared } : {}
|
|
145
485
|
});
|
|
486
|
+
reportFallback(sessionId, req, resolution);
|
|
146
487
|
const response = {
|
|
147
488
|
jsonrpc: "2.0",
|
|
148
489
|
id,
|
|
149
490
|
result: {
|
|
150
491
|
protocolVersion: "2025-03-26",
|
|
151
|
-
capabilities:
|
|
152
|
-
tools: {},
|
|
153
|
-
resources: {},
|
|
154
|
-
prompts: {}
|
|
155
|
-
},
|
|
492
|
+
capabilities: capabilities(),
|
|
156
493
|
serverInfo: state.serverInfo
|
|
157
494
|
}
|
|
158
495
|
};
|
|
@@ -161,28 +498,193 @@ function createMCPRequestHandler(state) {
|
|
|
161
498
|
"Mcp-Session-Id": sessionId
|
|
162
499
|
});
|
|
163
500
|
res.end(JSON.stringify(response));
|
|
164
|
-
return;
|
|
501
|
+
return record;
|
|
502
|
+
}
|
|
503
|
+
const resolution = resolveMcpIdentity(req, knownSession);
|
|
504
|
+
if (!resolution.ok) {
|
|
505
|
+
rejectIdentity(res, resolution);
|
|
506
|
+
return record;
|
|
165
507
|
}
|
|
166
|
-
|
|
508
|
+
record.identity = resolution.identity;
|
|
509
|
+
const sessionId = headerSessionId;
|
|
167
510
|
if (!sessionId) {
|
|
168
511
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
169
512
|
res.end(JSON.stringify({ error: "Missing mcp-session-id header" }));
|
|
170
|
-
return;
|
|
513
|
+
return record;
|
|
171
514
|
}
|
|
172
515
|
if (!state.sessions.has(sessionId)) {
|
|
173
516
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
174
517
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
175
|
-
return;
|
|
518
|
+
return record;
|
|
176
519
|
}
|
|
520
|
+
reportFallback(sessionId, req, resolution);
|
|
177
521
|
if (!state.sessions.get(sessionId).initialized && method !== "notifications/initialized") {
|
|
178
522
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
179
523
|
res.end(JSON.stringify(jsonRpcError(typeof parsed === "object" && parsed !== null && "id" in parsed ? parsed.id : null, -32002, "Session not initialized")));
|
|
180
|
-
return;
|
|
524
|
+
return record;
|
|
525
|
+
}
|
|
526
|
+
const notifications = (Array.isArray(parsed) ? parsed : [parsed]).filter((m) => isToolsCall(m) && m.jsonrpc === "2.0").map((m) => !("id" in m));
|
|
527
|
+
const ctx = {
|
|
528
|
+
identity: resolution.identity,
|
|
529
|
+
mount,
|
|
530
|
+
notifications,
|
|
531
|
+
calls: []
|
|
532
|
+
};
|
|
533
|
+
contexts.set(req, ctx);
|
|
534
|
+
const single = singleToolsCall(parsed);
|
|
535
|
+
if (single) {
|
|
536
|
+
const outcome = {
|
|
537
|
+
mcpFake: null,
|
|
538
|
+
error: null
|
|
539
|
+
};
|
|
540
|
+
ctx.calls.push(outcome);
|
|
541
|
+
const fake = callFake(ctx, outcome, single.name, single.args, single.id);
|
|
542
|
+
const replay = fake && outcome.mcpFake?.outcome === "answered" && outcome.mcpFake.id !== null ? state.fakes.replayOf(outcome.mcpFake.id) : null;
|
|
543
|
+
if (fake && "result" in fake && replay && (replay.notifications.length > 0 || replay.timing === "recorded" && replay.durationMs !== void 0)) {
|
|
544
|
+
await writeFakeToolAnswer(req, res, single.id, fake.result, replay, single.params, state.replaySpeed(), logLevels.get(sessionId) ?? null, arrivedAt);
|
|
545
|
+
record.messages = [{
|
|
546
|
+
body: single.message,
|
|
547
|
+
mcpFake: outcome.mcpFake,
|
|
548
|
+
error: outcome.error
|
|
549
|
+
}];
|
|
550
|
+
return record;
|
|
551
|
+
}
|
|
552
|
+
ctx.precomputed = fake ? {
|
|
553
|
+
kind: "reply",
|
|
554
|
+
value: fake
|
|
555
|
+
} : { kind: "none" };
|
|
181
556
|
}
|
|
182
557
|
await dispatcher(req, res, body);
|
|
558
|
+
let next = 0;
|
|
559
|
+
const messageOf = (message) => {
|
|
560
|
+
if (!isToolsCall(message)) return {
|
|
561
|
+
body: null,
|
|
562
|
+
mcpFake: null,
|
|
563
|
+
error: null
|
|
564
|
+
};
|
|
565
|
+
const outcome = message.jsonrpc === "2.0" ? ctx.calls[next++] : void 0;
|
|
566
|
+
return {
|
|
567
|
+
body: message,
|
|
568
|
+
mcpFake: outcome?.mcpFake ?? null,
|
|
569
|
+
error: outcome?.error ?? null
|
|
570
|
+
};
|
|
571
|
+
};
|
|
572
|
+
record.messages = Array.isArray(parsed) ? parsed.map(messageOf) : [messageOf(parsed)];
|
|
573
|
+
return record;
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* A single JSON-RPC `tools/call` request (not a batch, not a notification)
|
|
578
|
+
* with a string or number id and a string tool name, else `null`. Only such
|
|
579
|
+
* a message is claimed by the outer handler (MR15).
|
|
580
|
+
*/
|
|
581
|
+
function singleToolsCall(parsed) {
|
|
582
|
+
if (!isToolsCall(parsed) || parsed.jsonrpc !== "2.0" || !("id" in parsed)) return null;
|
|
583
|
+
const id = parsed.id;
|
|
584
|
+
if (typeof id !== "string" && typeof id !== "number") return null;
|
|
585
|
+
const params = parsed.params;
|
|
586
|
+
if (typeof params !== "object" || params === null) return null;
|
|
587
|
+
const name = params.name;
|
|
588
|
+
if (typeof name !== "string" || name === "") return null;
|
|
589
|
+
return {
|
|
590
|
+
message: parsed,
|
|
591
|
+
id,
|
|
592
|
+
name,
|
|
593
|
+
args: params.arguments,
|
|
594
|
+
params
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
/** Resolve after `ms` (no wait when `ms <= 0`). */
|
|
598
|
+
function sleep(ms) {
|
|
599
|
+
return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
600
|
+
}
|
|
601
|
+
/** The client's `_meta.progressToken` of a request's params, else `undefined`. */
|
|
602
|
+
function progressTokenOf(params) {
|
|
603
|
+
if (typeof params !== "object" || params === null) return void 0;
|
|
604
|
+
const meta = params._meta;
|
|
605
|
+
if (typeof meta !== "object" || meta === null) return void 0;
|
|
606
|
+
const token = meta.progressToken;
|
|
607
|
+
return typeof token === "string" || typeof token === "number" ? token : void 0;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* FA3 gate of one recorded notification for this request: the params to send,
|
|
611
|
+
* or `null` when it does not replay. `notifications/progress` replays only
|
|
612
|
+
* when the request carries a progress token, which replaces the recorded one;
|
|
613
|
+
* `notifications/message` only when the session set a log level (C7) and the
|
|
614
|
+
* line is at or above it. No other method replays.
|
|
615
|
+
*/
|
|
616
|
+
function replayedParams(notification, progressToken, logLevel) {
|
|
617
|
+
if (notification.method === "notifications/progress") return progressToken === void 0 ? null : {
|
|
618
|
+
...notification.params,
|
|
619
|
+
progressToken
|
|
183
620
|
};
|
|
621
|
+
if (notification.method === "notifications/message") {
|
|
622
|
+
const min = logLevelRank(logLevel);
|
|
623
|
+
const rank = logLevelRank(notification.params.level);
|
|
624
|
+
return min !== -1 && rank >= min ? { ...notification.params } : null;
|
|
625
|
+
}
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Write a fake's tools/call answer. SSE iff the entry has a notification whose
|
|
630
|
+
* FA3 gate passes AND Accept lists text/event-stream; else JSON. Timing per T1
|
|
631
|
+
* (value / speed) unless the block's timing is "immediate". `logLevel` is the
|
|
632
|
+
* session's `logging/setLevel` level (C7), `arrivedAt` the `performance.now()`
|
|
633
|
+
* of the request's arrival, which `atMs` and `durationMs` are measured from.
|
|
634
|
+
*/
|
|
635
|
+
async function writeFakeToolAnswer(req, res, id, result, replay, params, speed, logLevel = null, arrivedAt = performance.now()) {
|
|
636
|
+
const timed = replay !== null && replay.timing === "recorded";
|
|
637
|
+
const waitUntil = (atMs) => timed ? sleep(atMs / speed - (performance.now() - arrivedAt)) : Promise.resolve();
|
|
638
|
+
const token = progressTokenOf(params);
|
|
639
|
+
const notifications = [...replay?.notifications ?? []].sort((a, b) => a.atMs - b.atMs).flatMap((n) => {
|
|
640
|
+
const sent = replayedParams(n, token, logLevel);
|
|
641
|
+
return sent ? [{
|
|
642
|
+
atMs: n.atMs,
|
|
643
|
+
method: n.method,
|
|
644
|
+
params: sent
|
|
645
|
+
}] : [];
|
|
646
|
+
});
|
|
647
|
+
const accept = req.headers.accept ?? "";
|
|
648
|
+
const answer = {
|
|
649
|
+
jsonrpc: "2.0",
|
|
650
|
+
id,
|
|
651
|
+
result
|
|
652
|
+
};
|
|
653
|
+
if (notifications.length > 0 && accept.includes("text/event-stream")) {
|
|
654
|
+
res.writeHead(200, {
|
|
655
|
+
"Content-Type": "text/event-stream",
|
|
656
|
+
"Cache-Control": "no-cache"
|
|
657
|
+
});
|
|
658
|
+
res.flushHeaders();
|
|
659
|
+
for (const n of notifications) {
|
|
660
|
+
await waitUntil(n.atMs);
|
|
661
|
+
if (res.destroyed) return;
|
|
662
|
+
res.write(`event: message\ndata: ${JSON.stringify({
|
|
663
|
+
jsonrpc: "2.0",
|
|
664
|
+
method: n.method,
|
|
665
|
+
params: n.params
|
|
666
|
+
})}\n\n`);
|
|
667
|
+
}
|
|
668
|
+
if (replay?.durationMs !== void 0) await waitUntil(replay.durationMs);
|
|
669
|
+
if (res.destroyed) return;
|
|
670
|
+
res.end(`event: message\ndata: ${JSON.stringify(answer)}\n\n`);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (replay?.durationMs !== void 0) await waitUntil(replay.durationMs);
|
|
674
|
+
if (res.destroyed) return;
|
|
675
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
676
|
+
res.end(JSON.stringify(answer));
|
|
677
|
+
}
|
|
678
|
+
/** The identity header of each field whose value can fall back (H1). */
|
|
679
|
+
const IDENTITY_HEADERS = {
|
|
680
|
+
testId: "x-test-id",
|
|
681
|
+
context: "x-aimock-context"
|
|
682
|
+
};
|
|
683
|
+
/** A JSON-RPC message object whose method is `tools/call`. */
|
|
684
|
+
function isToolsCall(message) {
|
|
685
|
+
return typeof message === "object" && message !== null && !Array.isArray(message) && "method" in message && message.method === "tools/call";
|
|
184
686
|
}
|
|
185
687
|
|
|
186
688
|
//#endregion
|
|
187
|
-
export { createMCPRequestHandler };
|
|
689
|
+
export { configToolHandlers, createMCPRequestHandler, describeMcpIdentity, fakeFailure, newMCPRequestRecord, writeFakeToolAnswer };
|
|
188
690
|
//# sourceMappingURL=mcp-handler.js.map
|