@vostride/agent-qa-mcp 0.1.21 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,139 +1,1019 @@
1
- import * as _modelcontextprotocol_sdk_server_mcp from '@modelcontextprotocol/sdk/server/mcp';
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { AnalyticsService, AnalyticsServiceConfig, AnalyticsEventProperties } from '@vostride/agent-qa-core';
4
- import * as _vostride_agent_qa_ids from '@vostride/agent-qa-ids';
5
- import { EntityIdType } from '@vostride/agent-qa-ids';
6
- import { IncomingMessage, ServerResponse } from 'node:http';
1
+ import { StandardSchemaWithJSON, McpServer, McpRequestContext, CallToolResult, RequestMetaEnvelope, ResourceTemplate, ResourceMetadata, ReadResourceTemplateCallback, Variables, ReadResourceResult, ToolAnnotations } from '@modelcontextprotocol/server';
2
+ import { FunctionRegistry } from '@vostride/agent-qa-agent';
3
+ import { FunctionCaller, RiskActionDescriptor, Job } from '@vostride/agent-qa-schema';
4
+ import * as _modelcontextprotocol_node from '@modelcontextprotocol/node';
7
5
 
8
- declare const SCHEMA_REFERENCE_NAMES: readonly ["config", "test", "suite", "hooks", "ids"];
9
- type SchemaReferenceName = typeof SCHEMA_REFERENCE_NAMES[number];
10
- declare const VALIDATION_KINDS: readonly ["config", "test", "suite", "hooks"];
11
- type ValidationKind = typeof VALIDATION_KINDS[number];
12
- declare const AGENT_QA_SCHEMA_REFERENCES: {
13
- readonly config: {
14
- readonly name: "agent-qa config";
15
- readonly rootKeys: readonly ["workspace", "services", "registry", "use", "analytics"];
16
- readonly services: {
17
- readonly dashboard: readonly ["port", "dbPath", "artifactsDir"];
18
- readonly mcp: readonly ["enabled", "transport", "host", "port", "path"];
19
- readonly cache: readonly ["dir", "ttl"];
20
- readonly logging: readonly ["level"];
21
- readonly recording: readonly ["enabled"];
22
- readonly accessibility: readonly ["enabled", "standard", "runAfter", "failOnViolation", "disableRules", "exclude"];
23
- readonly memory: readonly ["enabled", "provider", "dir", "minTrust", "maxInjections", "curatorEnabled"];
24
- };
25
- readonly mcpLocalOnly: {
26
- readonly transports: readonly ["http", "stdio"];
27
- readonly hosts: readonly ["localhost", "127.0.0.1", "::1"];
28
- readonly defaultPort: 3471;
29
- readonly defaultPath: "/mcp";
30
- };
31
- };
32
- readonly test: {
33
- readonly name: "agent-qa test YAML";
34
- readonly requiredKeys: readonly ["test-id", "name", "target", "steps"];
35
- readonly optionalKeys: readonly ["context", "use", "meta", "setup", "teardown"];
36
- readonly idType: EntityIdType;
37
- readonly hookReferences: "setup and teardown arrays must contain canonical h_ hook IDs.";
38
- readonly stepFormats: readonly ["string", "object with step/timeout/retries/screenshot/capture/maxAttempts"];
39
- };
40
- readonly suite: {
41
- readonly name: "agent-qa suite YAML";
42
- readonly requiredKeys: readonly ["name", "target", "tests"];
43
- readonly optionalKeys: readonly ["suite-id", "context", "setup", "teardown", "use"];
44
- readonly idType: EntityIdType;
45
- readonly testReference: "Each tests[] entry uses { test: path, id: canonical t_ test ID }.";
46
- readonly hookReferences: "setup and teardown arrays must contain canonical h_ hook IDs.";
47
- };
48
- readonly hooks: {
49
- readonly name: "agent-qa hooks file";
50
- readonly rootKeys: readonly ["hooks"];
51
- readonly hookKeys: readonly ["id", "name", "runtime", "file", "deps", "packageFile", "timeout", "network"];
52
- readonly idType: EntityIdType;
53
- readonly runtimes: readonly ["node", "bun", "python", "bash"];
6
+ /**
7
+ * The tokenless dashboard live page for one run: `<base>/runs/<runId>/live` (D-08).
8
+ *
9
+ * TRAILING SLASHES ARE COLLAPSED rather than tolerated. An operator writing
10
+ * `baseUrl: http://dash.internal/` is writing the same base as one who did not, and a `//` in the
11
+ * middle of a path is a different path to some proxies — so the composition normalises instead of
12
+ * producing two addresses for one dashboard.
13
+ *
14
+ * THE RUN ID IS PERCENT-ENCODED, which is a no-op for every id this system mints (`runIdSchema`
15
+ * refuses any id the path layer would have to rewrite) and is written anyway: this function is
16
+ * exported, and a caller composing a link for an id that came from somewhere else should not be the
17
+ * one place a path segment escapes unencoded.
18
+ */
19
+ declare function liveRunLinkOf(base: string, runId: string): string;
20
+ /**
21
+ * The human-readable line a carrier result carries when a base exists.
22
+ *
23
+ * AN EXPORTED TEMPLATE RATHER THAN A STRING EACH CALL SITE COMPOSES, so a cell asserts the SAME
24
+ * sentence the surface prints. Two carriers and two hosts print this; four hand-written spellings
25
+ * would be four things to keep in step, and the drift would be invisible because nobody diffs two
26
+ * tool results.
27
+ *
28
+ * IT NAMES BOTH READINGS OF THE ONE PAGE — watching a run that is going, and reviewing one that has
29
+ * finished — because the address is the same either way and a line that promised only "live" would
30
+ * read as broken the moment the run ended, which is precisely when a person follows it.
31
+ */
32
+ declare function liveLinkLine(url: string): string;
33
+ /**
34
+ * What a carrier result says when no dashboard base exists ANYWHERE (RESEARCH Open Question 3).
35
+ *
36
+ * It names the config knob an operator sets to point this host at a dashboard (the 387 D-10
37
+ * mechanism). It contains no URL — an absence that fabricated an address would be worse than no
38
+ * line at all.
39
+ */
40
+ declare const LIVE_LINK_ABSENT_LINE = "no dashboard configured \u2014 set services.dashboard.baseUrl";
41
+ /**
42
+ * What a carrier result says when a dashboard base EXISTS but this result names no run to link to
43
+ * (2026-09-01, Phase 408 plan 10, WR-03 · T-408-22).
44
+ *
45
+ * A SECOND NAMED ABSENCE, BECAUSE THERE ARE TWO DISTINCT FACTS. `carrierRunIdOf`
46
+ * (`catalog.ts`) is DEFENSIVE ON BOTH ARMS by design and can honestly answer `undefined` — the
47
+ * `runIdFrom: 'output'` arm reads an op's own result, which is a `RunStartAcknowledgement` today
48
+ * and could be a fold's arm tomorrow, and the documented treatment of a shape it cannot read is a
49
+ * named absence rather than a crash. Until this constant existed, that arm printed
50
+ * `LIVE_LINK_ABSENT_LINE` — so a host that HAS `services.dashboard.baseUrl` set told the caller,
51
+ * and the model reading the result, to set a value already set and pointed at the wrong knob. That
52
+ * is a FALSE STATEMENT on a model-readable channel in a milestone whose law is honest absence, and
53
+ * a wrong sentence is worse than a vague one because a model will act on it.
54
+ *
55
+ * IT NAMES NO CONFIG KNOB, deliberately: there is nothing for the operator to set. The dashboard is
56
+ * configured; this particular result simply is not about a run. And it CONTAINS NO URL, the same
57
+ * property `LIVE_LINK_ABSENT_LINE` above is held to — an absence that fabricated an address would
58
+ * be the dishonesty this file exists to remove.
59
+ */
60
+ declare const LIVE_LINK_NO_RUN_LINE = "no run to link to \u2014 this result names no run id";
61
+ /**
62
+ * How a HOST answers "which dashboard is this process pointed at".
63
+ *
64
+ * A FACTORY, NOT A STRING, on `commands/dashboard.ts`'s own reasoning about the same value: the
65
+ * server has not bound a port when the host is composed, and an ephemeral port (`port: 0`) is a
66
+ * real posture in this suite and in tests. A value captured at composition time would be a guess; a
67
+ * factory read per result is the truth.
68
+ *
69
+ * `undefined` is the degenerate case rather than a second branch: a host that supplies no supplier
70
+ * at all and a supplier that answers `undefined` are the same fact — no dashboard — and they take
71
+ * the same path to the same line.
72
+ */
73
+ interface LiveLinkSupplier {
74
+ readonly dashboardBaseUrl: () => string | undefined;
75
+ }
76
+ /**
77
+ * The one line a carrier result carries, given whatever the host's supplier answers.
78
+ *
79
+ * COMPOSED HERE so the "base or named absence" decision has exactly one implementation. It is the
80
+ * decision the whole file exists for, and a second copy of it — in the catalog, in a command, in a
81
+ * later carrier — is the copy that fabricates a URL from an empty string.
82
+ *
83
+ * A BLANK BASE IS AN ABSENT BASE. A configured `baseUrl: ""` (or a whitespace-only one) is somebody
84
+ * who has not pointed this host anywhere, and composing `"/runs/x/live"` from it would produce a
85
+ * relative fragment that reads as a link in a transcript and resolves against nothing.
86
+ *
87
+ * THE GUARD ORDER IS THE FIX, NOT AN ACCIDENT (2026-09-01, plan 10, WR-03). The BASE is tested
88
+ * first: with no base anywhere, "no dashboard configured" is the true and the more actionable
89
+ * sentence whatever the run id is, so the both-absent case answers with the fact an operator can do
90
+ * something about. Only with a base PRESENT does a missing run id become the fact worth naming, and
91
+ * that arm answers `LIVE_LINK_NO_RUN_LINE`. Swapping these two lines back reinstates the false
92
+ * sentence a configured host used to receive.
93
+ */
94
+ declare function liveLinkLineFor(base: string | undefined, runId: string | undefined): string;
95
+
96
+ /**
97
+ * What a tool DOES, coarsely — the axis annotations are derived along (`projection.ts`).
98
+ *
99
+ * `read` answers; `workflow` changes something in the call (a test file, a run, knowledge,
100
+ * configuration). `apply` is the sovereign mark door alone: since 432-12 (D-05/D-14) no tool files a
101
+ * proposal and no tool asks for a confirmation, and `apply` names only the mark door, which lands a
102
+ * mark's gold, evidence and fix in the call. Its credential check left with the MCP bearer
103
+ * credential (432-21, D-12), so `apply` is now a name for that one door and gates nothing.
104
+ *
105
+ * KEPT, DATED 2026-10-02 (432-42 census decision): the member is not renamed. It is a published MCP
106
+ * contract value (`mapping-bump-guard.test.ts` pins `kind: 'apply'` on `agent_qa_mark_run`, and the
107
+ * projection derives annotations from it); no D-decision renames a published name here, and the
108
+ * word now means "the mark door applies its fix in the call", which is true. It asks nothing.
109
+ */
110
+ type ToolProjectionKind = 'read' | 'workflow' | 'apply';
111
+ /**
112
+ * How a tool's arms compose — DERIVED from the entry, never stated a second time on it.
113
+ *
114
+ * `single` is a 1:1 projection. `union` is D-01's declared fold: several sibling ops behind one
115
+ * discriminated-union input, the arm selected by a literal. `pipeline` is D-02's one read door:
116
+ * two ops invoked in sequence, the first one's answer choosing whether the second runs at all.
117
+ *
118
+ * A third field on the entry would have been a third thing to keep in step with the first two, and
119
+ * the failure it would have hidden is a fold declared `single` — which registers a tool advertising
120
+ * one arm's contract while the guard reports two arms and agrees with itself.
121
+ */
122
+ type ToolComposition = 'single' | 'union' | 'pipeline';
123
+ /** One declared tool. */
124
+ interface ToolProjection {
125
+ /** Curated `verb_noun` name per spec section 3.3 (D-04). The bump guard pins the exact list. */
126
+ readonly name: string;
127
+ /** The registry op ids this tool projects. Length 1 = 1:1; length > 1 = a declared fold. */
128
+ readonly ops: readonly string[];
129
+ readonly kind: ToolProjectionKind;
130
+ /**
131
+ * A union fold's discriminant key — absent on a 1:1 tool AND on the pipeline.
132
+ *
133
+ * Its presence is what tells a fold from a pipeline, which is why it is spelled per entry rather
134
+ * than assumed from `ops.length > 1`.
135
+ */
136
+ readonly discriminant?: string;
137
+ /**
138
+ * This tool CARRIES the live-view MCP App (2026-08-31, Phase 408 plan 01 · D-05 / MCPA-01).
139
+ *
140
+ * ITS PRESENCE IS THE SIGNAL, exactly as `discriminant`'s above is. A carrier is a tool whose
141
+ * declaration references the `ui://` App resource, so a host that negotiated the Apps extension
142
+ * can render the run's live view beside the result; a tool without this member references
143
+ * nothing, and `projection.ts` derives an ABSENT `_meta` for it rather than an empty one. Spelled
144
+ * per entry rather than inferred from a name, a kind or an op id, because the census that
145
+ * enumerates carriers reads THIS field — and a predicate over names would be a second, unwritten
146
+ * declaration of which doors reach a credential channel (the 399.1 lesson).
147
+ *
148
+ * `resource` names which `ui://` template the reference points at; one template exists today and
149
+ * the field is spelled anyway, so a second App is an added member here rather than a second
150
+ * meaning for a boolean.
151
+ *
152
+ * `runIdFrom` names where the CONCRETE runId for a per-call reference is found — on the call's
153
+ * input or in the op's own result. `agent_qa_start_run` answers a `RunStartAcknowledgement`
154
+ * carrying the id the run was given, so its reference is derivable only from the OUTPUT;
155
+ * `agent_qa_get_run_digest` is asked about a run by id, so its reference is derivable from the
156
+ * INPUT, which is what makes it the late-attach door (D-07).
157
+ *
158
+ * THE TASK-SHAPE QUESTION D-05 LEFT OPEN IS CLOSED, and closed by measurement rather than by
159
+ * preference: the task seam is dormant — "It registers nothing, advertises nothing and routes
160
+ * nothing" (task-seam.ts:21-23, and catalog.ts:34-36 states the matching absence) — so both
161
+ * carriers are plain tools answering immediate results, and there is no task completion or
162
+ * status poll for a reference to ride. (2026-08-31; the 2.0.0 core SDK has since grown the Tasks
163
+ * vocabulary, which makes the seam's own dated deferral stale at the SDK layer and changes
164
+ * nothing here: registering Tasks is not this phase's job.)
165
+ */
166
+ readonly ui?: {
167
+ readonly resource: 'live-run';
168
+ readonly runIdFrom: 'input' | 'output';
54
169
  };
55
- readonly ids: {
56
- readonly name: "agent-qa canonical IDs";
57
- readonly instruction: "Generate IDs with agent_qa_generate_id or `agent-qa ids generate <type>`; never hand-write IDs.";
58
- readonly contracts: _vostride_agent_qa_ids.EntityIdContract[];
170
+ /**
171
+ * 2026-09-28 (431 D-05): the result-size hint, advertised as `_meta[MAX_RESULT_SIZE_META_KEY]`.
172
+ * Present only on reads whose one-shot answer can outgrow a host's default result budget.
173
+ */
174
+ readonly maxResultSizeChars?: number;
175
+ }
176
+ /** The composition, read off the entry. See {@link ToolComposition}. */
177
+ declare function compositionOf(projection: ToolProjection): ToolComposition;
178
+ /**
179
+ * The live input/output contracts of one projected op.
180
+ *
181
+ * WHY THIS TABLE EXISTS AT ALL, since it looks like a second declaration of something the registry
182
+ * already knows. It is not: `FunctionSpec` — the record holding the LIVE validators — deliberately
183
+ * never leaves the function layer's own directory (T-384-01 / T-384-14), and what a surface can see
184
+ * through `registry.list(caller)` is a `FunctionDeclaration` whose `input`/`output` are already
185
+ * INERT JSON-Schema projections. D-16 mechanic (a) needs the live Standard-Schema objects, so the
186
+ * projection names them here, imported from the ONE module the ops themselves import them from.
187
+ * Drift is therefore impossible in the only sense that matters: this is not a copy of the contract,
188
+ * it is the same object, so the schema advertised in `tools/list` and the schema `invokeFunction`
189
+ * validates with are one thing that cannot disagree.
190
+ *
191
+ * The alternative — `fromJsonSchema(declaration.output)` — was available and rejected: it is a
192
+ * SECOND conversion recipe beside the SDK's own, and the anti-pattern this phase's research names
193
+ * first is exactly two recipes for one conversion.
194
+ *
195
+ * The field type is the SDK's `StandardSchemaWithJSON` and NOT a zod type, which is the same
196
+ * decision seen from the other side: this package's runtime dependency allow-list is exactly the
197
+ * two protocol packages (D-20), so naming a validator library here — even in a type position, which
198
+ * the dependency lint reads no differently — would reopen the collapse the phase exists to make.
199
+ * A zod 4.4 schema satisfies the interface, which is the whole of what either side needs to know.
200
+ */
201
+ interface ToolOpContracts {
202
+ readonly input: StandardSchemaWithJSON;
203
+ readonly output: StandardSchemaWithJSON;
204
+ }
205
+ /**
206
+ * Op id -> its live contracts, for every op any declared projection names.
207
+ *
208
+ * `catalog.ts` refuses to build a tool whose op is missing from it, so the two tables can never
209
+ * drift apart silently.
210
+ */
211
+ declare const AGENT_QA_OP_CONTRACTS: Readonly<Record<string, ToolOpContracts>>;
212
+ /**
213
+ * THE CATALOGUE. Twenty tools, and the bump guard pins this exact list by name and by op set.
214
+ * (Sixteen until 2026-09-28, when 431 D-01 added the eight discovery reads; twenty-four until
215
+ * 2026-09-30, when 432-12 removed the four proposal tools.)
216
+ *
217
+ * The read side is D-02: one universal entity door plus the reads a workflow needs mid-flight and
218
+ * the 431 discovery reads. The write side changes things IN THE CALL (432 D-05/D-14): every
219
+ * `workflow` entry writes what it names and answers what it changed, and nothing waits for a second
220
+ * call. There is no apply tool, no `confirmed` input and no elicitation; the mark door is the one
221
+ * `apply` entry, and since 432-21 (D-12) it checks no credential.
222
+ *
223
+ * WHY THERE IS NO PROPOSAL TOOL AT ALL. The inbox's verbs declared a human-origin requirement, so a
224
+ * tool over either decision verb would have listed for every caller and refused every call; the
225
+ * bump guard stated the rule generally until 432-39 deleted the field. 432-12 unprojected the whole
226
+ * inbox and 432-17 deleted it, ops and all (re-spelled 2026-10-02, 432-42).
227
+ */
228
+ declare const AGENT_QA_TOOL_PROJECTIONS: readonly ToolProjection[];
229
+ /**
230
+ * Tool name -> the contract that tool ADVERTISES, where it is not simply its one op's contract.
231
+ *
232
+ * Exactly two reasons an entry exists here, and no third:
233
+ *
234
+ * 1. A UNION FOLD validates with the discriminated union of its arms' own DTOs, composed in
235
+ * `@vostride/agent-qa-schema` (see `functions/tool-folds.ts` for why it is composed there and
236
+ * not here). Its output is the union of the arms' own outputs, collapsed when they are one.
237
+ * Its input is listed as one object by `projection.ts` (2026-10-20, 446-11).
238
+ * 2. THE PIPELINE advertises the FIRST op's contract on both sides: a caller hands `entities.resolve`
239
+ * the reference it has, and reads back the resolution union — a card, a small disambiguation
240
+ * set, or an honest miss. `entities.get` runs inside the resolved arm and contributes no shape
241
+ * of its own, which is why its contract is absent from the advertisement.
242
+ *
243
+ * 2026-10-01 (432-21, D-12): THE MARK DOOR'S ENTRY IS DELETED. It advertised the op's outcome OR the
244
+ * credential refusal (419 D-20); the refusal left with the MCP bearer credential, so the door
245
+ * advertises exactly its op's own contracts and needs no entry. (The D-11 apply envelope it once
246
+ * carried, `proposal_id` and `confirmed`, went with the apply tools in 432-12, D-05/D-14.)
247
+ */
248
+ declare const AGENT_QA_COMPOSED_CONTRACTS: Readonly<Record<string, ToolOpContracts>>;
249
+ /** The contract a tool advertises: its composition when it has one, its one op's otherwise. */
250
+ declare function advertisedContracts(projection: ToolProjection): ToolOpContracts | undefined;
251
+ /**
252
+ * A tool whose advertised OUTPUT cannot be represented as JSON Schema, and therefore ships with no
253
+ * `outputSchema` at all (2026-08-16, D-16's explicit-degradation clause).
254
+ *
255
+ * MEASURED, NOT PREDICTED. `tools/list` converts every `outputSchema` in zod's OUTPUT mode
256
+ * (`mcp-DXXb3Vv3.mjs:1391`, uncaught), and a contract built over a branded id — `z.string()
257
+ * .transform(asRunId)` — makes that conversion throw `Transforms cannot be represented in JSON
258
+ * Schema`. The consequence is not a poorer listing for one tool: the throw takes the WHOLE
259
+ * `tools/list` response down for every caller who can see the tool. Phase 384 hit the same wall from
260
+ * the other side and answered it by projecting both sides in input mode (`declaration-projection.ts`).
261
+ *
262
+ * WHY OMISSION RATHER THAN A SECOND CONVERSION. Advertising the INPUT-mode document as the output
263
+ * would have worked and been a lie of exactly the kind D-16 forbids: a schema derived by a second
264
+ * recipe, no longer the object `invokeFunction` validates with, presented as though it were. What a
265
+ * caller gets instead is `content` plus `structuredContent` and no advertised shape — less, and
266
+ * true. `validateToolOutput` skips a tool with no `outputSchema`, so the result still flows.
267
+ *
268
+ * THE LIST IS TWO-SIDED. `projection.ts` PROBES the conversion for every tool: an undeclared
269
+ * failure throws, and a declared entry whose contract has since become convertible throws too. So
270
+ * this list cannot silently grow and cannot silently rot.
271
+ *
272
+ * 2026-09-28 (431-03): the way off this list is a canonical `OutputContract` from
273
+ * `@vostride/agent-qa-schema` — the op's own runtime schema as validator, with an explicit output
274
+ * document — never a narrowed stand-in authored here. `agent_qa_start_run` keeps its row.
275
+ */
276
+ interface OutputSchemaDegradation {
277
+ readonly tool: string;
278
+ readonly opId: string;
279
+ /** The schema symbol whose transform the converter refused, for a reader chasing the cause. */
280
+ readonly schema: string;
281
+ /** The converter's own message, verbatim as measured on 2026-08-16. */
282
+ readonly measured: string;
283
+ readonly adjudication: '397';
284
+ }
285
+ declare const DEGRADED_OUTPUT_CONTRACTS: readonly OutputSchemaDegradation[];
286
+ /**
287
+ * A registry op that this surface deliberately does NOT project, and why (2026-08-16 · D-03/D-15).
288
+ *
289
+ * THE DATED SCOPING NOTE IS THIS TABLE, not a comment beside it. 397's parity suite ranges over the
290
+ * projected set, and a suite that scoped itself to whatever happened to be in the mapping would be
291
+ * discovering an accident rather than honouring a decision. Every op the registry declares appears
292
+ * either above or here — the bump guard asserts that partition against the registry's own
293
+ * enumeration, so a new op cannot land undispositioned.
294
+ *
295
+ * `agent.ask` LEADS THE LIST because its exclusion is the one with a decision behind it (D-03).
296
+ * Projecting the agent's own turn entry over MCP is nested-agent delegation: a caller's tool call
297
+ * would spend this project's model tokens, take the lease the local host holds, and stream through a
298
+ * channel a single `tools/call` round trip has no shape for — four independent reasons, and no MCPS
299
+ * requirement asking for any of them. It is deferred as its own seam rather than left out by
300
+ * oversight, and the deferral is dated so a later reader can tell the two apart.
301
+ *
302
+ * The rest are D-02's line — this surface carries the reads a workflow needs MID-FLIGHT and not the
303
+ * whole catalogue — and the two ops that cannot serve an agent-origin caller at all.
304
+ */
305
+ interface UnprojectedOp {
306
+ readonly opId: string;
307
+ readonly reason: string;
308
+ /**
309
+ * Who decided this absence is not a gap.
310
+ *
311
+ * 397 for every op that existed when 397's parity walk ruled the whole register (D-15). WIDENED
312
+ * to admit `'398'` on 2026-08-17 for `agent.explain`, and the widening is the honest move rather
313
+ * than a convenience: 397 could not have adjudicated an op that did not exist, and stamping this
314
+ * one `'397'` to keep a literal green would have made the field record a ruling nobody made — the
315
+ * one thing a provenance column is for. The vocabulary is a CLOSED set and the bump guard pins
316
+ * which entries carry which adjudicator by name, so it cannot widen again by habit.
317
+ *
318
+ * 405-05 adds `runs.artifact` under the SAME `'405'` stamp on the same day and by the same
319
+ * ruling; the vocabulary does not widen for it.
320
+ *
321
+ * WIDENED AGAIN on 2026-08-27 (405-01, D-03) for `runs.detail`, on exactly the 398 and 403
322
+ * reasoning and for no new one: 397 could not have ruled on an op registered eight phases
323
+ * later. The bump guard names which entry carries this adjudicator, in both directions, so an
324
+ * entry stamped `'405'` because it was easier reds there rather than joining a set that has
325
+ * quietly become "any phase number".
326
+ *
327
+ * WIDENED AGAIN on 2026-08-29 (407-01, D-05) for `runs.knowledge`, on exactly that reasoning and
328
+ * for no new one: 397 could not have ruled on an op registered ten phases later. Membership is
329
+ * still spelled by op id in the bump guard, in both directions.
330
+ *
331
+ * RE-STAMPED on 2026-08-31 (408 D-01): `'403'` LEAVES this vocabulary and `'408'` enters. This is
332
+ * not a widening and not a rename — it is the provenance moving with the ruling. `runs.liveurl`
333
+ * was the ONLY `'403'` entry, and its 403 stamp recorded a DEFERRAL ("Phase 408's question");
334
+ * 408 D-01 answered that question, so the phase that decided this absence is now 408 and saying
335
+ * `'403'` would credit 403 with a ruling it explicitly declined to make. Leaving `'403'` in a
336
+ * closed set with no member would also be exactly the empty literal the bump guard's
337
+ * non-vacuity check passes silently over, which is the hole that check exists to close.
338
+ */
339
+ readonly adjudication: UnprojectedAdjudication;
340
+ }
341
+ /** The closed adjudicator vocabulary as a runtime tuple; the type above is derived from it. */
342
+ declare const UNPROJECTED_ADJUDICATIONS: readonly ["397", "398", "405", "408", "428"];
343
+ type UnprojectedAdjudication = (typeof UNPROJECTED_ADJUDICATIONS)[number];
344
+ declare const UNPROJECTED_OPS: readonly UnprojectedOp[];
345
+
346
+ /** Which of D-13's three closed answers a retired v1 name gets. */
347
+ type V1MigrationArm = 'renamed-to' | 'retired' | 'pending';
348
+ /** One retired v1 tool name and its ruled disposition. */
349
+ interface V1ToolMigrationEntry {
350
+ /** The v1 tool name, verbatim from the inventory's tool table. */
351
+ readonly v1Name: string;
352
+ readonly arm: V1MigrationArm;
353
+ /** The live v2 tool that carries this capability. Present ONLY on `renamed-to`. */
354
+ readonly v2Tool?: string;
355
+ /** The inventory's ruled reason, dated. Never empty. */
356
+ readonly note: string;
357
+ /** What to call instead. Present on `retired` and `pending`; a `renamed-to` row's route is its tool. */
358
+ readonly route?: string;
359
+ /** The milestone that owns the absence. Present ONLY on `pending`. */
360
+ readonly owner?: string;
361
+ }
362
+ /**
363
+ * THE MAP. One entry per v1 tool, in the inventory's original row order.
364
+ *
365
+ * The order is the inventory's rather than alphabetical, and the bump guard pins the SORTED set
366
+ * separately — order is a reading convenience here and a pinned fact nowhere, which is why nothing
367
+ * below depends on it.
368
+ */
369
+ declare const V1_TOOL_MIGRATION_MAP: readonly V1ToolMigrationEntry[];
370
+
371
+ /**
372
+ * The version this server reports.
373
+ *
374
+ * A constant here and NOT the package version read off disk: reading `package.json` at runtime is a
375
+ * host capability this package does not have and must not acquire (its runtime dependency allow-list
376
+ * is the two protocol packages, D-20). Plan 05 decides whether the host should pass its own version
377
+ * in, which is the only place that fact honestly lives.
378
+ */
379
+ declare const AGENT_QA_MCP_SERVER_VERSION = "2.0.0";
380
+ /** What a host must supply to build a server for one caller. */
381
+ interface AgentQaMcpServerDeps {
382
+ /** The composed function registry. Built by the HOST, never by this package (D-09). */
383
+ readonly registry: FunctionRegistry;
384
+ /**
385
+ * WHO this server answers as. Minted by the composition root and passed through untouched to
386
+ * `invokeFunction`. Since 432-20 (D-12) nothing here reads it to decide what to list or serve;
387
+ * its `surface` and `origin` reach only the receipts that already consume them. Since 432-21 no
388
+ * field of it is a credential this package reads.
389
+ */
390
+ readonly caller: FunctionCaller;
391
+ /**
392
+ * How text leaving this process is masked (threat T-396-01).
393
+ *
394
+ * REQUIRED, not optional, and the reason is a measured one rather than a preference: the defect
395
+ * class this replaces (T-395-R5-10, closed in 395's final round) was a credential reaching an MCP
396
+ * caller verbatim through an error serialization. An optional masker is absent exactly where
397
+ * nobody thought to pass one, which is the same place the leak was found.
398
+ */
399
+ readonly maskText: (text: string) => string;
400
+ /**
401
+ * Which dashboard this process is pointed at, for D-08's permanent link (408 plan 03).
402
+ *
403
+ * OPTIONAL, AND ITS ABSENCE IS THE DEGENERATE CASE RATHER THAN A SECOND BRANCH: a host that
404
+ * supplies nothing and a supplier that answers `undefined` are the same fact — no dashboard is
405
+ * configured and none is running here — and both take the same path to the same named-absence
406
+ * line. There is no arm below that asks which of the two happened, deliberately: a distinction
407
+ * nothing acts on is a distinction that eventually acquires a wrong action.
408
+ *
409
+ * A FACTORY, NOT A STRING, because on the co-hosted composition the bound origin does not exist
410
+ * until a port has been bound — `commands/dashboard.ts` composes this host before it listens.
411
+ */
412
+ readonly liveLink?: LiveLinkSupplier;
413
+ }
414
+ /**
415
+ * Build the server this caller sees.
416
+ *
417
+ * The listing is read through `registry.list(deps.caller)` — the registry's own view of itself for
418
+ * this host — so `summary` and `risk` describe the composition in front of the caller rather than a
419
+ * table in this package that could describe a different one. The view is the same for every caller
420
+ * (432-20, D-12).
421
+ */
422
+ declare function buildAgentQaMcpServer(deps: AgentQaMcpServerDeps): McpServer;
423
+
424
+ declare const AGENT_QA_MCP_INSTRUCTIONS: string;
425
+
426
+ /**
427
+ * How a host composes the server for one request.
428
+ *
429
+ * It takes the SDK's own construction context, so a per-request fact such as `era` arrives here
430
+ * rather than being read back out of a header inside a tool handler. What it returns is the same
431
+ * `deps` a stdio host composes, which is what keeps ONE `buildAgentQaMcpServer` shape across both
432
+ * transports. 2026-10-02 (432-42, D-12): nothing about the caller decides what it may see; the
433
+ * shipped host passes one fixed tokenless caller.
434
+ */
435
+ type AgentQaMcpDepsResolver = (ctx: McpRequestContext) => AgentQaMcpServerDeps;
436
+ /**
437
+ * A Node request handler serving this catalog over stateless streamable HTTP.
438
+ *
439
+ * The factory is re-run per request, which is the SDK's own model and not an inefficiency to route
440
+ * around: a per-request instance is what makes a caller's tool set a property of that caller rather
441
+ * than of whoever connected first, and it is why absence stays structural on this transport too.
442
+ *
443
+ * IT TAKES A RESOLVER AND NOT A FIXED `deps` (plan 05). 2026-10-02 (432-42, D-12/D-13): the
444
+ * bearer check that once sat in front of this handler is deleted with MCP tokens (432-21); what
445
+ * stays in front of it is the host's Host and Origin guard on the loopback listener. The resolver
446
+ * receives the SDK context and builds the instance; it reads no header and verifies nothing.
447
+ *
448
+ * A host with one fixed caller passes `() => deps` — the static case is the degenerate resolver,
449
+ * not a second signature to keep in step.
450
+ */
451
+ declare function createAgentQaMcpNodeHandler(resolveDeps: AgentQaMcpDepsResolver): _modelcontextprotocol_node.NodeMcpRequestHandler;
452
+
453
+ /** A JSON-Schema document as it travels: an ordinary object, with nothing runnable on it. */
454
+ type JsonDocument = Record<string, unknown>;
455
+ /**
456
+ * Drop `$schema`, which the SDK's conversion stamps and the registry's projection does not.
457
+ *
458
+ * A DIALECT MARKER, not content: it says which JSON Schema draft the document speaks and describes
459
+ * nothing about the contract. Removing it is the one normalisation this file performs, and it is
460
+ * called out rather than buried because a normalisation that removed content would be the way to
461
+ * make a drift cell pass over real drift.
462
+ */
463
+ declare function withoutDialect(document: unknown): JsonDocument;
464
+ /**
465
+ * Drop the `type: "object"` the SDK stamps onto a COMPOSITION root.
466
+ *
467
+ * The second and last normalisation, and it is as narrow as the first. `standardSchemaToJsonSchema`
468
+ * stamps `type: "object"` on a typeless root it can prove is object-shaped — a `oneOf` whose every
469
+ * member is `type: "object"`, which is exactly what a zod discriminated union emits. The SDK's own
470
+ * comment calls the stamp "redundant-but-valid", and a document is not changed by adding a
471
+ * constraint every one of its branches already imposes.
472
+ *
473
+ * IT IS GATED ON THE COMPOSITION KEYWORD, so a genuine `type` disagreement still reds: the only
474
+ * case dropped is a root that carries `oneOf`/`anyOf` AND the stamp, which is the one case the SDK
475
+ * manufactures. An ungated `delete document.type` would have made the whole drift cell blind to a
476
+ * contract whose root type actually moved.
477
+ */
478
+ declare function stripRedundantObjectStamp(document: JsonDocument): JsonDocument;
479
+ /**
480
+ * One union arm of a fold's composed contract, with the fold's discriminant taken back out.
481
+ *
482
+ * `required: []` is DROPPED rather than kept, and the case is real rather than defensive:
483
+ * `config.undo.propose` takes an all-optional input, so its own projection carries no `required`
484
+ * key at all while the extended arm carries `['action']` — and removing the discriminant from that
485
+ * leaves an empty array where the other side has nothing. JSON Schema reads an absent `required`
486
+ * and an empty one identically, so the two documents agree; keeping the empty array would have
487
+ * failed the cell on a difference that describes no difference.
488
+ */
489
+ declare function armWithoutDiscriminant(arm: JsonDocument, discriminant: string): JsonDocument;
490
+ /**
491
+ * What a fold's one listed object leaves out of, or adds to, one arm's own contract.
492
+ *
493
+ * 2026-10-20 (446-11, D-25): a fold lists one object instead of its union, so an arm is no longer
494
+ * a member to compare whole. It is covered when the `discriminant` enum names it, every field it
495
+ * takes is listed with its own shape (the listing may add a description, never drop one), and the
496
+ * listing requires nothing the arm does not. That a call is still validated arm by arm is the
497
+ * C446-TOOL-SCHEMA counter-cell's claim, not this lens's.
498
+ */
499
+ declare function foldArmFindings(listed: JsonDocument, discriminant: string, opId: string, own: JsonDocument): readonly string[];
500
+ /**
501
+ * Undo the SEP-2106 legacy `{ result }` wrap the SDK's 2025-11-25 codec puts around a non-object
502
+ * output root (2026-09-28, 431-03).
503
+ *
504
+ * A transport artifact, like the two stamps above: the 2026-07-28 codec advertises a list's array
505
+ * root as-is, while the 2025-11-25 codec projects it to `{ type: 'object', properties: { result },
506
+ * required: ['result'] }` and wraps `structuredContent` to match. Gated to exactly that document —
507
+ * nothing but those three keys (and a dialect marker), one property named `result`, and a wrapped
508
+ * root that is not itself an object — so an op whose real output is an object with a `result`
509
+ * field is left alone. Apply it BEFORE `withoutDialect`: the wrapped root keeps its own `$schema`.
510
+ */
511
+ declare function withoutLegacyResultWrap(document: unknown): JsonDocument;
512
+
513
+ /**
514
+ * Mask once, and answer both channels from the one masked value.
515
+ *
516
+ * `structuredContent` IS the masked value and `content` is its serialization, which is the whole
517
+ * point: the object a machine reads and the text a person reads agree about what was hidden,
518
+ * because there is only one masking and one document. One exception: the evidence image arm
519
+ * replaces `bytesBase64` in the text with a marker, while `structuredContent` keeps the bytes.
520
+ */
521
+ declare function maskedToolResult(output: unknown, maskText: (text: string) => string): CallToolResult;
522
+
523
+ /**
524
+ * The official MCP Apps extension id (spec revision 2026-01-26).
525
+ *
526
+ * RESTATED LOCALLY RATHER THAN IMPORTED, and the reason is a measured peer conflict:
527
+ * `@modelcontextprotocol/ext-apps` declares `peerDependencies: { '@modelcontextprotocol/sdk':
528
+ * '^1.29.0' }` — the v1 monolith — while this package ships the 2.0.0 split SDK, so importing its
529
+ * `/server` entry would drag a second SDK generation into production (Pitfall 3). The string is
530
+ * therefore ours, and Plan 06's census pins it against ext-apps' shipped schema so a typo'd
531
+ * reverse-DNS id cannot fail silently as "never negotiated".
532
+ */
533
+ declare const MCP_APPS_EXTENSION_ID = "io.modelcontextprotocol/ui";
534
+ /** The media type an App resource is served as. Restated locally for the reason above. */
535
+ declare const APP_RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
536
+ /** The published App address. ONE template, per run — the runId is the only variable. */
537
+ declare const APP_RESOURCE_URI_TEMPLATE = "ui://agent-qa/live-run/{runId}";
538
+ /**
539
+ * The template EXPANDED for one concrete run (408 plan 03, D-05).
540
+ *
541
+ * WHY THIS IS A FUNCTION AND NOT A STRING EACH CALLER COMPOSES. `projection.ts` advertises the
542
+ * TEMPLATE on a carrier tool's `_meta`, and a carrier RESULT advertises the concrete address of the
543
+ * run it just named — two spellings of one address, in two files, that would be free to disagree
544
+ * about the scheme, the namespace or the segment order. Expanding through the template constant
545
+ * makes drift impossible: there is one place the address is written down and one rule for filling
546
+ * it in.
547
+ *
548
+ * ENCODED WITH `encodeURIComponent`, which is the exact inverse of the `decodeURIComponent` the one
549
+ * codec above applies — so `parseAppUri(appResourceUriFor(id)).runId === id` for every id, and an
550
+ * address this package composes is one this package can read back.
551
+ */
552
+ declare function appResourceUriFor(runId: string): string;
553
+ /** The registered resource name. Prefixed like the tools, so one namespace reads as one server. */
554
+ declare const APP_RESOURCE_NAME = "agent_qa_live_run_app";
555
+ /**
556
+ * The ONE parser of a `ui://` app address.
557
+ *
558
+ * TOTAL AND UNDECORATED: it answers the run id or `undefined`, and it never explains which clause
559
+ * refused — the caller-facing sentence is the read handler's, phrased once.
560
+ *
561
+ * THE RUN ID IS JUDGED BY THE MINT OP'S OWN INPUT CONTRACT, not by a pattern this file invented.
562
+ * `RunsLiveUrlInputSchema` carries `runIdSchema` — the codec that decides whether a string may name
563
+ * this run's own directory, so an address carrying `..`, a path separator or a control character is
564
+ * refused here by exactly the rule that refuses it everywhere else. Stated the other way round,
565
+ * which is the honest reading: an app address is well-formed precisely when the op this read mints
566
+ * through would accept the run it names, so the two can never disagree about one string.
567
+ */
568
+ declare function parseAppUri(href: string): {
569
+ readonly runId: string;
570
+ } | undefined;
571
+ /**
572
+ * The envelope member carrying the request's declared client capabilities.
573
+ *
574
+ * MEASURED, NOT GUESSED, and re-exported from the SDK rather than restated as a literal so a
575
+ * spelling drift is a compile error instead of a gate that silently answers "never negotiated".
576
+ * The measurement (2026-08-31, `src/__tests__/app-negotiation.test.ts`) drove a real
577
+ * `@modelcontextprotocol/client@2.0.0` against `createAgentQaMcpNodeHandler` over a loopback
578
+ * socket and read the resource-read context back out of the handler:
579
+ *
580
+ * ctx.mcpReq.envelope['io.modelcontextprotocol/clientCapabilities'].extensions['io.modelcontextprotocol/ui']
581
+ *
582
+ * That cell is the living record — it asserts the path against the wire, so an SDK that moves the
583
+ * member reds it rather than quietly turning this gate into a blanket refusal.
584
+ */
585
+ declare const APP_NEGOTIATION_CLIENT_CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
586
+ /**
587
+ * The ONE member of the SDK's read context this module reads.
588
+ *
589
+ * Declared structurally rather than as `Pick<ServerContext, 'mcpReq'>` for a reason a cell feels
590
+ * immediately: the SDK's context carries a dozen live functions, and a predicate whose input
591
+ * cannot be written down without them is a predicate only the wire can test. `ServerContext` is
592
+ * assignable to this, so the production call site still hands over the SDK's own object untouched.
593
+ */
594
+ interface AppReadContext {
595
+ readonly mcpReq?: {
596
+ readonly envelope?: Partial<RequestMetaEnvelope>;
59
597
  };
60
- };
61
- interface ValidationIssue {
62
- path: string;
63
- message: string;
64
598
  }
65
- interface DefinitionValidationResult {
66
- valid: boolean;
67
- kind: ValidationKind;
68
- issues: ValidationIssue[];
599
+ /**
600
+ * Did THIS request declare the MCP Apps extension?
601
+ *
602
+ * FAIL-CLOSED at every step: no context, no envelope, no capabilities member, no `extensions`
603
+ * record, or an `extensions` record without our id all answer `false`. The question is "is there
604
+ * evidence this caller negotiated the extension", and absence of evidence is not evidence — which
605
+ * matters most for the case the measurement surfaced, where a 2025-era caller carries no envelope
606
+ * at all and would otherwise be waved through by a predicate written as "not explicitly absent".
607
+ *
608
+ * WHAT FAILING CLOSED HERE DOES AND DOES NOT PROTECT (dated 2026-09-01, 408 gap round, road
609
+ * `demote` — the full ruling is in this file's header). The predicate is still fail-closed AS A
610
+ * PREDICATE, and every clause above is unchanged. What is CORRECTED is the reading that failing
611
+ * closed here protects the credential. It does not. The value read below is composed by the
612
+ * caller in its own per-request `_meta` and forwarded by the SDK unauthenticated
613
+ * (`_inputRequestCapabilityView` returns it verbatim; `checkInboundEnvelope` checks shape only),
614
+ * so a `false` answer means "this caller did not SAY it can render an App" — a host-capability
615
+ * fact — and never "this caller is not authorized to hold a live-view credential". What bounds
616
+ * the credential is `bindingStatus === 'bound'` and the local network controls D-13 keeps; the
617
+ * scope check that once sat beside it is deleted (432-20, D-12). See header clause 5.
618
+ *
619
+ * `RequestMetaEnvelope` is an intentionally EMPTY augmentable type in the shipped d.mts, so the
620
+ * envelope is read through one narrow cast at this single seam rather than by widening the SDK's
621
+ * types somewhere a second reader would have to find.
622
+ */
623
+ declare function negotiatedAppsExtension(ctx: AppReadContext | undefined): boolean;
624
+ /**
625
+ * A read refused because the request never declared the Apps extension (clause 3 of the ruling
626
+ * in this file's header; D-09, T-408-01).
627
+ *
628
+ * IT NAMES THE EXTENSION AND CARRIES NOTHING ELSE. The message is authored prose plus a constant
629
+ * this repository owns, so there is nothing caller-supplied in it to mask and — the point of the
630
+ * whole arm — nothing minted in it to leak, because it is thrown before the first mint.
631
+ */
632
+ declare class AppResourceNegotiationError extends Error {
633
+ constructor();
634
+ }
635
+ /** An address the one codec does not recognise. */
636
+ declare class AppResourceUriError extends Error {
637
+ constructor(detail: string);
638
+ }
639
+ /**
640
+ * A refusal from the App read that is NOT a run-liveness fact.
641
+ *
642
+ * Deliberately narrow: absence of a live session is rendered, never thrown (D-07), so anything that
643
+ * reaches this class is a genuine failure of the read — a missing scope, an unknown op, a broken
644
+ * composition — and it carries its message masked for the same reason `resources.ts`'s does.
645
+ */
646
+ declare class AppResourceReadError extends Error {
647
+ constructor(message: string);
648
+ }
649
+ /**
650
+ * A document whose panes would span more than one origin (IN-02, T-408-26; added 2026-09-01).
651
+ *
652
+ * THE INVARIANT THIS SERVES USED TO BE AN ASSUMPTION. The `_meta.ui.csp` block below was derived
653
+ * from `mints[0]` alone, which is correct today only because `runs.liveurl` reads
654
+ * `liveView.origin()` once per invocation — a fact held in ANOTHER package, which nothing on that
655
+ * line checked. A composition that ever minted across origins would have shipped a frame allowance
656
+ * covering the first pane and blocking every other one: a blank inner frame, which is the exact
657
+ * failure this milestone exists to remove, arriving silently.
658
+ *
659
+ * WHY REFUSAL RATHER THAN A WIDER ALLOWANCE. Naming every origin in `frameDomains` would keep the
660
+ * document renderable, but it would also make the allowance a function of how many panes happened
661
+ * to be minted — precisely the "never broader than the document needs" property T-408-05 pins.
662
+ * This module's own reasoning is that the allow is as narrow as the document needs; a document that
663
+ * needs two origins is a composition this server does not know how to serve narrowly, and saying so
664
+ * is honest absence rather than a silent widening.
665
+ *
666
+ * ITS MESSAGE CARRIES NO CALLER TEXT AND NO CREDENTIAL. The origins are host-composed and
667
+ * `originOf` returns an origin only — never the path and never the fragment the token lives in — so
668
+ * there is nothing here to mask and nothing here to leak.
669
+ */
670
+ declare class AppResourceOriginSpanError extends Error {
671
+ constructor(origins: readonly string[]);
672
+ }
673
+ /** The host-bound `(registry, caller)` closure over `invokeFunction`. */
674
+ type AppResourceInvoke = (opId: string, input: unknown) => Promise<unknown>;
675
+ interface AppResourceReadDeps {
676
+ readonly invoke: AppResourceInvoke;
677
+ /** How CALLER text leaving this handler is masked. Required — see the header's channel split. */
678
+ readonly maskText: (text: string) => string;
679
+ /**
680
+ * Which dashboard this process is pointed at, for D-08's permanent link (408 plan 03).
681
+ *
682
+ * OPTIONAL, and its absence is a real posture rather than a gap: a composition that knows of no
683
+ * dashboard renders the wrapper with NO anchor at all, which is what plan 01 shipped and what the
684
+ * honest-absence law requires — a link to nothing is the dishonesty this milestone removes.
685
+ *
686
+ * THIS IS THE ONE SLOT WHOSE ABSENCE COSTS A PERSON SOMETHING, which is why it is wired rather
687
+ * than left where plan 01 left it: the wrapper's `expired` and `unreachable-origin` states are
688
+ * D-11's answers for "the embed cannot show you this", and both of them are supposed to hand a
689
+ * person the browser instead. A wrapper reaching those states with no link is a dead end wearing
690
+ * an honest sentence.
691
+ */
692
+ readonly liveLink?: LiveLinkSupplier;
693
+ }
694
+ /**
695
+ * Build the App read.
696
+ *
697
+ * Exported so a cell can drive it without a server, and so the parse/mint/render behaviour is
698
+ * observable independently of registration — the two halves fail in different directions.
699
+ */
700
+ declare function createAppResourceReadHandler(deps: AppResourceReadDeps): (uri: URL, variables: Variables, ctx: AppReadContext) => Promise<ReadResourceResult>;
701
+ /**
702
+ * The slice of `McpServer` this module uses.
703
+ *
704
+ * The same narrow structural type `resources.ts` declares, spelled with the SDK's own
705
+ * `ResourceTemplate` / `ResourceMetadata` / `ReadResourceTemplateCallback` — so a cell can observe
706
+ * the registration without assembling a protocol server while a real `McpServer` still satisfies
707
+ * it. Read that module's note before narrowing any member here: a hand-narrowed config or a
708
+ * `readonly` result array makes `McpServer` unassignable and the compiler names the wrong cause.
709
+ *
710
+ * THE READ CALLBACK IS THE SDK'S OWN `ReadResourceTemplateCallback` AND NOT A LOCAL NARROWING, for
711
+ * the reason 408 plan 02 needed a third argument at all: the negotiation evidence arrives in the
712
+ * SDK's read CONTEXT, so a slice that spelled the callback `(uri) => …` would have declared the
713
+ * one parameter the gate reads out of existence.
714
+ */
715
+ interface AppResourceRegistrar {
716
+ registerResource(name: string, template: ResourceTemplate, config: ResourceMetadata, read: ReadResourceTemplateCallback): unknown;
69
717
  }
70
- declare function validateAgentQaDefinition(kind: ValidationKind, definition: unknown): DefinitionValidationResult;
718
+ /** Exactly what this module needs from the host — the same record `catalog.ts` already holds. */
719
+ interface AppResourceDeps {
720
+ readonly registry: FunctionRegistry;
721
+ readonly caller: FunctionCaller;
722
+ readonly maskText: (text: string) => string;
723
+ /** D-08's base supplier, passed straight through to the read. See {@link AppResourceReadDeps}. */
724
+ readonly liveLink?: LiveLinkSupplier;
725
+ }
726
+ /**
727
+ * Register the App template when this composition can honestly serve it, and answer what it
728
+ * registered.
729
+ *
730
+ * Returns the registered template URIs rather than nothing, for `resources.ts`'s reason: "registered
731
+ * none" and "registered one" are the two halves of the claim, and a caller that had to inspect a
732
+ * server to tell them apart could only ever test one of them.
733
+ *
734
+ * TWO CONDITIONS, BOTH READ OFF THE REGISTRY'S OWN DECLARATIONS (D-02/D-03), AND NEITHER ABOUT THE
735
+ * CALLER (432-20, D-12 deleted the third, a `visibleToCaller` scope check):
736
+ *
737
+ * 1. Both ops this surface reads through — the mint and the roster read — are declared here. A
738
+ * composition that registered neither has no App to offer.
739
+ * 2. The mint is BOUND in this composition. This is D-03's floor made structural: the standalone
740
+ * `agent-qa mcp` process composes no `liveView` port, so its `runs.liveurl` is declared-and-
741
+ * unbound and every mint there would refuse by name. Registering an App on that host would
742
+ * advertise an address whose every read is honestly empty — so it registers nothing at all,
743
+ * and the link-out is that host's whole answer. On a co-hosted composition the same three
744
+ * lines register it, by composition rather than by configuration.
745
+ *
746
+ * A composition that fails any of them gets NO template and NO error — the same answer `catalog.ts`
747
+ * gives a tool whose op is missing, because refusing to build would take an entire MCP connection
748
+ * down over one unavailable capability.
749
+ */
750
+ declare function registerAppResources(server: AppResourceRegistrar, deps: AppResourceDeps): readonly string[];
71
751
 
72
- interface AgentQaMcpServerOptions {
73
- configPath?: string;
74
- dashboardUrl?: string;
75
- endpointUrl?: string;
76
- analyticsService?: AnalyticsService;
77
- analyticsConfig?: AnalyticsServiceConfig;
78
- analyticsStandardProperties?: AnalyticsEventProperties;
79
- transport?: 'stdio' | 'http';
80
- }
81
- declare function resolveDashboardApiUrl(dashboardUrl: string, apiPath: string): string;
82
- type FailureCategory = 'passed' | 'timeout' | 'appium_startup' | 'browser_disconnect' | 'element_not_found' | 'assertion_failure' | 'hook_failure' | 'infrastructure' | 'unknown_failure';
83
- interface FailureClassification {
84
- category: FailureCategory;
85
- confidence: number;
86
- evidence: string[];
87
- recentRelatedCount: number;
88
- }
89
- declare function classifyRunFailureFromDashboardData(input: {
90
- runDetail: unknown;
91
- artifact?: unknown;
92
- logs?: unknown;
93
- executionLogs?: unknown;
94
- recentRuns?: unknown;
95
- }): FailureClassification;
96
- declare function createAgentQaMcpServer(options?: AgentQaMcpServerOptions): McpServer;
752
+ /**
753
+ * Every value `[data-app-state]` can take (D-11's closed set).
754
+ *
755
+ * Exported for the reason `LIVE_EMBED_STATES` is: the DOM cells range over this list, so a state
756
+ * added to the page without a cell is a visible diff rather than an unobserved arm.
757
+ */
758
+ declare const APP_WRAPPER_STATES: readonly ["connecting", "live", "expired", "unreachable-origin", "run-ended"];
759
+ type AppWrapperState = (typeof APP_WRAPPER_STATES)[number];
760
+ /** What the page says while the inner frame has not painted yet. */
761
+ declare const APP_WRAPPER_CONNECTING_MESSAGE = "connecting to the live view\u2026";
762
+ /**
763
+ * The second line under `connecting`, and the WHOLE of what an initial render says on a host that
764
+ * never runs this document's script (WR-10, RESEARCH assumption A1; the cell is
765
+ * `app-wrapper-dom.test.ts` cell (7)).
766
+ *
767
+ * Named for the same reason every other absence here is named: a document that says only that it is
768
+ * connecting has made a promise its own script has to keep, and under a host CSP of
769
+ * `script-src 'none'` there is no script to keep it. The sentence therefore points at the ONE
770
+ * useful action available with no script at all — the permanent link rendered directly beneath it,
771
+ * which is now markup rather than something the script assigns. One useful action, no apology.
772
+ */
773
+ declare const APP_WRAPPER_CONNECTING_HINT = "if the pane does not appear, open it in your browser";
774
+ /** What the page says once the inner frame has loaded on an origin this host can reach. */
775
+ declare const APP_WRAPPER_LIVE_MESSAGE = "live";
776
+ /**
777
+ * What an elapsed live link says (D-10).
778
+ *
779
+ * The wrapper's own sentence rather than the embed page's `link expired`, because the two are about
780
+ * different things: the embed says its socket was refused, and this says the link this App was
781
+ * rendered with has aged out.
782
+ */
783
+ declare const APP_WRAPPER_EXPIRED_MESSAGE = "live link expired";
784
+ /**
785
+ * The second half of the expired state, and the WHOLE of D-10's refresh story (D-10).
786
+ *
787
+ * There is no re-mint inside this sandbox and there is never going to be one — a UI-initiated mint
788
+ * door was rejected precisely because its model-context hygiene is per-host behaviour nobody here
789
+ * controls. What replaces it is this sentence: the way to a fresh token is another watch call, one
790
+ * the person can simply ask for, and saying so is the difference between a dead end and a next
791
+ * move. Separate from the message above so the DOM can show both and a cell can assert each.
792
+ */
793
+ declare const APP_WRAPPER_EXPIRED_HINT = "ask to watch again \u2014 a fresh watch mints a new link";
794
+ /**
795
+ * The REQUIRED unreachable-origin sentence (D-11), spelled EXACTLY as CONTEXT ratified it.
796
+ *
797
+ * A host running somewhere that cannot route to a loopback dashboard origin must say so and offer
798
+ * the browser, never show a blank inner frame. This string is a contract: the DOM cells assert
799
+ * against this constant, so the sentence and the assertion can never drift apart.
800
+ */
801
+ declare const APP_WRAPPER_UNREACHABLE_MESSAGE = "this host can't reach the live view \u2014 open it in your browser";
802
+ /**
803
+ * What a run with no live session on this host says (D-07).
804
+ *
805
+ * Not an error and not a blank frame — and it points at the way to see the run anyway, which is
806
+ * the permanent link rendered directly beneath it. A run that has finished still HAPPENED, and the
807
+ * dashboard's own page is where its history lives.
808
+ */
809
+ declare const APP_WRAPPER_RUN_ENDED_MESSAGE = "this run is not live \u2014 open it in your browser";
810
+ /** The label on the permanent link-out slot, when one is composed (D-08). */
811
+ declare const APP_WRAPPER_PERMANENT_LINK_LABEL = "open in your browser";
812
+ /**
813
+ * How long silence from the inner frame is allowed to last before it is named (D-11, T-408-08).
814
+ *
815
+ * Exported because the DOM cell must wait on the page's OWN bound rather than on a number a test
816
+ * file guessed. Ten seconds is chosen against the two shapes this arm exists for: a black-holed
817
+ * origin never answers at all, and a slow-but-reachable dashboard on loopback answers in
818
+ * milliseconds — there is no realistic case in between, so a longer bound would only make a person
819
+ * stare at a spinner for longer before reading the same sentence.
820
+ */
821
+ declare const APP_WRAPPER_LOAD_TIMEOUT_MS = 10000;
822
+ /**
823
+ * The MCP Apps protocol version this wrapper announces in its `ui/initialize` request.
824
+ *
825
+ * MEASURED, not guessed (408-06 Task 1): `@modelcontextprotocol/ext-apps@1.7.5` carries exactly
826
+ * two protocol versions — `2025-11-21` and `2026-01-26` — and `2026-01-26` is the one its
827
+ * `AppBridge` treats as latest. `ext-apps-constants.test.ts` pins the spelling against the
828
+ * package's own shipped schema, so this literal cannot drift from the vocabulary in silence.
829
+ */
830
+ declare const APP_WRAPPER_UI_PROTOCOL_VERSION = "2026-01-26";
831
+ /**
832
+ * What this document calls itself to a host (`ui/initialize`'s required `appInfo`).
833
+ *
834
+ * Exported so a cell asserts the SAME bytes the page posts, per this file's standing rule that a
835
+ * value a person or a host can observe is never restated in a test.
836
+ */
837
+ declare const APP_WRAPPER_APP_NAME = "agent-qa-live-view";
838
+ /** The wrapper's own version, bumped only when the handshake it performs changes. */
839
+ declare const APP_WRAPPER_APP_VERSION = "1.0.0";
840
+ /**
841
+ * ONE minted view onto ONE member handle of the run.
842
+ *
843
+ * `liveUrl` carries its token in the FRAGMENT (403 D-04), so this value is the only place a live
844
+ * credential exists on this page — it is written into the frame's `src` and nowhere else: not into
845
+ * the document text, not into the picker's labels, not into any attribute a host serialises back.
846
+ */
847
+ interface AppWrapperMint {
848
+ readonly handle: number;
849
+ readonly liveUrl: string;
850
+ readonly expiresAt: number;
851
+ readonly streamKind: string;
852
+ }
853
+ interface AppWrapperInput {
854
+ readonly runId: string;
855
+ /** One mint per handle on the run's announced roster. Empty is the D-07 not-live case. */
856
+ readonly mints: readonly AppWrapperMint[];
857
+ /** The tokenless dashboard page (D-08). `undefined` when no dashboard origin is composable. */
858
+ readonly permanentLink: string | undefined;
859
+ /** Whether this host holds a live session for the run at all. `false` renders `run-ended`. */
860
+ readonly runLive: boolean;
861
+ /**
862
+ * The instant this document is being built, in ms-epoch (D-10).
863
+ *
864
+ * A PARAMETER rather than a `Date.now()` call, for the reason every clock in this repository is a
865
+ * port: the expiry arm is the one thing this renderer decides about time, and a cell that cannot
866
+ * choose "now" could only ever assert the arm it happened to land in. Defaults to the wall clock,
867
+ * so the read handler passes nothing and gets the honest answer.
868
+ */
869
+ readonly now?: number;
870
+ }
871
+ /**
872
+ * Render the App wrapper for one run.
873
+ *
874
+ * The whole document, every time — there is no partial render and no update path, because D-10's
875
+ * answer to a stale App is a FRESH read that mints again rather than a credential machine inside
876
+ * the sandbox.
877
+ */
878
+ declare function renderAppWrapperPage(input: AppWrapperInput): string;
97
879
 
98
- declare const MCP_STDIO_STARTUP_MESSAGE = "agent-qa MCP server running over stdio. Waiting for MCP client messages on stdin. Stdout is reserved for MCP protocol traffic.";
99
- interface StartMcpServerOptions extends AgentQaMcpServerOptions {
100
- startupOutput?: Pick<NodeJS.WritableStream, 'write'>;
880
+ /**
881
+ * Everything the derivation reads about ONE op, assembled by `catalog.ts`.
882
+ *
883
+ * The two halves come from two places on purpose. `summary` and `risk` are read
884
+ * off the registry's own declaration for this host, so they describe the composition a caller is
885
+ * actually talking to; `input` and `output` are the live contracts named by the mapping table,
886
+ * because the registry deliberately publishes only inert projections of those (T-384-14). Nothing
887
+ * in this record is authored by this package — it is two views of the op, joined by its id.
888
+ */
889
+ interface ProjectedOpSpec {
890
+ readonly id: string;
891
+ readonly summary: string;
892
+ readonly risk: RiskActionDescriptor;
893
+ readonly input: StandardSchemaWithJSON;
894
+ readonly output: StandardSchemaWithJSON;
895
+ }
896
+ /**
897
+ * Exactly the `registerTool` config fields this projection derives.
898
+ *
899
+ * `outputSchema` is OPTIONAL, and its absence is a decision rather than a gap — see
900
+ * `DEGRADED_OUTPUT_CONTRACTS` and `probeOutputSchema` below.
901
+ */
902
+ interface RegisterToolConfig {
903
+ readonly title: string;
904
+ readonly description: string;
905
+ readonly inputSchema: StandardSchemaWithJSON;
906
+ readonly outputSchema?: StandardSchemaWithJSON;
907
+ readonly annotations: ToolAnnotations;
908
+ /**
909
+ * The SDK's own extension slot, carrying the live-view App reference for a CARRIER tool
910
+ * (2026-08-31, Phase 408 plan 01 · D-05/D-09 · MCPA-01).
911
+ *
912
+ * OPTIONAL, and its absence is a decision in the same sense `outputSchema`'s is: a tool that
913
+ * declares no `ui` member gets no `_meta` KEY at all rather than a `_meta` whose `ui` is
914
+ * `undefined`, because the two are different documents on the wire and only the first is honest
915
+ * about a tool that references no App.
916
+ *
917
+ * WHAT IT IS ON THE LISTING CHANNEL: inert metadata. Extensions are bilateral opt-in, so a host
918
+ * that never negotiated `io.modelcontextprotocol/ui` ignores this field by definition — it acts
919
+ * on nothing here, which is what makes "the App attaches only when the host negotiated" true of
920
+ * every channel that ACTS without needing a listing that varies per connection (SEP-2567 removed
921
+ * that, and the per-request factory context carries no client capabilities to key one on). The
922
+ * channels that act are gated where the capabilities actually arrive: in request handling.
923
+ */
924
+ readonly _meta?: Record<string, unknown>;
925
+ }
926
+ /**
927
+ * Derive one tool's `registerTool` config.
928
+ *
929
+ * `advertised` is passed IN rather than looked up here so this module stays a derivation over
930
+ * values: `catalog.ts` owns the join between the mapping's data and this host's registry, and a
931
+ * second lookup inside the derivation would be a second chance for the two to disagree.
932
+ *
933
+ * `maskText` is a REQUIRED parameter and covers the DERIVED prose. The reason is the fresh wound
934
+ * this phase inherits: 395 closed a high-severity defect where a credential reached an MCP caller
935
+ * through a serialization nobody had thought of as an output channel. A description is composed
936
+ * from op summaries, and a summary is prose somebody may one day interpolate a resolved value into;
937
+ * routing it through the masker costs nothing on the ordinary path and closes the channel
938
+ * structurally rather than by remembering. Optionality is the failure mode here — an optional
939
+ * masker is a masker that is absent exactly where nobody looked — so the parameter has no default
940
+ * and the type refuses a call that omits it.
941
+ *
942
+ * TWO THINGS ARE DELIBERATELY NOT MASKED, and both would be damage rather than safety. The `title`
943
+ * is the curated tool NAME from the mapping table: a declared constant in this repository's own
944
+ * source, never data, and a masker that rewrote it would rename the tool a caller must call. The
945
+ * SCHEMAS are contracts rather than text — masking one would corrupt the very object
946
+ * `invokeFunction` validates against, and they carry field names and types, not values.
947
+ */
948
+ declare function toRegisterToolConfig(projection: ToolProjection, specs: readonly ProjectedOpSpec[], advertised: ToolOpContracts, maskText: (text: string) => string): RegisterToolConfig;
949
+ /** A projection that cannot be derived honestly, named with the tool it is about. */
950
+ declare class McpProjectionError extends Error {
951
+ readonly toolName: string;
952
+ constructor(toolName: string, detail: string);
101
953
  }
102
- declare function createMcpServer(options?: AgentQaMcpServerOptions): _modelcontextprotocol_sdk_server_mcp.McpServer;
103
- declare function startMcpServer(options?: StartMcpServerOptions): Promise<void>;
104
954
 
105
- declare const DEFAULT_MCP_HOST = "127.0.0.1";
106
- declare const DEFAULT_MCP_PORT = 3471;
107
- declare const DEFAULT_MCP_PATH = "/mcp";
108
- type LocalMcpTransport = 'http' | 'stdio';
109
- interface LocalMcpEndpointConfig {
110
- enabled?: boolean;
111
- transport?: LocalMcpTransport;
112
- host?: string;
113
- port?: number;
114
- path?: string;
115
- }
116
- interface LocalMcpEndpoint {
117
- enabled: boolean;
118
- transport: LocalMcpTransport;
119
- host: string;
120
- port: number;
121
- path: string;
122
- url: string;
123
- }
124
- interface McpHttpHandlerOptions {
125
- endpoint?: Partial<LocalMcpEndpoint>;
126
- dashboardUrl?: string;
127
- configPath?: string;
128
- analyticsConfig?: AgentQaMcpServerOptions['analyticsConfig'];
129
- }
130
- interface LocalMcpEndpointShape {
131
- mcp_host_kind: 'loopback' | 'other';
132
- mcp_port_kind: 'default' | 'custom';
133
- mcp_path_kind: 'default' | 'custom';
134
- }
135
- declare function resolveLocalMcpEndpoint(config?: LocalMcpEndpointConfig): LocalMcpEndpoint;
136
- declare function resolveMcpEndpointShape(endpoint: Pick<LocalMcpEndpoint, 'host' | 'port' | 'path'>): LocalMcpEndpointShape;
137
- declare function createLocalMcpHttpHandler(options?: McpHttpHandlerOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
955
+ /**
956
+ * The MCP task states this projection can produce.
957
+ *
958
+ * Four of the wire's five. The fifth, `input_required`, is deliberately absent from the table
959
+ * below and NOT from this union: it is a real state of the protocol that no agent-qa job status
960
+ * currently means, and a future status that did mean it should be able to say so by adding one row
961
+ * rather than by widening a type.
962
+ */
963
+ type McpTaskState = 'working' | 'input_required' | 'completed' | 'failed' | 'cancelled';
964
+ /**
965
+ * The whole mapping, as data.
966
+ *
967
+ * `as const satisfies Record<JobStatus, McpTaskState>` is what makes this exhaustive AT COMPILE
968
+ * TIME: add an eighth member to `JobStatus` and this object stops satisfying the record, so the
969
+ * build fails at the one place that has to make a decision about it. The alternative — a `switch`
970
+ * with a `default` — is the same table with a hole in it, and the hole answers `working` for a
971
+ * status nobody has thought about yet, which is the wrong answer precisely when it matters.
972
+ *
973
+ * Exported because the table IS the reconciliation D-12 asks to be pinned, and a later phase
974
+ * registering the extension will read it rather than restate it.
975
+ */
976
+ declare const JOB_STATUS_TO_MCP_TASK_STATE: {
977
+ readonly queued: "working";
978
+ readonly authored: "working";
979
+ readonly bundling: "working";
980
+ readonly committing: "working";
981
+ readonly done: "completed";
982
+ readonly failed: "failed";
983
+ readonly cancelled: "cancelled";
984
+ };
985
+ /**
986
+ * What one job looks like to a remote poller.
987
+ *
988
+ * Field names are the wire's (`taskId`, `statusMessage`, `lastUpdatedAt`) rather than the job
989
+ * record's, because this record's whole purpose is to be handed OUT: naming it after the source
990
+ * would push the rename onto the registration, which is the step this seam exists to make trivial.
991
+ * `lastUpdatedAt` carries `Job.updatedAt` verbatim; only the label differs.
992
+ */
993
+ interface McpTaskProjection {
994
+ readonly taskId: string;
995
+ readonly status: McpTaskState;
996
+ /** The job's own one-line summary. The only prose that crosses. */
997
+ readonly statusMessage: string;
998
+ readonly createdAt: string;
999
+ readonly lastUpdatedAt: string;
1000
+ }
1001
+ /**
1002
+ * Project one job record into the task view.
1003
+ *
1004
+ * Total, pure, synchronous, and deliberately narrow. `attempts`, `kind`, `runRef` and
1005
+ * `conclusionKind` are NOT carried, and their absence is asserted by an exact-key cell rather than
1006
+ * left to be noticed: `attempts` is the retry discipline's private counter, and `conclusionKind` is
1007
+ * internal vocabulary (`knowledge_skipped_draft`, `insufficient_model_output`) that means nothing to
1008
+ * a caller outside this system. A projection that leaks the source record's shape is not a
1009
+ * projection; it is the coupling with an extra step.
1010
+ *
1011
+ * NOTHING IS MASKED HERE, and that is a statement rather than an oversight. This function is called
1012
+ * by nothing — it registers no handler and returns no result to any transport — so there is no
1013
+ * output channel to mask. Whoever eventually registers it inherits the masking obligation every
1014
+ * other handler in this package carries, because `statusMessage` is a job's own summary text and
1015
+ * summaries are prose somebody may one day interpolate a resolved value into.
1016
+ */
1017
+ declare function projectJobAsTask(job: Job): McpTaskProjection;
138
1018
 
139
- export { AGENT_QA_SCHEMA_REFERENCES, type AgentQaMcpServerOptions, DEFAULT_MCP_HOST, DEFAULT_MCP_PATH, DEFAULT_MCP_PORT, type DefinitionValidationResult, type FailureCategory, type FailureClassification, type LocalMcpEndpoint, type LocalMcpEndpointConfig, type LocalMcpEndpointShape, type LocalMcpTransport, MCP_STDIO_STARTUP_MESSAGE, type McpHttpHandlerOptions, SCHEMA_REFERENCE_NAMES, type SchemaReferenceName, type StartMcpServerOptions, VALIDATION_KINDS, type ValidationIssue, type ValidationKind, classifyRunFailureFromDashboardData, createAgentQaMcpServer, createLocalMcpHttpHandler, createMcpServer, resolveDashboardApiUrl, resolveLocalMcpEndpoint, resolveMcpEndpointShape, startMcpServer, validateAgentQaDefinition };
1019
+ export { AGENT_QA_COMPOSED_CONTRACTS, AGENT_QA_MCP_INSTRUCTIONS, AGENT_QA_MCP_SERVER_VERSION, AGENT_QA_OP_CONTRACTS, AGENT_QA_TOOL_PROJECTIONS, APP_NEGOTIATION_CLIENT_CAPABILITIES_KEY, APP_RESOURCE_MIME_TYPE, APP_RESOURCE_NAME, APP_RESOURCE_URI_TEMPLATE, APP_WRAPPER_APP_NAME, APP_WRAPPER_APP_VERSION, APP_WRAPPER_CONNECTING_HINT, APP_WRAPPER_CONNECTING_MESSAGE, APP_WRAPPER_EXPIRED_HINT, APP_WRAPPER_EXPIRED_MESSAGE, APP_WRAPPER_LIVE_MESSAGE, APP_WRAPPER_LOAD_TIMEOUT_MS, APP_WRAPPER_PERMANENT_LINK_LABEL, APP_WRAPPER_RUN_ENDED_MESSAGE, APP_WRAPPER_STATES, APP_WRAPPER_UI_PROTOCOL_VERSION, APP_WRAPPER_UNREACHABLE_MESSAGE, type AgentQaMcpDepsResolver, type AgentQaMcpServerDeps, type AppReadContext, type AppResourceDeps, type AppResourceInvoke, AppResourceNegotiationError, AppResourceOriginSpanError, type AppResourceReadDeps, AppResourceReadError, type AppResourceRegistrar, AppResourceUriError, type AppWrapperInput, type AppWrapperMint, type AppWrapperState, DEGRADED_OUTPUT_CONTRACTS, JOB_STATUS_TO_MCP_TASK_STATE, type JsonDocument, LIVE_LINK_ABSENT_LINE, LIVE_LINK_NO_RUN_LINE, type LiveLinkSupplier, MCP_APPS_EXTENSION_ID, McpProjectionError, type McpTaskProjection, type McpTaskState, type OutputSchemaDegradation, type ProjectedOpSpec, type RegisterToolConfig, type ToolComposition, type ToolOpContracts, type ToolProjection, type ToolProjectionKind, UNPROJECTED_OPS, type UnprojectedOp, type V1MigrationArm, type V1ToolMigrationEntry, V1_TOOL_MIGRATION_MAP, advertisedContracts, appResourceUriFor, armWithoutDiscriminant, buildAgentQaMcpServer, compositionOf, createAgentQaMcpNodeHandler, createAppResourceReadHandler, foldArmFindings, liveLinkLine, liveLinkLineFor, liveRunLinkOf, maskedToolResult, negotiatedAppsExtension, parseAppUri, projectJobAsTask, registerAppResources, renderAppWrapperPage, stripRedundantObjectStamp, toRegisterToolConfig, withoutDialect, withoutLegacyResultWrap };