orcareplay 0.1.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/dist/api.d.ts +127 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +159 -0
- package/dist/api.js.map +1 -0
- package/dist/args.d.ts +24 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +124 -0
- package/dist/args.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/compare.d.ts +27 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +182 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +358 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gc.d.ts +24 -0
- package/dist/commands/gc.d.ts.map +1 -0
- package/dist/commands/gc.js +326 -0
- package/dist/commands/gc.js.map +1 -0
- package/dist/commands/inspect.d.ts +24 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +262 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/record.d.ts +17 -0
- package/dist/commands/record.d.ts.map +1 -0
- package/dist/commands/record.js +497 -0
- package/dist/commands/record.js.map +1 -0
- package/dist/commands/replay.d.ts +43 -0
- package/dist/commands/replay.d.ts.map +1 -0
- package/dist/commands/replay.js +800 -0
- package/dist/commands/replay.js.map +1 -0
- package/dist/commands/scrub.d.ts +34 -0
- package/dist/commands/scrub.d.ts.map +1 -0
- package/dist/commands/scrub.js +452 -0
- package/dist/commands/scrub.js.map +1 -0
- package/dist/commands/setup.d.ts +23 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +177 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/config.d.ts +80 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +117 -0
- package/dist/config.js.map +1 -0
- package/dist/exchange-events.d.ts +72 -0
- package/dist/exchange-events.d.ts.map +1 -0
- package/dist/exchange-events.js +170 -0
- package/dist/exchange-events.js.map +1 -0
- package/dist/flags.d.ts +10 -0
- package/dist/flags.d.ts.map +1 -0
- package/dist/flags.js +83 -0
- package/dist/flags.js.map +1 -0
- package/dist/fs-events.d.ts +19 -0
- package/dist/fs-events.d.ts.map +1 -0
- package/dist/fs-events.js +53 -0
- package/dist/fs-events.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +282 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp-server.d.ts +44 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +245 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +111 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +221 -0
- package/dist/mcp.js.map +1 -0
- package/dist/out.d.ts +69 -0
- package/dist/out.d.ts.map +1 -0
- package/dist/out.js +138 -0
- package/dist/out.js.map +1 -0
- package/dist/rasterize.d.ts +62 -0
- package/dist/rasterize.d.ts.map +1 -0
- package/dist/rasterize.js +154 -0
- package/dist/rasterize.js.map +1 -0
- package/dist/serial.d.ts +19 -0
- package/dist/serial.d.ts.map +1 -0
- package/dist/serial.js +34 -0
- package/dist/serial.js.map +1 -0
- package/dist/share-card.d.ts +73 -0
- package/dist/share-card.d.ts.map +1 -0
- package/dist/share-card.js +413 -0
- package/dist/share-card.js.map +1 -0
- package/dist/snapshot.d.ts +30 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +43 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/tls-capture.d.ts +57 -0
- package/dist/tls-capture.d.ts.map +1 -0
- package/dist/tls-capture.js +198 -0
- package/dist/tls-capture.js.map +1 -0
- package/dist/upstream.d.ts +20 -0
- package/dist/upstream.d.ts.map +1 -0
- package/dist/upstream.js +31 -0
- package/dist/upstream.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { JsonRpcFramer } from '@orcareplay/mcp-shim';
|
|
2
|
+
import { Orca } from './api.js';
|
|
3
|
+
import { ORCA_VERSION } from './version.js';
|
|
4
|
+
/**
|
|
5
|
+
* Orca as tools an agent can call.
|
|
6
|
+
*
|
|
7
|
+
* `@orcareplay/mcp-shim` records an agent's *own* MCP traffic — it is a tee, so nothing has ever
|
|
8
|
+
* let an agent ask orca a question. That is backwards for this product in particular: "replay my
|
|
9
|
+
* last run and tell me what diverged" is the most useful thing an agent could ask a replay
|
|
10
|
+
* debugger, and it is a question no observability tool can answer, because a trace is a file.
|
|
11
|
+
*
|
|
12
|
+
* Written against the framer the shim already has rather than an SDK. The stdio transport is
|
|
13
|
+
* newline-delimited JSON-RPC and the surface is four method names, so a runtime dependency here
|
|
14
|
+
* would cost more than it carries — and this package has none.
|
|
15
|
+
*
|
|
16
|
+
* The tool set is deliberately narrow and read-mostly. `orca_replay` costs nothing and touches no
|
|
17
|
+
* network; `orca_compare` spends real tokens, and says so in its own description, because an
|
|
18
|
+
* agent choosing a tool reads that string and nothing else.
|
|
19
|
+
*/
|
|
20
|
+
/** The version of the MCP spec these messages are shaped for. */
|
|
21
|
+
const PROTOCOL_VERSION = '2025-06-18';
|
|
22
|
+
const METHOD_NOT_FOUND = -32601;
|
|
23
|
+
/** Every tool takes `run`, because "which run" is the only question all of them share. */
|
|
24
|
+
const RUN_ARG = {
|
|
25
|
+
run: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Run id, or "last" for the newest recording. Defaults to "last". Replay traces are ' +
|
|
28
|
+
'skipped when resolving "last", so it means the newest run you actually recorded.',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const MCP_TOOLS = [
|
|
32
|
+
{
|
|
33
|
+
name: 'orca_list_runs',
|
|
34
|
+
description: 'List every agent run recorded in this project, newest first, with the run it was forked ' +
|
|
35
|
+
'from where there is one. Start here when you do not already know which run to look at.',
|
|
36
|
+
inputSchema: { type: 'object', properties: {} },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'orca_show_run',
|
|
40
|
+
description: 'The full timeline of one run: every model turn with its token counts and stop reason, ' +
|
|
41
|
+
'every tool call with its arguments and result, every shell command with its exit code, ' +
|
|
42
|
+
'and every file the run changed. This is what tells you why an agent did something, ' +
|
|
43
|
+
'rather than what it cost.',
|
|
44
|
+
inputSchema: { type: 'object', properties: { ...RUN_ARG } },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'orca_checkpoints',
|
|
48
|
+
description: 'The points in a run a fork can start from — where the conversation prefix is complete and ' +
|
|
49
|
+
'the workspace was snapshotted. Use before orca_compare to pick a fork point.',
|
|
50
|
+
inputSchema: { type: 'object', properties: { ...RUN_ARG } },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'orca_graph',
|
|
54
|
+
description: 'What caused what in a run, as a list of edges. Each edge says which event produced which, ' +
|
|
55
|
+
'and whether it is `recorded` — the recorder watched it happen and wrote it into the trace ' +
|
|
56
|
+
'— or `inferred`, meaning this derived it just now from the rule it names and the trace ' +
|
|
57
|
+
'does not vouch for it. Pass `to` to get only the chain that produced one event, which is ' +
|
|
58
|
+
'the shape of an answer to "why did this fail" rather than "what happened".',
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
...RUN_ARG,
|
|
63
|
+
to: {
|
|
64
|
+
type: 'number',
|
|
65
|
+
description: 'Narrow to the chain that produced this event seq. Omit for the whole run.',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'orca_replay',
|
|
72
|
+
description: 'Re-run a recording exactly, with the network blocked and no tokens spent, and report what ' +
|
|
73
|
+
'could not be reproduced: divergences, and requests the recording could not serve. Free ' +
|
|
74
|
+
'and repeatable. Use it to confirm a failure is deterministic before trying to explain it.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
...RUN_ARG,
|
|
79
|
+
worktree: {
|
|
80
|
+
type: 'boolean',
|
|
81
|
+
description: 'Replay in a scratch copy instead of over the working tree. Slower, and leaves the ' +
|
|
82
|
+
'files you are looking at untouched.',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'orca_compare',
|
|
89
|
+
description: 'Fork one recorded run onto several models from the same checkpoint — same files, same ' +
|
|
90
|
+
'conversation prefix — and grade each with a command you choose. SPENDS REAL TOKENS and ' +
|
|
91
|
+
'reaches the network: every model named is actually called. Ask before using it.',
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
...RUN_ARG,
|
|
96
|
+
models: {
|
|
97
|
+
type: 'array',
|
|
98
|
+
items: { type: 'string' },
|
|
99
|
+
description: 'Model ids to compare. Required.',
|
|
100
|
+
},
|
|
101
|
+
from: { type: 'number', description: 'Checkpoint seq to fork at, from orca_checkpoints.' },
|
|
102
|
+
verify: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'Shell command whose exit code is the verdict, e.g. "npm test".',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ['models'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
function asRecord(value) {
|
|
112
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
113
|
+
? value
|
|
114
|
+
: {};
|
|
115
|
+
}
|
|
116
|
+
/** A tool result in MCP's shape: text content, plus the flag that says it failed. */
|
|
117
|
+
function content(text, isError = false) {
|
|
118
|
+
return { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) };
|
|
119
|
+
}
|
|
120
|
+
function ok(json) {
|
|
121
|
+
return content(JSON.stringify(json, null, 2));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Handle one message.
|
|
125
|
+
*
|
|
126
|
+
* Returns `undefined` for a notification, which by definition has no id to answer — replying to
|
|
127
|
+
* one is a protocol error that some clients treat as fatal.
|
|
128
|
+
*/
|
|
129
|
+
export async function handleMcpMessage(orca, message) {
|
|
130
|
+
const req = asRecord(message);
|
|
131
|
+
const { id, method } = req;
|
|
132
|
+
if (id === undefined)
|
|
133
|
+
return undefined;
|
|
134
|
+
const reply = (result) => ({ jsonrpc: '2.0', id, result });
|
|
135
|
+
const fail = (code, message) => ({
|
|
136
|
+
jsonrpc: '2.0',
|
|
137
|
+
id,
|
|
138
|
+
error: { code, message },
|
|
139
|
+
});
|
|
140
|
+
switch (method) {
|
|
141
|
+
case 'initialize':
|
|
142
|
+
return reply({
|
|
143
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
144
|
+
capabilities: { tools: { listChanged: false } },
|
|
145
|
+
serverInfo: { name: 'orcareplay', version: ORCA_VERSION },
|
|
146
|
+
});
|
|
147
|
+
case 'ping':
|
|
148
|
+
return reply({});
|
|
149
|
+
case 'tools/list':
|
|
150
|
+
return reply({ tools: MCP_TOOLS });
|
|
151
|
+
case 'tools/call':
|
|
152
|
+
return reply(await callTool(orca, asRecord(req.params)));
|
|
153
|
+
default:
|
|
154
|
+
return fail(METHOD_NOT_FOUND, `orca's MCP server does not implement '${method ?? '(none)'}'`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Run one tool.
|
|
159
|
+
*
|
|
160
|
+
* Every failure comes back as `isError` content rather than a JSON-RPC error: the spec draws that
|
|
161
|
+
* line so a model can read what went wrong and try something else, where a transport error ends
|
|
162
|
+
* the session. A bad run id is an ordinary answer here, not a crash.
|
|
163
|
+
*/
|
|
164
|
+
async function callTool(orca, params) {
|
|
165
|
+
const name = typeof params['name'] === 'string' ? params['name'] : '';
|
|
166
|
+
const args = asRecord(params['arguments']);
|
|
167
|
+
const run = typeof args['run'] === 'string' && args['run'] !== '' ? args['run'] : 'last';
|
|
168
|
+
const tool = MCP_TOOLS.find((t) => t.name === name);
|
|
169
|
+
if (!tool) {
|
|
170
|
+
return content(`no such tool '${name}'. orca serves: ${MCP_TOOLS.map((t) => t.name).join(', ')}`, true);
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
switch (name) {
|
|
174
|
+
case 'orca_list_runs':
|
|
175
|
+
return ok(await orca.list());
|
|
176
|
+
case 'orca_show_run':
|
|
177
|
+
return ok(await orca.show(run));
|
|
178
|
+
case 'orca_checkpoints':
|
|
179
|
+
return ok(await orca.checkpoints(run));
|
|
180
|
+
case 'orca_graph': {
|
|
181
|
+
const to = args['to'];
|
|
182
|
+
if (to !== undefined && typeof to !== 'number') {
|
|
183
|
+
return content('to must be a number', true);
|
|
184
|
+
}
|
|
185
|
+
return ok(await orca.graph(run, to === undefined ? {} : { to }));
|
|
186
|
+
}
|
|
187
|
+
case 'orca_replay': {
|
|
188
|
+
const worktree = args['worktree'];
|
|
189
|
+
if (worktree !== undefined && typeof worktree !== 'boolean') {
|
|
190
|
+
return content('worktree must be a boolean', true);
|
|
191
|
+
}
|
|
192
|
+
return ok(await orca.replay(run, worktree === true ? { worktree: true } : {}));
|
|
193
|
+
}
|
|
194
|
+
case 'orca_compare': {
|
|
195
|
+
const models = Array.isArray(args['models'])
|
|
196
|
+
? args['models'].filter((m) => typeof m === 'string')
|
|
197
|
+
: [];
|
|
198
|
+
if (models.length === 0)
|
|
199
|
+
return content('compare needs at least one model id', true);
|
|
200
|
+
const from = args['from'];
|
|
201
|
+
// Coercing a string here would silently fork from checkpoint 0 and produce a comparison
|
|
202
|
+
// whose one variable was not the model.
|
|
203
|
+
if (from !== undefined && typeof from !== 'number') {
|
|
204
|
+
return content(`from must be a number, got ${JSON.stringify(from)}`, true);
|
|
205
|
+
}
|
|
206
|
+
const verify = args['verify'];
|
|
207
|
+
if (verify !== undefined && typeof verify !== 'string') {
|
|
208
|
+
return content('verify must be a string', true);
|
|
209
|
+
}
|
|
210
|
+
return ok(await orca.compare(run, {
|
|
211
|
+
models,
|
|
212
|
+
...(typeof from === 'number' ? { from } : {}),
|
|
213
|
+
...(typeof verify === 'string' ? { verify } : {}),
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
return content(`no such tool '${name}'`, true);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
return content(err instanceof Error ? err.message : String(err), true);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Serve MCP over a stream pair until the input ends.
|
|
226
|
+
*
|
|
227
|
+
* Messages are handled one at a time. Concurrency would buy nothing — every tool here is either a
|
|
228
|
+
* file read or a subprocess that owns the working tree — and it would let two replays fight over
|
|
229
|
+
* the same checkout.
|
|
230
|
+
*/
|
|
231
|
+
export async function serveMcp({ orca, input, output }) {
|
|
232
|
+
const framer = new JsonRpcFramer();
|
|
233
|
+
for await (const chunk of input) {
|
|
234
|
+
for (const frame of framer.push(chunk)) {
|
|
235
|
+
// A line that is not JSON is a stray log from something upstream, not a message. Dropping it
|
|
236
|
+
// is the only safe answer: there is no id to report an error against.
|
|
237
|
+
if (frame.message === undefined)
|
|
238
|
+
continue;
|
|
239
|
+
const response = await handleMcpMessage(orca, frame.message);
|
|
240
|
+
if (response !== undefined)
|
|
241
|
+
output.write(`${JSON.stringify(response)}\n`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { TraceWriter } from '@orcareplay/core';
|
|
2
|
+
import type { McpFrameRecord } from '@orcareplay/mcp-shim';
|
|
3
|
+
import type { Output } from './out.js';
|
|
4
|
+
export interface McpCapture {
|
|
5
|
+
/** Path to the rewritten config the agent should be pointed at. */
|
|
6
|
+
configPath: string;
|
|
7
|
+
rewritten: string[];
|
|
8
|
+
skipped: string[];
|
|
9
|
+
/** Frames observed so far, drained by the recorder. */
|
|
10
|
+
drain(): Promise<McpFrameRecord[]>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A captured frame, as the shim writes it.
|
|
14
|
+
*
|
|
15
|
+
* Re-exported rather than redeclared. The version that used to live here named two of the same
|
|
16
|
+
* fields differently — `server` for `name`, `direction` for `dir` — and since `JSON.parse` casts to
|
|
17
|
+
* whatever the call site claims, nothing anywhere disagreed: every MCP event went into the trace
|
|
18
|
+
* with an undefined server, and `direction === 'in'` was false for all of them, so every request
|
|
19
|
+
* was recorded as a response. A format has one owner, and it is the thing that writes it.
|
|
20
|
+
*/
|
|
21
|
+
export type { McpFrameRecord };
|
|
22
|
+
/**
|
|
23
|
+
* The MCP config a *replay* should instrument: the flag if one was given, else whatever the
|
|
24
|
+
* recording itself used.
|
|
25
|
+
*
|
|
26
|
+
* Replay and fork launch the same agent the recording did, so they hit the same blind spot record
|
|
27
|
+
* had before `--mcp-config` existed: without a config the harness either talks to servers orca
|
|
28
|
+
* cannot see, or — for one that requires the variable — does not start at all. The recording knows
|
|
29
|
+
* the answer; it just had nowhere to write it down, since `manifest.argv` holds only the agent's
|
|
30
|
+
* own arguments and `--mcp-config` is orca's.
|
|
31
|
+
*
|
|
32
|
+
* The path is read back from the run's own `mcp_instrumented` note. Nothing is re-instrumented from
|
|
33
|
+
* the *rewritten* config in the parent run directory: its servers already point at the parent's
|
|
34
|
+
* frames file, so reusing it would append this replay's traffic to the recording it is replaying.
|
|
35
|
+
*/
|
|
36
|
+
export declare function mcpSourceFrom(flagValue: string | undefined, events: {
|
|
37
|
+
type: string;
|
|
38
|
+
attrs?: Record<string, unknown>;
|
|
39
|
+
}[]): string | undefined;
|
|
40
|
+
/** Did this run capture MCP at all? Decides whether a replay without a config is worth warning about. */
|
|
41
|
+
export declare function usedMcp(events: {
|
|
42
|
+
type: string;
|
|
43
|
+
}[]): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Set up MCP capture for a replay or a fork, from the flag or from what the recording used.
|
|
46
|
+
*
|
|
47
|
+
* Returns undefined — quietly — when the run never had MCP, which is most runs. When it did and the
|
|
48
|
+
* source config has since moved or been deleted, that is said out loud rather than silently
|
|
49
|
+
* dropping a capture layer: an absent `mcp.*` in the replay would otherwise read as "the agent made
|
|
50
|
+
* no MCP calls" instead of "orca was not looking".
|
|
51
|
+
*/
|
|
52
|
+
export declare function mcpForReplay(args: {
|
|
53
|
+
str(name: string): string | undefined;
|
|
54
|
+
bool?(name: string, dflt?: boolean): boolean;
|
|
55
|
+
}, events: {
|
|
56
|
+
type: string;
|
|
57
|
+
attrs?: Record<string, unknown>;
|
|
58
|
+
}[], writer: TraceWriter, out: Output,
|
|
59
|
+
/** The recording's own frames, so the servers are answered from rather than started. */
|
|
60
|
+
recordedFrames?: string): Promise<McpCapture | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* Point the launched agent at the instrumented config.
|
|
63
|
+
*
|
|
64
|
+
* Every target harness reads one of these; setting all three costs nothing and avoids making the
|
|
65
|
+
* user work out which one their agent uses.
|
|
66
|
+
*/
|
|
67
|
+
export declare function pointAtMcpConfig(env: Record<string, string>, configPath: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Drain captured frames into a trace as `mcp.request` / `mcp.response`.
|
|
70
|
+
*
|
|
71
|
+
* Frames are read off disk after the agent exits, so each carries the moment it passed through the
|
|
72
|
+
* shim and is stamped with that rather than with the drain — `mono_us` is authoritative for
|
|
73
|
+
* duration (spec §2.1), and a frame stamped at the drain can never interleave with the model turns
|
|
74
|
+
* it actually sat between.
|
|
75
|
+
*/
|
|
76
|
+
export declare function drainMcpFrames(mcp: McpCapture, writer: TraceWriter, turnAt: (at: number) => number, fallbackTurn: number): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Set up MCP capture for a run.
|
|
79
|
+
*
|
|
80
|
+
* The mechanism is a config rewrite, not a patch: each stdio server is relaunched through
|
|
81
|
+
* `orca-mcp-shim`, a transparent JSON-RPC tee. The agent's own config file is never touched — a
|
|
82
|
+
* rewritten copy goes in the run directory, and the agent is pointed at that.
|
|
83
|
+
*
|
|
84
|
+
* HTTP and SSE servers are deliberately left alone and reported as skipped; they route through the
|
|
85
|
+
* HTTP proxy instead, and silently dropping them would be worse than not capturing them.
|
|
86
|
+
*/
|
|
87
|
+
export declare function setupMcpCapture(opts: {
|
|
88
|
+
sourceConfigPath: string;
|
|
89
|
+
runDir: string;
|
|
90
|
+
out: Output;
|
|
91
|
+
/**
|
|
92
|
+
* Frames to answer from instead of starting the servers.
|
|
93
|
+
*
|
|
94
|
+
* Capture was only half of what this layer is for. The reason to record a server's traffic is
|
|
95
|
+
* that the run stays reproducible once the server is not — the token revoked, the repository
|
|
96
|
+
* moved, the service retired. Replay re-instrumented the same config and started the real server
|
|
97
|
+
* again, so an MCP recording could be read but not reproduced, and came apart on the first call
|
|
98
|
+
* when the server had gone. Pointed at a capture, the shim serves from it and launches nothing.
|
|
99
|
+
*/
|
|
100
|
+
replayFrames?: string;
|
|
101
|
+
}): Promise<McpCapture | undefined>;
|
|
102
|
+
/**
|
|
103
|
+
* Exposed for the doctor command: is the shim runnable at all?
|
|
104
|
+
*
|
|
105
|
+
* It checks that the thing it launched *is the shim*, not merely that a process started and
|
|
106
|
+
* stopped. Asking only for a clean exit is what let doctor vouch for a resolution that had landed
|
|
107
|
+
* on the library: that module exits 0 having done nothing, which is indistinguishable from success
|
|
108
|
+
* unless you look at what it said.
|
|
109
|
+
*/
|
|
110
|
+
export declare function shimIsRunnable(): Promise<boolean>;
|
|
111
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,WAAW,UAAU;IACzB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uDAAuD;IACvD,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAAE,GAC1D,MAAM,GAAG,SAAS,CAQpB;AAED,yGAAyG;AACzG,wBAAgB,OAAO,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,OAAO,CAE3D;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAAC,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;CAAE,EAC7F,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAAE,EAC3D,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,MAAM;AACX,wFAAwF;AACxF,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA2BjC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAItF;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,UAAU,EACf,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,EAC9B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAsDlC;AAwBD;;;;;;;GAOG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAqBvD"}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { rewriteMcpConfig } from '@orcareplay/adapters';
|
|
6
|
+
/**
|
|
7
|
+
* The MCP config a *replay* should instrument: the flag if one was given, else whatever the
|
|
8
|
+
* recording itself used.
|
|
9
|
+
*
|
|
10
|
+
* Replay and fork launch the same agent the recording did, so they hit the same blind spot record
|
|
11
|
+
* had before `--mcp-config` existed: without a config the harness either talks to servers orca
|
|
12
|
+
* cannot see, or — for one that requires the variable — does not start at all. The recording knows
|
|
13
|
+
* the answer; it just had nowhere to write it down, since `manifest.argv` holds only the agent's
|
|
14
|
+
* own arguments and `--mcp-config` is orca's.
|
|
15
|
+
*
|
|
16
|
+
* The path is read back from the run's own `mcp_instrumented` note. Nothing is re-instrumented from
|
|
17
|
+
* the *rewritten* config in the parent run directory: its servers already point at the parent's
|
|
18
|
+
* frames file, so reusing it would append this replay's traffic to the recording it is replaying.
|
|
19
|
+
*/
|
|
20
|
+
export function mcpSourceFrom(flagValue, events) {
|
|
21
|
+
if (flagValue)
|
|
22
|
+
return flagValue;
|
|
23
|
+
for (const event of events) {
|
|
24
|
+
if (event.type !== 'note' || event.attrs?.rule !== 'mcp_instrumented')
|
|
25
|
+
continue;
|
|
26
|
+
const source = event.attrs.source;
|
|
27
|
+
if (typeof source === 'string' && source !== '')
|
|
28
|
+
return source;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
/** Did this run capture MCP at all? Decides whether a replay without a config is worth warning about. */
|
|
33
|
+
export function usedMcp(events) {
|
|
34
|
+
return events.some((e) => e.type === 'mcp.request' || e.type === 'mcp.response');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Set up MCP capture for a replay or a fork, from the flag or from what the recording used.
|
|
38
|
+
*
|
|
39
|
+
* Returns undefined — quietly — when the run never had MCP, which is most runs. When it did and the
|
|
40
|
+
* source config has since moved or been deleted, that is said out loud rather than silently
|
|
41
|
+
* dropping a capture layer: an absent `mcp.*` in the replay would otherwise read as "the agent made
|
|
42
|
+
* no MCP calls" instead of "orca was not looking".
|
|
43
|
+
*/
|
|
44
|
+
export async function mcpForReplay(args, events, writer, out,
|
|
45
|
+
/** The recording's own frames, so the servers are answered from rather than started. */
|
|
46
|
+
recordedFrames) {
|
|
47
|
+
const source = mcpSourceFrom(args.str('mcp-config'), events);
|
|
48
|
+
if (source === undefined) {
|
|
49
|
+
if (usedMcp(events)) {
|
|
50
|
+
out.warn('mcp.not_instrumented', {
|
|
51
|
+
why: 'the recording captured MCP but did not record which config it came from',
|
|
52
|
+
next: 'pass --mcp-config <path> to capture it here too',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
if (!(await stat(source).catch(() => null))) {
|
|
58
|
+
out.warn('mcp.source_missing', {
|
|
59
|
+
path: source,
|
|
60
|
+
note: 'the config the recording used is no longer there; MCP will not be captured',
|
|
61
|
+
});
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return setupMcpCapture({
|
|
65
|
+
sourceConfigPath: source,
|
|
66
|
+
runDir: writer.runDir,
|
|
67
|
+
out,
|
|
68
|
+
// A replay that starts the real servers is not a replay of them: it is a second live run whose
|
|
69
|
+
// answers happen to be read back through the same shim. Answering from the recording is what
|
|
70
|
+
// makes an MCP run reproducible after the server it talked to is gone.
|
|
71
|
+
...(recordedFrames === undefined ? {} : { replayFrames: recordedFrames }),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Point the launched agent at the instrumented config.
|
|
76
|
+
*
|
|
77
|
+
* Every target harness reads one of these; setting all three costs nothing and avoids making the
|
|
78
|
+
* user work out which one their agent uses.
|
|
79
|
+
*/
|
|
80
|
+
export function pointAtMcpConfig(env, configPath) {
|
|
81
|
+
env.MCP_CONFIG_PATH = configPath;
|
|
82
|
+
env.CLAUDE_MCP_CONFIG = configPath;
|
|
83
|
+
env.OPENCODE_MCP_CONFIG = configPath;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Drain captured frames into a trace as `mcp.request` / `mcp.response`.
|
|
87
|
+
*
|
|
88
|
+
* Frames are read off disk after the agent exits, so each carries the moment it passed through the
|
|
89
|
+
* shim and is stamped with that rather than with the drain — `mono_us` is authoritative for
|
|
90
|
+
* duration (spec §2.1), and a frame stamped at the drain can never interleave with the model turns
|
|
91
|
+
* it actually sat between.
|
|
92
|
+
*/
|
|
93
|
+
export async function drainMcpFrames(mcp, writer, turnAt, fallbackTurn) {
|
|
94
|
+
for (const frame of await mcp.drain()) {
|
|
95
|
+
const at = frame.ts === undefined ? Number.NaN : Date.parse(frame.ts);
|
|
96
|
+
const when = Number.isNaN(at) ? undefined : new Date(at);
|
|
97
|
+
await writer.append({
|
|
98
|
+
type: frame.dir === 'in' ? 'mcp.request' : 'mcp.response',
|
|
99
|
+
actor: 'agent',
|
|
100
|
+
turn: when === undefined ? fallbackTurn : turnAt(at),
|
|
101
|
+
...(when === undefined ? {} : { occurredAt: when }),
|
|
102
|
+
attrs: { server: frame.name, kind: frame.kind, method: frame.method, id: frame.id },
|
|
103
|
+
payload: frame.raw,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Set up MCP capture for a run.
|
|
109
|
+
*
|
|
110
|
+
* The mechanism is a config rewrite, not a patch: each stdio server is relaunched through
|
|
111
|
+
* `orca-mcp-shim`, a transparent JSON-RPC tee. The agent's own config file is never touched — a
|
|
112
|
+
* rewritten copy goes in the run directory, and the agent is pointed at that.
|
|
113
|
+
*
|
|
114
|
+
* HTTP and SSE servers are deliberately left alone and reported as skipped; they route through the
|
|
115
|
+
* HTTP proxy instead, and silently dropping them would be worse than not capturing them.
|
|
116
|
+
*/
|
|
117
|
+
export async function setupMcpCapture(opts) {
|
|
118
|
+
const raw = await readFile(opts.sourceConfigPath, 'utf8').catch(() => undefined);
|
|
119
|
+
if (raw === undefined) {
|
|
120
|
+
opts.out.warn('mcp.config_unreadable', { path: opts.sourceConfigPath });
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
let parsed;
|
|
124
|
+
try {
|
|
125
|
+
parsed = JSON.parse(raw);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
opts.out.warn('mcp.config_unparseable', { path: opts.sourceConfigPath, error: String(err) });
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
const framesPath = join(opts.runDir, 'mcp-frames.jsonl');
|
|
132
|
+
const shim = resolveShimEntry();
|
|
133
|
+
// Recording and replay are the same rewrite with a different flag: `--out` writes what the
|
|
134
|
+
// servers say, `--replay` says it back. Both keep `--out` so a replay records its own frames and
|
|
135
|
+
// is itself a run that can be read.
|
|
136
|
+
const shimArgs = opts.replayFrames === undefined
|
|
137
|
+
? [shim, '--out', framesPath]
|
|
138
|
+
: [shim, '--out', framesPath, '--replay', opts.replayFrames];
|
|
139
|
+
const { config, rewritten, skipped } = rewriteMcpConfig(parsed, process.execPath, shimArgs);
|
|
140
|
+
const configPath = join(opts.runDir, 'mcp-config.json');
|
|
141
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
142
|
+
opts.out.info('mcp.instrumented', {
|
|
143
|
+
servers: rewritten.length,
|
|
144
|
+
skipped: skipped.length,
|
|
145
|
+
mode: opts.replayFrames === undefined ? 'record' : 'replay',
|
|
146
|
+
config: configPath,
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
configPath,
|
|
150
|
+
rewritten,
|
|
151
|
+
skipped,
|
|
152
|
+
async drain() {
|
|
153
|
+
const text = await readFile(framesPath, 'utf8').catch(() => '');
|
|
154
|
+
const records = [];
|
|
155
|
+
for (const line of text.split('\n')) {
|
|
156
|
+
if (line.trim() === '')
|
|
157
|
+
continue;
|
|
158
|
+
try {
|
|
159
|
+
records.push(JSON.parse(line));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// A malformed capture line must never break the recorder; the agent's run matters more.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return records;
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Locate the installed shim entry point, whether running from source or from a published dist.
|
|
171
|
+
*
|
|
172
|
+
* This used to ask for `@orcareplay/mcp-shim/dist/cli.js` and fall back to `@orcareplay/mcp-shim`
|
|
173
|
+
* when that threw. It always threw — the package declares an `exports` map, and an exports map
|
|
174
|
+
* blocks every subpath it does not list — so the fallback ran every time and resolved to
|
|
175
|
+
* `dist/index.js`, which is the library.
|
|
176
|
+
*
|
|
177
|
+
* The consequence was not a missing capture layer. Every stdio MCP server in the agent's config was
|
|
178
|
+
* rewritten to launch that module, which exports and exits: the real server was never started at
|
|
179
|
+
* all, so `--mcp-config` silently broke the agent's MCP servers *and* recorded nothing, while
|
|
180
|
+
* `orca doctor` reported the shim runnable because the wrong process exited 0.
|
|
181
|
+
*
|
|
182
|
+
* So: one declared subpath, and no fallback that can succeed with the wrong file. A resolution
|
|
183
|
+
* failure has to stay a failure — `setupMcpCapture` reports it, and the run continues without MCP
|
|
184
|
+
* capture, which is the honest outcome.
|
|
185
|
+
*/
|
|
186
|
+
function resolveShimEntry() {
|
|
187
|
+
const require = createRequire(import.meta.url);
|
|
188
|
+
return require.resolve('@orcareplay/mcp-shim/cli');
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Exposed for the doctor command: is the shim runnable at all?
|
|
192
|
+
*
|
|
193
|
+
* It checks that the thing it launched *is the shim*, not merely that a process started and
|
|
194
|
+
* stopped. Asking only for a clean exit is what let doctor vouch for a resolution that had landed
|
|
195
|
+
* on the library: that module exits 0 having done nothing, which is indistinguishable from success
|
|
196
|
+
* unless you look at what it said.
|
|
197
|
+
*/
|
|
198
|
+
export async function shimIsRunnable() {
|
|
199
|
+
let entry;
|
|
200
|
+
try {
|
|
201
|
+
entry = resolveShimEntry();
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return new Promise((resolve) => {
|
|
207
|
+
const child = spawn(process.execPath, [entry, '--help'], {
|
|
208
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
209
|
+
});
|
|
210
|
+
let stderr = '';
|
|
211
|
+
child.stderr?.setEncoding('utf8');
|
|
212
|
+
child.stderr?.on('data', (chunk) => {
|
|
213
|
+
stderr += chunk;
|
|
214
|
+
});
|
|
215
|
+
child.on('error', () => resolve(false));
|
|
216
|
+
// `--help` is not a flag the shim takes, so it prints its usage and exits non-zero. That usage
|
|
217
|
+
// line is the identification: any other program is not this one.
|
|
218
|
+
child.on('close', () => resolve(stderr.includes('orca-mcp-shim --name')));
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
|