@vostride/agent-qa-mcp 0.1.21 → 0.2.2
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.cjs +2095 -1028
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1009 -129
- package/dist/index.d.ts +1009 -129
- package/dist/index.js +2113 -1036
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,139 +1,1019 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import * as
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
declare
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 {
|
|
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 };
|