@copilotkit/aimock 1.44.0 → 1.45.1
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 +42 -0
- package/README.md +6 -0
- package/dist/agui-types.d.cts.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 +312 -39
- 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 +313 -40
- 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 +1 -0
- package/dist/chaos.js +1 -1
- package/dist/cli.cjs +152 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +155 -8
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +132 -71
- 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 +133 -72
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +62 -4
- 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 +62 -4
- package/dist/config-loader.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/fixture-loader-services.cjs +1 -1
- package/dist/fixture-loader-services.cjs.map +1 -1
- package/dist/fixture-loader-services.js +1 -1
- package/dist/fixture-loader-services.js.map +1 -1
- package/dist/fixture-loader.cjs +389 -27
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +47 -3
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +47 -3
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +384 -29
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-interactions.cjs +250 -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 +251 -53
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +226 -37
- 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 +227 -38
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +881 -7
- 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 +839 -8
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +9 -0
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/jest.cjs +31 -7
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +43 -3
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +43 -3
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +33 -9
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +135 -13
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +38 -4
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +38 -4
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +135 -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 +123 -30
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +16 -1
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +16 -1
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +125 -32
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +1 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -1
- package/dist/mcp-fakes-mount.js +1 -0
- package/dist/mcp-fakes-mount.js.map +1 -1
- 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 +331 -8
- package/dist/mcp-fakes.cjs.map +1 -1
- package/dist/mcp-fakes.d.cts +406 -3
- package/dist/mcp-fakes.d.cts.map +1 -1
- package/dist/mcp-fakes.d.ts +406 -3
- package/dist/mcp-fakes.d.ts.map +1 -1
- package/dist/mcp-fakes.js +331 -8
- package/dist/mcp-fakes.js.map +1 -1
- package/dist/mcp-handler.cjs +216 -17
- 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 +214 -18
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +112 -1
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +33 -2
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +33 -2
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +112 -1
- 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/messages.cjs +362 -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 +363 -58
- package/dist/messages.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 +310 -112
- 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 +311 -113
- package/dist/ollama.js.map +1 -1
- package/dist/openrouter-video.cjs +30 -2
- 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 +30 -2
- 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 +106 -5
- 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 +107 -7
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +667 -171
- 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 +665 -174
- package/dist/responses.js.map +1 -1
- 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 +572 -76
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +574 -78
- package/dist/server.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/types.cjs.map +1 -1
- package/dist/types.d.cts +315 -4
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +315 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-cli.cjs +36 -4
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +37 -5
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-types.d.ts.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/vitest.cjs +66 -10
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +35 -4
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +35 -4
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +67 -11
- 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 +109 -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 +111 -18
- package/dist/ws-responses.js.map +1 -1
- package/package.json +10 -2
- package/skills/write-fixtures/SKILL.md +179 -21
package/dist/mcp-mock.cjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_message_text = require('./message-text.cjs');
|
|
3
3
|
const require_helpers = require('./helpers.cjs');
|
|
4
|
+
const require_record_sanitize = require('./record-sanitize.cjs');
|
|
4
5
|
const require_mcp_handler = require('./mcp-handler.cjs');
|
|
5
6
|
const require_mcp_fakes = require('./mcp-fakes.cjs');
|
|
7
|
+
const require_mcp_recorder = require('./mcp-recorder.cjs');
|
|
6
8
|
let node_http = require("node:http");
|
|
7
9
|
node_http = require_runtime.__toESM(node_http);
|
|
8
10
|
|
|
@@ -27,6 +29,10 @@ var MCPMock = class {
|
|
|
27
29
|
registry = null;
|
|
28
30
|
/** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */
|
|
29
31
|
logger = null;
|
|
32
|
+
/** T1: recorded timing (`durationMs`, `atMs`) plays at value / speed. */
|
|
33
|
+
replaySpeed = 1;
|
|
34
|
+
/** MR1: the recorder while recording is on, else null (AM1). */
|
|
35
|
+
recorder = null;
|
|
30
36
|
options;
|
|
31
37
|
requestHandler;
|
|
32
38
|
constructor(options) {
|
|
@@ -79,6 +85,10 @@ var MCPMock = class {
|
|
|
79
85
|
return this.addMcpFakes(sources, { kind: "code" });
|
|
80
86
|
}
|
|
81
87
|
addMcpFakes(blocks, origin) {
|
|
88
|
+
return this.addFakes(blocks, origin);
|
|
89
|
+
}
|
|
90
|
+
/** `addMcpFakes`, plus the internal `record` origin of the MCP recorder. */
|
|
91
|
+
addFakes(blocks, origin) {
|
|
82
92
|
const result = this.fakes.add(blocks, origin);
|
|
83
93
|
this.fakeBlocks += blocks.length;
|
|
84
94
|
return result;
|
|
@@ -94,14 +104,65 @@ var MCPMock = class {
|
|
|
94
104
|
clearMcpFakes() {
|
|
95
105
|
this.fakes.clear();
|
|
96
106
|
this.fakeBlocks = 0;
|
|
107
|
+
this.recorder?.reset();
|
|
97
108
|
}
|
|
98
109
|
resetScenarioState(testId) {
|
|
99
110
|
this.fakes.resetState(testId);
|
|
111
|
+
this.recorder?.reset(testId);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Record a real upstream MCP server (MR1): from now on every request to
|
|
115
|
+
* this mount is forwarded to `config.upstream` (all paths and methods), a
|
|
116
|
+
* fake that applies still answers locally, and each forwarded `tools/list`
|
|
117
|
+
* and `tools/call` is written to an `mcpFakes` file under
|
|
118
|
+
* `config.fixturePath`. `secretValues` and `upstreamAuth` that are
|
|
119
|
+
* `undefined` come from `AIMOCK_RECORD_SECRET_VALUES` and
|
|
120
|
+
* `AIMOCK_MCP_UPSTREAM_AUTH`, as on the CLI and config paths. Throws on an
|
|
121
|
+
* invalid upstream URL, a malformed `upstreamAuth` (or env auth), or a
|
|
122
|
+
* `secretValues` entry shorter than 8 characters.
|
|
123
|
+
*/
|
|
124
|
+
enableRecording(config) {
|
|
125
|
+
let resolved = config;
|
|
126
|
+
if (config.secretValues === void 0 || config.upstreamAuth === void 0) {
|
|
127
|
+
const fromEnv = require_mcp_recorder.mcpRecordEnv(process.env);
|
|
128
|
+
resolved = {
|
|
129
|
+
...config,
|
|
130
|
+
secretValues: config.secretValues ?? fromEnv.secretValues,
|
|
131
|
+
upstreamAuth: config.upstreamAuth ?? fromEnv.upstreamAuth
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
require_record_sanitize.validateSecretValues(resolved.secretValues ?? []);
|
|
135
|
+
const recorder = new require_mcp_recorder.McpRecorder(this.recorderHost(), resolved);
|
|
136
|
+
this.recorder?.close();
|
|
137
|
+
this.recorder = recorder;
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
/** Stop recording: the mount serves exactly as before `enableRecording`. */
|
|
141
|
+
disableRecording() {
|
|
142
|
+
this.recorder?.close();
|
|
143
|
+
this.recorder = null;
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
/** @internal MR13: the recorder's write hashes and write listener, or null when not recording. */
|
|
147
|
+
recorderEvents() {
|
|
148
|
+
return this.recorder;
|
|
100
149
|
}
|
|
101
150
|
setLogger(logger) {
|
|
102
151
|
this.logger = logger;
|
|
103
152
|
}
|
|
153
|
+
/** T1: the speed recorded fake timing plays at; a non-positive or non-finite value is ignored. */
|
|
154
|
+
setReplaySpeed(speed) {
|
|
155
|
+
if (Number.isFinite(speed) && speed > 0) this.replaySpeed = speed;
|
|
156
|
+
}
|
|
157
|
+
/** @internal RP2 report part for this mount (src/mcp-fakes-report.ts reads it). */
|
|
158
|
+
fakesReportPart(testId, context, mount) {
|
|
159
|
+
return this.fakes.reportPart(testId, context, mount);
|
|
160
|
+
}
|
|
104
161
|
async handleRequest(req, res, pathname) {
|
|
162
|
+
if (this.recorder) {
|
|
163
|
+
await this.recorder.handle(req, res, mountPathOf(req.url, pathname), pathname);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
105
166
|
if (pathname !== "/" && pathname !== "") return false;
|
|
106
167
|
if (req.method === "GET") {
|
|
107
168
|
this.answerMethodNotAllowed(req, res);
|
|
@@ -183,6 +244,13 @@ var MCPMock = class {
|
|
|
183
244
|
const port = this.options.port ?? 0;
|
|
184
245
|
return new Promise((resolve, reject) => {
|
|
185
246
|
const srv = node_http.createServer((req, res) => {
|
|
247
|
+
if (this.recorder) {
|
|
248
|
+
this.recorder.handle(req, res, "/", "/").catch((err) => {
|
|
249
|
+
console.error("MCPMock request error:", err);
|
|
250
|
+
if (!res.writableEnded) res.end();
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
186
254
|
if (req.method === "GET") {
|
|
187
255
|
this.answerMethodNotAllowed(req, res);
|
|
188
256
|
return;
|
|
@@ -266,6 +334,7 @@ var MCPMock = class {
|
|
|
266
334
|
* like a failure, under `MCP_FAKE_INTERNAL_ERROR`.
|
|
267
335
|
*/
|
|
268
336
|
onFakeEvent(evt) {
|
|
337
|
+
this.fakes.logEvent(evt.identity, toReportEvent(evt));
|
|
269
338
|
if (evt.kind === "answered") return;
|
|
270
339
|
this.registry?.incrementCounter("aimock_mcp_fake_failures_total", { code: evt.code });
|
|
271
340
|
if (!this.logger) return;
|
|
@@ -274,6 +343,17 @@ var MCPMock = class {
|
|
|
274
343
|
const line = evt.kind === "evicted" ? require_message_text.msg`MCP-FAKE: evicted testId ${require_message_text.quote(evt.testId)} (cap ${evt.cap}) for tools/call ${require_message_text.plainText(evt.tool)} (${where})` : require_message_text.msg`MCP-FAKE: ${require_message_text.fixed(kind)} for tools/call ${require_message_text.plainText(evt.tool)} (${require_mcp_handler.describeMcpIdentity(evt.identity)}, ${where}): ${require_message_text.plainText(evt.message)}`;
|
|
275
344
|
this.logger.error(require_message_text.build(line));
|
|
276
345
|
}
|
|
346
|
+
/** RP2, C9: a tools/call no fake applied to is always logged, with who answered it. */
|
|
347
|
+
logUnfaked(evt) {
|
|
348
|
+
this.fakes.logEvent(evt.identity, {
|
|
349
|
+
outcome: "unfaked",
|
|
350
|
+
answeredBy: evt.answeredBy,
|
|
351
|
+
tool: evt.tool,
|
|
352
|
+
args: evt.args,
|
|
353
|
+
context: evt.identity.context,
|
|
354
|
+
mount: evt.mount
|
|
355
|
+
});
|
|
356
|
+
}
|
|
277
357
|
/**
|
|
278
358
|
* L2: one warning per session for each identity field and source whose
|
|
279
359
|
* value is not valid percent-encoding (H1), naming that source and value,
|
|
@@ -298,6 +378,17 @@ var MCPMock = class {
|
|
|
298
378
|
const fate = fallback.used ? "it is used as sent" : "it is ignored (the header value is used)";
|
|
299
379
|
this.logger?.warn(require_message_text.build(require_message_text.msg`MCP-FAKE: ${require_message_text.fixed(label)} value ${require_message_text.quote(fallback.raw)} on session ${session} is not valid percent-encoding; ${require_message_text.fixed(fate)}`));
|
|
300
380
|
}
|
|
381
|
+
/** The recorder's view of this mount (fakes, journal, logger, replay speed, side channels). */
|
|
382
|
+
recorderHost() {
|
|
383
|
+
return {
|
|
384
|
+
fakes: this.fakes,
|
|
385
|
+
addMcpFakes: (blocks, origin) => this.addFakes(blocks, origin),
|
|
386
|
+
journalEntry: (entry) => void this.journal?.add(entry),
|
|
387
|
+
logger: () => this.logger,
|
|
388
|
+
replaySpeed: () => this.replaySpeed,
|
|
389
|
+
onFakeEvent: (evt) => this.onFakeEvent(evt)
|
|
390
|
+
};
|
|
391
|
+
}
|
|
301
392
|
buildHandler() {
|
|
302
393
|
return require_mcp_handler.createMCPRequestHandler({
|
|
303
394
|
serverInfo: this.options.serverInfo ?? {
|
|
@@ -312,10 +403,30 @@ var MCPMock = class {
|
|
|
312
403
|
hasFakes: () => this.fakeBlocks > 0,
|
|
313
404
|
onFakeEvent: (evt) => this.onFakeEvent(evt),
|
|
314
405
|
onDecodeFallback: (sessionId, fallback) => this.onDecodeFallback(sessionId, fallback),
|
|
315
|
-
onSessionClosed: (sessionId) => void this.decodeWarned.delete(sessionId)
|
|
406
|
+
onSessionClosed: (sessionId) => void this.decodeWarned.delete(sessionId),
|
|
407
|
+
onUnfaked: (evt) => this.logUnfaked(evt),
|
|
408
|
+
replaySpeed: () => this.replaySpeed
|
|
316
409
|
});
|
|
317
410
|
}
|
|
318
411
|
};
|
|
412
|
+
/** RP2: a fake outcome as an event-log entry (the store assigns `seq`). */
|
|
413
|
+
function toReportEvent(evt) {
|
|
414
|
+
const base = {
|
|
415
|
+
tool: evt.tool,
|
|
416
|
+
args: evt.args,
|
|
417
|
+
context: evt.identity.context,
|
|
418
|
+
mount: evt.mount
|
|
419
|
+
};
|
|
420
|
+
return evt.kind === "answered" ? {
|
|
421
|
+
...base,
|
|
422
|
+
outcome: "answered",
|
|
423
|
+
entryId: evt.entryId
|
|
424
|
+
} : {
|
|
425
|
+
...base,
|
|
426
|
+
outcome: evt.kind,
|
|
427
|
+
code: evt.code
|
|
428
|
+
};
|
|
429
|
+
}
|
|
319
430
|
/** B1: the only methods the mounted mount root serves; every other one is 405. */
|
|
320
431
|
function isServedMethod(method) {
|
|
321
432
|
return method === "POST" || method === "DELETE";
|
package/dist/mcp-mock.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-mock.cjs","names":["McpFakeStore","newMCPRequestRecord","readBody","build","describeMcpIdentity","plainText","http","flattenHeaders","MCP_FAKES_DEFAULT_TEST_ID","quote","fixed","createMCPRequestHandler"],"sources":["../src/mcp-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { McpFakeSource, Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type { Logger } from \"./logger.js\";\nimport type {\n MCPMockOptions,\n MCPToolDefinition,\n MCPResourceDefinition,\n MCPResourceContent,\n MCPPromptDefinition,\n MCPPromptResult,\n MCPContent,\n MCPSession,\n} from \"./mcp-types.js\";\nimport {\n createMCPRequestHandler,\n describeMcpIdentity,\n newMCPRequestRecord,\n type McpDecodeFallback,\n type McpFakeEvent,\n type MCPMessageRecord,\n type MCPRequestRecord,\n type MCPState,\n} from \"./mcp-handler.js\";\nimport {\n MCP_FAKES_DEFAULT_TEST_ID,\n McpFakeStore,\n type McpFakeAddOrigin,\n type McpFakeAddResult,\n type McpFakeBlockSnapshot,\n} from \"./mcp-fakes.js\";\nimport { build, fixed, msg, plainText, quote } from \"./message-text.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class MCPMock implements Mountable {\n private tools: Map<\n string,\n { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }\n > = new Map();\n private resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }> =\n new Map();\n private prompts: Map<\n string,\n {\n def: MCPPromptDefinition;\n handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n }\n > = new Map();\n private sessions: Map<string, MCPSession> = new Map();\n /** This mount's fakes; kept across `reset()` so the id counters are never rewound. */\n private fakes = new McpFakeStore();\n /** Fake blocks loaded since the last clear. */\n private fakeBlocks = 0;\n /**\n * Decode fallbacks already reported (L2), per live session: one key per\n * field and source, plus one for undecodable query names. Dropped when the\n * session is deleted.\n */\n private decodeWarned = new Map<string, Set<string>>();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n /** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */\n private logger: Logger | null = null;\n private options: MCPMockOptions;\n private requestHandler: ReturnType<typeof createMCPRequestHandler>;\n\n constructor(options?: MCPMockOptions) {\n this.options = options ?? {};\n this.requestHandler = this.buildHandler();\n }\n\n // ---- Configuration: Tools ----\n\n addTool(def: MCPToolDefinition): this {\n this.tools.set(def.name, { def });\n return this;\n }\n\n onToolCall(\n name: string,\n handler: (args: unknown) => MCPContent[] | string | Promise<MCPContent[] | string>,\n ): this {\n const entry = this.tools.get(name);\n if (entry) {\n entry.handler = handler;\n } else {\n this.tools.set(name, { def: { name }, handler });\n }\n return this;\n }\n\n // ---- Configuration: Resources ----\n\n addResource(def: MCPResourceDefinition, content?: MCPResourceContent): this {\n this.resources.set(def.uri, { def, content });\n return this;\n }\n\n // ---- Configuration: Prompts ----\n\n addPrompt(\n def: MCPPromptDefinition,\n handler?: (args: unknown) => MCPPromptResult | Promise<MCPPromptResult>,\n ): this {\n this.prompts.set(def.name, { def, handler });\n return this;\n }\n\n // ---- Configuration: MCP fakes ----\n\n /**\n * Load MCP fakes from code: one `mcpFakes` value (a block, or an array of\n * blocks), in the fixture-file shape, validated here. One run-time addition\n * (`code#<n>` entry ids). Always appends. Returns the shadowed-entry\n * warnings; on any bad block throws one `McpFakesAddError` and adds nothing.\n */\n loadFakes(blocks: unknown): McpFakeAddResult {\n const sources: McpFakeSource[] = Array.isArray(blocks)\n ? blocks.map((raw: unknown, blockIndex) => ({ source: \"code\", blockIndex, raw }))\n : [{ source: \"code\", blockIndex: null, raw: blocks }];\n return this.addMcpFakes(sources, { kind: \"code\" });\n }\n\n addMcpFakes(blocks: McpFakeSource[], origin: McpFakeAddOrigin): McpFakeAddResult {\n const result = this.fakes.add(blocks, origin);\n this.fakeBlocks += blocks.length;\n return result;\n }\n\n /**\n * The blocks on this mount that apply to `testId` and `context` (spec 6.5,\n * I9), with every entry id and its consumed state for that test id. `null`\n * means \"none sent\". Read-only: the result is deeply frozen.\n */\n fakesSnapshot(\n testId: string | null = null,\n context: string | null = null,\n ): readonly McpFakeBlockSnapshot[] {\n return this.fakes.snapshot(testId, context);\n }\n\n clearMcpFakes(): void {\n this.fakes.clear();\n this.fakeBlocks = 0;\n }\n\n resetScenarioState(testId?: string): void {\n this.fakes.resetState(testId);\n }\n\n setLogger(logger: Logger): void {\n this.logger = logger;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Off the mount root: not this mount's request (fall through).\n if (pathname !== \"/\" && pathname !== \"\") {\n return false;\n }\n // B1: a GET on the mount root is 405. Any other method but POST and\n // DELETE falls through to the next route, as on origin/main.\n if (req.method === \"GET\") {\n this.answerMethodNotAllowed(req, res);\n return true;\n }\n if (!isServedMethod(req.method)) {\n return false;\n }\n await this.serve(req, res, mountPathOf(req.url, pathname), false);\n return true;\n }\n\n /**\n * Serve one request, in both modes (mounted: a POST or DELETE on the mount\n * root; standalone: any method but GET on any path): count it, run the\n * handler, journal it. A throw from the mount's own code is logged with the\n * identity resolved so far, answered 500 and journaled with its message,\n * never left to a generic catch.\n */\n private async serve(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n mount: string,\n standalone: boolean,\n ): Promise<void> {\n const record = newMCPRequestRecord();\n try {\n const body = await readBody(req);\n this.countRequest(req, body);\n await this.requestHandler(req, res, body, mount, record);\n this.journalRequest(req, res, record);\n } catch (err) {\n const thrown = err instanceof Error ? err.message : String(err);\n const who = record.identity\n ? describeMcpIdentity(record.identity)\n : msg`no identity resolved`;\n const line = build(\n msg`MCPMock request error (${who}, mount ${plainText(mount)}): ${plainText(thrown)}`,\n );\n // W5: the standalone server always prints the error, as on origin/main,\n // even when the mount also has a server's (possibly silent) logger.\n // Mounted, it goes to the server's logger, as origin/main's route error did.\n if (standalone || !this.logger) console.error(line);\n else this.logger.error(line);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n this.journalRequest(req, res, record, thrown);\n }\n }\n\n /** `aimock_mcp_requests_total` by JSON-RPC method (`session/delete` for DELETE). */\n private countRequest(req: http.IncomingMessage, body: string): void {\n if (this.registry) {\n if (req.method === \"DELETE\") {\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"session/delete\" });\n } else {\n try {\n const parsed = JSON.parse(body);\n const method =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? String(parsed.method)\n : \"unknown\";\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method });\n } catch {\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"unknown\" });\n }\n }\n }\n }\n\n /**\n * B1: answer 405 (`Allow: POST, DELETE`), journaled. Mounted, this is a GET\n * on the mount root; standalone, a GET on any path.\n */\n private answerMethodNotAllowed(req: http.IncomingMessage, res: http.ServerResponse): void {\n res.writeHead(405, { \"Content-Type\": \"application/json\", Allow: \"POST, DELETE\" });\n res.end(JSON.stringify({ error: build(msg`Method not allowed: use POST or DELETE`) }));\n req.resume();\n this.journalRequest(req, res, null);\n }\n\n health(): { status: string; [key: string]: unknown } {\n return {\n status: \"ok\",\n tools: this.tools.size,\n resources: this.resources.size,\n prompts: this.prompts.size,\n sessions: this.sessions.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n // I5: the fakes' test-id cap follows the journal's (`0` = unbounded).\n this.fakes.setMaxTestIds(journal.fixtureCountsMaxTestIdsCap);\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"Server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise((resolve, reject) => {\n // The standalone server serves every path, as it always has: every\n // method but GET goes to the MCP handler on any path. B1: a GET on any\n // path is 405 (`Allow: POST, DELETE`) and journaled, so the SDK's\n // optional GET SSE stream is declined, not mishandled.\n const srv = http.createServer((req, res) => {\n if (req.method === \"GET\") {\n this.answerMethodNotAllowed(req, res);\n return;\n }\n // serve() catches the request's own errors; this is only a logger\n // that threw while reporting one.\n this.serve(req, res, \"/\", true).catch((err: unknown) => {\n console.error(\"MCPMock request error:\", err);\n if (!res.writableEnded) res.end();\n });\n });\n\n srv.listen(port, host, () => {\n this.server = srv;\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n resolve(`http://${host}:${addr.port}`);\n } else {\n resolve(`http://${host}:${port}`);\n }\n });\n\n srv.on(\"error\", reject);\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"Server not started\");\n }\n const srv = this.server;\n this.server = null;\n await new Promise<void>((resolve, reject) => {\n srv.close((err) => (err ? reject(err) : resolve()));\n });\n }\n\n // ---- Inspection ----\n\n getRequests(): unknown[] {\n if (!this.journal) return [];\n return this.journal.getAll().filter((e) => e.service === \"mcp\");\n }\n\n getSessions(): Map<string, MCPSession> {\n return new Map(this.sessions);\n }\n\n reset(): this {\n this.tools.clear();\n this.resources.clear();\n this.prompts.clear();\n this.sessions.clear();\n this.decodeWarned.clear();\n // R8: fakes and their consumption state go; the id counters stay.\n this.clearMcpFakes();\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Internal ----\n\n /**\n * The journal entries of one handled request (B2, B3): one per JSON-RPC\n * message (each element of a batch), or one with no body when the request\n * carried none that was read; none without a journal (W5). `error` is what\n * the mount's own code threw while serving it.\n */\n private journalRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n record: MCPRequestRecord | null,\n error?: string,\n ): void {\n if (!this.journal) return;\n const messages: MCPMessageRecord[] = record?.messages.length\n ? record.messages\n : [{ body: null, mcpFake: null, error: null }];\n for (const message of messages) {\n const failed = message.error ?? error;\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: message.body,\n service: \"mcp\",\n ...(record?.identity\n ? {\n testId: record.identity.testId ?? MCP_FAKES_DEFAULT_TEST_ID,\n context: record.identity.context,\n }\n : {}),\n response: {\n status: res.statusCode,\n fixture: null,\n ...(message.mcpFake ? { mcpFake: message.mcpFake } : {}),\n ...(failed !== undefined && failed !== null ? { error: failed } : {}),\n },\n });\n }\n }\n\n /**\n * Side channels of a fake outcome (9.3): the failure metric and the L1 /\n * L10 line. A claim that threw (`internal_error`) is counted and logged\n * like a failure, under `MCP_FAKE_INTERNAL_ERROR`.\n */\n private onFakeEvent(evt: McpFakeEvent): void {\n if (evt.kind === \"answered\") return;\n this.registry?.incrementCounter(\"aimock_mcp_fake_failures_total\", { code: evt.code });\n if (!this.logger) return;\n const where = msg`mount ${plainText(evt.mount)}`;\n // \"not declared\", \"internal error\"; the other kinds are one word.\n const kind = evt.kind.replace(\"_\", \" \");\n const line =\n evt.kind === \"evicted\"\n ? msg`MCP-FAKE: evicted testId ${quote(evt.testId)} (cap ${evt.cap}) for tools/call ${plainText(evt.tool)} (${where})`\n : msg`MCP-FAKE: ${fixed(kind)} for tools/call ${plainText(evt.tool)} (${describeMcpIdentity(evt.identity)}, ${where}): ${plainText(evt.message)}`;\n this.logger.error(build(line));\n }\n\n /**\n * L2: one warning per session for each identity field and source whose\n * value is not valid percent-encoding (H1), naming that source and value,\n * and one for query names that are not valid percent-encoding.\n */\n private onDecodeFallback(sessionId: string, fallback: McpDecodeFallback): void {\n let warned = this.decodeWarned.get(sessionId);\n if (!warned) {\n warned = new Set();\n this.decodeWarned.set(sessionId, warned);\n }\n const key = fallback.kind === \"names\" ? \"names\" : `${fallback.field}:${fallback.source}`;\n if (warned.has(key)) return;\n warned.add(key);\n const session = quote(sessionId);\n if (fallback.kind === \"names\") {\n const one = fallback.count === 1;\n this.logger?.warn(\n build(\n msg`MCP-FAKE: ${fallback.count} query parameter ${fixed(one ? \"name\" : \"names\")} on session ${session} ${fixed(one ? \"is\" : \"are\")} not valid percent-encoding; ${fixed(one ? \"it matches\" : \"they match\")} no identity field`,\n ),\n );\n return;\n }\n const label = DECODE_LABELS[fallback.field][fallback.source];\n const fate = fallback.used ? \"it is used as sent\" : \"it is ignored (the header value is used)\";\n this.logger?.warn(\n build(\n msg`MCP-FAKE: ${fixed(label)} value ${quote(fallback.raw)} on session ${session} is not valid percent-encoding; ${fixed(fate)}`,\n ),\n );\n }\n\n private buildHandler() {\n const state: MCPState = {\n serverInfo: this.options.serverInfo ?? { name: \"mcp-mock\", version: \"1.0.0\" },\n tools: this.tools,\n resources: this.resources,\n prompts: this.prompts,\n sessions: this.sessions,\n fakes: this.fakes,\n hasFakes: () => this.fakeBlocks > 0,\n onFakeEvent: (evt) => this.onFakeEvent(evt),\n onDecodeFallback: (sessionId, fallback) => this.onDecodeFallback(sessionId, fallback),\n onSessionClosed: (sessionId) => void this.decodeWarned.delete(sessionId),\n };\n return createMCPRequestHandler(state);\n }\n}\n\n/** B1: the only methods the mounted mount root serves; every other one is 405. */\nfunction isServedMethod(method: string | undefined): boolean {\n return method === \"POST\" || method === \"DELETE\";\n}\n\n/** The L2 label of each identity field's header and query source. */\nconst DECODE_LABELS = {\n testId: { header: \"X-Test-Id header\", query: \"?testId= query parameter\" },\n context: { header: \"X-AIMock-Context header\", query: \"?context= query parameter\" },\n} as const;\n\n/**\n * The path a mount is served at (I9): the request path without the\n * sub-path the server handed the mount (`/mcp` for `/mcp` or `/mcp/`), `/`\n * at the root.\n */\nfunction mountPathOf(url: string | undefined, subPath: string): string {\n const full = new URL(url ?? \"/\", \"http://mount.invalid\").pathname;\n const sub = subPath === \"\" ? \"/\" : subPath;\n const mount = full.endsWith(sub) ? full.slice(0, full.length - sub.length) : full;\n return mount === \"\" ? \"/\" : mount;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mcp-mock.cjs","names":["McpFakeStore","mcpRecordEnv","McpRecorder","newMCPRequestRecord","readBody","build","describeMcpIdentity","plainText","http","flattenHeaders","MCP_FAKES_DEFAULT_TEST_ID","quote","fixed","createMCPRequestHandler"],"sources":["../src/mcp-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { McpFakeSource, McpRecordConfig, Mountable } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type { Logger } from \"./logger.js\";\nimport type {\n MCPMockOptions,\n MCPToolDefinition,\n MCPResourceDefinition,\n MCPResourceContent,\n MCPPromptDefinition,\n MCPPromptResult,\n MCPContent,\n MCPSession,\n} from \"./mcp-types.js\";\nimport {\n createMCPRequestHandler,\n describeMcpIdentity,\n newMCPRequestRecord,\n type McpDecodeFallback,\n type McpFakeEvent,\n type MCPMessageRecord,\n type MCPRequestRecord,\n type MCPState,\n} from \"./mcp-handler.js\";\nimport {\n MCP_FAKES_DEFAULT_TEST_ID,\n McpFakeStore,\n type McpFakeAddOrigin,\n type McpFakeAddOriginInternal,\n type McpFakeAddResult,\n type McpFakeBlockSnapshot,\n type McpFakeReportEventInput,\n type McpFakeReportPart,\n} from \"./mcp-fakes.js\";\nimport { build, fixed, msg, plainText, quote } from \"./message-text.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\nimport { McpRecorder, mcpRecordEnv, type RecorderHost } from \"./mcp-recorder.js\";\nimport { validateSecretValues } from \"./record-sanitize.js\";\n\nexport class MCPMock implements Mountable {\n private tools: Map<\n string,\n { def: MCPToolDefinition; handler?: (...args: unknown[]) => unknown }\n > = new Map();\n private resources: Map<string, { def: MCPResourceDefinition; content?: MCPResourceContent }> =\n new Map();\n private prompts: Map<\n string,\n {\n def: MCPPromptDefinition;\n handler?: (...args: unknown[]) => MCPPromptResult | Promise<MCPPromptResult>;\n }\n > = new Map();\n private sessions: Map<string, MCPSession> = new Map();\n /** This mount's fakes; kept across `reset()` so the id counters are never rewound. */\n private fakes = new McpFakeStore();\n /** Fake blocks loaded since the last clear. */\n private fakeBlocks = 0;\n /**\n * Decode fallbacks already reported (L2), per live session: one key per\n * field and source, plus one for undecodable query names. Dropped when the\n * session is deleted.\n */\n private decodeWarned = new Map<string, Set<string>>();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n /** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */\n private logger: Logger | null = null;\n /** T1: recorded timing (`durationMs`, `atMs`) plays at value / speed. */\n private replaySpeed = 1;\n /** MR1: the recorder while recording is on, else null (AM1). */\n private recorder: McpRecorder | null = null;\n private options: MCPMockOptions;\n private requestHandler: ReturnType<typeof createMCPRequestHandler>;\n\n constructor(options?: MCPMockOptions) {\n this.options = options ?? {};\n this.requestHandler = this.buildHandler();\n }\n\n // ---- Configuration: Tools ----\n\n addTool(def: MCPToolDefinition): this {\n this.tools.set(def.name, { def });\n return this;\n }\n\n onToolCall(\n name: string,\n handler: (args: unknown) => MCPContent[] | string | Promise<MCPContent[] | string>,\n ): this {\n const entry = this.tools.get(name);\n if (entry) {\n entry.handler = handler;\n } else {\n this.tools.set(name, { def: { name }, handler });\n }\n return this;\n }\n\n // ---- Configuration: Resources ----\n\n addResource(def: MCPResourceDefinition, content?: MCPResourceContent): this {\n this.resources.set(def.uri, { def, content });\n return this;\n }\n\n // ---- Configuration: Prompts ----\n\n addPrompt(\n def: MCPPromptDefinition,\n handler?: (args: unknown) => MCPPromptResult | Promise<MCPPromptResult>,\n ): this {\n this.prompts.set(def.name, { def, handler });\n return this;\n }\n\n // ---- Configuration: MCP fakes ----\n\n /**\n * Load MCP fakes from code: one `mcpFakes` value (a block, or an array of\n * blocks), in the fixture-file shape, validated here. One run-time addition\n * (`code#<n>` entry ids). Always appends. Returns the shadowed-entry\n * warnings; on any bad block throws one `McpFakesAddError` and adds nothing.\n */\n loadFakes(blocks: unknown): McpFakeAddResult {\n const sources: McpFakeSource[] = Array.isArray(blocks)\n ? blocks.map((raw: unknown, blockIndex) => ({ source: \"code\", blockIndex, raw }))\n : [{ source: \"code\", blockIndex: null, raw: blocks }];\n return this.addMcpFakes(sources, { kind: \"code\" });\n }\n\n addMcpFakes(blocks: McpFakeSource[], origin: McpFakeAddOrigin): McpFakeAddResult {\n return this.addFakes(blocks, origin);\n }\n\n /** `addMcpFakes`, plus the internal `record` origin of the MCP recorder. */\n private addFakes(blocks: McpFakeSource[], origin: McpFakeAddOriginInternal): McpFakeAddResult {\n const result = this.fakes.add(blocks, origin);\n this.fakeBlocks += blocks.length;\n return result;\n }\n\n /**\n * The blocks on this mount that apply to `testId` and `context` (spec 6.5,\n * I9), with every entry id and its consumed state for that test id. `null`\n * means \"none sent\". Read-only: the result is deeply frozen.\n */\n fakesSnapshot(\n testId: string | null = null,\n context: string | null = null,\n ): readonly McpFakeBlockSnapshot[] {\n return this.fakes.snapshot(testId, context);\n }\n\n clearMcpFakes(): void {\n this.fakes.clear();\n this.fakeBlocks = 0;\n this.recorder?.reset();\n }\n\n resetScenarioState(testId?: string): void {\n this.fakes.resetState(testId);\n this.recorder?.reset(testId);\n }\n\n // ---- MCP recording (MR1) ----\n\n /**\n * Record a real upstream MCP server (MR1): from now on every request to\n * this mount is forwarded to `config.upstream` (all paths and methods), a\n * fake that applies still answers locally, and each forwarded `tools/list`\n * and `tools/call` is written to an `mcpFakes` file under\n * `config.fixturePath`. `secretValues` and `upstreamAuth` that are\n * `undefined` come from `AIMOCK_RECORD_SECRET_VALUES` and\n * `AIMOCK_MCP_UPSTREAM_AUTH`, as on the CLI and config paths. Throws on an\n * invalid upstream URL, a malformed `upstreamAuth` (or env auth), or a\n * `secretValues` entry shorter than 8 characters.\n */\n enableRecording(config: McpRecordConfig): this {\n let resolved = config;\n if (config.secretValues === undefined || config.upstreamAuth === undefined) {\n const fromEnv = mcpRecordEnv(process.env);\n resolved = {\n ...config,\n secretValues: config.secretValues ?? fromEnv.secretValues,\n upstreamAuth: config.upstreamAuth ?? fromEnv.upstreamAuth,\n };\n }\n validateSecretValues(resolved.secretValues ?? []);\n const recorder = new McpRecorder(this.recorderHost(), resolved);\n this.recorder?.close();\n this.recorder = recorder;\n return this;\n }\n\n /** Stop recording: the mount serves exactly as before `enableRecording`. */\n disableRecording(): this {\n this.recorder?.close();\n this.recorder = null;\n return this;\n }\n\n /** @internal MR13: the recorder's write hashes and write listener, or null when not recording. */\n recorderEvents(): Pick<McpRecorder, \"lastWrittenHash\" | \"onWrite\"> | null {\n return this.recorder;\n }\n\n setLogger(logger: Logger): void {\n this.logger = logger;\n }\n\n /** T1: the speed recorded fake timing plays at; a non-positive or non-finite value is ignored. */\n setReplaySpeed(speed: number): void {\n if (Number.isFinite(speed) && speed > 0) this.replaySpeed = speed;\n }\n\n /** @internal RP2 report part for this mount (src/mcp-fakes-report.ts reads it). */\n fakesReportPart(testId: string | null, context: string | null, mount: string): McpFakeReportPart {\n return this.fakes.reportPart(testId, context, mount);\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // AM1 (a): in record mode every path and method under the mount is the\n // recorder's, before the off-root check and the B1 GET answer.\n if (this.recorder) {\n await this.recorder.handle(req, res, mountPathOf(req.url, pathname), pathname);\n return true;\n }\n // Off the mount root: not this mount's request (fall through).\n if (pathname !== \"/\" && pathname !== \"\") {\n return false;\n }\n // B1: a GET on the mount root is 405. Any other method but POST and\n // DELETE falls through to the next route, as on origin/main.\n if (req.method === \"GET\") {\n this.answerMethodNotAllowed(req, res);\n return true;\n }\n if (!isServedMethod(req.method)) {\n return false;\n }\n await this.serve(req, res, mountPathOf(req.url, pathname), false);\n return true;\n }\n\n /**\n * Serve one request, in both modes (mounted: a POST or DELETE on the mount\n * root; standalone: any method but GET on any path): count it, run the\n * handler, journal it. A throw from the mount's own code is logged with the\n * identity resolved so far, answered 500 and journaled with its message,\n * never left to a generic catch.\n */\n private async serve(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n mount: string,\n standalone: boolean,\n ): Promise<void> {\n const record = newMCPRequestRecord();\n try {\n const body = await readBody(req);\n this.countRequest(req, body);\n await this.requestHandler(req, res, body, mount, record);\n this.journalRequest(req, res, record);\n } catch (err) {\n const thrown = err instanceof Error ? err.message : String(err);\n const who = record.identity\n ? describeMcpIdentity(record.identity)\n : msg`no identity resolved`;\n const line = build(\n msg`MCPMock request error (${who}, mount ${plainText(mount)}): ${plainText(thrown)}`,\n );\n // W5: the standalone server always prints the error, as on origin/main,\n // even when the mount also has a server's (possibly silent) logger.\n // Mounted, it goes to the server's logger, as origin/main's route error did.\n if (standalone || !this.logger) console.error(line);\n else this.logger.error(line);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal server error\");\n } else if (!res.writableEnded) {\n res.end();\n }\n this.journalRequest(req, res, record, thrown);\n }\n }\n\n /** `aimock_mcp_requests_total` by JSON-RPC method (`session/delete` for DELETE). */\n private countRequest(req: http.IncomingMessage, body: string): void {\n if (this.registry) {\n if (req.method === \"DELETE\") {\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"session/delete\" });\n } else {\n try {\n const parsed = JSON.parse(body);\n const method =\n typeof parsed === \"object\" && parsed !== null && \"method\" in parsed\n ? String(parsed.method)\n : \"unknown\";\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method });\n } catch {\n this.registry.incrementCounter(\"aimock_mcp_requests_total\", { method: \"unknown\" });\n }\n }\n }\n }\n\n /**\n * B1: answer 405 (`Allow: POST, DELETE`), journaled. Mounted, this is a GET\n * on the mount root; standalone, a GET on any path.\n */\n private answerMethodNotAllowed(req: http.IncomingMessage, res: http.ServerResponse): void {\n res.writeHead(405, { \"Content-Type\": \"application/json\", Allow: \"POST, DELETE\" });\n res.end(JSON.stringify({ error: build(msg`Method not allowed: use POST or DELETE`) }));\n req.resume();\n this.journalRequest(req, res, null);\n }\n\n health(): { status: string; [key: string]: unknown } {\n return {\n status: \"ok\",\n tools: this.tools.size,\n resources: this.resources.size,\n prompts: this.prompts.size,\n sessions: this.sessions.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n // I5: the fakes' test-id cap follows the journal's (`0` = unbounded).\n this.fakes.setMaxTestIds(journal.fixtureCountsMaxTestIdsCap);\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"Server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise((resolve, reject) => {\n // The standalone server serves every path, as it always has: every\n // method but GET goes to the MCP handler on any path. B1: a GET on any\n // path is 405 (`Allow: POST, DELETE`) and journaled, so the SDK's\n // optional GET SSE stream is declined, not mishandled.\n const srv = http.createServer((req, res) => {\n // AM1 (b): in record mode the recorder takes every request first. A\n // standalone server serves every path as its root, so every request\n // maps to the upstream endpoint itself (review r2 N3).\n if (this.recorder) {\n this.recorder.handle(req, res, \"/\", \"/\").catch((err: unknown) => {\n console.error(\"MCPMock request error:\", err);\n if (!res.writableEnded) res.end();\n });\n return;\n }\n if (req.method === \"GET\") {\n this.answerMethodNotAllowed(req, res);\n return;\n }\n // serve() catches the request's own errors; this is only a logger\n // that threw while reporting one.\n this.serve(req, res, \"/\", true).catch((err: unknown) => {\n console.error(\"MCPMock request error:\", err);\n if (!res.writableEnded) res.end();\n });\n });\n\n srv.listen(port, host, () => {\n this.server = srv;\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n resolve(`http://${host}:${addr.port}`);\n } else {\n resolve(`http://${host}:${port}`);\n }\n });\n\n srv.on(\"error\", reject);\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"Server not started\");\n }\n const srv = this.server;\n this.server = null;\n await new Promise<void>((resolve, reject) => {\n srv.close((err) => (err ? reject(err) : resolve()));\n });\n }\n\n // ---- Inspection ----\n\n getRequests(): unknown[] {\n if (!this.journal) return [];\n return this.journal.getAll().filter((e) => e.service === \"mcp\");\n }\n\n getSessions(): Map<string, MCPSession> {\n return new Map(this.sessions);\n }\n\n reset(): this {\n this.tools.clear();\n this.resources.clear();\n this.prompts.clear();\n this.sessions.clear();\n this.decodeWarned.clear();\n // R8: fakes and their consumption state go; the id counters stay.\n this.clearMcpFakes();\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Internal ----\n\n /**\n * The journal entries of one handled request (B2, B3): one per JSON-RPC\n * message (each element of a batch), or one with no body when the request\n * carried none that was read; none without a journal (W5). `error` is what\n * the mount's own code threw while serving it.\n */\n private journalRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n record: MCPRequestRecord | null,\n error?: string,\n ): void {\n if (!this.journal) return;\n const messages: MCPMessageRecord[] = record?.messages.length\n ? record.messages\n : [{ body: null, mcpFake: null, error: null }];\n for (const message of messages) {\n const failed = message.error ?? error;\n this.journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: message.body,\n service: \"mcp\",\n ...(record?.identity\n ? {\n testId: record.identity.testId ?? MCP_FAKES_DEFAULT_TEST_ID,\n context: record.identity.context,\n }\n : {}),\n response: {\n status: res.statusCode,\n fixture: null,\n ...(message.mcpFake ? { mcpFake: message.mcpFake } : {}),\n ...(failed !== undefined && failed !== null ? { error: failed } : {}),\n },\n });\n }\n }\n\n /**\n * Side channels of a fake outcome (9.3): the failure metric and the L1 /\n * L10 line. A claim that threw (`internal_error`) is counted and logged\n * like a failure, under `MCP_FAKE_INTERNAL_ERROR`.\n */\n private onFakeEvent(evt: McpFakeEvent): void {\n // RP2, C9: every fake outcome goes to the event log.\n this.fakes.logEvent(evt.identity, toReportEvent(evt));\n if (evt.kind === \"answered\") return;\n this.registry?.incrementCounter(\"aimock_mcp_fake_failures_total\", { code: evt.code });\n if (!this.logger) return;\n const where = msg`mount ${plainText(evt.mount)}`;\n // \"not declared\", \"internal error\"; the other kinds are one word.\n const kind = evt.kind.replace(\"_\", \" \");\n const line =\n evt.kind === \"evicted\"\n ? msg`MCP-FAKE: evicted testId ${quote(evt.testId)} (cap ${evt.cap}) for tools/call ${plainText(evt.tool)} (${where})`\n : msg`MCP-FAKE: ${fixed(kind)} for tools/call ${plainText(evt.tool)} (${describeMcpIdentity(evt.identity)}, ${where}): ${plainText(evt.message)}`;\n this.logger.error(build(line));\n }\n\n /** RP2, C9: a tools/call no fake applied to is always logged, with who answered it. */\n private logUnfaked(evt: Parameters<MCPState[\"onUnfaked\"]>[0]): void {\n this.fakes.logEvent(evt.identity, {\n outcome: \"unfaked\",\n answeredBy: evt.answeredBy,\n tool: evt.tool,\n args: evt.args,\n context: evt.identity.context,\n mount: evt.mount,\n });\n }\n\n /**\n * L2: one warning per session for each identity field and source whose\n * value is not valid percent-encoding (H1), naming that source and value,\n * and one for query names that are not valid percent-encoding.\n */\n private onDecodeFallback(sessionId: string, fallback: McpDecodeFallback): void {\n let warned = this.decodeWarned.get(sessionId);\n if (!warned) {\n warned = new Set();\n this.decodeWarned.set(sessionId, warned);\n }\n const key = fallback.kind === \"names\" ? \"names\" : `${fallback.field}:${fallback.source}`;\n if (warned.has(key)) return;\n warned.add(key);\n const session = quote(sessionId);\n if (fallback.kind === \"names\") {\n const one = fallback.count === 1;\n this.logger?.warn(\n build(\n msg`MCP-FAKE: ${fallback.count} query parameter ${fixed(one ? \"name\" : \"names\")} on session ${session} ${fixed(one ? \"is\" : \"are\")} not valid percent-encoding; ${fixed(one ? \"it matches\" : \"they match\")} no identity field`,\n ),\n );\n return;\n }\n const label = DECODE_LABELS[fallback.field][fallback.source];\n const fate = fallback.used ? \"it is used as sent\" : \"it is ignored (the header value is used)\";\n this.logger?.warn(\n build(\n msg`MCP-FAKE: ${fixed(label)} value ${quote(fallback.raw)} on session ${session} is not valid percent-encoding; ${fixed(fate)}`,\n ),\n );\n }\n\n /** The recorder's view of this mount (fakes, journal, logger, replay speed, side channels). */\n private recorderHost(): RecorderHost {\n const fakes = this.fakes;\n return {\n fakes,\n addMcpFakes: (blocks, origin) => this.addFakes(blocks, origin),\n journalEntry: (entry) => void this.journal?.add(entry),\n logger: () => this.logger,\n replaySpeed: () => this.replaySpeed,\n onFakeEvent: (evt) => this.onFakeEvent(evt),\n };\n }\n\n private buildHandler() {\n const state: MCPState = {\n serverInfo: this.options.serverInfo ?? { name: \"mcp-mock\", version: \"1.0.0\" },\n tools: this.tools,\n resources: this.resources,\n prompts: this.prompts,\n sessions: this.sessions,\n fakes: this.fakes,\n hasFakes: () => this.fakeBlocks > 0,\n onFakeEvent: (evt) => this.onFakeEvent(evt),\n onDecodeFallback: (sessionId, fallback) => this.onDecodeFallback(sessionId, fallback),\n onSessionClosed: (sessionId) => void this.decodeWarned.delete(sessionId),\n onUnfaked: (evt) => this.logUnfaked(evt),\n replaySpeed: () => this.replaySpeed,\n };\n return createMCPRequestHandler(state);\n }\n}\n\n/** RP2: a fake outcome as an event-log entry (the store assigns `seq`). */\nfunction toReportEvent(evt: McpFakeEvent): McpFakeReportEventInput {\n const base = { tool: evt.tool, args: evt.args, context: evt.identity.context, mount: evt.mount };\n return evt.kind === \"answered\"\n ? { ...base, outcome: \"answered\", entryId: evt.entryId }\n : { ...base, outcome: evt.kind, code: evt.code };\n}\n\n/** B1: the only methods the mounted mount root serves; every other one is 405. */\nfunction isServedMethod(method: string | undefined): boolean {\n return method === \"POST\" || method === \"DELETE\";\n}\n\n/** The L2 label of each identity field's header and query source. */\nconst DECODE_LABELS = {\n testId: { header: \"X-Test-Id header\", query: \"?testId= query parameter\" },\n context: { header: \"X-AIMock-Context header\", query: \"?context= query parameter\" },\n} as const;\n\n/**\n * The path a mount is served at (I9): the request path without the\n * sub-path the server handed the mount (`/mcp` for `/mcp` or `/mcp/`), `/`\n * at the root.\n */\nfunction mountPathOf(url: string | undefined, subPath: string): string {\n const full = new URL(url ?? \"/\", \"http://mount.invalid\").pathname;\n const sub = subPath === \"\" ? \"/\" : subPath;\n const mount = full.endsWith(sub) ? full.slice(0, full.length - sub.length) : full;\n return mount === \"\" ? \"/\" : mount;\n}\n"],"mappings":"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"}
|
package/dist/mcp-mock.d.cts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Journal } from "./journal.cjs";
|
|
2
2
|
import { Logger } from "./logger.cjs";
|
|
3
3
|
import { MetricsRegistry } from "./metrics.cjs";
|
|
4
|
-
import { McpFakeAddOrigin, McpFakeAddResult, McpFakeBlockSnapshot } from "./mcp-fakes.cjs";
|
|
5
|
-
import { McpFakeSource, Mountable } from "./types.cjs";
|
|
4
|
+
import { McpFakeAddOrigin, McpFakeAddResult, McpFakeBlockSnapshot, McpFakeReportPart } from "./mcp-fakes.cjs";
|
|
5
|
+
import { McpFakeSource, McpRecordConfig, Mountable } from "./types.cjs";
|
|
6
6
|
import { MCPContent, MCPMockOptions, MCPPromptDefinition, MCPPromptResult, MCPResourceContent, MCPResourceDefinition, MCPSession, MCPToolDefinition } from "./mcp-types.cjs";
|
|
7
|
+
import { McpRecorder } from "./mcp-recorder.cjs";
|
|
7
8
|
import * as http$1 from "node:http";
|
|
8
9
|
|
|
9
10
|
//#region src/mcp-mock.d.ts
|
|
@@ -27,6 +28,10 @@ declare class MCPMock implements Mountable {
|
|
|
27
28
|
private registry;
|
|
28
29
|
/** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */
|
|
29
30
|
private logger;
|
|
31
|
+
/** T1: recorded timing (`durationMs`, `atMs`) plays at value / speed. */
|
|
32
|
+
private replaySpeed;
|
|
33
|
+
/** MR1: the recorder while recording is on, else null (AM1). */
|
|
34
|
+
private recorder;
|
|
30
35
|
private options;
|
|
31
36
|
private requestHandler;
|
|
32
37
|
constructor(options?: MCPMockOptions);
|
|
@@ -42,6 +47,8 @@ declare class MCPMock implements Mountable {
|
|
|
42
47
|
*/
|
|
43
48
|
loadFakes(blocks: unknown): McpFakeAddResult;
|
|
44
49
|
addMcpFakes(blocks: McpFakeSource[], origin: McpFakeAddOrigin): McpFakeAddResult;
|
|
50
|
+
/** `addMcpFakes`, plus the internal `record` origin of the MCP recorder. */
|
|
51
|
+
private addFakes;
|
|
45
52
|
/**
|
|
46
53
|
* The blocks on this mount that apply to `testId` and `context` (spec 6.5,
|
|
47
54
|
* I9), with every entry id and its consumed state for that test id. `null`
|
|
@@ -50,7 +57,27 @@ declare class MCPMock implements Mountable {
|
|
|
50
57
|
fakesSnapshot(testId?: string | null, context?: string | null): readonly McpFakeBlockSnapshot[];
|
|
51
58
|
clearMcpFakes(): void;
|
|
52
59
|
resetScenarioState(testId?: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Record a real upstream MCP server (MR1): from now on every request to
|
|
62
|
+
* this mount is forwarded to `config.upstream` (all paths and methods), a
|
|
63
|
+
* fake that applies still answers locally, and each forwarded `tools/list`
|
|
64
|
+
* and `tools/call` is written to an `mcpFakes` file under
|
|
65
|
+
* `config.fixturePath`. `secretValues` and `upstreamAuth` that are
|
|
66
|
+
* `undefined` come from `AIMOCK_RECORD_SECRET_VALUES` and
|
|
67
|
+
* `AIMOCK_MCP_UPSTREAM_AUTH`, as on the CLI and config paths. Throws on an
|
|
68
|
+
* invalid upstream URL, a malformed `upstreamAuth` (or env auth), or a
|
|
69
|
+
* `secretValues` entry shorter than 8 characters.
|
|
70
|
+
*/
|
|
71
|
+
enableRecording(config: McpRecordConfig): this;
|
|
72
|
+
/** Stop recording: the mount serves exactly as before `enableRecording`. */
|
|
73
|
+
disableRecording(): this;
|
|
74
|
+
/** @internal MR13: the recorder's write hashes and write listener, or null when not recording. */
|
|
75
|
+
recorderEvents(): Pick<McpRecorder, "lastWrittenHash" | "onWrite"> | null;
|
|
53
76
|
setLogger(logger: Logger): void;
|
|
77
|
+
/** T1: the speed recorded fake timing plays at; a non-positive or non-finite value is ignored. */
|
|
78
|
+
setReplaySpeed(speed: number): void;
|
|
79
|
+
/** @internal RP2 report part for this mount (src/mcp-fakes-report.ts reads it). */
|
|
80
|
+
fakesReportPart(testId: string | null, context: string | null, mount: string): McpFakeReportPart;
|
|
54
81
|
handleRequest(req: http$1.IncomingMessage, res: http$1.ServerResponse, pathname: string): Promise<boolean>;
|
|
55
82
|
/**
|
|
56
83
|
* Serve one request, in both modes (mounted: a POST or DELETE on the mount
|
|
@@ -91,12 +118,16 @@ declare class MCPMock implements Mountable {
|
|
|
91
118
|
* like a failure, under `MCP_FAKE_INTERNAL_ERROR`.
|
|
92
119
|
*/
|
|
93
120
|
private onFakeEvent;
|
|
121
|
+
/** RP2, C9: a tools/call no fake applied to is always logged, with who answered it. */
|
|
122
|
+
private logUnfaked;
|
|
94
123
|
/**
|
|
95
124
|
* L2: one warning per session for each identity field and source whose
|
|
96
125
|
* value is not valid percent-encoding (H1), naming that source and value,
|
|
97
126
|
* and one for query names that are not valid percent-encoding.
|
|
98
127
|
*/
|
|
99
128
|
private onDecodeFallback;
|
|
129
|
+
/** The recorder's view of this mount (fakes, journal, logger, replay speed, side channels). */
|
|
130
|
+
private recorderHost;
|
|
100
131
|
private buildHandler;
|
|
101
132
|
}
|
|
102
133
|
//# sourceMappingURL=mcp-mock.d.ts.map
|
package/dist/mcp-mock.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-mock.d.cts","names":[],"sources":["../src/mcp-mock.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp-mock.d.cts","names":[],"sources":["../src/mcp-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cAwCa,OAAA,YAAmB;;EAAnB,QAAA,SAAQ;EAAA,QAAA,OAAA;UAqCG,QAAA;;UAcQ,KAAA;;UAAwB,UAAA;;;;;;UAsBL,YAAA;UAcrB,MAAA;UAOR,OAAA;UAAyB,QAAA;;UAmBjC,MAAA;;UAqDW,WAAA;;UAIL,QAAA;UAU6D,OAAA;UAOxE,cAAK;aACA,CAAA,OAAA,CAAA,EAvJU,cAuJV;SAET,CAAA,GAAA,EAlJU,iBAkJV,CAAA,EAAA,IAAA;YA2GiB,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GAtPU,UAsPV,EAAA,GAAA,MAAA,GAtPkC,OAsPlC,CAtP0C,UAsP1C,EAAA,GAAA,MAAA,CAAA,CAAA,EAAA,IAAA;aAME,CAAA,GAAA,EA/OL,qBA+OK,EAAA,OAAA,CAAA,EA/O4B,kBA+O5B,CAAA,EAAA,IAAA;WAMP,CAAA,GAAA,EA7OR,mBA6OQ,EAAA,OAAA,CAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GA5OgB,eA4OhB,GA5OkC,OA4OlC,CA5O0C,eA4O1C,CAAA,CAAA,EAAA,IAAA;;;;;;;8BA9Na;sBAOR,yBAAyB,mBAAmB;;;;;;;;2EAmBpD;;;;;;;;;;;;;;0BA4BY;;;;oBAyBN,KAAK;oBAIL;;;;iFAU6D;qBAOxE,MAAA,CAAK,sBACL,MAAA,CAAK,mCAET;;;;;;;;;;;;;;;;;;;;sBA2GiB;wBAME;WAMP;UAkDD;;iBAkBC,YAAY"}
|
package/dist/mcp-mock.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Journal } from "./journal.js";
|
|
2
2
|
import { Logger } from "./logger.js";
|
|
3
3
|
import { MetricsRegistry } from "./metrics.js";
|
|
4
|
-
import { McpFakeAddOrigin, McpFakeAddResult, McpFakeBlockSnapshot } from "./mcp-fakes.js";
|
|
5
|
-
import { McpFakeSource, Mountable } from "./types.js";
|
|
4
|
+
import { McpFakeAddOrigin, McpFakeAddResult, McpFakeBlockSnapshot, McpFakeReportPart } from "./mcp-fakes.js";
|
|
5
|
+
import { McpFakeSource, McpRecordConfig, Mountable } from "./types.js";
|
|
6
6
|
import { MCPContent, MCPMockOptions, MCPPromptDefinition, MCPPromptResult, MCPResourceContent, MCPResourceDefinition, MCPSession, MCPToolDefinition } from "./mcp-types.js";
|
|
7
|
+
import { McpRecorder } from "./mcp-recorder.js";
|
|
7
8
|
import * as http$1 from "node:http";
|
|
8
9
|
|
|
9
10
|
//#region src/mcp-mock.d.ts
|
|
@@ -27,6 +28,10 @@ declare class MCPMock implements Mountable {
|
|
|
27
28
|
private registry;
|
|
28
29
|
/** Mount logger for L1, L2 and L10; none (lines dropped) until `setLogger`. */
|
|
29
30
|
private logger;
|
|
31
|
+
/** T1: recorded timing (`durationMs`, `atMs`) plays at value / speed. */
|
|
32
|
+
private replaySpeed;
|
|
33
|
+
/** MR1: the recorder while recording is on, else null (AM1). */
|
|
34
|
+
private recorder;
|
|
30
35
|
private options;
|
|
31
36
|
private requestHandler;
|
|
32
37
|
constructor(options?: MCPMockOptions);
|
|
@@ -42,6 +47,8 @@ declare class MCPMock implements Mountable {
|
|
|
42
47
|
*/
|
|
43
48
|
loadFakes(blocks: unknown): McpFakeAddResult;
|
|
44
49
|
addMcpFakes(blocks: McpFakeSource[], origin: McpFakeAddOrigin): McpFakeAddResult;
|
|
50
|
+
/** `addMcpFakes`, plus the internal `record` origin of the MCP recorder. */
|
|
51
|
+
private addFakes;
|
|
45
52
|
/**
|
|
46
53
|
* The blocks on this mount that apply to `testId` and `context` (spec 6.5,
|
|
47
54
|
* I9), with every entry id and its consumed state for that test id. `null`
|
|
@@ -50,7 +57,27 @@ declare class MCPMock implements Mountable {
|
|
|
50
57
|
fakesSnapshot(testId?: string | null, context?: string | null): readonly McpFakeBlockSnapshot[];
|
|
51
58
|
clearMcpFakes(): void;
|
|
52
59
|
resetScenarioState(testId?: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Record a real upstream MCP server (MR1): from now on every request to
|
|
62
|
+
* this mount is forwarded to `config.upstream` (all paths and methods), a
|
|
63
|
+
* fake that applies still answers locally, and each forwarded `tools/list`
|
|
64
|
+
* and `tools/call` is written to an `mcpFakes` file under
|
|
65
|
+
* `config.fixturePath`. `secretValues` and `upstreamAuth` that are
|
|
66
|
+
* `undefined` come from `AIMOCK_RECORD_SECRET_VALUES` and
|
|
67
|
+
* `AIMOCK_MCP_UPSTREAM_AUTH`, as on the CLI and config paths. Throws on an
|
|
68
|
+
* invalid upstream URL, a malformed `upstreamAuth` (or env auth), or a
|
|
69
|
+
* `secretValues` entry shorter than 8 characters.
|
|
70
|
+
*/
|
|
71
|
+
enableRecording(config: McpRecordConfig): this;
|
|
72
|
+
/** Stop recording: the mount serves exactly as before `enableRecording`. */
|
|
73
|
+
disableRecording(): this;
|
|
74
|
+
/** @internal MR13: the recorder's write hashes and write listener, or null when not recording. */
|
|
75
|
+
recorderEvents(): Pick<McpRecorder, "lastWrittenHash" | "onWrite"> | null;
|
|
53
76
|
setLogger(logger: Logger): void;
|
|
77
|
+
/** T1: the speed recorded fake timing plays at; a non-positive or non-finite value is ignored. */
|
|
78
|
+
setReplaySpeed(speed: number): void;
|
|
79
|
+
/** @internal RP2 report part for this mount (src/mcp-fakes-report.ts reads it). */
|
|
80
|
+
fakesReportPart(testId: string | null, context: string | null, mount: string): McpFakeReportPart;
|
|
54
81
|
handleRequest(req: http$1.IncomingMessage, res: http$1.ServerResponse, pathname: string): Promise<boolean>;
|
|
55
82
|
/**
|
|
56
83
|
* Serve one request, in both modes (mounted: a POST or DELETE on the mount
|
|
@@ -91,12 +118,16 @@ declare class MCPMock implements Mountable {
|
|
|
91
118
|
* like a failure, under `MCP_FAKE_INTERNAL_ERROR`.
|
|
92
119
|
*/
|
|
93
120
|
private onFakeEvent;
|
|
121
|
+
/** RP2, C9: a tools/call no fake applied to is always logged, with who answered it. */
|
|
122
|
+
private logUnfaked;
|
|
94
123
|
/**
|
|
95
124
|
* L2: one warning per session for each identity field and source whose
|
|
96
125
|
* value is not valid percent-encoding (H1), naming that source and value,
|
|
97
126
|
* and one for query names that are not valid percent-encoding.
|
|
98
127
|
*/
|
|
99
128
|
private onDecodeFallback;
|
|
129
|
+
/** The recorder's view of this mount (fakes, journal, logger, replay speed, side channels). */
|
|
130
|
+
private recorderHost;
|
|
100
131
|
private buildHandler;
|
|
101
132
|
}
|
|
102
133
|
//# sourceMappingURL=mcp-mock.d.ts.map
|
package/dist/mcp-mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-mock.d.ts","names":[],"sources":["../src/mcp-mock.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp-mock.d.ts","names":[],"sources":["../src/mcp-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cAwCa,OAAA,YAAmB;;EAAnB,QAAA,SAAQ;EAAA,QAAA,OAAA;UAqCG,QAAA;;UAcQ,KAAA;;UAAwB,UAAA;;;;;;UAsBL,YAAA;UAcrB,MAAA;UAOR,OAAA;UAAyB,QAAA;;UAmBjC,MAAA;;UAqDW,WAAA;;UAIL,QAAA;UAU6D,OAAA;UAOxE,cAAK;aACA,CAAA,OAAA,CAAA,EAvJU,cAuJV;SAET,CAAA,GAAA,EAlJU,iBAkJV,CAAA,EAAA,IAAA;YA2GiB,CAAA,IAAA,EAAA,MAAA,EAAA,OAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GAtPU,UAsPV,EAAA,GAAA,MAAA,GAtPkC,OAsPlC,CAtP0C,UAsP1C,EAAA,GAAA,MAAA,CAAA,CAAA,EAAA,IAAA;aAME,CAAA,GAAA,EA/OL,qBA+OK,EAAA,OAAA,CAAA,EA/O4B,kBA+O5B,CAAA,EAAA,IAAA;WAMP,CAAA,GAAA,EA7OR,mBA6OQ,EAAA,OAAA,CAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GA5OgB,eA4OhB,GA5OkC,OA4OlC,CA5O0C,eA4O1C,CAAA,CAAA,EAAA,IAAA;;;;;;;8BA9Na;sBAOR,yBAAyB,mBAAmB;;;;;;;;2EAmBpD;;;;;;;;;;;;;;0BA4BY;;;;oBAyBN,KAAK;oBAIL;;;;iFAU6D;qBAOxE,MAAA,CAAK,sBACL,MAAA,CAAK,mCAET;;;;;;;;;;;;;;;;;;;;sBA2GiB;wBAME;WAMP;UAkDD;;iBAkBC,YAAY"}
|