@copilotkit/aimock 1.42.1 → 1.44.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 +65 -0
- package/README.md +108 -16
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-mock.cjs +1 -1
- package/dist/agui-mock.cjs.map +1 -1
- package/dist/agui-mock.d.cts.map +1 -1
- package/dist/agui-mock.d.ts.map +1 -1
- package/dist/agui-mock.js +1 -1
- package/dist/agui-mock.js.map +1 -1
- package/dist/agui-recorder.cjs +33 -11
- package/dist/agui-recorder.cjs.map +1 -1
- package/dist/agui-recorder.d.cts +10 -1
- package/dist/agui-recorder.d.cts.map +1 -1
- package/dist/agui-recorder.d.ts +10 -1
- package/dist/agui-recorder.d.ts.map +1 -1
- package/dist/agui-recorder.js +33 -11
- package/dist/agui-recorder.js.map +1 -1
- package/dist/agui-types.d.cts +1 -0
- package/dist/agui-types.d.cts.map +1 -1
- package/dist/agui-types.d.ts +1 -0
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/aimock-cli.cjs +38 -10
- package/dist/aimock-cli.cjs.map +1 -1
- package/dist/aimock-cli.d.cts +5 -0
- package/dist/aimock-cli.d.cts.map +1 -1
- package/dist/aimock-cli.d.ts +5 -0
- package/dist/aimock-cli.d.ts.map +1 -1
- package/dist/aimock-cli.js +38 -10
- package/dist/aimock-cli.js.map +1 -1
- package/dist/batches.cjs +458 -0
- package/dist/batches.cjs.map +1 -0
- package/dist/batches.d.cts +57 -0
- package/dist/batches.d.cts.map +1 -0
- package/dist/batches.d.ts +57 -0
- package/dist/batches.d.ts.map +1 -0
- package/dist/batches.js +453 -0
- package/dist/batches.js.map +1 -0
- package/dist/bedrock-converse.cjs +44 -12
- 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 +46 -14
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/bedrock.cjs +10 -8
- 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 +12 -10
- package/dist/bedrock.js.map +1 -1
- package/dist/byteplus-video.cjs +3 -3
- package/dist/byteplus-video.cjs.map +1 -1
- package/dist/byteplus-video.js +4 -4
- package/dist/byteplus-video.js.map +1 -1
- package/dist/chaos.cjs +625 -72
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts +164 -7
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts +164 -7
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +616 -74
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +131 -30
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +132 -31
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +37 -9
- 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 +39 -11
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +1 -1
- package/dist/config-loader.js +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/convert.cjs +1 -1
- package/dist/convert.cjs.map +1 -1
- package/dist/convert.js +1 -1
- package/dist/convert.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/elevenlabs-audio.cjs +3 -3
- package/dist/elevenlabs-audio.cjs.map +1 -1
- package/dist/elevenlabs-audio.js +4 -4
- package/dist/elevenlabs-audio.js.map +1 -1
- package/dist/elevenlabs-voice.cjs +1053 -0
- package/dist/elevenlabs-voice.cjs.map +1 -0
- package/dist/elevenlabs-voice.d.cts +72 -0
- package/dist/elevenlabs-voice.d.cts.map +1 -0
- package/dist/elevenlabs-voice.d.ts +72 -0
- package/dist/elevenlabs-voice.d.ts.map +1 -0
- package/dist/elevenlabs-voice.js +1048 -0
- package/dist/elevenlabs-voice.js.map +1 -0
- package/dist/embeddings.cjs +26 -5
- 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 +27 -6
- package/dist/embeddings.js.map +1 -1
- package/dist/fal-audio.cjs +3 -3
- package/dist/fal-audio.cjs.map +1 -1
- package/dist/fal-audio.js +4 -4
- package/dist/fal-audio.js.map +1 -1
- package/dist/fal.cjs +28 -4
- package/dist/fal.cjs.map +1 -1
- package/dist/fal.d.cts +16 -1
- package/dist/fal.d.cts.map +1 -1
- package/dist/fal.d.ts +16 -1
- package/dist/fal.d.ts.map +1 -1
- package/dist/fal.js +28 -5
- package/dist/fal.js.map +1 -1
- package/dist/files.cjs +1069 -0
- package/dist/files.cjs.map +1 -0
- package/dist/files.d.cts +68 -0
- package/dist/files.d.cts.map +1 -0
- package/dist/files.d.ts +68 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +1056 -0
- package/dist/files.js.map +1 -0
- package/dist/fine-tuning.cjs +1033 -0
- package/dist/fine-tuning.cjs.map +1 -0
- package/dist/fine-tuning.d.cts +96 -0
- package/dist/fine-tuning.d.cts.map +1 -0
- package/dist/fine-tuning.d.ts +96 -0
- package/dist/fine-tuning.d.ts.map +1 -0
- package/dist/fine-tuning.js +1026 -0
- package/dist/fine-tuning.js.map +1 -0
- 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 +0 -0
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +131 -5
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +131 -5
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +0 -0
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +44 -4
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +46 -6
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +51 -7
- 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 +53 -9
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +19 -8
- 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 +21 -10
- package/dist/gemini.js.map +1 -1
- package/dist/grok-video.cjs +14 -14
- package/dist/grok-video.cjs.map +1 -1
- package/dist/grok-video.js +15 -15
- package/dist/grok-video.js.map +1 -1
- package/dist/helpers.cjs +497 -10
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +38 -2
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts +38 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +487 -12
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +57 -125
- package/dist/images.cjs.map +1 -1
- package/dist/images.d.cts.map +1 -1
- package/dist/images.d.ts.map +1 -1
- package/dist/images.js +58 -126
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +71 -9
- package/dist/index.d.cts +17 -7
- package/dist/index.d.ts +17 -7
- package/dist/index.js +21 -11
- package/dist/jest.cjs +24 -12
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +6 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +6 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +25 -13
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +61 -4
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +60 -3
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +60 -3
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +61 -5
- package/dist/journal.js.map +1 -1
- package/dist/live-fixture.cjs +1192 -0
- package/dist/live-fixture.cjs.map +1 -0
- package/dist/live-fixture.js +1187 -0
- package/dist/live-fixture.js.map +1 -0
- package/dist/live-recorder.cjs +256 -0
- package/dist/live-recorder.cjs.map +1 -0
- package/dist/live-recorder.js +254 -0
- package/dist/live-recorder.js.map +1 -0
- package/dist/live-replay.cjs +286 -0
- package/dist/live-replay.cjs.map +1 -0
- package/dist/live-replay.js +285 -0
- package/dist/live-replay.js.map +1 -0
- package/dist/live-sanitize.cjs +196 -0
- package/dist/live-sanitize.cjs.map +1 -0
- package/dist/live-sanitize.js +196 -0
- package/dist/live-sanitize.js.map +1 -0
- package/dist/live-types.d.cts +71 -0
- package/dist/live-types.d.cts.map +1 -0
- package/dist/live-types.d.ts +71 -0
- package/dist/live-types.d.ts.map +1 -0
- package/dist/llmock.cjs +132 -49
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +51 -5
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +51 -5
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +133 -50
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +216 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +210 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- package/dist/mcp-fakes.cjs +1619 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +101 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +101 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1614 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +331 -17
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.js +330 -18
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +217 -49
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +66 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +66 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +218 -50
- package/dist/mcp-mock.js.map +1 -1
- 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 +49 -7
- 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 +51 -9
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +278 -45
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +17 -7
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +17 -7
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +270 -29
- package/dist/metrics.js.map +1 -1
- package/dist/moderation.cjs +10 -1
- package/dist/moderation.cjs.map +1 -1
- package/dist/moderation.d.cts.map +1 -1
- package/dist/moderation.d.ts.map +1 -1
- package/dist/moderation.js +10 -1
- package/dist/moderation.js.map +1 -1
- package/dist/ollama.cjs +10 -6
- 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 +11 -7
- 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 +9 -9
- 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 +10 -10
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +54 -10
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +52 -12
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +47 -7
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +48 -9
- 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 +21 -34
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +4 -1
- package/dist/router.d.cts.map +1 -1
- package/dist/router.d.ts +4 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +21 -35
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +1252 -777
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +5 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1185 -710
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +6 -4
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +7 -5
- package/dist/speech.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +6 -6
- 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 +7 -7
- 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 +320 -17
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +320 -17
- 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 +983 -0
- package/dist/validate-cli.cjs.map +1 -0
- package/dist/validate-cli.js +982 -0
- package/dist/validate-cli.js.map +1 -0
- 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/vector-types.d.ts.map +1 -1
- package/dist/veo-video.cjs +13 -13
- package/dist/veo-video.cjs.map +1 -1
- package/dist/veo-video.js +14 -14
- package/dist/veo-video.js.map +1 -1
- package/dist/video.cjs +23 -4
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +24 -5
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +23 -11
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +5 -2
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +5 -2
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +25 -13
- package/dist/vitest.js.map +1 -1
- package/dist/watcher.cjs +2 -1
- package/dist/watcher.cjs.map +1 -1
- package/dist/watcher.js +2 -1
- package/dist/watcher.js.map +1 -1
- package/dist/ws-framing.cjs +150 -35
- package/dist/ws-framing.cjs.map +1 -1
- package/dist/ws-framing.d.cts +18 -3
- package/dist/ws-framing.d.cts.map +1 -1
- package/dist/ws-framing.d.ts +18 -3
- package/dist/ws-framing.d.ts.map +1 -1
- package/dist/ws-framing.js +151 -36
- package/dist/ws-framing.js.map +1 -1
- package/dist/ws-gemini-live.cjs +2 -2
- package/dist/ws-gemini-live.js +2 -2
- package/dist/ws-live.cjs +401 -0
- package/dist/ws-live.cjs.map +1 -0
- package/dist/ws-live.js +400 -0
- package/dist/ws-live.js.map +1 -0
- package/dist/ws-realtime.cjs +46 -2
- package/dist/ws-realtime.cjs.map +1 -1
- package/dist/ws-realtime.d.cts.map +1 -1
- package/dist/ws-realtime.d.ts.map +1 -1
- package/dist/ws-realtime.js +46 -2
- package/dist/ws-realtime.js.map +1 -1
- package/dist/ws-responses.cjs +16 -4
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +16 -4
- package/dist/ws-responses.js.map +1 -1
- package/dist/ws-upstream.cjs +118 -0
- package/dist/ws-upstream.cjs.map +1 -0
- package/dist/ws-upstream.js +115 -0
- package/dist/ws-upstream.js.map +1 -0
- package/fixtures/openai-live-client.json +390 -0
- package/fixtures/openai-live-managed.json +709 -0
- package/package.json +4 -1
package/dist/mcp-handler.js
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
+
import { build, fixed, jsonText, msg, plainText, quote } from "./message-text.js";
|
|
2
|
+
import { decodeMcpHeaderValue, resolveMcpIdentity } from "./helpers.js";
|
|
1
3
|
import { createJsonRpcDispatcher } from "./jsonrpc.js";
|
|
4
|
+
import { MCP_FAKE_ERROR_CODES } from "./types.js";
|
|
2
5
|
import { randomUUID } from "node:crypto";
|
|
3
6
|
|
|
4
7
|
//#region src/mcp-handler.ts
|
|
8
|
+
/**
|
|
9
|
+
* `data.aimock.code` of a `tools/call` whose fake claim threw: an aimock
|
|
10
|
+
* defect (a validated entry whose result could not be built or sent), not a
|
|
11
|
+
* scenario failure. Answered with the JSON-RPC internal-error code `-32603`;
|
|
12
|
+
* the entry stays unconsumed.
|
|
13
|
+
*/
|
|
14
|
+
const MCP_FAKE_INTERNAL_ERROR = "MCP_FAKE_INTERNAL_ERROR";
|
|
15
|
+
const MCP_FAKE_INTERNAL_ERROR_RPC_CODE = -32603;
|
|
16
|
+
/** An empty record, for the caller to pass in and read even if the handler throws. */
|
|
17
|
+
function newMCPRequestRecord() {
|
|
18
|
+
return {
|
|
19
|
+
identity: null,
|
|
20
|
+
messages: []
|
|
21
|
+
};
|
|
22
|
+
}
|
|
5
23
|
function jsonRpcResult(id, result) {
|
|
6
24
|
return {
|
|
7
25
|
jsonrpc: "2.0",
|
|
@@ -9,17 +27,176 @@ function jsonRpcResult(id, result) {
|
|
|
9
27
|
result
|
|
10
28
|
};
|
|
11
29
|
}
|
|
12
|
-
function jsonRpcError(id, code, message) {
|
|
30
|
+
function jsonRpcError(id, code, message, data) {
|
|
13
31
|
return {
|
|
14
32
|
jsonrpc: "2.0",
|
|
15
33
|
id,
|
|
16
|
-
error: {
|
|
34
|
+
error: data === void 0 ? {
|
|
17
35
|
code,
|
|
18
36
|
message
|
|
37
|
+
} : {
|
|
38
|
+
code,
|
|
39
|
+
message,
|
|
40
|
+
data
|
|
19
41
|
}
|
|
20
42
|
};
|
|
21
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The identity part of a fake message or log line (I9): `testId "<id>"`,
|
|
46
|
+
* `context "<ctx>"`, both, or `no testId or context`.
|
|
47
|
+
*/
|
|
48
|
+
function describeMcpIdentity(identity) {
|
|
49
|
+
const { testId, context } = identity;
|
|
50
|
+
if (testId !== null && context !== null) return msg`testId ${quote(testId)}, context ${quote(context)}`;
|
|
51
|
+
if (testId !== null) return msg`testId ${quote(testId)}`;
|
|
52
|
+
if (context !== null) return msg`context ${quote(context)}`;
|
|
53
|
+
return msg`no testId or context`;
|
|
54
|
+
}
|
|
55
|
+
/** A value as JSON text for a message part. */
|
|
56
|
+
function jsonPart(value) {
|
|
57
|
+
return jsonText(JSON.stringify(value) ?? "null");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The 9.2 error for a failed claim (`mismatch`, `exhausted`, `evicted`), or
|
|
61
|
+
* for an undeclared tool under `deny` (`not_declared`).
|
|
62
|
+
*/
|
|
63
|
+
function fakeFailure(claim, tool, ctx, declaredTools) {
|
|
64
|
+
const who = describeMcpIdentity(ctx.identity);
|
|
65
|
+
const base = {
|
|
66
|
+
tool,
|
|
67
|
+
testId: ctx.identity.testId,
|
|
68
|
+
context: ctx.identity.context,
|
|
69
|
+
mount: ctx.mount
|
|
70
|
+
};
|
|
71
|
+
switch (claim.kind) {
|
|
72
|
+
case "mismatch": {
|
|
73
|
+
const list = claim.declared.map((d) => d.args !== void 0 ? msg`${plainText(d.id)} ${jsonPart(d.args)}` : msg`${plainText(d.id)} anyArgs`).reduce((acc, part, i) => i === 0 ? part : msg`${acc}; ${part}`, msg`none`);
|
|
74
|
+
return {
|
|
75
|
+
code: "MCP_FAKE_MISMATCH",
|
|
76
|
+
outcome: "mismatch",
|
|
77
|
+
message: build(msg`aimock MCP fake mismatch: tool ${quote(tool)} was called with arguments that match no declared fake (${who}). Received ${jsonPart(claim.received)}. Declared: ${list}.`),
|
|
78
|
+
aimock: {
|
|
79
|
+
code: "MCP_FAKE_MISMATCH",
|
|
80
|
+
...base,
|
|
81
|
+
received: claim.received,
|
|
82
|
+
declared: claim.declared,
|
|
83
|
+
firstDifference: claim.firstDifference
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
case "exhausted": {
|
|
88
|
+
const used = claim.matchingDeclared === 1 ? msg`the 1 matching fake is` : msg`all ${claim.matchingDeclared} matching fakes are`;
|
|
89
|
+
return {
|
|
90
|
+
code: "MCP_FAKE_EXHAUSTED",
|
|
91
|
+
outcome: "exhausted",
|
|
92
|
+
message: build(msg`aimock MCP fake exhausted: tool ${quote(tool)} with ${jsonPart(claim.received)} was called again, but ${used} already used (${who}).`),
|
|
93
|
+
aimock: {
|
|
94
|
+
code: "MCP_FAKE_EXHAUSTED",
|
|
95
|
+
...base,
|
|
96
|
+
received: claim.received,
|
|
97
|
+
matchingDeclared: claim.matchingDeclared,
|
|
98
|
+
matchingConsumed: claim.matchingConsumed,
|
|
99
|
+
matchingIds: claim.matchingIds
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
case "evicted": return {
|
|
104
|
+
code: "MCP_FAKE_EVICTED",
|
|
105
|
+
outcome: "evicted",
|
|
106
|
+
evicted: {
|
|
107
|
+
testId: claim.testId,
|
|
108
|
+
cap: claim.maxTestIds
|
|
109
|
+
},
|
|
110
|
+
message: build(msg`aimock MCP fake evicted: consumption state for testId ${quote(claim.testId)} was evicted by the per-mount test-id cap (${claim.maxTestIds}); reset before reusing this test id (${who}).`),
|
|
111
|
+
aimock: {
|
|
112
|
+
code: "MCP_FAKE_EVICTED",
|
|
113
|
+
...base,
|
|
114
|
+
testId: claim.testId,
|
|
115
|
+
cap: claim.maxTestIds
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
case "not_declared": return {
|
|
119
|
+
code: "MCP_FAKE_NOT_DECLARED",
|
|
120
|
+
outcome: "not_declared",
|
|
121
|
+
message: build(msg`Unknown tool: ${plainText(tool)} (aimock MCP fake not declared: this scenario denies undeclared tools; ${who}).`),
|
|
122
|
+
aimock: {
|
|
123
|
+
code: "MCP_FAKE_NOT_DECLARED",
|
|
124
|
+
...base,
|
|
125
|
+
declaredTools: declaredTools()
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
22
130
|
function createMCPRequestHandler(state) {
|
|
131
|
+
/** Per-request fake context, set by the outer handler for the dispatcher's methods. */
|
|
132
|
+
const contexts = /* @__PURE__ */ new WeakMap();
|
|
133
|
+
/**
|
|
134
|
+
* Spec 10 steps 1-2 for `tools/call`: a fake answer or fake failure, or
|
|
135
|
+
* `null` to fall through to today's behavior (step 3).
|
|
136
|
+
*/
|
|
137
|
+
const callFake = (ctx, outcome, tool, args, id) => {
|
|
138
|
+
const event = {
|
|
139
|
+
identity: ctx.identity,
|
|
140
|
+
tool,
|
|
141
|
+
mount: ctx.mount
|
|
142
|
+
};
|
|
143
|
+
let claim;
|
|
144
|
+
try {
|
|
145
|
+
claim = state.fakes.claim(tool, args, ctx.identity);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
const thrown = err instanceof Error ? err.message : String(err);
|
|
148
|
+
const message = build(msg`aimock MCP fake internal error: tool ${quote(tool)} could not be answered (${describeMcpIdentity(ctx.identity)}): ${plainText(thrown)}`);
|
|
149
|
+
outcome.error = message;
|
|
150
|
+
state.onFakeEvent({
|
|
151
|
+
...event,
|
|
152
|
+
kind: "internal_error",
|
|
153
|
+
code: MCP_FAKE_INTERNAL_ERROR,
|
|
154
|
+
message
|
|
155
|
+
});
|
|
156
|
+
return jsonRpcError(id, MCP_FAKE_INTERNAL_ERROR_RPC_CODE, message, { aimock: {
|
|
157
|
+
code: MCP_FAKE_INTERNAL_ERROR,
|
|
158
|
+
tool,
|
|
159
|
+
testId: ctx.identity.testId,
|
|
160
|
+
context: ctx.identity.context,
|
|
161
|
+
mount: ctx.mount,
|
|
162
|
+
error: thrown
|
|
163
|
+
} });
|
|
164
|
+
}
|
|
165
|
+
if (claim.kind === "answer") {
|
|
166
|
+
outcome.mcpFake = {
|
|
167
|
+
id: claim.entry.id,
|
|
168
|
+
outcome: "answered"
|
|
169
|
+
};
|
|
170
|
+
state.onFakeEvent({
|
|
171
|
+
...event,
|
|
172
|
+
kind: "answered",
|
|
173
|
+
entryId: claim.entry.id
|
|
174
|
+
});
|
|
175
|
+
return jsonRpcResult(id, claim.result);
|
|
176
|
+
}
|
|
177
|
+
let failure;
|
|
178
|
+
if (claim.kind === "none") {
|
|
179
|
+
if (state.fakes.policy(ctx.identity) !== "deny") return null;
|
|
180
|
+
failure = fakeFailure({ kind: "not_declared" }, tool, ctx, () => state.fakes.declaredTools(ctx.identity));
|
|
181
|
+
} else failure = fakeFailure(claim, tool, ctx, () => []);
|
|
182
|
+
outcome.mcpFake = {
|
|
183
|
+
id: null,
|
|
184
|
+
outcome: failure.outcome
|
|
185
|
+
};
|
|
186
|
+
state.onFakeEvent(failure.outcome === "evicted" ? {
|
|
187
|
+
...event,
|
|
188
|
+
kind: "evicted",
|
|
189
|
+
code: failure.code,
|
|
190
|
+
message: failure.message,
|
|
191
|
+
...failure.evicted
|
|
192
|
+
} : {
|
|
193
|
+
...event,
|
|
194
|
+
kind: failure.outcome,
|
|
195
|
+
code: failure.code,
|
|
196
|
+
message: failure.message
|
|
197
|
+
});
|
|
198
|
+
return jsonRpcError(id, MCP_FAKE_ERROR_CODES[failure.code], failure.message, { aimock: failure.aimock });
|
|
199
|
+
};
|
|
23
200
|
const dispatcher = createJsonRpcDispatcher({ methods: {
|
|
24
201
|
initialize: async (_params, id) => {
|
|
25
202
|
return jsonRpcResult(id, {
|
|
@@ -41,14 +218,32 @@ function createMCPRequestHandler(state) {
|
|
|
41
218
|
ping: async (_params, id) => {
|
|
42
219
|
return jsonRpcResult(id, {});
|
|
43
220
|
},
|
|
44
|
-
"tools/list": async (_params, id) => {
|
|
221
|
+
"tools/list": async (_params, id, req) => {
|
|
45
222
|
const tools = [];
|
|
46
223
|
for (const { def } of state.tools.values()) tools.push(def);
|
|
224
|
+
const ctx = contexts.get(req);
|
|
225
|
+
if (ctx) {
|
|
226
|
+
const registered = new Set(tools.map((t) => t.name));
|
|
227
|
+
for (const fake of state.fakes.listTools(ctx.identity)) if (!registered.has(fake.name)) tools.push(fake);
|
|
228
|
+
}
|
|
47
229
|
return jsonRpcResult(id, { tools });
|
|
48
230
|
},
|
|
49
|
-
"tools/call": async (params, id) => {
|
|
50
|
-
const
|
|
51
|
-
|
|
231
|
+
"tools/call": async (params, id, req) => {
|
|
232
|
+
const outcome = {
|
|
233
|
+
mcpFake: null,
|
|
234
|
+
error: null
|
|
235
|
+
};
|
|
236
|
+
const ctx = contexts.get(req);
|
|
237
|
+
const isNotification = ctx?.notifications[ctx.calls.length] === true;
|
|
238
|
+
ctx?.calls.push(outcome);
|
|
239
|
+
const { name: rawName, arguments: args } = params ?? {};
|
|
240
|
+
if (!rawName) return jsonRpcError(id, -32602, "Missing tool name");
|
|
241
|
+
if (typeof rawName !== "string") return jsonRpcError(id, -32602, build(msg`Invalid tool name: expected a string, got ${fixed(Array.isArray(rawName) ? "array" : typeof rawName)}`));
|
|
242
|
+
const name = rawName;
|
|
243
|
+
if (ctx && !isNotification) {
|
|
244
|
+
const fake = callFake(ctx, outcome, name, args, id);
|
|
245
|
+
if (fake) return fake;
|
|
246
|
+
}
|
|
52
247
|
const entry = state.tools.get(name);
|
|
53
248
|
if (!entry) return jsonRpcError(id, -32602, `Unknown tool: ${name}`);
|
|
54
249
|
if (entry.handler) try {
|
|
@@ -109,40 +304,117 @@ function createMCPRequestHandler(state) {
|
|
|
109
304
|
return jsonRpcResult(id, { messages: [] });
|
|
110
305
|
}
|
|
111
306
|
} });
|
|
112
|
-
|
|
307
|
+
/** Answer a rejected identity (H4, H5) with HTTP 400, in the session-error shape. */
|
|
308
|
+
const rejectIdentity = (res, resolution) => {
|
|
309
|
+
res.writeHead(resolution.error.status, { "Content-Type": "application/json" });
|
|
310
|
+
res.end(JSON.stringify({
|
|
311
|
+
error: resolution.error.message,
|
|
312
|
+
code: resolution.error.code
|
|
313
|
+
}));
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* L2: report each identity value that fell back to the raw value (H1),
|
|
317
|
+
* naming its source (header or query) and the value itself, and any query
|
|
318
|
+
* name that did not decode. `fellBack` says only that some value of the
|
|
319
|
+
* field fell back; the header is checked on its own, and the query by the
|
|
320
|
+
* same resolver given the query alone, so the rules are the resolver's.
|
|
321
|
+
*/
|
|
322
|
+
const reportFallback = (sessionId, req, resolution) => {
|
|
323
|
+
if (!state.hasFakes()) return;
|
|
324
|
+
const { fellBack } = resolution;
|
|
325
|
+
if (fellBack.testId || fellBack.context) {
|
|
326
|
+
const queryOnly = resolveMcpIdentity({
|
|
327
|
+
headers: {},
|
|
328
|
+
url: req.url
|
|
329
|
+
});
|
|
330
|
+
for (const field of ["testId", "context"]) {
|
|
331
|
+
if (!fellBack[field]) continue;
|
|
332
|
+
const header = req.headers[IDENTITY_HEADERS[field]];
|
|
333
|
+
const headerSent = typeof header === "string" && header !== "";
|
|
334
|
+
if (headerSent && decodeMcpHeaderValue(header).fellBack) state.onDecodeFallback(sessionId, {
|
|
335
|
+
kind: "value",
|
|
336
|
+
field,
|
|
337
|
+
source: "header",
|
|
338
|
+
raw: header,
|
|
339
|
+
used: true
|
|
340
|
+
});
|
|
341
|
+
const fromQuery = queryOnly.ok && queryOnly.fellBack[field] ? queryOnly.identity[field] : null;
|
|
342
|
+
if (fromQuery !== null) state.onDecodeFallback(sessionId, {
|
|
343
|
+
kind: "value",
|
|
344
|
+
field,
|
|
345
|
+
source: "query",
|
|
346
|
+
raw: fromQuery,
|
|
347
|
+
used: !headerSent
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (resolution.undecodedQueryNames) state.onDecodeFallback(sessionId, {
|
|
352
|
+
kind: "names",
|
|
353
|
+
count: resolution.undecodedQueryNames
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Handle one MCP request. `mount` is the path the mount is served at (I9;
|
|
358
|
+
* `/` for a standalone server). Resolves with what the request resolved,
|
|
359
|
+
* for the mount's journal entry.
|
|
360
|
+
*/
|
|
361
|
+
return async (req, res, body, mount = "/", record = newMCPRequestRecord()) => {
|
|
113
362
|
if (req.method === "DELETE") {
|
|
114
363
|
const sessionId = req.headers["mcp-session-id"];
|
|
115
364
|
if (!sessionId) {
|
|
116
365
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
117
366
|
res.end(JSON.stringify({ error: "Missing mcp-session-id header" }));
|
|
118
|
-
return;
|
|
367
|
+
return record;
|
|
119
368
|
}
|
|
120
369
|
if (!state.sessions.has(sessionId)) {
|
|
121
370
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
122
371
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
123
|
-
return;
|
|
372
|
+
return record;
|
|
124
373
|
}
|
|
374
|
+
const resolution = resolveMcpIdentity(req, state.sessions.get(sessionId));
|
|
375
|
+
if (resolution.ok) record.identity = resolution.identity;
|
|
125
376
|
state.sessions.delete(sessionId);
|
|
377
|
+
state.onSessionClosed(sessionId);
|
|
126
378
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
127
379
|
res.end(JSON.stringify({ ok: true }));
|
|
128
|
-
return;
|
|
380
|
+
return record;
|
|
129
381
|
}
|
|
382
|
+
const headerSessionId = req.headers["mcp-session-id"];
|
|
383
|
+
const knownSession = headerSessionId ? state.sessions.get(headerSessionId) : void 0;
|
|
130
384
|
let parsed;
|
|
131
385
|
try {
|
|
132
386
|
parsed = JSON.parse(body);
|
|
133
387
|
} catch {
|
|
388
|
+
const resolution = resolveMcpIdentity(req, knownSession);
|
|
389
|
+
if (!resolution.ok) {
|
|
390
|
+
rejectIdentity(res, resolution);
|
|
391
|
+
return record;
|
|
392
|
+
}
|
|
393
|
+
record.identity = resolution.identity;
|
|
394
|
+
if (headerSessionId && knownSession) reportFallback(headerSessionId, req, resolution);
|
|
134
395
|
await dispatcher(req, res, body);
|
|
135
|
-
return;
|
|
396
|
+
return record;
|
|
136
397
|
}
|
|
137
398
|
const method = typeof parsed === "object" && parsed !== null && "method" in parsed ? parsed.method : void 0;
|
|
138
399
|
if (method === "initialize") {
|
|
139
400
|
const id = typeof parsed === "object" && parsed !== null && "id" in parsed ? parsed.id : null;
|
|
401
|
+
const resolution = resolveMcpIdentity(req);
|
|
402
|
+
if (!resolution.ok) {
|
|
403
|
+
rejectIdentity(res, resolution);
|
|
404
|
+
return record;
|
|
405
|
+
}
|
|
406
|
+
record.identity = resolution.identity;
|
|
407
|
+
const { identity, supplied } = resolution;
|
|
140
408
|
const sessionId = randomUUID();
|
|
141
409
|
state.sessions.set(sessionId, {
|
|
142
410
|
id: sessionId,
|
|
143
411
|
initialized: false,
|
|
144
|
-
createdAt: Date.now()
|
|
412
|
+
createdAt: Date.now(),
|
|
413
|
+
...supplied.testId && identity.testId !== null ? { testId: identity.testId } : {},
|
|
414
|
+
...supplied.context && identity.context !== null ? { context: identity.context } : {},
|
|
415
|
+
...supplied.undeclared && identity.undeclared !== null ? { undeclared: identity.undeclared } : {}
|
|
145
416
|
});
|
|
417
|
+
reportFallback(sessionId, req, resolution);
|
|
146
418
|
const response = {
|
|
147
419
|
jsonrpc: "2.0",
|
|
148
420
|
id,
|
|
@@ -161,28 +433,68 @@ function createMCPRequestHandler(state) {
|
|
|
161
433
|
"Mcp-Session-Id": sessionId
|
|
162
434
|
});
|
|
163
435
|
res.end(JSON.stringify(response));
|
|
164
|
-
return;
|
|
436
|
+
return record;
|
|
437
|
+
}
|
|
438
|
+
const resolution = resolveMcpIdentity(req, knownSession);
|
|
439
|
+
if (!resolution.ok) {
|
|
440
|
+
rejectIdentity(res, resolution);
|
|
441
|
+
return record;
|
|
165
442
|
}
|
|
166
|
-
|
|
443
|
+
record.identity = resolution.identity;
|
|
444
|
+
const sessionId = headerSessionId;
|
|
167
445
|
if (!sessionId) {
|
|
168
446
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
169
447
|
res.end(JSON.stringify({ error: "Missing mcp-session-id header" }));
|
|
170
|
-
return;
|
|
448
|
+
return record;
|
|
171
449
|
}
|
|
172
450
|
if (!state.sessions.has(sessionId)) {
|
|
173
451
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
174
452
|
res.end(JSON.stringify({ error: "Session not found" }));
|
|
175
|
-
return;
|
|
453
|
+
return record;
|
|
176
454
|
}
|
|
455
|
+
reportFallback(sessionId, req, resolution);
|
|
177
456
|
if (!state.sessions.get(sessionId).initialized && method !== "notifications/initialized") {
|
|
178
457
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
179
458
|
res.end(JSON.stringify(jsonRpcError(typeof parsed === "object" && parsed !== null && "id" in parsed ? parsed.id : null, -32002, "Session not initialized")));
|
|
180
|
-
return;
|
|
459
|
+
return record;
|
|
181
460
|
}
|
|
461
|
+
const notifications = (Array.isArray(parsed) ? parsed : [parsed]).filter((m) => isToolsCall(m) && m.jsonrpc === "2.0").map((m) => !("id" in m));
|
|
462
|
+
const ctx = {
|
|
463
|
+
identity: resolution.identity,
|
|
464
|
+
mount,
|
|
465
|
+
notifications,
|
|
466
|
+
calls: []
|
|
467
|
+
};
|
|
468
|
+
contexts.set(req, ctx);
|
|
182
469
|
await dispatcher(req, res, body);
|
|
470
|
+
let next = 0;
|
|
471
|
+
const messageOf = (message) => {
|
|
472
|
+
if (!isToolsCall(message)) return {
|
|
473
|
+
body: null,
|
|
474
|
+
mcpFake: null,
|
|
475
|
+
error: null
|
|
476
|
+
};
|
|
477
|
+
const outcome = message.jsonrpc === "2.0" ? ctx.calls[next++] : void 0;
|
|
478
|
+
return {
|
|
479
|
+
body: message,
|
|
480
|
+
mcpFake: outcome?.mcpFake ?? null,
|
|
481
|
+
error: outcome?.error ?? null
|
|
482
|
+
};
|
|
483
|
+
};
|
|
484
|
+
record.messages = Array.isArray(parsed) ? parsed.map(messageOf) : [messageOf(parsed)];
|
|
485
|
+
return record;
|
|
183
486
|
};
|
|
184
487
|
}
|
|
488
|
+
/** The identity header of each field whose value can fall back (H1). */
|
|
489
|
+
const IDENTITY_HEADERS = {
|
|
490
|
+
testId: "x-test-id",
|
|
491
|
+
context: "x-aimock-context"
|
|
492
|
+
};
|
|
493
|
+
/** A JSON-RPC message object whose method is `tools/call`. */
|
|
494
|
+
function isToolsCall(message) {
|
|
495
|
+
return typeof message === "object" && message !== null && !Array.isArray(message) && "method" in message && message.method === "tools/call";
|
|
496
|
+
}
|
|
185
497
|
|
|
186
498
|
//#endregion
|
|
187
|
-
export { createMCPRequestHandler };
|
|
499
|
+
export { createMCPRequestHandler, describeMcpIdentity, newMCPRequestRecord };
|
|
188
500
|
//# sourceMappingURL=mcp-handler.js.map
|
package/dist/mcp-handler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-handler.js","names":[],"sources":["../src/mcp-handler.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport type {\n MCPToolDefinition,\n MCPResourceDefinition,\n MCPResourceContent,\n MCPPromptDefinition,\n MCPPromptResult,\n MCPContent,\n MCPSession,\n} from \"./mcp-types.js\";\n\nexport interface MCPState {\n serverInfo: { name: string; version: string };\n tools: Map<string, { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }>;\n resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }>;\n prompts: Map<\n string,\n {\n def: MCPPromptDefinition;\n handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n }\n >;\n sessions: Map<string, MCPSession>;\n}\n\nfunction jsonRpcResult(id: string | number, result: unknown) {\n return { jsonrpc: \"2.0\" as const, id, result };\n}\n\nfunction jsonRpcError(id: string | number | null, code: number, message: string) {\n return { jsonrpc: \"2.0\" as const, id, error: { code, message } };\n}\n\nexport function createMCPRequestHandler(state: MCPState) {\n const dispatcher = createJsonRpcDispatcher({\n methods: {\n // initialize is handled directly in the outer function — this entry is\n // only here so the dispatcher doesn't return \"Method not found\" if the\n // request somehow reaches it.\n initialize: async (_params, id) => {\n return jsonRpcResult(id, {\n protocolVersion: \"2025-03-26\",\n capabilities: { tools: {}, resources: {}, prompts: {} },\n serverInfo: state.serverInfo,\n });\n },\n\n \"notifications/initialized\": async (_params, _id, req) => {\n const sessionId = req.headers[\"mcp-session-id\"] as string;\n const session = state.sessions.get(sessionId);\n if (session) {\n session.initialized = true;\n }\n return null;\n },\n\n ping: async (_params, id) => {\n return jsonRpcResult(id, {});\n },\n\n \"tools/list\": async (_params, id) => {\n const tools: MCPToolDefinition[] = [];\n for (const { def } of state.tools.values()) {\n tools.push(def);\n }\n return jsonRpcResult(id, { tools });\n },\n\n \"tools/call\": async (params, id) => {\n const { name, arguments: args } = (params ?? {}) as { name?: string; arguments?: unknown };\n if (!name) {\n return jsonRpcError(id, -32602, \"Missing tool name\");\n }\n const entry = state.tools.get(name);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown tool: ${name}`);\n }\n if (entry.handler) {\n try {\n const result = await entry.handler(args);\n const content: MCPContent[] = Array.isArray(result)\n ? result\n : [{ type: \"text\", text: String(result) }];\n return jsonRpcResult(id, { content, isError: false });\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return jsonRpcResult(id, {\n content: [{ type: \"text\", text: message }],\n isError: true,\n });\n }\n }\n // No handler — return empty content\n return jsonRpcResult(id, { content: [], isError: false });\n },\n\n \"resources/list\": async (_params, id) => {\n const resources: MCPResourceDefinition[] = [];\n for (const { def } of state.resources.values()) {\n resources.push(def);\n }\n return jsonRpcResult(id, { resources });\n },\n\n \"resources/read\": async (params, id) => {\n const { uri } = (params ?? {}) as { uri?: string };\n if (!uri) {\n return jsonRpcError(id, -32602, \"Missing resource URI\");\n }\n const entry = state.resources.get(uri);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown resource: ${uri}`);\n }\n return jsonRpcResult(id, {\n contents: [\n {\n uri,\n ...(entry.content?.text !== undefined && { text: entry.content.text }),\n ...(entry.content?.blob !== undefined && { blob: entry.content.blob }),\n ...(entry.content?.mimeType !== undefined && { mimeType: entry.content.mimeType }),\n },\n ],\n });\n },\n\n \"prompts/list\": async (_params, id) => {\n const prompts: MCPPromptDefinition[] = [];\n for (const { def } of state.prompts.values()) {\n prompts.push(def);\n }\n return jsonRpcResult(id, { prompts });\n },\n\n \"prompts/get\": async (params, id) => {\n const { name, arguments: args } = (params ?? {}) as { name?: string; arguments?: unknown };\n if (!name) {\n return jsonRpcError(id, -32602, \"Missing prompt name\");\n }\n const entry = state.prompts.get(name);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown prompt: ${name}`);\n }\n if (entry.handler) {\n try {\n const result = await entry.handler(args);\n return jsonRpcResult(id, result);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return jsonRpcError(id, -32603, `Prompt handler error: ${message}`);\n }\n }\n // No handler — return empty messages\n return jsonRpcResult(id, { messages: [] });\n },\n },\n });\n\n return async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n ): Promise<void> => {\n // DELETE handler: session teardown\n if (req.method === \"DELETE\") {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (!sessionId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n return;\n }\n if (!state.sessions.has(sessionId)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Session not found\" }));\n return;\n }\n state.sessions.delete(sessionId);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n return;\n }\n\n // Parse the body to determine method for session validation\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n // Let the dispatcher handle parse errors\n await dispatcher(req, res, body);\n return;\n }\n\n const method =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? (parsed as { method: unknown }).method\n : undefined;\n\n // Handle initialize directly to control response headers\n if (method === \"initialize\") {\n const id =\n typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n ? (parsed as { id: unknown }).id\n : null;\n\n const sessionId = randomUUID();\n state.sessions.set(sessionId, {\n id: sessionId,\n initialized: false,\n createdAt: Date.now(),\n });\n\n const response = {\n jsonrpc: \"2.0\",\n id,\n result: {\n protocolVersion: \"2025-03-26\",\n capabilities: { tools: {}, resources: {}, prompts: {} },\n serverInfo: state.serverInfo,\n },\n };\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Mcp-Session-Id\": sessionId,\n });\n res.end(JSON.stringify(response));\n return;\n }\n\n // Session validation for all other methods\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (!sessionId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n return;\n }\n if (!state.sessions.has(sessionId)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Session not found\" }));\n return;\n }\n\n // Enforce initialization: only allow notifications/initialized through\n // before the session is fully initialized\n const session = state.sessions.get(sessionId)!;\n if (!session.initialized && method !== \"notifications/initialized\") {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify(\n jsonRpcError(\n typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n ? ((parsed as { id: unknown }).id as string | number)\n : null,\n -32002,\n \"Session not initialized\",\n ),\n ),\n );\n return;\n }\n\n // Delegate to the JSON-RPC dispatcher for all other methods\n await dispatcher(req, res, body);\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-handler.js","names":[],"sources":["../src/mcp-handler.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport { randomUUID } from \"node:crypto\";\nimport { createJsonRpcDispatcher } from \"./jsonrpc.js\";\nimport type {\n MCPToolDefinition,\n MCPResourceDefinition,\n MCPResourceContent,\n MCPPromptDefinition,\n MCPPromptResult,\n MCPContent,\n MCPSession,\n} from \"./mcp-types.js\";\nimport type { McpFakeClaim, McpFakeStore } from \"./mcp-fakes.js\";\nimport {\n MCP_FAKE_ERROR_CODES,\n type McpFakeErrorCode,\n type McpFakeIdentity,\n type McpFakeOutcome,\n} from \"./types.js\";\nimport { decodeMcpHeaderValue, resolveMcpIdentity, type McpIdentityResolution } from \"./helpers.js\";\nimport { Message, build, fixed, jsonText, msg, plainText, quote } from \"./message-text.js\";\n\n/**\n * `data.aimock.code` of a `tools/call` whose fake claim threw: an aimock\n * defect (a validated entry whose result could not be built or sent), not a\n * scenario failure. Answered with the JSON-RPC internal-error code `-32603`;\n * the entry stays unconsumed.\n */\nexport const MCP_FAKE_INTERNAL_ERROR = \"MCP_FAKE_INTERNAL_ERROR\";\nconst MCP_FAKE_INTERNAL_ERROR_RPC_CODE = -32603;\n\n/** What every fake event carries: who called which tool on which mount (I9). */\ninterface McpFakeEventBase {\n identity: McpFakeIdentity;\n tool: string;\n /** The mount path the request came in on (I9). */\n mount: string;\n}\n\n/**\n * One MCP fake outcome of a `tools/call`, for the mount's side channels\n * (metric and log line; spec 9.3). `answered` names the entry; every other\n * kind is a failure with its `data.aimock.code` and the JSON-RPC error\n * message sent to the client. `evicted` also carries the evicted test id and\n * the cap; `internal_error` is a claim that threw (`MCP_FAKE_INTERNAL_ERROR`).\n */\nexport type McpFakeEvent = McpFakeEventBase &\n (\n | { kind: \"answered\"; entryId: string }\n | {\n kind: Exclude<McpFakeOutcome, \"answered\" | \"evicted\">;\n code: McpFakeErrorCode;\n message: string;\n }\n | { kind: \"evicted\"; code: \"MCP_FAKE_EVICTED\"; message: string; testId: string; cap: number }\n | { kind: \"internal_error\"; code: typeof MCP_FAKE_INTERNAL_ERROR; message: string }\n );\n\n/** An identity field whose value can fall back to the raw value (H1). */\nexport type McpDecodeField = \"testId\" | \"context\";\n\n/**\n * A decode fallback of one request, for L2: a test id or context value that\n * is not valid percent-encoding (on the header or the query; `used` is false\n * for a query value the header overrode), or query names that are not valid\n * percent-encoding and so match no identity field.\n */\nexport type McpDecodeFallback =\n | { kind: \"value\"; field: McpDecodeField; source: \"header\" | \"query\"; raw: string; used: boolean }\n | { kind: \"names\"; count: number };\n\n/** One JSON-RPC message of a request, for one journal entry (B2). */\nexport interface MCPMessageRecord {\n /** The JSON-RPC request object of a `tools/call`, else `null`. */\n body: Record<string, unknown> | null;\n /** Set when a fake answered or failed this `tools/call`. */\n mcpFake: { id: string | null; outcome: McpFakeOutcome } | null;\n /** What the mount's own code threw while serving this message, else `null`. */\n error: string | null;\n}\n\n/**\n * What one request handled by `createMCPRequestHandler` resolved, for the\n * mount's journal (B2, B3). `identity` is `null` when the request had none to\n * resolve (a rejected identity; a DELETE with a missing or unknown session).\n * `messages` has one record per JSON-RPC message: one for a single message,\n * one per element of a batch, so each `tools/call` of a batch is journaled\n * with its own body and fake outcome. The mount writes one journal entry per\n * record, and one with no body when there is none (a request answered before\n * any message was read, or an empty batch).\n */\nexport interface MCPRequestRecord {\n identity: McpFakeIdentity | null;\n messages: MCPMessageRecord[];\n}\n\n/** An empty record, for the caller to pass in and read even if the handler throws. */\nexport function newMCPRequestRecord(): MCPRequestRecord {\n return { identity: null, messages: [] };\n}\n\nexport interface MCPState {\n serverInfo: { name: string; version: string };\n tools: Map<string, { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }>;\n resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }>;\n prompts: Map<\n string,\n {\n def: MCPPromptDefinition;\n handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n }\n >;\n sessions: Map<string, MCPSession>;\n /** The mount's fakes (one store per mount). */\n fakes: McpFakeStore;\n /** Whether any fake block is loaded on the mount (gates L2). */\n hasFakes(): boolean;\n /** A fake answered or failed a `tools/call` (metric, L1/L10 log). */\n onFakeEvent(evt: McpFakeEvent): void;\n /**\n * A request of `sessionId` sent an identity value (or query name) that is\n * not valid percent-encoding (H1). Called only when `hasFakes()`; the mount\n * logs L2 once per session per field and source.\n */\n onDecodeFallback(sessionId: string, fallback: McpDecodeFallback): void;\n /** `sessionId` was deleted (DELETE): the mount drops its per-session state. */\n onSessionClosed(sessionId: string): void;\n}\n\nfunction jsonRpcResult(id: string | number, result: unknown) {\n return { jsonrpc: \"2.0\" as const, id, result };\n}\n\nfunction jsonRpcError(id: string | number | null, code: number, message: string, data?: unknown) {\n return {\n jsonrpc: \"2.0\" as const,\n id,\n error: data === undefined ? { code, message } : { code, message, data },\n };\n}\n\n/**\n * The identity part of a fake message or log line (I9): `testId \"<id>\"`,\n * `context \"<ctx>\"`, both, or `no testId or context`.\n */\nexport function describeMcpIdentity(identity: McpFakeIdentity): Message {\n const { testId, context } = identity;\n if (testId !== null && context !== null) {\n return msg`testId ${quote(testId)}, context ${quote(context)}`;\n }\n if (testId !== null) return msg`testId ${quote(testId)}`;\n if (context !== null) return msg`context ${quote(context)}`;\n return msg`no testId or context`;\n}\n\n/** A value as JSON text for a message part. */\nfunction jsonPart(value: unknown): ReturnType<typeof jsonText> {\n return jsonText(JSON.stringify(value) ?? \"null\");\n}\n\n/** The outcome of one `tools/call` invocation, for its message record. */\ntype CallOutcome = Omit<MCPMessageRecord, \"body\">;\n\n/**\n * What `tools/call` reads of one request (its resolved identity and mount,\n * and which invocations are notifications) and writes back: one outcome per\n * `tools/call` invocation, in order, so a batch's calls each keep their own.\n */\ninterface FakeRequestContext {\n identity: McpFakeIdentity;\n mount: string;\n /**\n * Per `tools/call` invocation, in order: true when its message has no `id`\n * member. The dispatcher passes a null id both for a notification and for\n * a request whose id is null or not a string or number, so the handler\n * cannot tell them apart from its id.\n */\n notifications: boolean[];\n calls: CallOutcome[];\n}\n\n/**\n * A fake failure: the JSON-RPC error to send and the event to report; an\n * `evicted` one also carries the evicted test id and the cap.\n */\ntype FakeFailure = { message: string; aimock: Record<string, unknown> } & (\n | { outcome: Exclude<McpFakeOutcome, \"answered\" | \"evicted\">; code: McpFakeErrorCode }\n | { outcome: \"evicted\"; code: \"MCP_FAKE_EVICTED\"; evicted: { testId: string; cap: number } }\n);\n\n/**\n * The 9.2 error for a failed claim (`mismatch`, `exhausted`, `evicted`), or\n * for an undeclared tool under `deny` (`not_declared`).\n */\nfunction fakeFailure(\n claim: Exclude<McpFakeClaim, { kind: \"answer\" } | { kind: \"none\" }> | { kind: \"not_declared\" },\n tool: string,\n ctx: FakeRequestContext,\n declaredTools: () => string[],\n): FakeFailure {\n const who = describeMcpIdentity(ctx.identity);\n const base = {\n tool,\n testId: ctx.identity.testId,\n context: ctx.identity.context,\n mount: ctx.mount,\n };\n switch (claim.kind) {\n case \"mismatch\": {\n const declared = claim.declared.map((d) =>\n d.args !== undefined\n ? msg`${plainText(d.id)} ${jsonPart(d.args)}`\n : msg`${plainText(d.id)} anyArgs`,\n );\n const list = declared.reduce<Message>(\n (acc, part, i) => (i === 0 ? part : msg`${acc}; ${part}`),\n msg`none`,\n );\n return {\n code: \"MCP_FAKE_MISMATCH\",\n outcome: \"mismatch\",\n message: build(\n msg`aimock MCP fake mismatch: tool ${quote(tool)} was called with arguments that match no declared fake (${who}). Received ${jsonPart(claim.received)}. Declared: ${list}.`,\n ),\n aimock: {\n code: \"MCP_FAKE_MISMATCH\",\n ...base,\n received: claim.received,\n declared: claim.declared,\n firstDifference: claim.firstDifference,\n },\n };\n }\n case \"exhausted\": {\n const used =\n claim.matchingDeclared === 1\n ? msg`the 1 matching fake is`\n : msg`all ${claim.matchingDeclared} matching fakes are`;\n return {\n code: \"MCP_FAKE_EXHAUSTED\",\n outcome: \"exhausted\",\n message: build(\n msg`aimock MCP fake exhausted: tool ${quote(tool)} with ${jsonPart(claim.received)} was called again, but ${used} already used (${who}).`,\n ),\n aimock: {\n code: \"MCP_FAKE_EXHAUSTED\",\n ...base,\n received: claim.received,\n matchingDeclared: claim.matchingDeclared,\n matchingConsumed: claim.matchingConsumed,\n matchingIds: claim.matchingIds,\n },\n };\n }\n case \"evicted\":\n // Only a test id is ever evicted (never the untagged state), so the\n // claim's test id is the request's.\n return {\n code: \"MCP_FAKE_EVICTED\",\n outcome: \"evicted\",\n evicted: { testId: claim.testId, cap: claim.maxTestIds },\n message: build(\n msg`aimock MCP fake evicted: consumption state for testId ${quote(claim.testId)} was evicted by the per-mount test-id cap (${claim.maxTestIds}); reset before reusing this test id (${who}).`,\n ),\n aimock: { code: \"MCP_FAKE_EVICTED\", ...base, testId: claim.testId, cap: claim.maxTestIds },\n };\n case \"not_declared\":\n return {\n code: \"MCP_FAKE_NOT_DECLARED\",\n outcome: \"not_declared\",\n message: build(\n msg`Unknown tool: ${plainText(tool)} (aimock MCP fake not declared: this scenario denies undeclared tools; ${who}).`,\n ),\n aimock: { code: \"MCP_FAKE_NOT_DECLARED\", ...base, declaredTools: declaredTools() },\n };\n }\n}\n\nexport function createMCPRequestHandler(state: MCPState) {\n /** Per-request fake context, set by the outer handler for the dispatcher's methods. */\n const contexts = new WeakMap<http.IncomingMessage, FakeRequestContext>();\n\n /**\n * Spec 10 steps 1-2 for `tools/call`: a fake answer or fake failure, or\n * `null` to fall through to today's behavior (step 3).\n */\n const callFake = (\n ctx: FakeRequestContext,\n outcome: CallOutcome,\n tool: string,\n args: unknown,\n id: string | number,\n ): ReturnType<typeof jsonRpcResult> | ReturnType<typeof jsonRpcError> | null => {\n const event = { identity: ctx.identity, tool, mount: ctx.mount };\n let claim: McpFakeClaim;\n try {\n claim = state.fakes.claim(tool, args, ctx.identity);\n } catch (err) {\n // A validated entry whose result cannot be built or sent: an aimock\n // defect. Fail loud on every side channel, never a generic -32603.\n const thrown = err instanceof Error ? err.message : String(err);\n const message = build(\n msg`aimock MCP fake internal error: tool ${quote(tool)} could not be answered (${describeMcpIdentity(ctx.identity)}): ${plainText(thrown)}`,\n );\n outcome.error = message;\n state.onFakeEvent({\n ...event,\n kind: \"internal_error\",\n code: MCP_FAKE_INTERNAL_ERROR,\n message,\n });\n return jsonRpcError(id, MCP_FAKE_INTERNAL_ERROR_RPC_CODE, message, {\n aimock: {\n code: MCP_FAKE_INTERNAL_ERROR,\n tool,\n testId: ctx.identity.testId,\n context: ctx.identity.context,\n mount: ctx.mount,\n error: thrown,\n },\n });\n }\n if (claim.kind === \"answer\") {\n outcome.mcpFake = { id: claim.entry.id, outcome: \"answered\" };\n state.onFakeEvent({ ...event, kind: \"answered\", entryId: claim.entry.id });\n return jsonRpcResult(id, claim.result);\n }\n let failure: FakeFailure;\n if (claim.kind === \"none\") {\n if (state.fakes.policy(ctx.identity) !== \"deny\") return null;\n failure = fakeFailure({ kind: \"not_declared\" }, tool, ctx, () =>\n state.fakes.declaredTools(ctx.identity),\n );\n } else {\n failure = fakeFailure(claim, tool, ctx, () => []);\n }\n outcome.mcpFake = { id: null, outcome: failure.outcome };\n state.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 return jsonRpcError(id, MCP_FAKE_ERROR_CODES[failure.code], failure.message, {\n aimock: failure.aimock,\n });\n };\n\n const dispatcher = createJsonRpcDispatcher({\n methods: {\n // initialize is handled directly in the outer function — this entry is\n // only here so the dispatcher doesn't return \"Method not found\" if the\n // request somehow reaches it.\n initialize: async (_params, id) => {\n return jsonRpcResult(id, {\n protocolVersion: \"2025-03-26\",\n capabilities: { tools: {}, resources: {}, prompts: {} },\n serverInfo: state.serverInfo,\n });\n },\n\n \"notifications/initialized\": async (_params, _id, req) => {\n const sessionId = req.headers[\"mcp-session-id\"] as string;\n const session = state.sessions.get(sessionId);\n if (session) {\n session.initialized = true;\n }\n return null;\n },\n\n ping: async (_params, id) => {\n return jsonRpcResult(id, {});\n },\n\n \"tools/list\": async (_params, id, req) => {\n const tools: MCPToolDefinition[] = [];\n for (const { def } of state.tools.values()) {\n tools.push(def);\n }\n // Spec 10: the union with the applicable fake tools; a registered\n // definition wins for metadata.\n const ctx = contexts.get(req);\n if (ctx) {\n const registered = new Set(tools.map((t) => t.name));\n for (const fake of state.fakes.listTools(ctx.identity)) {\n if (!registered.has(fake.name)) tools.push(fake);\n }\n }\n return jsonRpcResult(id, { tools });\n },\n\n \"tools/call\": async (params, id, req) => {\n // One outcome per invocation, in order, even when nothing is claimed,\n // so each message of a batch is matched with its own.\n const outcome: CallOutcome = { mcpFake: null, error: null };\n const ctx = contexts.get(req);\n const isNotification = ctx?.notifications[ctx.calls.length] === true;\n ctx?.calls.push(outcome);\n // A notification (no `id` member) gets no answer (JSON-RPC 2.0) and\n // claims no fake entry, but a registered handler still runs for its\n // side effects, as on origin/main. A request whose id is null or of\n // another type is still served, with id null.\n const { name: rawName, arguments: args } = (params ?? {}) as {\n name?: unknown;\n arguments?: unknown;\n };\n if (!rawName) {\n return jsonRpcError(id, -32602, \"Missing tool name\");\n }\n if (typeof rawName !== \"string\") {\n const got = Array.isArray(rawName) ? \"array\" : typeof rawName;\n return jsonRpcError(\n id,\n -32602,\n build(msg`Invalid tool name: expected a string, got ${fixed(got)}`),\n );\n }\n const name = rawName;\n // Spec 10 steps 1-2: fakes, then the deny policy; never a handler\n // after a fake failure.\n if (ctx && !isNotification) {\n const fake = callFake(ctx, outcome, name, args, id);\n if (fake) return fake;\n }\n const entry = state.tools.get(name);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown tool: ${name}`);\n }\n if (entry.handler) {\n try {\n const result = await entry.handler(args);\n const content: MCPContent[] = Array.isArray(result)\n ? result\n : [{ type: \"text\", text: String(result) }];\n return jsonRpcResult(id, { content, isError: false });\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return jsonRpcResult(id, {\n content: [{ type: \"text\", text: message }],\n isError: true,\n });\n }\n }\n // No handler — return empty content\n return jsonRpcResult(id, { content: [], isError: false });\n },\n\n \"resources/list\": async (_params, id) => {\n const resources: MCPResourceDefinition[] = [];\n for (const { def } of state.resources.values()) {\n resources.push(def);\n }\n return jsonRpcResult(id, { resources });\n },\n\n \"resources/read\": async (params, id) => {\n const { uri } = (params ?? {}) as { uri?: string };\n if (!uri) {\n return jsonRpcError(id, -32602, \"Missing resource URI\");\n }\n const entry = state.resources.get(uri);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown resource: ${uri}`);\n }\n return jsonRpcResult(id, {\n contents: [\n {\n uri,\n ...(entry.content?.text !== undefined && { text: entry.content.text }),\n ...(entry.content?.blob !== undefined && { blob: entry.content.blob }),\n ...(entry.content?.mimeType !== undefined && { mimeType: entry.content.mimeType }),\n },\n ],\n });\n },\n\n \"prompts/list\": async (_params, id) => {\n const prompts: MCPPromptDefinition[] = [];\n for (const { def } of state.prompts.values()) {\n prompts.push(def);\n }\n return jsonRpcResult(id, { prompts });\n },\n\n \"prompts/get\": async (params, id) => {\n const { name, arguments: args } = (params ?? {}) as { name?: string; arguments?: unknown };\n if (!name) {\n return jsonRpcError(id, -32602, \"Missing prompt name\");\n }\n const entry = state.prompts.get(name);\n if (!entry) {\n return jsonRpcError(id, -32602, `Unknown prompt: ${name}`);\n }\n if (entry.handler) {\n try {\n const result = await entry.handler(args);\n return jsonRpcResult(id, result);\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n return jsonRpcError(id, -32603, `Prompt handler error: ${message}`);\n }\n }\n // No handler — return empty messages\n return jsonRpcResult(id, { messages: [] });\n },\n },\n });\n\n /** Answer a rejected identity (H4, H5) with HTTP 400, in the session-error shape. */\n const rejectIdentity = (\n res: http.ServerResponse,\n resolution: Extract<McpIdentityResolution, { ok: false }>,\n ): void => {\n res.writeHead(resolution.error.status, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: resolution.error.message, code: resolution.error.code }));\n };\n\n /**\n * L2: report each identity value that fell back to the raw value (H1),\n * naming its source (header or query) and the value itself, and any query\n * name that did not decode. `fellBack` says only that some value of the\n * field fell back; the header is checked on its own, and the query by the\n * same resolver given the query alone, so the rules are the resolver's.\n */\n const reportFallback = (\n sessionId: string,\n req: http.IncomingMessage,\n resolution: Extract<McpIdentityResolution, { ok: true }>,\n ): void => {\n if (!state.hasFakes()) return;\n const { fellBack } = resolution;\n if (fellBack.testId || fellBack.context) {\n const queryOnly = resolveMcpIdentity({ headers: {}, url: req.url });\n for (const field of [\"testId\", \"context\"] as const) {\n if (!fellBack[field]) continue;\n const header = req.headers[IDENTITY_HEADERS[field]];\n const headerSent = typeof header === \"string\" && header !== \"\";\n if (headerSent && decodeMcpHeaderValue(header).fellBack) {\n state.onDecodeFallback(sessionId, {\n kind: \"value\",\n field,\n source: \"header\",\n raw: header,\n used: true,\n });\n }\n const fromQuery =\n queryOnly.ok && queryOnly.fellBack[field] ? queryOnly.identity[field] : null;\n if (fromQuery !== null) {\n state.onDecodeFallback(sessionId, {\n kind: \"value\",\n field,\n source: \"query\",\n raw: fromQuery,\n used: !headerSent,\n });\n }\n }\n }\n if (resolution.undecodedQueryNames) {\n state.onDecodeFallback(sessionId, { kind: \"names\", count: resolution.undecodedQueryNames });\n }\n };\n\n /**\n * Handle one MCP request. `mount` is the path the mount is served at (I9;\n * `/` for a standalone server). Resolves with what the request resolved,\n * for the mount's journal entry.\n */\n return async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n body: string,\n mount = \"/\",\n record: MCPRequestRecord = newMCPRequestRecord(),\n ): Promise<MCPRequestRecord> => {\n // DELETE handler: session teardown\n if (req.method === \"DELETE\") {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n if (!sessionId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n return record;\n }\n if (!state.sessions.has(sessionId)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Session not found\" }));\n return record;\n }\n // Journal only: identity inputs are not checked on a session teardown.\n const resolution = resolveMcpIdentity(req, state.sessions.get(sessionId));\n if (resolution.ok) record.identity = resolution.identity;\n state.sessions.delete(sessionId);\n state.onSessionClosed(sessionId);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n return record;\n }\n\n // Identity inputs (I1-I3, H4, H5): on every POST, a bad undeclared\n // override or a repeated field is rejected before the request is served.\n const headerSessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n const knownSession = headerSessionId ? state.sessions.get(headerSessionId) : undefined;\n\n // Parse the body to determine method for session validation\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n const resolution = resolveMcpIdentity(req, knownSession);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n if (headerSessionId && knownSession) reportFallback(headerSessionId, req, resolution);\n // Let the dispatcher handle parse errors\n await dispatcher(req, res, body);\n return record;\n }\n\n const method =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? (parsed as { method: unknown }).method\n : undefined;\n\n // Handle initialize directly to control response headers\n if (method === \"initialize\") {\n const id =\n typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n ? (parsed as { id: unknown }).id\n : null;\n\n // I3: only the values the initialize request itself supplies are bound.\n const resolution = resolveMcpIdentity(req);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n const { identity, supplied } = resolution;\n\n const sessionId = randomUUID();\n state.sessions.set(sessionId, {\n id: sessionId,\n initialized: false,\n createdAt: Date.now(),\n ...(supplied.testId && identity.testId !== null ? { testId: identity.testId } : {}),\n ...(supplied.context && identity.context !== null ? { context: identity.context } : {}),\n ...(supplied.undeclared && identity.undeclared !== null\n ? { undeclared: identity.undeclared }\n : {}),\n });\n reportFallback(sessionId, req, resolution);\n\n const response = {\n jsonrpc: \"2.0\",\n id,\n result: {\n protocolVersion: \"2025-03-26\",\n capabilities: { tools: {}, resources: {}, prompts: {} },\n serverInfo: state.serverInfo,\n },\n };\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Mcp-Session-Id\": sessionId,\n });\n res.end(JSON.stringify(response));\n return record;\n }\n\n const resolution = resolveMcpIdentity(req, knownSession);\n if (!resolution.ok) {\n rejectIdentity(res, resolution);\n return record;\n }\n record.identity = resolution.identity;\n\n // Session validation for all other methods\n const sessionId = headerSessionId;\n if (!sessionId) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing mcp-session-id header\" }));\n return record;\n }\n if (!state.sessions.has(sessionId)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Session not found\" }));\n return record;\n }\n reportFallback(sessionId, req, resolution);\n\n // Enforce initialization: only allow notifications/initialized through\n // before the session is fully initialized\n const session = state.sessions.get(sessionId)!;\n if (!session.initialized && method !== \"notifications/initialized\") {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify(\n jsonRpcError(\n typeof parsed === \"object\" && parsed !== null && \"id\" in parsed\n ? ((parsed as { id: unknown }).id as string | number)\n : null,\n -32002,\n \"Session not initialized\",\n ),\n ),\n );\n return record;\n }\n\n // Delegate to the JSON-RPC dispatcher for all other methods\n // The dispatcher invokes `tools/call` once per message that is an object\n // with `jsonrpc: \"2.0\"` and that method, in order (see B2 below).\n const notifications = (Array.isArray(parsed) ? parsed : [parsed])\n .filter((m): m is Record<string, unknown> => isToolsCall(m) && m.jsonrpc === \"2.0\")\n .map((m) => !(\"id\" in m));\n const ctx: FakeRequestContext = {\n identity: resolution.identity,\n mount,\n notifications,\n calls: [],\n };\n contexts.set(req, ctx);\n await dispatcher(req, res, body);\n // B2: each tools/call message is journaled with its body and its own\n // outcome. The dispatcher invokes `tools/call` once per message that is\n // an object with `jsonrpc: \"2.0\"` and that method, in order, so the n-th\n // such message takes the n-th outcome.\n let next = 0;\n const messageOf = (message: unknown): MCPMessageRecord => {\n if (!isToolsCall(message)) return { body: null, mcpFake: null, error: null };\n const outcome = message.jsonrpc === \"2.0\" ? ctx.calls[next++] : undefined;\n return { body: message, mcpFake: outcome?.mcpFake ?? null, error: outcome?.error ?? null };\n };\n record.messages = Array.isArray(parsed) ? parsed.map(messageOf) : [messageOf(parsed)];\n return record;\n };\n}\n\n/** The identity header of each field whose value can fall back (H1). */\nconst IDENTITY_HEADERS: Record<McpDecodeField, string> = {\n testId: \"x-test-id\",\n context: \"x-aimock-context\",\n};\n\n/** A JSON-RPC message object whose method is `tools/call`. */\nfunction isToolsCall(message: unknown): message is Record<string, unknown> {\n return (\n typeof message === \"object\" &&\n message !== null &&\n !Array.isArray(message) &&\n \"method\" in message &&\n message.method === \"tools/call\"\n );\n}\n"],"mappings":"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"}
|