@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-recorder.js","names":["http","crypto"],"sources":["../src/mcp-recorder.ts"],"sourcesContent":["/**\n * MCP record mode (rr: MR1-MR17, par: AM1-AM9): a pass-through proxy inside\n * `MCPMock` that forwards to a real upstream MCP server, relays its answers,\n * and writes the `tools/list` and `tools/call` exchanges it sees as an\n * `mcpFakes` file (FA1-FA5), sanitized (S1-S7).\n *\n * Every file this module writes goes through `sanitizeRecording` and then\n * `persistServiceFakes`; there is no other filesystem write here.\n */\nimport * as http from \"node:http\";\nimport * as https from \"node:https\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport { StringDecoder } from \"node:string_decoder\";\nimport type {\n JournalEntry,\n McpFakeIdentity,\n McpFakeSource,\n McpFakeUndeclaredPolicy,\n McpRecordConfig,\n Mountable,\n} from \"./types.js\";\nimport { MCP_FAKE_ERROR_CODES } from \"./types.js\";\nimport { Logger } from \"./logger.js\";\nimport { isAuthenticatedRequest } from \"./api-key-auth.js\";\nimport {\n flattenHeaders,\n readBody,\n resolveMcpIdentity,\n resolveStrictMode,\n slugifyContext,\n slugifyTestId,\n} from \"./helpers.js\";\nimport {\n buildForwardHeaders,\n persistServiceFakes,\n removeForwardHeader,\n sanitizeHeaderValue,\n} from \"./recorder.js\";\nimport {\n MCP_FAKES_DEFAULT_TEST_ID,\n validateMcpFakes,\n type McpFakeAddOriginInternal,\n type McpFakeAddResult,\n type McpFakeClaim,\n type McpFakeStore,\n} from \"./mcp-fakes.js\";\nimport { fakeFailure, writeFakeToolAnswer, type McpFakeEvent } from \"./mcp-handler.js\";\nimport {\n REDACTED,\n RecordUnsafeError,\n knownSecrets,\n sanitizeRecording,\n scrubUrl,\n validateSecretValues,\n} from \"./record-sanitize.js\";\nimport { aimockVersion } from \"./version.js\";\nimport { build, fixed, msg, plainText } from \"./message-text.js\";\n\n/** AM4: the protocol versions this recorder writes (legacy era only). */\nexport const RECORDABLE_VERSIONS = new Set([\"2025-03-26\", \"2025-06-18\", \"2025-11-25\"]);\n/** MR2 (d): default bytes of one recordable response that are buffered. */\nexport const DEFAULT_RECORD_BUFFER_BYTES = 64 * 1024 * 1024;\n/** MR2 (d): the ceiling `maxRecordBufferBytes` is clamped to. */\nexport const MAX_RECORD_BUFFER_BYTES = 256 * 1024 * 1024;\n\n/** The MR14 body of a 502 answered for an upstream auth failure. */\nconst AUTH_FAILURE_ERROR =\n \"aimock MCP recording got an upstream auth failure; OAuth discovery is not supported through the recorder; record with a pre-acquired token via upstreamAuth or the client's Authorization header\";\n\n/** Hop-by-hop response headers that are never relayed. */\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"transfer-encoding\",\n \"te\",\n \"trailer\",\n \"upgrade\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n]);\n\n/** The identity query parameters aimock reads and never forwards (MR2 c). */\nconst IDENTITY_QUERY = new Set([\"testId\", \"context\", \"undeclared\"]);\n\n/** MR7: `_meta` keys dropped from a recorded result. */\nconst DROPPED_META = [\"traceparent\", \"tracestate\", \"baggage\", \"io.modelcontextprotocol/serverInfo\"];\n\nconst SERVER_INFO_META = \"io.modelcontextprotocol/serverInfo\";\nconst RELATED_TASK_META = \"io.modelcontextprotocol/related-task\";\nconst PROTOCOL_VERSION_META = \"io.modelcontextprotocol/protocolVersion\";\n/** The version assumed when nothing names one (MR4 source 4). */\nconst FALLBACK_VERSION = \"2025-03-26\";\n\n/** A header token (RFC 9110 `token`). */\nconst HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\n\n/** What the recorder needs from its `MCPMock`. */\nexport interface RecorderHost {\n /** MR4 claims, MR12 registration. */\n readonly fakes: McpFakeStore;\n addMcpFakes(blocks: McpFakeSource[], origin: McpFakeAddOriginInternal): McpFakeAddResult;\n journalEntry(entry: Omit<JournalEntry, \"id\" | \"timestamp\">): void;\n logger(): Logger | null;\n replaySpeed(): number;\n /** Reuses the mount's metric and log side channels for a fake outcome. */\n onFakeEvent(evt: McpFakeEvent): void;\n}\n\n/** A parsed `Name: value` upstream credential header (MR14, AM6). */\nexport interface UpstreamAuthHeader {\n name: string;\n value: string;\n}\n\n/**\n * MR14 / AM6: parse a `Name: value` header, from `upstreamAuth` or\n * `AIMOCK_MCP_UPSTREAM_AUTH`. `undefined` or an empty string is no header. A\n * malformed value is a start error that names `source`, never the value.\n */\nexport function parseUpstreamAuth(\n raw: string | undefined,\n source = \"AIMOCK_MCP_UPSTREAM_AUTH\",\n): UpstreamAuthHeader | undefined {\n if (raw === undefined || raw === \"\") return undefined;\n const colon = raw.indexOf(\":\");\n const name = colon === -1 ? \"\" : raw.slice(0, colon).trim();\n const value = colon === -1 ? \"\" : raw.slice(colon + 1).trim();\n if (!HEADER_NAME.test(name) || value === \"\") {\n throw new Error(`${source} must be \"Name: value\" (a header name, a colon, then a value)`);\n }\n return { name, value };\n}\n\n/** MR3 / AM9: what an upstream `initialize` bound to its session. Private to the recorder. */\ninterface RecordSession {\n protocolVersion: string | null;\n /** AM4: false when the negotiated version is not recordable. */\n recordable: boolean;\n serverInfo?: Record<string, unknown>;\n clientCapabilities?: unknown;\n clientInfo?: unknown;\n testId?: string;\n context?: string;\n undeclared?: McpFakeUndeclaredPolicy;\n}\n\n/** MR16: a `tools/list` being collected page by page. */\ninterface PendingList {\n tools: unknown[];\n /** The `nextCursor` the next page must be requested with. */\n next: string;\n testId: string | null;\n}\n\n/** What is recorded from one request's response. */\ntype RecordKind = \"initialize\" | \"tools/list\" | \"tools/call\";\n\n/** One JSON-RPC message of an upstream response, with its arrival (ms after the request arrived). */\ninterface TimedMessage {\n at: number;\n message: Record<string, unknown>;\n}\n\n/** The state of one handled request, for its journal entry. */\ninterface Exchange {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n mount: string;\n subPath: string;\n arrivedAt: number;\n identity: McpFakeIdentity | null;\n /** The resolution's supplied flags (I3: only supplied values are bound at initialize). */\n supplied: { testId: boolean; context: boolean; undeclared: boolean };\n /** The JSON-RPC request (`null` for a batch, an unparsable body, GET or DELETE). */\n message: Record<string, unknown> | null;\n rpcMethod: string | null;\n /** The JSON-RPC id of the request, when it is a request with a string or number id. */\n rpcId: string | number | undefined;\n /** The request's `params`, when it has an object one. */\n params: Record<string, unknown> | undefined;\n recordSkipped?: string;\n mcpFake?: JournalEntry[\"response\"][\"mcpFake\"];\n proxied: boolean;\n /** X-AIMock-Record-Error text, when the response is not yet sent. */\n recordError?: string;\n}\n\n/**\n * The MCP recorder of one `MCPMock`. `handle` always answers the request:\n * by a fake (MR4), a fake error (MR5), or by forwarding it (MR2).\n */\nexport class McpRecorder {\n private readonly target: URL;\n private readonly auth: UpstreamAuthHeader | undefined;\n private readonly maxBuffer: number;\n private readonly fixturePath: string;\n private readonly secretValues: readonly string[];\n private readonly fallbackLogger = new Logger(\"warn\");\n /** MR3: upstream `Mcp-Session-Id` → what its `initialize` bound. */\n private readonly sessions = new Map<string, RecordSession>();\n /** MR16: pending lists per `${mount}\\0${scope}`. */\n private readonly pendingLists = new Map<string, PendingList>();\n /** MR16: completed lists waiting for their block's first call entry. */\n private readonly completedLists = new Map<string, { tools: unknown[]; testId: string | null }>();\n /** MR13: resolved path → sha256 of the last write. */\n private readonly hashes = new Map<string, string>();\n private readonly listeners = new Set<(filepath: string, mcpFakes: unknown) => void>();\n /** RL3: mounts already warned about a request with no scope. */\n private readonly warnedNoScope = new Set<string>();\n\n constructor(\n private readonly host: RecorderHost,\n private readonly config: McpRecordConfig,\n ) {\n let target: URL;\n try {\n target = new URL(config.upstream);\n } catch {\n // Never echo the value: it can carry credentials (S2 c).\n throw new Error(\"MCP record upstream is not a valid URL\");\n }\n if (target.protocol !== \"http:\" && target.protocol !== \"https:\") {\n throw new Error(\"MCP record upstream must be an http: or https: URL\");\n }\n this.target = target;\n this.secretValues = config.secretValues ?? [];\n validateSecretValues(this.secretValues);\n this.auth = parseUpstreamAuth(config.upstreamAuth, \"upstreamAuth\");\n const max = config.maxRecordBufferBytes;\n this.maxBuffer =\n typeof max === \"number\" && Number.isFinite(max) && max > 0\n ? Math.min(Math.floor(max), MAX_RECORD_BUFFER_BYTES)\n : DEFAULT_RECORD_BUFFER_BYTES;\n this.fixturePath = config.fixturePath ?? \"./fixtures/recorded\";\n if (!config.proxyOnly) checkMcpRecordDestination(this.fixturePath);\n }\n\n /** AM1 entry point. Reads the body itself. Always handles the request. */\n async handle(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n mount: string,\n subPath: string,\n ): Promise<void> {\n const x: Exchange = {\n req,\n res,\n mount,\n subPath,\n arrivedAt: performance.now(),\n identity: null,\n supplied: { testId: false, context: false, undeclared: false },\n message: null,\n rpcMethod: null,\n rpcId: undefined,\n params: undefined,\n proxied: false,\n };\n try {\n const body = await readBody(req);\n const sessionId = headerOf(req, \"mcp-session-id\");\n const session = sessionId ? this.sessions.get(sessionId) : undefined;\n const resolution = resolveMcpIdentity(req, session);\n if (!resolution.ok) {\n res.writeHead(resolution.error.status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: resolution.error.message, code: resolution.error.code }));\n return;\n }\n x.identity = resolution.identity;\n x.supplied = resolution.supplied;\n\n let parsed: unknown;\n let parsedOk = false;\n if (body !== \"\") {\n try {\n parsed = JSON.parse(body);\n parsedOk = true;\n } catch {\n /* forwarded as is */\n }\n }\n if (parsedOk && Array.isArray(parsed)) {\n this.skip(x, \"batch\", \"batch\");\n await this.forward(x, body, null);\n return;\n }\n if (parsedOk && isObject(parsed) && typeof parsed.method === \"string\") {\n x.rpcMethod = parsed.method;\n const isRequest = \"id\" in parsed;\n if (typeof parsed.id === \"string\" || typeof parsed.id === \"number\") x.rpcId = parsed.id;\n if (isObject(parsed.params)) x.params = parsed.params;\n if (parsed.method === \"tools/call\") x.message = parsed;\n if (parsed.method === \"initialize\" && isRequest) {\n await this.forward(x, body, \"initialize\");\n return;\n }\n if (parsed.method === \"tools/list\" && isRequest) {\n await this.forward(x, body, \"tools/list\");\n return;\n }\n if (parsed.method === \"tools/call\" && isRequest) {\n if (await this.answerByFake(x, parsed)) return;\n this.logForwardedCall(x);\n await this.forward(x, body, \"tools/call\");\n return;\n }\n // MR8: every other request is forwarded and not recorded. A\n // notification has no response to record, so it is not reported.\n if (isRequest) this.skip(x, \"not-tools\", parsed.method);\n await this.forward(x, body, null);\n return;\n }\n if (body !== \"\" && req.method === \"POST\") this.skip(x, \"not-tools\", \"(no method)\");\n await this.forward(x, body, null);\n } catch (err) {\n const thrown = err instanceof Error ? err.message : String(err);\n this.log().error(\n build(msg`MCP-RECORD: request error on mount ${plainText(mount)}: ${plainText(thrown)}`),\n );\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n } finally {\n this.journal(x);\n }\n }\n\n /** MR3 / MR16 reset doors (fakes:R1-R5, R8): one test id, or everything. */\n reset(testId?: string): void {\n for (const [id, session] of this.sessions) {\n if (testId === undefined || session.testId === testId) this.sessions.delete(id);\n }\n for (const lists of [this.pendingLists, this.completedLists]) {\n for (const [key, list] of lists) {\n if (testId !== undefined && list.testId !== testId) continue;\n lists.delete(key);\n this.log().warn(\n build(\n msg`MCP-RECORD: forwarded, not recorded (list-incomplete): tools/list on mount ${plainText(mountOfKey(key))}`,\n ),\n );\n }\n }\n }\n\n /** MR13: the sha256 of the last write to `filepath`, if this recorder wrote it. */\n lastWrittenHash(filepath: string): string | undefined {\n return this.hashes.get(path.resolve(filepath));\n }\n\n /** MR13: called after each write with the resolved path and the `mcpFakes` written. */\n onWrite(listener: (filepath: string, mcpFakes: unknown) => void): () => void {\n this.listeners.add(listener);\n return () => void this.listeners.delete(listener);\n }\n\n /** @internal MR3: whether the record-session map holds `sessionId` (tests). */\n hasRecordSession(sessionId: string): boolean {\n return this.sessions.has(sessionId);\n }\n\n /** MR16: drop pending lists (RL2 list-incomplete) and clear the session map. */\n close(): void {\n this.reset();\n this.listeners.clear();\n }\n\n // ---- MR4 / MR5 ----\n\n /**\n * MR4 / MR5 for one `tools/call` request: answer it by a fake, or by a fake\n * error (deny, strict, eviction), and return true; false to forward it.\n */\n private async answerByFake(x: Exchange, message: Record<string, unknown>): Promise<boolean> {\n const params = message.params;\n const name = isObject(params) ? params.name : undefined;\n const id = message.id;\n if (\n typeof name !== \"string\" ||\n name === \"\" ||\n (typeof id !== \"string\" && typeof id !== \"number\")\n ) {\n return false;\n }\n const identity = x.identity!;\n const args = isObject(params) ? params.arguments : undefined;\n const event = { identity, tool: name, mount: x.mount, args };\n let claim: McpFakeClaim;\n try {\n claim = this.host.fakes.claim(name, args, identity);\n } catch {\n // A claim that throws is an aimock defect: forward, never guess.\n return false;\n }\n if (claim.kind === \"answer\") {\n x.mcpFake = { id: claim.entry.id, outcome: \"answered\" };\n this.host.onFakeEvent({ ...event, kind: \"answered\", entryId: claim.entry.id });\n await writeFakeToolAnswer(\n x.req,\n x.res,\n id,\n claim.result,\n this.host.fakes.replayOf(claim.entry.id),\n params,\n this.host.replaySpeed(),\n null,\n x.arrivedAt,\n );\n return true;\n }\n const strict = resolveStrictMode(this.config.strict, x.req.headers);\n const ctx = { identity, mount: x.mount, notifications: [], calls: [] };\n let failure: ReturnType<typeof fakeFailure> | null = null;\n if (claim.kind === \"none\") {\n if (this.host.fakes.policy(identity) === \"deny\" || strict) {\n failure = fakeFailure({ kind: \"not_declared\" }, name, ctx, () =>\n this.host.fakes.declaredTools(identity),\n );\n }\n } else if (claim.kind === \"evicted\" || strict) {\n failure = fakeFailure(claim, name, ctx, () => []);\n }\n if (!failure) return false;\n x.mcpFake = { id: null, outcome: failure.outcome };\n this.host.onFakeEvent(\n failure.outcome === \"evicted\"\n ? {\n ...event,\n kind: \"evicted\",\n code: failure.code,\n message: failure.message,\n ...failure.evicted,\n }\n : { ...event, kind: failure.outcome, code: failure.code, message: failure.message },\n );\n x.res.writeHead(200, { \"Content-Type\": \"application/json\" });\n x.res.end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n error: {\n code: MCP_FAKE_ERROR_CODES[failure.code],\n message: failure.message,\n data: { aimock: failure.aimock },\n },\n }),\n );\n return true;\n }\n\n /** RP2: a forwarded `tools/call` is logged as unfaked, answered by the upstream. */\n private logForwardedCall(x: Exchange): void {\n const name = x.params?.name;\n if (typeof name !== \"string\" || x.identity === null) return;\n this.host.fakes.logEvent(x.identity, {\n outcome: \"unfaked\",\n answeredBy: \"upstream\",\n tool: name,\n args: x.params?.arguments,\n context: x.identity.context,\n mount: x.mount,\n });\n }\n\n // ---- MR2: forwarding ----\n\n /** Forward one request to the upstream and relay its answer; record it when `kind` says so. */\n private forward(x: Exchange, body: string, kind: RecordKind | null): Promise<void> {\n const { req, res } = x;\n x.proxied = true;\n if (isAuthenticatedRequest(req) && !this.auth) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"No configured upstream credential\" }));\n return Promise.resolve();\n }\n const headers = buildForwardHeaders(req);\n removeForwardHeader(headers, \"x-aimock-mcp-undeclared\");\n if (this.auth) {\n removeForwardHeader(headers, this.auth.name);\n headers[this.auth.name] = this.auth.value;\n }\n if (body !== \"\") headers[\"content-length\"] = String(Buffer.byteLength(body));\n const url = this.upstreamUrl(req, x.subPath);\n const transport = url.protocol === \"https:\" ? https : http;\n\n return new Promise<void>((resolve) => {\n let done = false;\n const finish = (): void => {\n if (done) return;\n done = true;\n resolve();\n };\n const upstreamReq = transport.request(url, { method: req.method ?? \"POST\", headers });\n // MR2: a client that goes away aborts the upstream request.\n res.on(\"close\", () => {\n if (!upstreamReq.destroyed) upstreamReq.destroy();\n finish();\n });\n upstreamReq.on(\"error\", (err) => {\n if (done) return;\n this.log().error(\n build(\n msg`MCP-RECORD: upstream request failed for ${plainText(x.rpcMethod ?? req.method ?? \"?\")} on mount ${plainText(x.mount)}: ${plainText(err.message)}`,\n ),\n );\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"aimock MCP recorder could not reach the upstream\" }));\n } else if (!res.writableEnded) {\n res.end();\n }\n finish();\n });\n upstreamReq.on(\"response\", (up) => {\n this.relay(x, up, kind, finish);\n });\n upstreamReq.end(body === \"\" ? undefined : body);\n });\n }\n\n /** The upstream URL of a request: the endpoint plus the sub-path, the query minus identity params. */\n private upstreamUrl(req: http.IncomingMessage, subPath: string): URL {\n const url = new URL(this.target.href);\n if (subPath !== \"\" && subPath !== \"/\") {\n url.pathname = url.pathname.replace(/\\/+$/, \"\") + subPath;\n }\n // MR2 (c): the client's query pairs are forwarded byte for byte (never\n // re-serialized, which would change their encoding), minus the identity\n // parameters aimock reads.\n const raw = (req.url ?? \"\").split(\"#\")[0];\n const q = raw.indexOf(\"?\");\n const kept =\n q === -1\n ? []\n : raw\n .slice(q + 1)\n .split(\"&\")\n .filter((pair) => pair !== \"\" && !IDENTITY_QUERY.has(queryName(pair)));\n if (kept.length > 0) {\n const own = url.search.replace(/^\\?/, \"\");\n url.search = [own, ...kept].filter((part) => part !== \"\").join(\"&\");\n }\n return url;\n }\n\n /** MR2 (d), MR14: relay one upstream response, buffering a recordable one. */\n private relay(\n x: Exchange,\n up: http.IncomingMessage,\n kind: RecordKind | null,\n finish: () => void,\n ): void {\n const { req, res } = x;\n const status = up.statusCode ?? 502;\n const sessionId = headerOf(req, \"mcp-session-id\");\n const rpcId = x.rpcId;\n const UPSTREAM_ABORTED_ERROR = \"aimock MCP recorder: the upstream response failed\";\n // Set by the recordable paths below; true once the call was recorded or skipped.\n let processed = false;\n // A client that goes away makes forward() destroy the upstream request,\n // which errors `up`: that is not an upstream failure.\n let clientGone = false;\n res.on(\"close\", () => {\n if (!res.writableFinished) clientGone = true;\n });\n // Set by the SSE path: a JSON-RPC error for the open request, so a client\n // waiting on the stream fails at once instead of at its own timeout.\n let sseError: (() => string) | null = null;\n // An upstream that dies mid-body is never relayed as a complete answer, on\n // any path: a 502 before headers, a terminated stream after them.\n up.on(\"error\", (err: Error) => {\n // A client that left, or an answer already complete (MR14), has nothing to fail.\n if (clientGone || res.writableEnded) {\n finish();\n return;\n }\n // An SSE call recorded before the stream died keeps its recording; one\n // whose response was already relayed gets no second answer.\n const answered = processed;\n const recorded = answered && x.recordSkipped === undefined && x.recordError === undefined;\n processed = true;\n if (!recorded) x.recordSkipped = \"upstream-aborted\";\n this.log().error(\n build(\n msg`MCP-RECORD: the upstream response failed mid-body (${fixed(\"upstream-aborted\")}) for ${plainText(x.rpcMethod ?? req.method ?? \"?\")} on mount ${plainText(x.mount)}: ${plainText(err.message)}`,\n ),\n );\n if (!res.headersSent) {\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: UPSTREAM_ABORTED_ERROR }));\n finish();\n return;\n }\n if (!answered && sseError !== null && !res.writableEnded) {\n res.write(sseError(), () => res.destroy());\n } else {\n res.destroy();\n }\n finish();\n });\n\n // MR14: a 401, or a 403 with insufficient_scope, is never relayed.\n const challenge = String(up.headers[\"www-authenticate\"] ?? \"\");\n if (status === 401 || (status === 403 && /insufficient_scope/i.test(challenge))) {\n up.resume();\n x.recordSkipped = \"upstream-auth\";\n this.log().error(\n build(\n msg`MCP-RECORD: recording refused (upstream-auth): the upstream answered ${status} for ${plainText(x.rpcMethod ?? req.method ?? \"?\")} on mount ${plainText(x.mount)}`,\n ),\n );\n res.writeHead(502, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: AUTH_FAILURE_ERROR, upstreamStatus: status }));\n finish();\n return;\n }\n // MR3: the session map entry goes with an upstream 404 or a deleted session.\n if (\n sessionId &&\n (status === 404 || (req.method === \"DELETE\" && status >= 200 && status < 300))\n ) {\n this.sessions.delete(sessionId);\n }\n\n const headers: Record<string, string | string[]> = {};\n for (const [name, value] of Object.entries(up.headers)) {\n if (value === undefined || HOP_BY_HOP.has(name.toLowerCase())) continue;\n headers[name] = value;\n }\n const ok = status >= 200 && status < 300;\n if (kind === \"tools/call\" && !ok) this.skip(x, \"upstream-status\", \"tools/call\");\n if (kind === \"tools/list\" && !ok) this.skip(x, \"upstream-status\", \"tools/list\");\n\n if (kind === null || !ok) {\n res.writeHead(status, headers);\n up.pipe(res);\n up.on(\"end\", finish);\n return;\n }\n\n const isSse = String(up.headers[\"content-type\"] ?? \"\").includes(\"text/event-stream\");\n const messages: TimedMessage[] = [];\n let size = 0;\n let overflow = false;\n const process = (): void => {\n if (processed) return;\n processed = true;\n if (overflow) {\n this.skip(x, \"buffer-cap\", kind);\n return;\n }\n // This runs in a stream event handler, outside handle()'s try: a throw\n // here must never reach the process (G2b A1).\n try {\n this.onRecordable(x, kind, messages, up);\n } catch (err) {\n // RL1 never carries values: an unexpected error is named, not quoted.\n this.fail(x, \"record-error\", err instanceof Error ? err.name : typeof err);\n }\n };\n\n if (isSse) {\n res.writeHead(status, headers);\n res.flushHeaders();\n const decoder = new StringDecoder(\"utf8\");\n let pending = \"\";\n if (rpcId !== undefined) {\n const error = { code: -32603, message: UPSTREAM_ABORTED_ERROR };\n sseError = () => {\n // A half-sent upstream event is closed under a type clients ignore,\n // so it never merges into the error event.\n const close =\n pending === \"\"\n ? \"\"\n : `${/[\\r\\n]$/.test(pending) ? \"\" : \"\\n\"}event: aimock-discarded\\n\\n`;\n return `${close}event: message\\ndata: ${JSON.stringify({ jsonrpc: \"2.0\", id: rpcId, error })}\\n\\n`;\n };\n }\n up.on(\"data\", (chunk: Buffer) => {\n if (!processed && !overflow) {\n size += chunk.length;\n if (size > this.maxBuffer) {\n overflow = true;\n process();\n } else {\n pending += decoder.write(chunk);\n const events = pending.split(/\\r?\\n\\r?\\n/);\n pending = events.pop() ?? \"\";\n for (const event of events) {\n for (const message of sseMessages(event)) {\n messages.push({ at: performance.now() - x.arrivedAt, message });\n }\n }\n // The response is recorded before the client sees it, so a\n // client that reads the file right after its call sees the write.\n if (messages.some((m) => isResponseTo(m.message, rpcId))) process();\n }\n }\n res.write(chunk);\n });\n up.on(\"end\", () => {\n if (!processed && !overflow) {\n for (const message of sseMessages(pending + decoder.end())) {\n messages.push({ at: performance.now() - x.arrivedAt, message });\n }\n }\n process();\n res.end();\n finish();\n });\n return;\n }\n\n // JSON: buffered whole, so a record error can still be sent as a header.\n const chunks: Buffer[] = [];\n up.on(\"data\", (chunk: Buffer) => {\n if (overflow) {\n res.write(chunk);\n return;\n }\n size += chunk.length;\n if (size > this.maxBuffer) {\n overflow = true;\n process();\n res.writeHead(status, headers);\n for (const c of chunks) res.write(c);\n res.write(chunk);\n chunks.length = 0;\n return;\n }\n chunks.push(chunk);\n });\n up.on(\"end\", () => {\n if (!overflow) {\n const text = Buffer.concat(chunks).toString(\"utf8\");\n try {\n const parsed: unknown = JSON.parse(text);\n for (const message of Array.isArray(parsed) ? parsed : [parsed]) {\n if (isObject(message)) messages.push({ at: performance.now() - x.arrivedAt, message });\n }\n } catch {\n /* not JSON: nothing to record */\n }\n process();\n if (x.recordError !== undefined) {\n headers[\"X-AIMock-Record-Error\"] = sanitizeHeaderValue(x.recordError);\n }\n res.writeHead(status, headers);\n res.end(text === \"\" ? undefined : Buffer.concat(chunks));\n } else {\n res.end();\n }\n finish();\n });\n }\n\n // ---- MR3, MR6-MR10, MR16 ----\n\n /** A recordable response, once its JSON-RPC response arrived (or its stream ended). */\n private onRecordable(\n x: Exchange,\n kind: RecordKind,\n messages: TimedMessage[],\n up: http.IncomingMessage,\n ): void {\n const response = messages.find((m) => isResponseTo(m.message, x.rpcId));\n if (kind === \"initialize\") {\n this.onInitialize(x, response?.message, up);\n return;\n }\n if (!response) {\n this.skip(x, \"upstream-error\", kind);\n return;\n }\n const reason =\n kind === \"tools/call\"\n ? callSkipReason(response.message, messages)\n : isObject(response.message.result)\n ? null\n : \"upstream-error\";\n if (reason !== null) {\n this.skip(x, reason, kind);\n return;\n }\n if (!this.versionRecordable(x)) {\n this.skip(x, \"unsupported-version\", kind);\n return;\n }\n const identity = x.identity!;\n if (identity.testId === null && identity.context === null) {\n // MR9 / RL3: forwarded, not written, warned once per mount.\n x.recordSkipped = \"no-scope\";\n if (!this.warnedNoScope.has(x.mount)) {\n this.warnedNoScope.add(x.mount);\n this.log().warn(\n build(\n msg`MCP-RECORD: forwarded, not recorded: a request on mount ${plainText(x.mount)} has no testId or context, so it has no recording scope (logged once per mount)`,\n ),\n );\n }\n return;\n }\n if (this.config.proxyOnly) return;\n if (kind === \"tools/list\") this.onToolsList(x, response.message);\n else this.recordCall(x, response, messages);\n }\n\n /** MR3 / AM4: bind what an upstream `initialize` negotiated to its session. */\n private onInitialize(\n x: Exchange,\n response: Record<string, unknown> | undefined,\n up: http.IncomingMessage,\n ): void {\n const result = response?.result;\n if (!isObject(result)) return;\n const version = typeof result.protocolVersion === \"string\" ? result.protocolVersion : null;\n const recordable = version !== null && RECORDABLE_VERSIONS.has(version);\n if (!recordable) this.skip(x, \"unsupported-version\", \"initialize\");\n const sessionId = headerOf(up, \"mcp-session-id\");\n if (!sessionId) return;\n const identity = x.identity!;\n this.sessions.set(sessionId, {\n protocolVersion: version,\n recordable,\n ...(isObject(result.serverInfo) ? { serverInfo: result.serverInfo } : {}),\n clientCapabilities: x.params?.capabilities,\n clientInfo: x.params?.clientInfo,\n // I3: only the values the initialize request itself supplied are bound.\n ...(x.supplied.testId && identity.testId !== null ? { testId: identity.testId } : {}),\n ...(x.supplied.context && identity.context !== null ? { context: identity.context } : {}),\n ...(x.supplied.undeclared && identity.undeclared !== null\n ? { undeclared: identity.undeclared }\n : {}),\n });\n }\n\n /** AM4: false when the session negotiated, or the request names, a version not recorded. */\n private versionRecordable(x: Exchange): boolean {\n const sessionId = headerOf(x.req, \"mcp-session-id\");\n const session = sessionId ? this.sessions.get(sessionId) : undefined;\n if (session && !session.recordable) return false;\n const header = headerOf(x.req, \"mcp-protocol-version\");\n return header === undefined || RECORDABLE_VERSIONS.has(header);\n }\n\n /** MR16: collect a `tools/list` page; write the list when it completes. */\n private onToolsList(x: Exchange, response: Record<string, unknown>): void {\n const result = response.result as Record<string, unknown>;\n if (!Array.isArray(result.tools)) {\n this.skip(x, \"upstream-error\", \"tools/list\");\n return;\n }\n const identity = x.identity!;\n const key = listKey(x.mount, identity);\n const cursor = x.params?.cursor;\n const next = typeof result.nextCursor === \"string\" ? result.nextCursor : undefined;\n let tools: unknown[];\n if (cursor === undefined) {\n if (this.pendingLists.has(key)) {\n this.log().warn(\n build(\n msg`MCP-RECORD: forwarded, not recorded (list-incomplete): tools/list on mount ${plainText(x.mount)}`,\n ),\n );\n }\n tools = [...result.tools];\n } else {\n const pending = this.pendingLists.get(key);\n if (!pending || pending.next !== cursor) {\n this.skip(x, \"list-orphan-page\", \"tools/list\");\n return;\n }\n tools = [...pending.tools, ...result.tools];\n }\n this.pendingLists.delete(key);\n if (next !== undefined) {\n this.pendingLists.set(key, { tools, next, testId: identity.testId });\n return;\n }\n const filepath = this.targetFile(identity);\n const scope = scopeOf(identity);\n if (!fileHasBlock(filepath, scope, x.mount)) {\n // A block needs a call entry; the list waits for the block's first one.\n this.completedLists.set(key, { tools, testId: identity.testId });\n return;\n }\n let list: { value: unknown[]; warnings: string[] };\n try {\n list = sanitizeRecording(tools, this.secrets(x), isMetaBranch);\n } catch (err) {\n this.failUnsafe(x, err);\n return;\n }\n this.persist(x, filepath, scope, (block) => ({\n block: withList(block ?? {}, list.value),\n warnings: (prefix) => list.warnings.map((w) => `${prefix}/list${w}`),\n }));\n }\n\n /** MR6 / MR7 / MR10-MR12: record one forwarded `tools/call`. */\n private recordCall(x: Exchange, response: TimedMessage, messages: TimedMessage[]): void {\n const identity = x.identity!;\n const name = String(x.params?.name ?? \"\");\n const result = cleanResult(response.message.result as Record<string, unknown>);\n const entry: Record<string, unknown> = { args: x.params?.arguments ?? {} };\n const errorText = errorTextOf(result);\n if (errorText !== null) entry.error = errorText;\n else entry.result = result;\n const notifications = messages\n .filter(\n (m) =>\n m !== response &&\n m.at <= response.at &&\n !(\"id\" in m.message) &&\n typeof m.message.method === \"string\" &&\n m.message.method.startsWith(\"notifications/\"),\n )\n .map((m) => ({\n atMs: Math.round(m.at),\n method: m.message.method as string,\n params: isObject(m.message.params) ? m.message.params : {},\n }));\n if (notifications.length > 0) entry.notifications = notifications;\n entry.durationMs = Math.round(response.at);\n\n const filepath = this.targetFile(identity);\n const scope = scopeOf(identity);\n const key = listKey(x.mount, identity);\n const waitingList = this.completedLists.get(key);\n const secrets = this.secrets(x);\n let clean: { value: Record<string, unknown>; warnings: string[] };\n let recorded: { value: Record<string, unknown>; warnings: string[] };\n let list: { value: unknown[]; warnings: string[] } | null = null;\n try {\n clean = sanitizeRecording(entry, secrets, isMetaBranch);\n recorded = sanitizeRecording(this.provenance(x, response.message), secrets, () => true);\n if (waitingList) list = sanitizeRecording(waitingList.tools, secrets, isMetaBranch);\n } catch (err) {\n this.failUnsafe(x, err);\n return;\n }\n const written = this.persist(x, filepath, scope, (block) => {\n if (!block) {\n return {\n block: {\n scope,\n mount: x.mount,\n ...(list ? { list: list.value } : {}),\n recorded: recorded.value,\n tools: [{ name, calls: [clean.value] }],\n },\n warnings: (prefix) => [\n ...(list ? list.warnings.map((w) => `${prefix}/list${w}`) : []),\n ...recorded.warnings.map((w) => `${prefix}/recorded${w}`),\n ...clean.warnings.map((w) => `${prefix}/tools/0/calls/0${w}`),\n ],\n };\n }\n const tools = Array.isArray(block.tools) ? [...(block.tools as unknown[])] : [];\n let t = tools.findIndex((tool) => isObject(tool) && tool.name === name);\n let c = 0;\n if (t === -1) {\n tools.push({ name, calls: [clean.value] });\n t = tools.length - 1;\n } else {\n const tool = tools[t] as Record<string, unknown>;\n const calls = Array.isArray(tool.calls) ? [...(tool.calls as unknown[])] : [];\n calls.push(clean.value);\n c = calls.length - 1;\n tools[t] = { ...tool, calls };\n }\n // A list that completed before this block appeared (G2b A2) replaces its list.\n const updated = list ? withList({ ...block, tools }, list.value) : { ...block, tools };\n return {\n block: updated,\n warnings: (prefix) => [\n ...(list ? list.warnings.map((w) => `${prefix}/list${w}`) : []),\n ...clean.warnings.map((w) => `${prefix}/tools/${t}/calls/${c}${w}`),\n ],\n };\n });\n if (!written) return;\n if (waitingList) this.completedLists.delete(key);\n\n // MR12: the new entry is served from memory as `record#<n>`, consumed\n // for the recording test id, so an identical call misses and is recorded.\n const before = new Set(this.host.fakes.allEntryIds());\n try {\n this.host.addMcpFakes(\n [\n {\n source: \"record\",\n blockIndex: null,\n raw: { scope, mount: x.mount, tools: [{ name, calls: [clean.value] }] },\n },\n ],\n { kind: \"record\" },\n );\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n this.log().error(\n build(\n msg`MCP-RECORD: the recorded entry was written but not registered on mount ${plainText(x.mount)}: ${plainText(detail)}`,\n ),\n );\n return;\n }\n const added = this.host.fakes.allEntryIds().filter((id) => !before.has(id));\n this.host.fakes.markConsumed(identity.testId, added);\n }\n\n /** FA2 `recorded` for a new block (MR6 3). */\n private provenance(x: Exchange, response: Record<string, unknown>): Record<string, unknown> {\n const sessionId = headerOf(x.req, \"mcp-session-id\");\n const session = sessionId ? this.sessions.get(sessionId) : undefined;\n const meta = isObject(x.params?._meta) ? x.params._meta : undefined;\n const result = isObject(response.result) ? response.result : undefined;\n const resultMeta = isObject(result?._meta) ? result._meta : undefined;\n const protocolVersion =\n session?.protocolVersion ??\n (typeof meta?.[PROTOCOL_VERSION_META] === \"string\"\n ? meta[PROTOCOL_VERSION_META]\n : undefined) ??\n headerOf(x.req, \"mcp-protocol-version\") ??\n FALLBACK_VERSION;\n const serverInfo =\n session?.serverInfo ??\n (isObject(resultMeta?.[SERVER_INFO_META]) ? resultMeta[SERVER_INFO_META] : undefined);\n return {\n upstream: this.target.origin,\n protocolVersion,\n ...(serverInfo ? { serverInfo } : {}),\n aimockVersion: aimockVersion((m) => this.log().warn(m)),\n at: new Date().toISOString(),\n };\n }\n\n /** S2 (a)-(d): the known secrets of one request. */\n private secrets(x: Exchange): string[] {\n // The raw values: `flattenHeaders` masks API-key headers for the\n // journal, which would hide the very values S2 (a) must redact.\n const headers: Record<string, string> = {};\n for (const [name, value] of Object.entries(x.req.headers)) {\n if (value !== undefined) headers[name] = Array.isArray(value) ? value.join(\", \") : value;\n }\n return knownSecrets({\n headers,\n ...(this.auth ? { upstreamAuth: this.auth } : {}),\n // G2b B1: the URL really sent upstream, so a credential in the\n // client's query string (forwarded per MR2 c) is a known secret (S2 c).\n upstreamUrl: this.upstreamUrl(x.req, x.subPath).href,\n secretValues: this.secretValues,\n });\n }\n\n /** S6 / RL1: an unsafe recording is not written. */\n private failUnsafe(x: Exchange, err: unknown): void {\n if (!(err instanceof RecordUnsafeError)) throw err;\n this.fail(x, \"unsafe\", err.reason);\n }\n\n /** RL1: a recording that was not written, with its reason (never a value). */\n private fail(x: Exchange, reason: string, detail: string): void {\n x.recordSkipped = reason;\n x.recordError = `${reason}: ${detail}`;\n this.log().error(\n build(\n msg`MCP-RECORD: recording failed (${fixed(reason)}) for ${plainText(x.rpcMethod ?? \"?\")} on mount ${plainText(x.mount)}: ${plainText(detail)}`,\n ),\n );\n }\n\n /**\n * MR10 / MR11: merge one change into the block of `scope` and this mount\n * (appending a block when none matches), validate the whole file, and write\n * it. `apply` gets that block (`undefined` when it is new) and returns the\n * new block and its sanitizer warnings under the block's pointer.\n */\n private persist(\n x: Exchange,\n filepath: string,\n scope: Record<string, string>,\n apply: (block: Record<string, unknown> | undefined) => {\n block: Record<string, unknown>;\n warnings: (blockPointer: string) => string[];\n },\n ): boolean {\n const result = persistServiceFakes({\n filepath,\n logger: this.log(),\n merge: (existing) => mergeBlock(existing, scope, x.mount, apply),\n validate: (content) => {\n const first = validateMcpFakes(content.mcpFakes, filepath).errors[0];\n if (!first) return null;\n const where = first.entryId ?? first.blockId;\n return where ? `${first.rule} (${where})` : first.rule;\n },\n });\n if (result.kind === \"failed\") {\n this.fail(x, result.stage === \"write\" ? \"write-failed\" : \"invalid-merge\", result.error);\n return false;\n }\n const resolved = path.resolve(result.filepath);\n this.hashes.set(resolved, result.sha256);\n for (const listener of this.listeners) {\n try {\n listener(resolved, result.content.mcpFakes);\n } catch (err) {\n const detail = err instanceof Error ? err.message : String(err);\n this.log().error(\n build(\n msg`MCP-RECORD: a write listener failed for ${plainText(resolved)} on mount ${plainText(x.mount)}: ${plainText(detail)}`,\n ),\n );\n }\n }\n return true;\n }\n\n /** MR10: the file a recording of `identity` goes to. */\n private targetFile(identity: McpFakeIdentity): string {\n const slug =\n identity.testId !== null\n ? slugifyTestId(identity.testId)\n : identity.context !== null\n ? slugifyContext(identity.context)\n : \"\";\n if (slug) return path.join(this.fixturePath, slug, \"mcp.json\");\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n return path.join(this.fixturePath, `mcp-${timestamp}-${crypto.randomUUID().slice(0, 8)}.json`);\n }\n\n /** MR8 / RL2: mark the request forwarded-not-recorded and warn. */\n private skip(x: Exchange, reason: string, method: string): void {\n x.recordSkipped = reason;\n this.log().warn(\n build(\n msg`MCP-RECORD: forwarded, not recorded (${fixed(reason)}): ${plainText(method)} on mount ${plainText(x.mount)}`,\n ),\n );\n }\n\n private log(): Logger {\n return this.host.logger() ?? this.fallbackLogger;\n }\n\n /** AM8: one journal entry per request. */\n private journal(x: Exchange): void {\n const { req, res } = x;\n this.host.journalEntry({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: x.message,\n service: \"mcp\",\n ...(x.identity\n ? {\n testId: x.identity.testId ?? MCP_FAKES_DEFAULT_TEST_ID,\n context: x.identity.context,\n }\n : {}),\n response: {\n status: res.statusCode,\n fixture: null,\n ...(x.proxied ? { source: \"proxy\" as const } : {}),\n ...(x.recordSkipped !== undefined ? { recordSkipped: x.recordSkipped } : {}),\n ...(x.mcpFake ? { mcpFake: x.mcpFake } : {}),\n },\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** One header's value (the first, when repeated), or undefined. */\nfunction headerOf(req: http.IncomingMessage, name: string): string | undefined {\n const value = req.headers[name];\n return Array.isArray(value) ? value[0] : value;\n}\n\n/** The JSON-RPC messages of one SSE event (its `data:` lines). */\nfunction sseMessages(event: string): Record<string, unknown>[] {\n const data = event\n .split(/\\r?\\n/)\n .filter((line) => line.startsWith(\"data:\"))\n .map((line) => line.slice(5).replace(/^ /, \"\"))\n .join(\"\\n\");\n if (data === \"\") return [];\n try {\n const parsed: unknown = JSON.parse(data);\n return (Array.isArray(parsed) ? parsed : [parsed]).filter(isObject);\n } catch {\n return [];\n }\n}\n\n/** Whether `message` is the JSON-RPC response to request `id`. */\nfunction isResponseTo(message: Record<string, unknown>, id: string | number | undefined): boolean {\n return id !== undefined && message.id === id && (\"result\" in message || \"error\" in message);\n}\n\n/** The decoded name of one raw `name=value` query pair (form rules: `+` is a space). */\nfunction queryName(pair: string): string {\n const eq = pair.indexOf(\"=\");\n const name = (eq === -1 ? pair : pair.slice(0, eq)).replace(/\\+/g, \" \");\n try {\n return decodeURIComponent(name);\n } catch {\n return name;\n }\n}\n\nfunction mountOfKey(key: string): string {\n return key.slice(0, key.indexOf(\"\\u0000\"));\n}\n\n/** MR8: why a forwarded `tools/call` response is not recorded, or null. */\nfunction callSkipReason(\n response: Record<string, unknown>,\n messages: TimedMessage[],\n): string | null {\n if (\"error\" in response) return \"upstream-error\";\n const result = response.result;\n if (!isObject(result)) return \"upstream-error\";\n if (result.resultType === \"input_required\") return \"input-required\";\n if (result.task !== undefined || (isObject(result._meta) && RELATED_TASK_META in result._meta)) {\n return \"task-handle\";\n }\n if (messages.some((m) => typeof m.message.method === \"string\" && \"id\" in m.message)) {\n return \"server-request\";\n }\n return null;\n}\n\n/** MR7: a recorded result without trace `_meta` keys, serverInfo and `resultType: \"complete\"`. */\nfunction cleanResult(result: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = { ...result };\n if (out.resultType === \"complete\") delete out.resultType;\n if (isObject(out._meta)) {\n const meta: Record<string, unknown> = { ...out._meta };\n for (const key of DROPPED_META) delete meta[key];\n if (Object.keys(meta).length > 0) out._meta = meta;\n else delete out._meta;\n }\n return out;\n}\n\n/** MR6: the `error` text of an `isError` result that is exactly one text block, else null. */\nfunction errorTextOf(result: Record<string, unknown>): string | null {\n if (result.isError !== true) return null;\n if (!Object.keys(result).every((k) => k === \"content\" || k === \"isError\")) return null;\n const content = result.content;\n if (!Array.isArray(content) || content.length !== 1) return null;\n const block: unknown = content[0];\n if (!isObject(block) || block.type !== \"text\" || typeof block.text !== \"string\") return null;\n return Object.keys(block).length === 2 ? block.text : null;\n}\n\n/** S4: credential-shaped keys are removed only inside `_meta`. */\nfunction isMetaBranch(pointer: string): boolean {\n return /(?:^|\\/)_meta(?:\\/|$)/.test(pointer);\n}\n\n/** MR10 block scope: the test id, plus the context when there is one. */\nfunction scopeOf(identity: McpFakeIdentity): Record<string, string> {\n if (identity.testId === null) return { context: identity.context ?? \"\" };\n return identity.context === null\n ? { testId: identity.testId }\n : { testId: identity.testId, context: identity.context };\n}\n\n/** MR10: a block scope exactly equal to `scope`. */\nfunction sameScope(raw: unknown, scope: Record<string, string>): boolean {\n if (!isObject(raw)) return false;\n const keys = Object.keys(scope);\n return Object.keys(raw).length === keys.length && keys.every((k) => raw[k] === scope[k]);\n}\n\n/** MR16 pending-list key: mount and scope. */\nfunction listKey(mount: string, identity: McpFakeIdentity): string {\n return `${mount}\\u0000${JSON.stringify([identity.testId, identity.context])}`;\n}\n\n/** Whether the file at `filepath` already has the block of `scope` and `mount`. */\nfunction fileHasBlock(filepath: string, scope: Record<string, string>, mount: string): boolean {\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(filepath, \"utf-8\"));\n } catch {\n return false;\n }\n if (!isObject(parsed)) return false;\n const raw = parsed.mcpFakes;\n const blocks: unknown[] = Array.isArray(raw) ? raw : raw === undefined ? [] : [raw];\n return blocks.some((b) => isObject(b) && sameScope(b.scope, scope) && b.mount === mount);\n}\n\n/** MR10 exception: a block with `list` replaced (or inserted after `mount`), key order kept. */\nfunction withList(block: Record<string, unknown>, list: unknown[]): Record<string, unknown> {\n if (\"list\" in block) return { ...block, list };\n const out: Record<string, unknown> = {};\n let placed = false;\n for (const [key, value] of Object.entries(block)) {\n out[key] = value;\n if (key === \"mount\") {\n out.list = list;\n placed = true;\n }\n }\n if (!placed) out.list = list;\n return out;\n}\n\n/**\n * MR10 append rule over a whole file: the first block whose scope and mount\n * are exactly equal is changed by `apply`; otherwise a block is appended.\n * Object form becomes array form only when a second block is needed. Other\n * top-level keys are kept (fakes:F9); `_warnings` gains the new pointers.\n */\nfunction mergeBlock(\n existing: Record<string, unknown> | null,\n scope: Record<string, string>,\n mount: string,\n apply: (block: Record<string, unknown> | undefined) => {\n block: Record<string, unknown>;\n warnings: (blockPointer: string) => string[];\n },\n): Record<string, unknown> {\n const content: Record<string, unknown> = existing ? { ...existing } : {};\n const raw = content.mcpFakes;\n const blocks: unknown[] = Array.isArray(raw) ? [...raw] : raw === undefined ? [] : [raw];\n let index = blocks.findIndex(\n (b) => isObject(b) && sameScope(b.scope, scope) && b.mount === mount,\n );\n const applied = apply(index === -1 ? undefined : (blocks[index] as Record<string, unknown>));\n if (index === -1) {\n blocks.push(applied.block);\n index = blocks.length - 1;\n } else {\n blocks[index] = applied.block;\n }\n const arrayForm = Array.isArray(raw) || blocks.length > 1;\n content.mcpFakes = arrayForm ? blocks : blocks[0];\n let warnings = Array.isArray(content._warnings)\n ? content._warnings.filter((w): w is string => typeof w === \"string\")\n : [];\n if (isObject(raw) && arrayForm) {\n // The file's only block moved from /mcpFakes to /mcpFakes/0.\n warnings = warnings.map((w) =>\n /^\\/mcpFakes(?=[/:])/.test(w) ? `/mcpFakes/0${w.slice(\"/mcpFakes\".length)}` : w,\n );\n }\n const added = applied.warnings(arrayForm ? `/mcpFakes/${index}` : \"/mcpFakes\");\n if (warnings.length > 0 || added.length > 0)\n content._warnings = [...new Set([...warnings, ...added])];\n return content;\n}\n\n// ---- Wiring: the `llmock` flags and `llm.record.mcp` (MR1, AM6) ----\n\n/** One `--mcp-record` / `--mcp-proxy-only` value: `<mount>=<url>`. */\nexport interface McpRecordFlag {\n mount: string;\n upstream: string;\n}\n\n/**\n * A mount key as an error message may show it: JSON-quoted, with any\n * credential (URL userinfo, a credential query parameter, a secret-looking\n * fragment) removed by the record sanitizer's URL rule (S2 c) and marked as\n * removed. A key with an `@` that the URL rule cannot parse is not shown.\n */\nexport function redactMountForError(mount: string): string {\n const absolute = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(mount);\n const prefix = absolute ? \"\" : mount.startsWith(\"/\") ? \"http://localhost\" : \"http://\";\n const scrubbed = scrubUrl(\n prefix + mount,\n () => false,\n () => {\n throw new Error(\"unreachable: no value is a secret here\");\n },\n );\n if (scrubbed !== prefix + mount) {\n const shown = scrubbed.startsWith(prefix) ? scrubbed.slice(prefix.length) : REDACTED;\n return `${JSON.stringify(shown)} (credentials removed)`;\n }\n return mount.includes(\"@\") && !URL.canParse(prefix + mount)\n ? JSON.stringify(REDACTED)\n : JSON.stringify(mount);\n}\n\n/**\n * Why a mount path can never be reached, or `undefined` when it can. The\n * server matches a request's URL pathname against the mount exactly or as a\n * `<mount>/` prefix, so a mount must be a plain, normalized URL path: it\n * starts with one `/`, has no trailing `/` (except the root `/`), and has no\n * query, fragment, whitespace, dot segment or character the URL parser escapes.\n */\nexport function unreachableMountReason(mount: string): string | undefined {\n if (!mount.startsWith(\"/\")) return 'it must start with \"/\"';\n if (mount.startsWith(\"//\")) return 'it must not start with \"//\"';\n if (mount.length > 1 && mount.endsWith(\"/\")) return 'it must not end with \"/\"';\n if (new URL(mount, \"http://localhost\").pathname !== mount) {\n return \"it must be a plain URL path, with no query, fragment, whitespace, dot segment or escaped character\";\n }\n return undefined;\n}\n\n/**\n * Why an upstream URL is unusable, or `undefined` when it is a valid http(s)\n * URL. Never includes the value: it can carry credentials (S2 c).\n */\nfunction badUpstreamReason(upstream: string): string | undefined {\n let url: URL;\n try {\n url = new URL(upstream);\n } catch {\n return \"is not a valid URL\";\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n return \"must be an http: or https: URL\";\n }\n return undefined;\n}\n\n/**\n * F2: fail at start, not at every write, when the recording destination can\n * never be written. The deepest path component that exists must be a\n * directory this process can write to and enter. A path that does not exist\n * yet is created on the first write, but not through a broken symbolic link.\n * `label` prefixes the error (for example the mount it belongs to).\n */\nexport function checkMcpRecordDestination(fixturePath: string, label?: string): void {\n const destination = path.resolve(fixturePath);\n const fail = (problem: string): never => {\n const text = `MCP record destination ${destination} ${problem}`;\n throw new Error(label ? `${label}: ${text}` : text);\n };\n const code = (err: unknown): string => (err as NodeJS.ErrnoException | null)?.code ?? String(err);\n let probe = destination;\n for (;;) {\n let stat: fs.Stats | undefined;\n let broken = false;\n try {\n const entry = fs.lstatSync(probe, { throwIfNoEntry: false });\n stat = entry?.isSymbolicLink() ? fs.statSync(probe, { throwIfNoEntry: false }) : entry;\n broken = entry !== undefined && stat === undefined;\n } catch (err) {\n // ENOTDIR: an ancestor is a file; the walk up finds it.\n if (code(err) !== \"ENOTDIR\") fail(`cannot be checked (${code(err)} at ${probe})`);\n }\n if (broken) fail(`goes through a broken symbolic link at ${probe}`);\n if (stat !== undefined) {\n if (!stat.isDirectory()) fail(\"is not a directory\");\n try {\n fs.accessSync(probe, fs.constants.W_OK | fs.constants.X_OK);\n } catch (err) {\n fail(`is not writable (${code(err)} at ${probe})`);\n }\n return;\n }\n const parent = path.dirname(probe);\n if (parent === probe) return;\n probe = parent;\n }\n}\n\n/**\n * Parse a `--mcp-record` / `--mcp-proxy-only` value at its first `=`. The\n * mount must be a reachable path and the upstream an http(s) URL. A bad value\n * throws an error that names `flag`, never the URL (it can carry credentials).\n */\nexport function parseMcpRecordFlag(value: string, flag = \"--mcp-record\"): McpRecordFlag {\n const eq = value.indexOf(\"=\");\n const mount = eq === -1 ? \"\" : value.slice(0, eq);\n const upstream = eq === -1 ? \"\" : value.slice(eq + 1);\n if (!mount.startsWith(\"/\")) {\n throw new Error(`${flag} must be <mount>=<url>, with a mount that starts with \"/\"`);\n }\n const unreachable = unreachableMountReason(mount);\n if (unreachable !== undefined) {\n throw new Error(\n `${flag} mount ${redactMountForError(mount)} can never be reached: ${unreachable}`,\n );\n }\n const badUpstream = badUpstreamReason(upstream);\n if (badUpstream !== undefined) {\n throw new Error(`${flag} ${mount}: the upstream ${badUpstream}`);\n }\n return { mount, upstream };\n}\n\n/**\n * AM6 / S2 (d): the recorder settings that come from the environment.\n * `AIMOCK_RECORD_SECRET_VALUES` is newline-separated (LF or CRLF); empty\n * entries are dropped and nothing else is trimmed. A malformed\n * `AIMOCK_MCP_UPSTREAM_AUTH` throws an error that names the variable.\n */\nexport function mcpRecordEnv(env: NodeJS.ProcessEnv): {\n secretValues: string[];\n upstreamAuth: string | undefined;\n} {\n const upstreamAuth = env.AIMOCK_MCP_UPSTREAM_AUTH;\n parseUpstreamAuth(upstreamAuth);\n return {\n secretValues: (env.AIMOCK_RECORD_SECRET_VALUES ?? \"\").split(/\\r?\\n/).filter(Boolean),\n upstreamAuth: upstreamAuth === \"\" ? undefined : upstreamAuth,\n };\n}\n\n/** A mount that records MCP: an `MCPMock`. */\nexport interface RecordableMcpMount extends Mountable {\n enableRecording(config: McpRecordConfig): unknown;\n}\n\nexport interface WireMcpRecordingOptions {\n /** What the caller already mounted, by path. */\n mounted: ReadonlyMap<string, Mountable>;\n /**\n * The `MCPMock` class. It is passed in, because mcp-mock.ts imports this\n * module: a mounted instance of it records, and a free path gets a new one.\n */\n mcpMock: new () => RecordableMcpMount;\n /** `llm.fixtures`: the recording destination is `<fixtures>/recorded`. */\n fixtures?: string;\n /** `llm.record.fixturePath`: overrides the destination. */\n fixturePath?: string;\n strict?: boolean;\n env: NodeJS.ProcessEnv;\n}\n\n/**\n * MR1: `llm.record.mcp` — record each mount path. A path the caller mounted\n * with an `MCPMock` records through it; a path held by another kind of mount\n * is a start error; a free path gets a new `MCPMock`, mounted on `llmock`.\n * A string value is `{ upstream }`. `secretValues` and `upstreamAuth` come\n * from the value, else from the environment, as on the `llmock` CLI.\n */\nexport function wireMcpRecording(\n llmock: { mount(path: string, handler: Mountable): unknown },\n record: Record<string, string | McpRecordConfig>,\n opts: WireMcpRecordingOptions,\n): void {\n // F1: the config file is untyped JSON. Before anything is mounted, check\n // every mount's path, settings, upstream URL, holder and destination. The\n // McpRecorder constructor still checks secretValues and upstreamAuth while\n // mounting, so a failure there can leave earlier mounts mounted.\n // Errors name the mount and key, never a value (S2 c).\n const where = \"llm.record.mcp\";\n const plain = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n const keyTypes: Record<keyof McpRecordConfig, string> = {\n upstream: \"a string\",\n fixturePath: \"a non-empty string\",\n proxyOnly: \"a boolean\",\n maxRecordBufferBytes: `a finite number greater than 0 and at most ${MAX_RECORD_BUFFER_BYTES}`,\n upstreamAuth: \"a string\",\n secretValues: \"an array of strings\",\n strict: \"a boolean\",\n };\n const hasType = (key: keyof McpRecordConfig, v: unknown): boolean => {\n switch (key) {\n case \"proxyOnly\":\n case \"strict\":\n return typeof v === \"boolean\";\n case \"maxRecordBufferBytes\":\n return typeof v === \"number\" && Number.isFinite(v) && v > 0 && v <= MAX_RECORD_BUFFER_BYTES;\n case \"secretValues\":\n return Array.isArray(v) && v.every((s) => typeof s === \"string\");\n case \"fixturePath\":\n return typeof v === \"string\" && v.length > 0;\n default:\n return typeof v === \"string\";\n }\n };\n const isConfigKey = (key: string): key is keyof McpRecordConfig =>\n Object.prototype.hasOwnProperty.call(keyTypes, key);\n const unknownRecord: unknown = record;\n if (!plain(unknownRecord)) {\n throw new Error(`${where} must be an object of <mount>: <url or record settings>`);\n }\n const sharedFixturePath: unknown = opts.fixturePath;\n if (\n sharedFixturePath !== undefined &&\n !(typeof sharedFixturePath === \"string\" && sharedFixturePath.length > 0)\n ) {\n throw new Error(\"llm.record.fixturePath must be a non-empty string\");\n }\n const wired: {\n mountPath: string;\n config: McpRecordConfig;\n fixturePath: string | undefined;\n held: RecordableMcpMount | undefined;\n }[] = [];\n for (const [mountPath, value] of Object.entries(unknownRecord)) {\n const mount = redactMountForError(mountPath);\n if (!mountPath.startsWith(\"/\")) {\n throw new Error(\n `${where} must be <mount>: <url or object>, with a mount that starts with \"/\"; got ${mount}`,\n );\n }\n const unreachable = unreachableMountReason(mountPath);\n if (unreachable !== undefined) {\n throw new Error(`${where} mount ${mount} can never be reached: ${unreachable}`);\n }\n if (typeof value !== \"string\" && !plain(value)) {\n throw new Error(`${where} mount ${mount} must be an upstream URL string or an object`);\n }\n if (typeof value !== \"string\") {\n for (const [key, v] of Object.entries(value)) {\n if (!isConfigKey(key)) {\n throw new Error(\n `${where} mount ${mount} has an unknown key ${JSON.stringify(key)} (expected one of ${Object.keys(keyTypes).join(\", \")})`,\n );\n }\n if (!hasType(key, v)) {\n throw new Error(`${where} mount ${mount} key ${key} must be ${keyTypes[key]}`);\n }\n }\n if (!(\"upstream\" in value)) {\n throw new Error(`${where} mount ${mount} key upstream is required`);\n }\n }\n // Every key now has its declared type.\n const typed = record[mountPath];\n const config: McpRecordConfig = typeof typed === \"string\" ? { upstream: typed } : typed;\n const badUpstream = badUpstreamReason(config.upstream);\n if (badUpstream !== undefined) {\n throw new Error(`${where} mount ${mount}: the upstream ${badUpstream}`);\n }\n const held = opts.mounted.get(mountPath);\n if (held !== undefined && !(held instanceof opts.mcpMock)) {\n throw new Error(\n `llm.record.mcp mount ${mountPath} is held by a mount that is not an MCP mock`,\n );\n }\n const fixturePath =\n config.fixturePath ??\n opts.fixturePath ??\n (opts.fixtures ? path.resolve(opts.fixtures, \"recorded\") : undefined);\n if (!config.proxyOnly) {\n if (fixturePath === undefined) {\n throw new Error(\n \"llm.record.mcp requires llm.fixtures or llm.record.fixturePath for the recording destination\",\n );\n }\n checkMcpRecordDestination(fixturePath, `${where} mount ${mount}`);\n }\n wired.push({ mountPath, config, fixturePath, held });\n }\n\n const fromEnv = mcpRecordEnv(opts.env);\n for (const { mountPath, config, fixturePath, held } of wired) {\n const mcp = held ?? new opts.mcpMock();\n mcp.enableRecording({\n ...config,\n fixturePath,\n secretValues: config.secretValues ?? fromEnv.secretValues,\n upstreamAuth: config.upstreamAuth ?? fromEnv.upstreamAuth,\n strict: config.strict ?? opts.strict,\n });\n if (held === undefined) llmock.mount(mountPath, mcp);\n }\n}\n"],"mappings":"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"}
|
package/dist/mcp-stub.cjs
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_types = require('./types.cjs');
|
|
3
|
+
const require_mcp_fakes = require('./mcp-fakes.cjs');
|
|
2
4
|
const require_mcp_mock = require('./mcp-mock.cjs');
|
|
3
5
|
|
|
4
|
-
exports.MCPMock = require_mcp_mock.MCPMock;
|
|
6
|
+
exports.MCPMock = require_mcp_mock.MCPMock;
|
|
7
|
+
exports.MCP_FAKE_ERROR_CODES = require_types.MCP_FAKE_ERROR_CODES;
|
|
8
|
+
exports.McpFakesAddError = require_mcp_fakes.McpFakesAddError;
|
package/dist/mcp-stub.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { McpFakeAddOrigin, McpFakeAddResult, McpFakeBlockSnapshot, McpFakeDeclaredEntry, McpFakeIssue, McpFakesAddError, McpFakesAddErrorJSON } from "./mcp-fakes.cjs";
|
|
2
|
+
import { MCP_FAKE_ERROR_CODES, McpFakeAnnotations, McpFakeBlock, McpFakeCall, McpFakeCallAnswer, McpFakeCallMatch, McpFakeContentBlock, McpFakeDeepReadonly, McpFakeErrorCode, McpFakeFullResult, McpFakeIcon, McpFakeIdentity, McpFakeOutcome, McpFakeResourceContents, McpFakeResult, McpFakeScope, McpFakeSource, McpFakeStructuredResult, McpFakeTool, McpFakeUndeclaredPolicy } from "./types.cjs";
|
|
1
3
|
import { MCPContent, MCPMockOptions, MCPPromptDefinition, MCPPromptResult, MCPResourceContent, MCPResourceDefinition, MCPSession, MCPToolDefinition } from "./mcp-types.cjs";
|
|
2
4
|
import { MCPMock } from "./mcp-mock.cjs";
|
|
3
|
-
export { type MCPContent, MCPMock, type MCPMockOptions, type MCPPromptDefinition, type MCPPromptResult, type MCPResourceContent, type MCPResourceDefinition, type MCPSession, type MCPToolDefinition };
|
|
5
|
+
export { type MCPContent, MCPMock, type MCPMockOptions, type MCPPromptDefinition, type MCPPromptResult, type MCPResourceContent, type MCPResourceDefinition, type MCPSession, type MCPToolDefinition, MCP_FAKE_ERROR_CODES, type McpFakeAddOrigin, type McpFakeAddResult, type McpFakeAnnotations, type McpFakeBlock, type McpFakeBlockSnapshot, type McpFakeCall, type McpFakeCallAnswer, type McpFakeCallMatch, type McpFakeContentBlock, type McpFakeDeclaredEntry, type McpFakeDeepReadonly, type McpFakeErrorCode, type McpFakeFullResult, type McpFakeIcon, type McpFakeIdentity, type McpFakeIssue, type McpFakeOutcome, type McpFakeResourceContents, type McpFakeResult, type McpFakeScope, type McpFakeSource, type McpFakeStructuredResult, type McpFakeTool, type McpFakeUndeclaredPolicy, McpFakesAddError, type McpFakesAddErrorJSON };
|
package/dist/mcp-stub.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { McpFakeAddOrigin, McpFakeAddResult, McpFakeBlockSnapshot, McpFakeDeclaredEntry, McpFakeIssue, McpFakesAddError, McpFakesAddErrorJSON } from "./mcp-fakes.js";
|
|
2
|
+
import { MCP_FAKE_ERROR_CODES, McpFakeAnnotations, McpFakeBlock, McpFakeCall, McpFakeCallAnswer, McpFakeCallMatch, McpFakeContentBlock, McpFakeDeepReadonly, McpFakeErrorCode, McpFakeFullResult, McpFakeIcon, McpFakeIdentity, McpFakeOutcome, McpFakeResourceContents, McpFakeResult, McpFakeScope, McpFakeSource, McpFakeStructuredResult, McpFakeTool, McpFakeUndeclaredPolicy } from "./types.js";
|
|
1
3
|
import { MCPContent, MCPMockOptions, MCPPromptDefinition, MCPPromptResult, MCPResourceContent, MCPResourceDefinition, MCPSession, MCPToolDefinition } from "./mcp-types.js";
|
|
2
4
|
import { MCPMock } from "./mcp-mock.js";
|
|
3
|
-
export { type MCPContent, MCPMock, type MCPMockOptions, type MCPPromptDefinition, type MCPPromptResult, type MCPResourceContent, type MCPResourceDefinition, type MCPSession, type MCPToolDefinition };
|
|
5
|
+
export { type MCPContent, MCPMock, type MCPMockOptions, type MCPPromptDefinition, type MCPPromptResult, type MCPResourceContent, type MCPResourceDefinition, type MCPSession, type MCPToolDefinition, MCP_FAKE_ERROR_CODES, type McpFakeAddOrigin, type McpFakeAddResult, type McpFakeAnnotations, type McpFakeBlock, type McpFakeBlockSnapshot, type McpFakeCall, type McpFakeCallAnswer, type McpFakeCallMatch, type McpFakeContentBlock, type McpFakeDeclaredEntry, type McpFakeDeepReadonly, type McpFakeErrorCode, type McpFakeFullResult, type McpFakeIcon, type McpFakeIdentity, type McpFakeIssue, type McpFakeOutcome, type McpFakeResourceContents, type McpFakeResult, type McpFakeScope, type McpFakeSource, type McpFakeStructuredResult, type McpFakeTool, type McpFakeUndeclaredPolicy, McpFakesAddError, type McpFakesAddErrorJSON };
|
package/dist/mcp-stub.js
CHANGED
package/dist/mcp-types.d.cts
CHANGED
|
@@ -58,6 +58,12 @@ interface MCPSession {
|
|
|
58
58
|
id: string;
|
|
59
59
|
initialized: boolean;
|
|
60
60
|
createdAt: number;
|
|
61
|
+
/** Test id supplied at `initialize`; per-request values win. */
|
|
62
|
+
testId?: string;
|
|
63
|
+
/** Context supplied at `initialize`; per-request values win. */
|
|
64
|
+
context?: string;
|
|
65
|
+
/** Undeclared-tool policy override supplied at `initialize`; per-request values win. */
|
|
66
|
+
undeclared?: "allow" | "deny";
|
|
61
67
|
}
|
|
62
68
|
//# sourceMappingURL=mcp-types.d.ts.map
|
|
63
69
|
//#endregion
|
package/dist/mcp-types.d.ts
CHANGED
|
@@ -58,6 +58,12 @@ interface MCPSession {
|
|
|
58
58
|
id: string;
|
|
59
59
|
initialized: boolean;
|
|
60
60
|
createdAt: number;
|
|
61
|
+
/** Test id supplied at `initialize`; per-request values win. */
|
|
62
|
+
testId?: string;
|
|
63
|
+
/** Context supplied at `initialize`; per-request values win. */
|
|
64
|
+
context?: string;
|
|
65
|
+
/** Undeclared-tool policy override supplied at `initialize`; per-request values win. */
|
|
66
|
+
undeclared?: "allow" | "deny";
|
|
61
67
|
}
|
|
62
68
|
//# sourceMappingURL=mcp-types.d.ts.map
|
|
63
69
|
//#endregion
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
const require_constants = require('./constants.cjs');
|
|
2
|
+
const require_echo_text = require('./echo-text.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/message-text.ts
|
|
5
|
+
/**
|
|
6
|
+
* The one message builder of MCP fakes: every error and warning message of
|
|
7
|
+
* mcp-fakes.ts, the `FixtureLoadError` message (fixture-loader.ts) and the MCP
|
|
8
|
+
* identity error messages (helpers.ts) are built here. A message is a list of
|
|
9
|
+
* parts: fixed text written by aimock, kept whole, and user parts (a tool
|
|
10
|
+
* name, an id, a source, a JSON path, a value, a thrown message), each quoted
|
|
11
|
+
* or rendered as JSON when it is made. `build` gives the user parts their
|
|
12
|
+
* shares of the message's length budget and cuts each one once, with
|
|
13
|
+
* `cutText` (echo-text.ts), so the fixed text is never cut and every
|
|
14
|
+
* `… (<n> more chars)` count is the true count of chars left out of that
|
|
15
|
+
* part. A leaf module (it imports only constants.ts and echo-text.ts).
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* User data in a message: its whole text, not cut until the message is built.
|
|
19
|
+
* `units` is what a cut of it must not split; `max` is the most chars it may
|
|
20
|
+
* take, whatever the budget: at least 1 (`Infinity` too), so a cut part
|
|
21
|
+
* always keeps at least a `…`. A `max` below 1, or `NaN`, is a `RangeError`.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
var UserText = class {
|
|
26
|
+
constructor(text, units, max) {
|
|
27
|
+
this.text = text;
|
|
28
|
+
this.units = units;
|
|
29
|
+
this.max = max;
|
|
30
|
+
if (!(max >= 1)) throw new RangeError(`a user part max must be at least 1, got ${max}`);
|
|
31
|
+
Object.freeze(this);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A message that is not built yet: fixed text and user parts, in order.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
var Message = class {
|
|
40
|
+
parts;
|
|
41
|
+
constructor(parts) {
|
|
42
|
+
this.parts = Object.freeze([...parts]);
|
|
43
|
+
Object.freeze(this);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A message from a template: its literal text is fixed text, a number is
|
|
48
|
+
* fixed text, a `Message` is spliced in part by part, and a `UserText` is a
|
|
49
|
+
* user part.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
function msg(strings, ...values) {
|
|
54
|
+
const parts = [];
|
|
55
|
+
strings.forEach((literal, i) => {
|
|
56
|
+
if (literal !== "") parts.push(literal);
|
|
57
|
+
if (i >= values.length) return;
|
|
58
|
+
const value = values[i];
|
|
59
|
+
if (typeof value === "number") parts.push(String(value));
|
|
60
|
+
else if (value instanceof Message) parts.push(...value.parts);
|
|
61
|
+
else parts.push(value);
|
|
62
|
+
});
|
|
63
|
+
return new Message(parts);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Aimock's own text as a message: a constant, a fixed list, a built-in tag,
|
|
67
|
+
* or a message that was already built. Never user data.
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
function fixed(text) {
|
|
72
|
+
return new Message([text]);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Every control char (C0, DEL, C1) and the line and paragraph separators.
|
|
76
|
+
* `JSON.stringify` escapes C0 controls but keeps DEL, C1 controls and the
|
|
77
|
+
* separators raw; text that is not JSON (a text `FixtureLoadError` detail) can
|
|
78
|
+
* hold any of them.
|
|
79
|
+
*/
|
|
80
|
+
const RAW_CONTROLS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g;
|
|
81
|
+
/**
|
|
82
|
+
* `text` JSON-quoted: quotes, backslashes and control chars escaped, DEL, C1
|
|
83
|
+
* controls and U+2028 / U+2029 too (as `\uXXXX`), so the result is one line
|
|
84
|
+
* of printable text that `JSON.parse` reads back as `text`.
|
|
85
|
+
*
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
function quoteJson(text) {
|
|
89
|
+
return escapeRawControls(JSON.stringify(text));
|
|
90
|
+
}
|
|
91
|
+
function escapeRawControls(json) {
|
|
92
|
+
return json.replace(RAW_CONTROLS, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A user string as a message part: JSON-quoted by `quoteJson`, cut as JSON
|
|
96
|
+
* text (never inside an escape) to its share, and never to more than `max`
|
|
97
|
+
* chars.
|
|
98
|
+
*
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
function quote(text, max = require_constants.MCP_FAKES_ECHO_LIMIT) {
|
|
102
|
+
return jsonText(JSON.stringify(text), max);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* User data already rendered as JSON text (a value, or a JSON path whose keys
|
|
106
|
+
* are JSON-quoted), as a message part: every raw control char (C0, DEL, C1)
|
|
107
|
+
* and line separator in it escaped as `\uXXXX` (in JSON text they can only be
|
|
108
|
+
* inside its strings), cut as JSON text.
|
|
109
|
+
*
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
function jsonText(json, max = require_constants.MCP_FAKES_ECHO_LIMIT) {
|
|
113
|
+
return new UserText(escapeRawControls(json), "json", max);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Text that is not JSON as a message part, cut as text (never inside a
|
|
117
|
+
* surrogate pair).
|
|
118
|
+
*
|
|
119
|
+
* @internal
|
|
120
|
+
*/
|
|
121
|
+
function plainText(text, max = require_constants.MCP_FAKES_ECHO_LIMIT) {
|
|
122
|
+
return new UserText(text, "text", max);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* `message` built in a budget of its own (`limit` chars), as fixed text of
|
|
126
|
+
* the message it goes into: a part of a message with its own cap.
|
|
127
|
+
*
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
function within(limit, message) {
|
|
131
|
+
return fixed(build(message, limit));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The text of `message`, no longer than `limit` when its fixed text and one
|
|
135
|
+
* char per user part fit in `limit`. The fixed text is kept whole. The user
|
|
136
|
+
* parts share what is left: a part that fits in an equal share keeps all of
|
|
137
|
+
* it, and the others split the rest (water-filling), each at most its `max`.
|
|
138
|
+
* Each user part is then cut once, by `cutText`, to its share. A `limit` of
|
|
139
|
+
* `Infinity` cuts each part only to its `max`. A `NaN` limit is a
|
|
140
|
+
* `RangeError`; a limit too small for the fixed text still gives each
|
|
141
|
+
* non-empty user part 1 char, a bare `…`.
|
|
142
|
+
*
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
function build(message, limit = Infinity) {
|
|
146
|
+
if (Number.isNaN(limit)) throw new RangeError("build limit must be a number, got NaN");
|
|
147
|
+
const users = message.parts.filter((part) => part instanceof UserText);
|
|
148
|
+
let fixedLength = 0;
|
|
149
|
+
for (const part of message.parts) if (typeof part === "string") fixedLength += part.length;
|
|
150
|
+
const shares = allocate(users, limit - fixedLength);
|
|
151
|
+
let i = 0;
|
|
152
|
+
return message.parts.map((part) => typeof part === "string" ? part : require_echo_text.cutText(part.text, shares[i++], part.units)).join("");
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Each user part's share of `room` chars, by water-filling: at least 1 char
|
|
156
|
+
* each (a bare `…` when room runs out), but never more than the part's cap.
|
|
157
|
+
*/
|
|
158
|
+
function allocate(users, room) {
|
|
159
|
+
const caps = users.map((u) => Math.min(u.text.length, Math.floor(u.max)));
|
|
160
|
+
const order = caps.map((_, i) => i).sort((a, b) => caps[a] - caps[b]);
|
|
161
|
+
const shares = new Array(users.length).fill(0);
|
|
162
|
+
let left = Math.max(0, room);
|
|
163
|
+
order.forEach((index, k) => {
|
|
164
|
+
const fair = Math.floor(left / (order.length - k));
|
|
165
|
+
const share = Math.min(caps[index], Math.max(1, fair));
|
|
166
|
+
shares[index] = share;
|
|
167
|
+
left -= share;
|
|
168
|
+
});
|
|
169
|
+
return shares;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The text of `message` with no cut at all: for a message that goes, as
|
|
173
|
+
* text, into a user part of another message (a placeholder reason inside a
|
|
174
|
+
* rendered value), which is then cut once there.
|
|
175
|
+
*
|
|
176
|
+
* @internal
|
|
177
|
+
*/
|
|
178
|
+
function fullText(message) {
|
|
179
|
+
return message.parts.map((part) => typeof part === "string" ? part : part.text).join("");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
exports.build = build;
|
|
184
|
+
exports.fixed = fixed;
|
|
185
|
+
exports.fullText = fullText;
|
|
186
|
+
exports.jsonText = jsonText;
|
|
187
|
+
exports.msg = msg;
|
|
188
|
+
exports.plainText = plainText;
|
|
189
|
+
exports.quote = quote;
|
|
190
|
+
exports.quoteJson = quoteJson;
|
|
191
|
+
exports.within = within;
|
|
192
|
+
//# sourceMappingURL=message-text.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-text.cjs","names":["MCP_FAKES_ECHO_LIMIT","cutText"],"sources":["../src/message-text.ts"],"sourcesContent":["/**\n * The one message builder of MCP fakes: every error and warning message of\n * mcp-fakes.ts, the `FixtureLoadError` message (fixture-loader.ts) and the MCP\n * identity error messages (helpers.ts) are built here. A message is a list of\n * parts: fixed text written by aimock, kept whole, and user parts (a tool\n * name, an id, a source, a JSON path, a value, a thrown message), each quoted\n * or rendered as JSON when it is made. `build` gives the user parts their\n * shares of the message's length budget and cuts each one once, with\n * `cutText` (echo-text.ts), so the fixed text is never cut and every\n * `… (<n> more chars)` count is the true count of chars left out of that\n * part. A leaf module (it imports only constants.ts and echo-text.ts).\n */\nimport { MCP_FAKES_ECHO_LIMIT } from \"./constants.js\";\nimport { cutText, type CutUnits } from \"./echo-text.js\";\n\n/**\n * User data in a message: its whole text, not cut until the message is built.\n * `units` is what a cut of it must not split; `max` is the most chars it may\n * take, whatever the budget: at least 1 (`Infinity` too), so a cut part\n * always keeps at least a `…`. A `max` below 1, or `NaN`, is a `RangeError`.\n *\n * @internal\n */\nexport class UserText {\n constructor(\n readonly text: string,\n readonly units: CutUnits,\n readonly max: number,\n ) {\n if (!(max >= 1)) throw new RangeError(`a user part max must be at least 1, got ${max}`);\n Object.freeze(this);\n }\n}\n\n/**\n * A message that is not built yet: fixed text and user parts, in order.\n *\n * @internal\n */\nexport class Message {\n readonly parts: readonly (string | UserText)[];\n\n constructor(parts: readonly (string | UserText)[]) {\n this.parts = Object.freeze([...parts]);\n Object.freeze(this);\n }\n}\n\n/** What a `msg` template may interpolate: a user part, a message, or a number. */\nexport type MessageValue = UserText | Message | number;\n\n/**\n * A message from a template: its literal text is fixed text, a number is\n * fixed text, a `Message` is spliced in part by part, and a `UserText` is a\n * user part.\n *\n * @internal\n */\nexport function msg(strings: TemplateStringsArray, ...values: readonly MessageValue[]): Message {\n const parts: (string | UserText)[] = [];\n strings.forEach((literal, i) => {\n if (literal !== \"\") parts.push(literal);\n if (i >= values.length) return;\n const value = values[i];\n if (typeof value === \"number\") parts.push(String(value));\n else if (value instanceof Message) parts.push(...value.parts);\n else parts.push(value);\n });\n return new Message(parts);\n}\n\n/**\n * Aimock's own text as a message: a constant, a fixed list, a built-in tag,\n * or a message that was already built. Never user data.\n *\n * @internal\n */\nexport function fixed(text: string): Message {\n return new Message([text]);\n}\n\n/**\n * Every control char (C0, DEL, C1) and the line and paragraph separators.\n * `JSON.stringify` escapes C0 controls but keeps DEL, C1 controls and the\n * separators raw; text that is not JSON (a text `FixtureLoadError` detail) can\n * hold any of them.\n */\n// eslint-disable-next-line no-control-regex -- matching control chars is the point\nconst RAW_CONTROLS = /[\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029]/g;\n\n/**\n * `text` JSON-quoted: quotes, backslashes and control chars escaped, DEL, C1\n * controls and U+2028 / U+2029 too (as `\\uXXXX`), so the result is one line\n * of printable text that `JSON.parse` reads back as `text`.\n *\n * @internal\n */\nexport function quoteJson(text: string): string {\n return escapeRawControls(JSON.stringify(text));\n}\n\nfunction escapeRawControls(json: string): string {\n return json.replace(RAW_CONTROLS, (c) => `\\\\u${c.charCodeAt(0).toString(16).padStart(4, \"0\")}`);\n}\n\n/**\n * A user string as a message part: JSON-quoted by `quoteJson`, cut as JSON\n * text (never inside an escape) to its share, and never to more than `max`\n * chars.\n *\n * @internal\n */\nexport function quote(text: string, max: number = MCP_FAKES_ECHO_LIMIT): UserText {\n return jsonText(JSON.stringify(text), max);\n}\n\n/**\n * User data already rendered as JSON text (a value, or a JSON path whose keys\n * are JSON-quoted), as a message part: every raw control char (C0, DEL, C1)\n * and line separator in it escaped as `\\uXXXX` (in JSON text they can only be\n * inside its strings), cut as JSON text.\n *\n * @internal\n */\nexport function jsonText(json: string, max: number = MCP_FAKES_ECHO_LIMIT): UserText {\n return new UserText(escapeRawControls(json), \"json\", max);\n}\n\n/**\n * Text that is not JSON as a message part, cut as text (never inside a\n * surrogate pair).\n *\n * @internal\n */\nexport function plainText(text: string, max: number = MCP_FAKES_ECHO_LIMIT): UserText {\n return new UserText(text, \"text\", max);\n}\n\n/**\n * `message` built in a budget of its own (`limit` chars), as fixed text of\n * the message it goes into: a part of a message with its own cap.\n *\n * @internal\n */\nexport function within(limit: number, message: Message): Message {\n return fixed(build(message, limit));\n}\n\n/**\n * The text of `message`, no longer than `limit` when its fixed text and one\n * char per user part fit in `limit`. The fixed text is kept whole. The user\n * parts share what is left: a part that fits in an equal share keeps all of\n * it, and the others split the rest (water-filling), each at most its `max`.\n * Each user part is then cut once, by `cutText`, to its share. A `limit` of\n * `Infinity` cuts each part only to its `max`. A `NaN` limit is a\n * `RangeError`; a limit too small for the fixed text still gives each\n * non-empty user part 1 char, a bare `…`.\n *\n * @internal\n */\nexport function build(message: Message, limit = Infinity): string {\n if (Number.isNaN(limit)) throw new RangeError(\"build limit must be a number, got NaN\");\n const users = message.parts.filter((part): part is UserText => part instanceof UserText);\n let fixedLength = 0;\n for (const part of message.parts) if (typeof part === \"string\") fixedLength += part.length;\n const shares = allocate(users, limit - fixedLength);\n let i = 0;\n return message.parts\n .map((part) => (typeof part === \"string\" ? part : cutText(part.text, shares[i++], part.units)))\n .join(\"\");\n}\n\n/**\n * Each user part's share of `room` chars, by water-filling: at least 1 char\n * each (a bare `…` when room runs out), but never more than the part's cap.\n */\nfunction allocate(users: readonly UserText[], room: number): number[] {\n // `max` is at least 1 (UserText checks it), so a non-empty part's cap is too.\n const caps = users.map((u) => Math.min(u.text.length, Math.floor(u.max)));\n const order = caps.map((_, i) => i).sort((a, b) => caps[a] - caps[b]);\n const shares: number[] = new Array<number>(users.length).fill(0);\n let left = Math.max(0, room);\n order.forEach((index, k) => {\n const fair = Math.floor(left / (order.length - k));\n const share = Math.min(caps[index], Math.max(1, fair));\n shares[index] = share;\n left -= share;\n });\n return shares;\n}\n\n/**\n * The text of `message` with no cut at all: for a message that goes, as\n * text, into a user part of another message (a placeholder reason inside a\n * rendered value), which is then cut once there.\n *\n * @internal\n */\nexport function fullText(message: Message): string {\n return message.parts.map((part) => (typeof part === \"string\" ? part : part.text)).join(\"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,WAAb,MAAsB;CACpB,YACE,AAAS,MACT,AAAS,OACT,AAAS,KACT;EAHS;EACA;EACA;AAET,MAAI,EAAE,OAAO,GAAI,OAAM,IAAI,WAAW,2CAA2C,MAAM;AACvF,SAAO,OAAO,KAAK;;;;;;;;AASvB,IAAa,UAAb,MAAqB;CACnB,AAAS;CAET,YAAY,OAAuC;AACjD,OAAK,QAAQ,OAAO,OAAO,CAAC,GAAG,MAAM,CAAC;AACtC,SAAO,OAAO,KAAK;;;;;;;;;;AAcvB,SAAgB,IAAI,SAA+B,GAAG,QAA0C;CAC9F,MAAM,QAA+B,EAAE;AACvC,SAAQ,SAAS,SAAS,MAAM;AAC9B,MAAI,YAAY,GAAI,OAAM,KAAK,QAAQ;AACvC,MAAI,KAAK,OAAO,OAAQ;EACxB,MAAM,QAAQ,OAAO;AACrB,MAAI,OAAO,UAAU,SAAU,OAAM,KAAK,OAAO,MAAM,CAAC;WAC/C,iBAAiB,QAAS,OAAM,KAAK,GAAG,MAAM,MAAM;MACxD,OAAM,KAAK,MAAM;GACtB;AACF,QAAO,IAAI,QAAQ,MAAM;;;;;;;;AAS3B,SAAgB,MAAM,MAAuB;AAC3C,QAAO,IAAI,QAAQ,CAAC,KAAK,CAAC;;;;;;;;AAU5B,MAAM,eAAe;;;;;;;;AASrB,SAAgB,UAAU,MAAsB;AAC9C,QAAO,kBAAkB,KAAK,UAAU,KAAK,CAAC;;AAGhD,SAAS,kBAAkB,MAAsB;AAC/C,QAAO,KAAK,QAAQ,eAAe,MAAM,MAAM,EAAE,WAAW,EAAE,CAAC,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,GAAG;;;;;;;;;AAUjG,SAAgB,MAAM,MAAc,MAAcA,wCAAgC;AAChF,QAAO,SAAS,KAAK,UAAU,KAAK,EAAE,IAAI;;;;;;;;;;AAW5C,SAAgB,SAAS,MAAc,MAAcA,wCAAgC;AACnF,QAAO,IAAI,SAAS,kBAAkB,KAAK,EAAE,QAAQ,IAAI;;;;;;;;AAS3D,SAAgB,UAAU,MAAc,MAAcA,wCAAgC;AACpF,QAAO,IAAI,SAAS,MAAM,QAAQ,IAAI;;;;;;;;AASxC,SAAgB,OAAO,OAAe,SAA2B;AAC/D,QAAO,MAAM,MAAM,SAAS,MAAM,CAAC;;;;;;;;;;;;;;AAerC,SAAgB,MAAM,SAAkB,QAAQ,UAAkB;AAChE,KAAI,OAAO,MAAM,MAAM,CAAE,OAAM,IAAI,WAAW,wCAAwC;CACtF,MAAM,QAAQ,QAAQ,MAAM,QAAQ,SAA2B,gBAAgB,SAAS;CACxF,IAAI,cAAc;AAClB,MAAK,MAAM,QAAQ,QAAQ,MAAO,KAAI,OAAO,SAAS,SAAU,gBAAe,KAAK;CACpF,MAAM,SAAS,SAAS,OAAO,QAAQ,YAAY;CACnD,IAAI,IAAI;AACR,QAAO,QAAQ,MACZ,KAAK,SAAU,OAAO,SAAS,WAAW,OAAOC,0BAAQ,KAAK,MAAM,OAAO,MAAM,KAAK,MAAM,CAAE,CAC9F,KAAK,GAAG;;;;;;AAOb,SAAS,SAAS,OAA4B,MAAwB;CAEpE,MAAM,OAAO,MAAM,KAAK,MAAM,KAAK,IAAI,EAAE,KAAK,QAAQ,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC;CACzE,MAAM,QAAQ,KAAK,KAAK,GAAG,MAAM,EAAE,CAAC,MAAM,GAAG,MAAM,KAAK,KAAK,KAAK,GAAG;CACrE,MAAM,SAAmB,IAAI,MAAc,MAAM,OAAO,CAAC,KAAK,EAAE;CAChE,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK;AAC5B,OAAM,SAAS,OAAO,MAAM;EAC1B,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,SAAS,GAAG;EAClD,MAAM,QAAQ,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,CAAC;AACtD,SAAO,SAAS;AAChB,UAAQ;GACR;AACF,QAAO;;;;;;;;;AAUT,SAAgB,SAAS,SAA0B;AACjD,QAAO,QAAQ,MAAM,KAAK,SAAU,OAAO,SAAS,WAAW,OAAO,KAAK,KAAM,CAAC,KAAK,GAAG"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CutUnits } from "./echo-text.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/message-text.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* User data in a message: its whole text, not cut until the message is built.
|
|
7
|
+
* `units` is what a cut of it must not split; `max` is the most chars it may
|
|
8
|
+
* take, whatever the budget: at least 1 (`Infinity` too), so a cut part
|
|
9
|
+
* always keeps at least a `…`. A `max` below 1, or `NaN`, is a `RangeError`.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
declare class UserText {
|
|
14
|
+
readonly text: string;
|
|
15
|
+
readonly units: CutUnits;
|
|
16
|
+
readonly max: number;
|
|
17
|
+
constructor(text: string, units: CutUnits, max: number);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A message that is not built yet: fixed text and user parts, in order.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
declare class Message {
|
|
25
|
+
readonly parts: readonly (string | UserText)[];
|
|
26
|
+
constructor(parts: readonly (string | UserText)[]);
|
|
27
|
+
}
|
|
28
|
+
/** What a `msg` template may interpolate: a user part, a message, or a number. */
|
|
29
|
+
//#endregion
|
|
30
|
+
export { Message };
|
|
31
|
+
//# sourceMappingURL=message-text.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-text.d.cts","names":[],"sources":["../src/message-text.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;AAgBA;AAAoB,cAhBP,QAAA,CAgBO;WACiB,IAAA,EAAA,MAAA;WAEG,KAAA,EAhBpB,QAgBoB;EAAQ,SAAA,GAAA,EAAA,MAAA;mCAhB5B;;;;;;;cAaP,OAAA;qCACwB;wCAEG"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CutUnits } from "./echo-text.js";
|
|
2
|
+
|
|
3
|
+
//#region src/message-text.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* User data in a message: its whole text, not cut until the message is built.
|
|
7
|
+
* `units` is what a cut of it must not split; `max` is the most chars it may
|
|
8
|
+
* take, whatever the budget: at least 1 (`Infinity` too), so a cut part
|
|
9
|
+
* always keeps at least a `…`. A `max` below 1, or `NaN`, is a `RangeError`.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
declare class UserText {
|
|
14
|
+
readonly text: string;
|
|
15
|
+
readonly units: CutUnits;
|
|
16
|
+
readonly max: number;
|
|
17
|
+
constructor(text: string, units: CutUnits, max: number);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A message that is not built yet: fixed text and user parts, in order.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
declare class Message {
|
|
25
|
+
readonly parts: readonly (string | UserText)[];
|
|
26
|
+
constructor(parts: readonly (string | UserText)[]);
|
|
27
|
+
}
|
|
28
|
+
/** What a `msg` template may interpolate: a user part, a message, or a number. */
|
|
29
|
+
//#endregion
|
|
30
|
+
export { Message };
|
|
31
|
+
//# sourceMappingURL=message-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-text.d.ts","names":[],"sources":["../src/message-text.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;AAgBA;AAAoB,cAhBP,QAAA,CAgBO;WACiB,IAAA,EAAA,MAAA;WAEG,KAAA,EAhBpB,QAgBoB;EAAQ,SAAA,GAAA,EAAA,MAAA;mCAhB5B;;;;;;;cAaP,OAAA;qCACwB;wCAEG"}
|