mcp-integration-harness 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -13
- package/dist/coverage.d.ts +56 -0
- package/dist/coverage.js +85 -0
- package/dist/coverage.js.map +1 -1
- package/dist/harness.d.ts +17 -0
- package/dist/harness.js +20 -3
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/schema-lint.d.ts +49 -0
- package/dist/schema-lint.js +224 -0
- package/dist/schema-lint.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,6 +75,62 @@ It fails in three directions, not one:
|
|
|
75
75
|
All three are reported together, so a fourteen-repository rollout is one
|
|
76
76
|
afternoon rather than fourteen CI rounds.
|
|
77
77
|
|
|
78
|
+
## `expectEveryToolDeclaresOutputSchema`
|
|
79
|
+
|
|
80
|
+
The same shape of check, one level up: not "was this tool called" but "does it
|
|
81
|
+
say what it returns".
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const { tools } = await harness.client.listTools();
|
|
85
|
+
expectEveryToolDeclaresOutputSchema(tools, {
|
|
86
|
+
call_tool: "forwards a child server's result; the shape is the child's",
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
It checks **presence**, and deliberately not conformance. A server that declares
|
|
91
|
+
an `outputSchema` and then answers with something else never gets that answer
|
|
92
|
+
onto the wire — the SDK validates `structuredContent` against the advertised
|
|
93
|
+
schema server-side and turns a mismatch into a failed call. So every ordinary
|
|
94
|
+
assertion in your suite is already a schema-against-reality check, and a
|
|
95
|
+
validator in here would only re-examine data that could not have arrived if it
|
|
96
|
+
were wrong.
|
|
97
|
+
|
|
98
|
+
The one thing it does check about the schema itself is that its root is an
|
|
99
|
+
object. SEP-2106 lets an output schema describe an array or a scalar, but a
|
|
100
|
+
2025-era client is served that same tool with the schema rewritten to
|
|
101
|
+
`{result: …}` — so a tool with a non-object root answers in two different shapes
|
|
102
|
+
depending on who asked. A list is `{ items: [...] }`.
|
|
103
|
+
|
|
104
|
+
Exemptions are a `Record<tool, reason>` and rot in the same three directions.
|
|
105
|
+
|
|
106
|
+
## `expectPortableToolSchemas`
|
|
107
|
+
|
|
108
|
+
Whether the schemas a server advertises are ones every client can read.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const { tools } = await harness.client.listTools();
|
|
112
|
+
expectPortableToolSchemas(tools);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Four spellings are legal JSON Schema and still get a tool refused or silently
|
|
116
|
+
stripped by some MCP clients:
|
|
117
|
+
|
|
118
|
+
| Written as | Comes from | Write instead |
|
|
119
|
+
| ---------------------------- | ------------------------------------ | --------------------------------------- |
|
|
120
|
+
| `"additionalProperties": {}` | `z.looseObject`, `.catchall()` | `.meta({ additionalProperties: true })` |
|
|
121
|
+
| `"anything": {}` | `z.unknown()`, `z.any()` | the type the value really has |
|
|
122
|
+
| `"type": ["string", "null"]` | `z.string().nullable()` from zod 4.5 | `.describe()` **before** `.nullable()` |
|
|
123
|
+
| `"$ref": "https://…"` | a hand-written schema | inline it, or `#/$defs/…` |
|
|
124
|
+
|
|
125
|
+
All four are spellings rather than contracts: each has an equivalent form that
|
|
126
|
+
says the same thing to a validator. Which is why this check takes no exemption
|
|
127
|
+
map — a reason could only ever read "not fixed yet".
|
|
128
|
+
|
|
129
|
+
It runs against the schema **as it goes on the wire**, which is the only place
|
|
130
|
+
it can run: zod emitted `anyOf` for a nullable string up to 4.4 and a `type`
|
|
131
|
+
array from 4.5 on, so the same source produces a portable schema or a
|
|
132
|
+
non-portable one depending on a patch release of a dependency.
|
|
133
|
+
|
|
78
134
|
## `assertLoopback`
|
|
79
135
|
|
|
80
136
|
The guard that matters more than the tests it protects.
|
|
@@ -104,19 +160,23 @@ would otherwise run your suite as you. Those names are blanked explicitly.
|
|
|
104
160
|
|
|
105
161
|
## API
|
|
106
162
|
|
|
107
|
-
| Export
|
|
108
|
-
|
|
|
109
|
-
| `startServer(options)`
|
|
110
|
-
| `harness.call(name, args)`
|
|
111
|
-
| `harness.raw(name, args)`
|
|
112
|
-
| `harness.confirmed(…)`
|
|
113
|
-
| `harness.prompts`
|
|
114
|
-
| `harness.stderr()`
|
|
115
|
-
| `expectEveryToolExercised`
|
|
116
|
-
| `toolCoverage`
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
163
|
+
| Export | What it does |
|
|
164
|
+
| ------------------------------------- | ------------------------------------------------------------------------------- |
|
|
165
|
+
| `startServer(options)` | Spawns `dist/index.js` over real stdio and returns a `LiveHarness` |
|
|
166
|
+
| `harness.call(name, args)` | Calls a tool, records it for coverage, returns the joined text parts |
|
|
167
|
+
| `harness.raw(name, args)` | The same, returning the whole result — for a tool that answers with an image |
|
|
168
|
+
| `harness.confirmed(…)` | Drives **both halves** of the two-call token, for the no-dialog fallback path |
|
|
169
|
+
| `harness.prompts` | Every message the server put in front of the user, in order |
|
|
170
|
+
| `harness.stderr()` | Everything the server wrote to stderr, including before the handshake completed |
|
|
171
|
+
| `expectEveryToolExercised` | The three-way coverage assertion above |
|
|
172
|
+
| `toolCoverage` | The same comparison without asserting, for printing the numbers |
|
|
173
|
+
| `expectEveryToolDeclaresOutputSchema` | Every advertised tool declares an output schema with an object root |
|
|
174
|
+
| `outputSchemaCoverage` | The same comparison without asserting |
|
|
175
|
+
| `expectPortableToolSchemas` | Every advertised schema avoids the four spellings clients mishandle |
|
|
176
|
+
| `schemaPortability` | The same lint without asserting, returning the findings |
|
|
177
|
+
| `assertLoopback(url)` | Throws unless the URL is on this machine |
|
|
178
|
+
| `waitForHttp(url, opts)` | Polls until an HTTP backend is ready, and says what the last attempt got |
|
|
179
|
+
| `waitForTcp(host, port)` | The same for a backend that is not HTTP — IMAP, SMTP — optionally on a greeting |
|
|
120
180
|
|
|
121
181
|
`elicit: 'accept' | 'decline' | 'cancel'` makes the harness declare the
|
|
122
182
|
elicitation capability and answer the dialog, which is the path a real client
|
package/dist/coverage.d.ts
CHANGED
|
@@ -54,3 +54,59 @@ export declare function toolCoverage(harness: Pick<LiveHarness, 'called'>, allTo
|
|
|
54
54
|
* Throws rather than returning, so it reads as one line in a test.
|
|
55
55
|
*/
|
|
56
56
|
export declare function expectEveryToolExercised(harness: Pick<LiveHarness, 'called'>, allTools: readonly string[], skipped?: SkipReasons): void;
|
|
57
|
+
/**
|
|
58
|
+
* One entry of `tools/list`, as much of it as this check reads.
|
|
59
|
+
*
|
|
60
|
+
* Structural rather than the SDK's `Tool`, like {@link expectEveryToolExercised}
|
|
61
|
+
* takes a `Pick<LiveHarness, …>`: the result of `client.listTools()` satisfies it
|
|
62
|
+
* as it comes, and the library stays free of a type-only import from a package it
|
|
63
|
+
* only peer-depends on.
|
|
64
|
+
*/
|
|
65
|
+
export interface AdvertisedTool {
|
|
66
|
+
name: string;
|
|
67
|
+
outputSchema?: unknown;
|
|
68
|
+
}
|
|
69
|
+
export interface OutputSchemaReport {
|
|
70
|
+
declared: readonly string[];
|
|
71
|
+
exempt: readonly string[];
|
|
72
|
+
/** Advertised without an `outputSchema`, and not exempt. */
|
|
73
|
+
missing: readonly string[];
|
|
74
|
+
/** Exempt, but declares one after all — the reason is stale. */
|
|
75
|
+
staleReasons: readonly string[];
|
|
76
|
+
/** Exempt, but no longer a tool — the reason outlived its tool. */
|
|
77
|
+
unknownReasons: readonly string[];
|
|
78
|
+
/** Declared with a root that is not `"object"`. */
|
|
79
|
+
nonObjectRoot: readonly string[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Compares the advertised tools against the rule, without asserting.
|
|
83
|
+
*
|
|
84
|
+
* Separate from the assertion for the reason {@link toolCoverage} is: "62 of 62
|
|
85
|
+
* tools declare an output schema" belongs in a CI log on a green run too.
|
|
86
|
+
*/
|
|
87
|
+
export declare function outputSchemaCoverage(tools: readonly AdvertisedTool[], exempt?: SkipReasons): OutputSchemaReport;
|
|
88
|
+
/**
|
|
89
|
+
* Fails unless every advertised tool declares an output schema with an object
|
|
90
|
+
* root.
|
|
91
|
+
*
|
|
92
|
+
* The presence half only. What the schema *says* needs no check here: a server
|
|
93
|
+
* that declares an `outputSchema` and then answers with something else never
|
|
94
|
+
* gets that answer past its own SDK, which validates `structuredContent`
|
|
95
|
+
* against the advertised schema before it goes on the wire and turns a mismatch
|
|
96
|
+
* into a failed call. So every ordinary assertion in the suite is already a
|
|
97
|
+
* schema-against-reality check, and a validator in here would only re-examine
|
|
98
|
+
* data that could not have arrived if it were wrong.
|
|
99
|
+
*
|
|
100
|
+
* The object root is checked, though, and is not pedantry. SEP-2106 lets an
|
|
101
|
+
* output schema describe an array or a scalar, but a 2025-era client is served
|
|
102
|
+
* that same tool with the schema rewritten to `{result: …}` — so a tool with a
|
|
103
|
+
* non-object root answers in two different shapes depending on who asked. A
|
|
104
|
+
* list is `{ items: [...] }`.
|
|
105
|
+
*
|
|
106
|
+
* Exemptions take a written reason and rot in the same three directions
|
|
107
|
+
* {@link expectEveryToolExercised} guards against. The one that has earned its
|
|
108
|
+
* place so far:
|
|
109
|
+
*
|
|
110
|
+
* call_tool: 'forwards a child server's result; the shape is the child's'
|
|
111
|
+
*/
|
|
112
|
+
export declare function expectEveryToolDeclaresOutputSchema(tools: readonly AdvertisedTool[], exempt?: SkipReasons): void;
|
package/dist/coverage.js
CHANGED
|
@@ -55,4 +55,89 @@ export function expectEveryToolExercised(harness, allTools, skipped = {}) {
|
|
|
55
55
|
`${report.skipped.length} excused.\n\n${problems.join('\n\n')}`);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
/** Whether a value is a JSON Schema whose instance root is an object. */
|
|
59
|
+
function hasObjectRoot(schema) {
|
|
60
|
+
if (typeof schema !== 'object' || schema === null)
|
|
61
|
+
return false;
|
|
62
|
+
return schema.type === 'object';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Compares the advertised tools against the rule, without asserting.
|
|
66
|
+
*
|
|
67
|
+
* Separate from the assertion for the reason {@link toolCoverage} is: "62 of 62
|
|
68
|
+
* tools declare an output schema" belongs in a CI log on a green run too.
|
|
69
|
+
*/
|
|
70
|
+
export function outputSchemaCoverage(tools, exempt = {}) {
|
|
71
|
+
const names = new Set(tools.map((tool) => tool.name));
|
|
72
|
+
const reasons = Object.keys(exempt);
|
|
73
|
+
const withSchema = tools.filter((tool) => tool.outputSchema !== undefined);
|
|
74
|
+
return {
|
|
75
|
+
declared: withSchema.map((tool) => tool.name).sort(),
|
|
76
|
+
exempt: reasons.sort(),
|
|
77
|
+
missing: tools
|
|
78
|
+
.filter((tool) => tool.outputSchema === undefined && !(tool.name in exempt))
|
|
79
|
+
.map((tool) => tool.name)
|
|
80
|
+
.sort(),
|
|
81
|
+
staleReasons: withSchema
|
|
82
|
+
.filter((tool) => tool.name in exempt)
|
|
83
|
+
.map((tool) => tool.name)
|
|
84
|
+
.sort(),
|
|
85
|
+
unknownReasons: reasons.filter((name) => !names.has(name)).sort(),
|
|
86
|
+
nonObjectRoot: withSchema
|
|
87
|
+
.filter((tool) => !hasObjectRoot(tool.outputSchema))
|
|
88
|
+
.map((tool) => tool.name)
|
|
89
|
+
.sort(),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Fails unless every advertised tool declares an output schema with an object
|
|
94
|
+
* root.
|
|
95
|
+
*
|
|
96
|
+
* The presence half only. What the schema *says* needs no check here: a server
|
|
97
|
+
* that declares an `outputSchema` and then answers with something else never
|
|
98
|
+
* gets that answer past its own SDK, which validates `structuredContent`
|
|
99
|
+
* against the advertised schema before it goes on the wire and turns a mismatch
|
|
100
|
+
* into a failed call. So every ordinary assertion in the suite is already a
|
|
101
|
+
* schema-against-reality check, and a validator in here would only re-examine
|
|
102
|
+
* data that could not have arrived if it were wrong.
|
|
103
|
+
*
|
|
104
|
+
* The object root is checked, though, and is not pedantry. SEP-2106 lets an
|
|
105
|
+
* output schema describe an array or a scalar, but a 2025-era client is served
|
|
106
|
+
* that same tool with the schema rewritten to `{result: …}` — so a tool with a
|
|
107
|
+
* non-object root answers in two different shapes depending on who asked. A
|
|
108
|
+
* list is `{ items: [...] }`.
|
|
109
|
+
*
|
|
110
|
+
* Exemptions take a written reason and rot in the same three directions
|
|
111
|
+
* {@link expectEveryToolExercised} guards against. The one that has earned its
|
|
112
|
+
* place so far:
|
|
113
|
+
*
|
|
114
|
+
* call_tool: 'forwards a child server's result; the shape is the child's'
|
|
115
|
+
*/
|
|
116
|
+
export function expectEveryToolDeclaresOutputSchema(tools, exempt = {}) {
|
|
117
|
+
const report = outputSchemaCoverage(tools, exempt);
|
|
118
|
+
const problems = [];
|
|
119
|
+
if (report.missing.length > 0) {
|
|
120
|
+
problems.push(`${report.missing.length} tool(s) declare no outputSchema: ` +
|
|
121
|
+
`${report.missing.join(', ')}. Declare one and return structuredContent, ` +
|
|
122
|
+
'or give each a reason saying why the shape is not this server to state.');
|
|
123
|
+
}
|
|
124
|
+
if (report.staleReasons.length > 0) {
|
|
125
|
+
problems.push(`${report.staleReasons.length} exempt tool(s) declare one after all: ` +
|
|
126
|
+
`${report.staleReasons.join(', ')}. Remove the reason — it is no longer true.`);
|
|
127
|
+
}
|
|
128
|
+
if (report.unknownReasons.length > 0) {
|
|
129
|
+
problems.push(`${report.unknownReasons.length} reason(s) name a tool that no longer ` +
|
|
130
|
+
`exists: ${report.unknownReasons.join(', ')}.`);
|
|
131
|
+
}
|
|
132
|
+
if (report.nonObjectRoot.length > 0) {
|
|
133
|
+
problems.push(`${report.nonObjectRoot.length} tool(s) declare a non-object root: ` +
|
|
134
|
+
`${report.nonObjectRoot.join(', ')}. A 2025-era client is served that ` +
|
|
135
|
+
'schema wrapped as {result: …}, so the answer has two shapes. Wrap the ' +
|
|
136
|
+
'value in an object — a list is { items: [...] }.');
|
|
137
|
+
}
|
|
138
|
+
if (problems.length > 0) {
|
|
139
|
+
throw new Error(`${report.declared.length} of ${tools.length} tools declare an output ` +
|
|
140
|
+
`schema, ${report.exempt.length} exempt.\n\n${problems.join('\n\n')}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
58
143
|
//# sourceMappingURL=coverage.js.map
|
package/dist/coverage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../src/coverage.ts"],"names":[],"mappings":"AAkCA;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAoC,EACpC,QAA2B,EAC3B,OAAoB;IAEpB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,OAAO;QACL,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE;QAClC,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE;QACvB,OAAO,EAAE,QAAQ;aACd,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC;aACjE,IAAI,EAAE;QACT,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE;QACvE,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE;KACtE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAoC,EACpC,QAA2B,EAC3B,OAAO,GAAgB,EAAE;IAEzB,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,QAAQ,CAAC,IAAI,CACX,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,yCAAyC;YAC/D,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,4CAA4C;YACxE,qDAAqD,CACxD,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,QAAQ,CAAC,IAAI,CACX,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,0CAA0C;YACrE,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,6CAA6C,CACjF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,QAAQ,CAAC,IAAI,CACX,GAAG,MAAM,CAAC,cAAc,CAAC,MAAM,wCAAwC;YACrE,WAAW,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACjD,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,OAAO,QAAQ,CAAC,MAAM,oBAAoB;YAC/D,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,gBAAgB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAClE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../src/coverage.ts"],"names":[],"mappings":"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"}
|
package/dist/harness.d.ts
CHANGED
|
@@ -63,6 +63,17 @@ export interface ToolResult {
|
|
|
63
63
|
mimeType?: string;
|
|
64
64
|
data?: string;
|
|
65
65
|
}[];
|
|
66
|
+
/**
|
|
67
|
+
* The machine-readable half of an answer, for a tool that declares an
|
|
68
|
+
* `outputSchema`.
|
|
69
|
+
*
|
|
70
|
+
* `unknown` rather than a shape, because the shape is the server's to declare
|
|
71
|
+
* and the suite's to assert. Worth knowing: a value that reaches here has
|
|
72
|
+
* already been validated against the advertised schema — the SDK does that
|
|
73
|
+
* server-side and turns a mismatch into a failed call — so a test that reads
|
|
74
|
+
* this field is checking *which* data came back, not whether it conformed.
|
|
75
|
+
*/
|
|
76
|
+
structuredContent?: unknown;
|
|
66
77
|
isError?: boolean;
|
|
67
78
|
}
|
|
68
79
|
export interface LiveHarness {
|
|
@@ -91,6 +102,12 @@ export interface LiveHarness {
|
|
|
91
102
|
* Only meaningful on a harness started **without** `elicit`: with a dialog
|
|
92
103
|
* available the server refuses to offer a token at all, which is the whole
|
|
93
104
|
* point of the dialog. Use it to prove the fallback path still works.
|
|
105
|
+
*
|
|
106
|
+
* The first half is expected to **fail** — `mcp-approval` marks the prompt
|
|
107
|
+
* `isError`, because the operation was asked for and did not happen, and
|
|
108
|
+
* because a tool that declares an `outputSchema` may not answer without
|
|
109
|
+
* `structuredContent` unless the result is an error. The second half is
|
|
110
|
+
* expected to succeed, and its text is what comes back.
|
|
94
111
|
*/
|
|
95
112
|
confirmed(name: string, args?: Record<string, unknown>): Promise<string>;
|
|
96
113
|
/** Every message the server put in front of the user, in order. */
|
package/dist/harness.js
CHANGED
|
@@ -89,7 +89,16 @@ export async function startServer(options) {
|
|
|
89
89
|
throw new Error(`mcp-integration-harness: ${process.execPath} ${entry} did not start.\n` +
|
|
90
90
|
`${String(error)}\n\nIts stderr:\n${errors.join('') || '(nothing)'}`);
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* The call itself: coverage bookkeeping, the transport, the framing check.
|
|
94
|
+
*
|
|
95
|
+
* Separate from {@link raw} because one caller has no expectation to state.
|
|
96
|
+
* `confirmed()` drives a path whose first half is an error result on a
|
|
97
|
+
* current `mcp-approval` and was not on an older one, and neither branch of
|
|
98
|
+
* `expectError` describes "I am about to read a token out of this, and if it
|
|
99
|
+
* is not there I have a better sentence than either".
|
|
100
|
+
*/
|
|
101
|
+
const invoke = async (name, args) => {
|
|
93
102
|
called.add(name);
|
|
94
103
|
let result;
|
|
95
104
|
try {
|
|
@@ -111,6 +120,11 @@ export async function startServer(options) {
|
|
|
111
120
|
'JSON-RPC message, and the reply is discarded inside the read buffer ' +
|
|
112
121
|
'where no hook can see it. stdout belongs to the transport.');
|
|
113
122
|
}
|
|
123
|
+
assertFramingIntact(`calling ${name}`);
|
|
124
|
+
return result;
|
|
125
|
+
};
|
|
126
|
+
const raw = async (name, args = {}, callOptions = {}) => {
|
|
127
|
+
const result = await invoke(name, args);
|
|
114
128
|
const expectation = callOptions.expectError ?? false;
|
|
115
129
|
const wantFailure = expectation !== false;
|
|
116
130
|
const failed = result.isError === true;
|
|
@@ -133,7 +147,6 @@ export async function startServer(options) {
|
|
|
133
147
|
`Expected the message to match: ${String(expectation)}\n` +
|
|
134
148
|
`Got: ${text.slice(0, 500)}`);
|
|
135
149
|
}
|
|
136
|
-
assertFramingIntact(`calling ${name}`);
|
|
137
150
|
return result;
|
|
138
151
|
};
|
|
139
152
|
const call = async (name, args = {}, callOptions = {}) => textOf(await raw(name, args, callOptions));
|
|
@@ -163,7 +176,11 @@ export async function startServer(options) {
|
|
|
163
176
|
called,
|
|
164
177
|
stderr: () => errors.join(''),
|
|
165
178
|
confirmed: async (name, args = {}) => {
|
|
166
|
-
|
|
179
|
+
// `invoke`, not `call`: the first half is an error result — the prompt
|
|
180
|
+
// says the operation did not happen — and asserting on that here would
|
|
181
|
+
// replace `tokenOf`'s sentence, which names the real mistake (calling
|
|
182
|
+
// this on a harness started *with* `elicit`), with a generic one.
|
|
183
|
+
const first = textOf(await invoke(name, args));
|
|
167
184
|
return call(name, { ...args, confirm_token: tokenOf(first) });
|
|
168
185
|
},
|
|
169
186
|
close: async () => {
|
package/dist/harness.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtD,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,GACrB,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { startServer, tokenOf, type CallOptions, type ElicitBehaviour, type LiveHarness, type StartServerOptions, type ToolResult, } from './harness.js';
|
|
2
|
-
export { expectEveryToolExercised, toolCoverage, type CoverageReport, type SkipReasons, } from './coverage.js';
|
|
2
|
+
export { expectEveryToolDeclaresOutputSchema, expectEveryToolExercised, outputSchemaCoverage, toolCoverage, type AdvertisedTool, type CoverageReport, type OutputSchemaReport, type SkipReasons, } from './coverage.js';
|
|
3
|
+
export { expectPortableToolSchemas, schemaPortability, type SchemaBearingTool, type SchemaFinding, type SchemaLintRule, } from './schema-lint.js';
|
|
3
4
|
export { assertLoopback, assertLoopbackHost } from './loopback.js';
|
|
4
5
|
export { waitForHttp, waitForTcp, type TcpWaitOptions, type WaitOptions, } from './wait.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { startServer, tokenOf, } from './harness.js';
|
|
2
|
-
export { expectEveryToolExercised, toolCoverage, } from './coverage.js';
|
|
2
|
+
export { expectEveryToolDeclaresOutputSchema, expectEveryToolExercised, outputSchemaCoverage, toolCoverage, } from './coverage.js';
|
|
3
|
+
export { expectPortableToolSchemas, schemaPortability, } from './schema-lint.js';
|
|
3
4
|
export { assertLoopback, assertLoopbackHost } from './loopback.js';
|
|
4
5
|
export { waitForHttp, waitForTcp, } from './wait.js';
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,OAAO,GAMR,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,wBAAwB,EACxB,YAAY,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,OAAO,GAMR,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,mCAAmC,EACnC,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,GAKb,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,yBAAyB,EACzB,iBAAiB,GAIlB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EACL,WAAW,EACX,UAAU,GAGX,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portability rules for the JSON Schemas a server advertises.
|
|
3
|
+
*
|
|
4
|
+
* A schema can be perfectly legal JSON Schema and still be refused or
|
|
5
|
+
* mishandled by a client, and the four rules below are the ones this family of
|
|
6
|
+
* servers has actually walked into. All four are spellings rather than
|
|
7
|
+
* contracts: each has an equivalent form that says the same thing to a
|
|
8
|
+
* validator and survives the trip to every client.
|
|
9
|
+
*
|
|
10
|
+
* The checks run against the schema as it goes out on the wire, which is the
|
|
11
|
+
* only place they can run. Zod is what writes that schema here, and what it
|
|
12
|
+
* writes changes between its own releases: `z.string().nullable()` emitted
|
|
13
|
+
* `anyOf` up to zod 4.4 and a `"type": ["string", "null"]` array from 4.5 on.
|
|
14
|
+
* A source-level rule would have missed that; a wire-level one cannot.
|
|
15
|
+
*/
|
|
16
|
+
/** Which spelling a finding is about. */
|
|
17
|
+
export type SchemaLintRule = 'untyped-schema' | 'boolean-schema' | 'type-union' | 'remote-ref';
|
|
18
|
+
export interface SchemaFinding {
|
|
19
|
+
rule: SchemaLintRule;
|
|
20
|
+
/** The tool whose schema carries it. */
|
|
21
|
+
tool: string;
|
|
22
|
+
/** `inputSchema` or `outputSchema`, plus the path inside it. */
|
|
23
|
+
path: string;
|
|
24
|
+
/** What is wrong, and what to write instead. */
|
|
25
|
+
message: string;
|
|
26
|
+
}
|
|
27
|
+
/** One entry of `tools/list`, as much of it as this check reads. */
|
|
28
|
+
export interface SchemaBearingTool {
|
|
29
|
+
name: string;
|
|
30
|
+
inputSchema?: unknown;
|
|
31
|
+
outputSchema?: unknown;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Lints the advertised schemas, without asserting.
|
|
35
|
+
*
|
|
36
|
+
* Separate from the assertion for the reason {@link toolCoverage} is: the count
|
|
37
|
+
* is worth a line in a CI log on a green run too.
|
|
38
|
+
*/
|
|
39
|
+
export declare function schemaPortability(tools: readonly SchemaBearingTool[]): SchemaFinding[];
|
|
40
|
+
/**
|
|
41
|
+
* Fails unless every advertised schema is one every client can read.
|
|
42
|
+
*
|
|
43
|
+
* No exemption map, unlike the checks in `coverage.ts`. Those excuse what a
|
|
44
|
+
* *backend* cannot provide, which is a fact about the world and needs writing
|
|
45
|
+
* down. There is nothing to excuse here: every finding has an equivalent
|
|
46
|
+
* spelling that says the same thing, so a reason could only ever read "not
|
|
47
|
+
* fixed yet".
|
|
48
|
+
*/
|
|
49
|
+
export declare function expectPortableToolSchemas(tools: readonly SchemaBearingTool[]): void;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portability rules for the JSON Schemas a server advertises.
|
|
3
|
+
*
|
|
4
|
+
* A schema can be perfectly legal JSON Schema and still be refused or
|
|
5
|
+
* mishandled by a client, and the four rules below are the ones this family of
|
|
6
|
+
* servers has actually walked into. All four are spellings rather than
|
|
7
|
+
* contracts: each has an equivalent form that says the same thing to a
|
|
8
|
+
* validator and survives the trip to every client.
|
|
9
|
+
*
|
|
10
|
+
* The checks run against the schema as it goes out on the wire, which is the
|
|
11
|
+
* only place they can run. Zod is what writes that schema here, and what it
|
|
12
|
+
* writes changes between its own releases: `z.string().nullable()` emitted
|
|
13
|
+
* `anyOf` up to zod 4.4 and a `"type": ["string", "null"]` array from 4.5 on.
|
|
14
|
+
* A source-level rule would have missed that; a wire-level one cannot.
|
|
15
|
+
*/
|
|
16
|
+
/** Keywords that constrain the instance. Anything else only annotates it. */
|
|
17
|
+
const CONSTRAINING_KEYWORDS = new Set([
|
|
18
|
+
// Assertions
|
|
19
|
+
'type',
|
|
20
|
+
'enum',
|
|
21
|
+
'const',
|
|
22
|
+
'multipleOf',
|
|
23
|
+
'maximum',
|
|
24
|
+
'exclusiveMaximum',
|
|
25
|
+
'minimum',
|
|
26
|
+
'exclusiveMinimum',
|
|
27
|
+
'maxLength',
|
|
28
|
+
'minLength',
|
|
29
|
+
'pattern',
|
|
30
|
+
'maxItems',
|
|
31
|
+
'minItems',
|
|
32
|
+
'uniqueItems',
|
|
33
|
+
'maxContains',
|
|
34
|
+
'minContains',
|
|
35
|
+
'maxProperties',
|
|
36
|
+
'minProperties',
|
|
37
|
+
'required',
|
|
38
|
+
'dependentRequired',
|
|
39
|
+
'format',
|
|
40
|
+
'contentEncoding',
|
|
41
|
+
'contentMediaType',
|
|
42
|
+
'contentSchema',
|
|
43
|
+
// Applicators
|
|
44
|
+
'items',
|
|
45
|
+
'prefixItems',
|
|
46
|
+
'contains',
|
|
47
|
+
'additionalItems',
|
|
48
|
+
'unevaluatedItems',
|
|
49
|
+
'properties',
|
|
50
|
+
'patternProperties',
|
|
51
|
+
'additionalProperties',
|
|
52
|
+
'propertyNames',
|
|
53
|
+
'unevaluatedProperties',
|
|
54
|
+
'dependentSchemas',
|
|
55
|
+
'allOf',
|
|
56
|
+
'anyOf',
|
|
57
|
+
'oneOf',
|
|
58
|
+
'not',
|
|
59
|
+
// References
|
|
60
|
+
'$ref',
|
|
61
|
+
'$dynamicRef',
|
|
62
|
+
'$recursiveRef',
|
|
63
|
+
]);
|
|
64
|
+
/**
|
|
65
|
+
* Keywords whose value may be a bare boolean without anyone minding.
|
|
66
|
+
*
|
|
67
|
+
* `"additionalProperties": true` is the ordinary way to write an open object
|
|
68
|
+
* and every client reads it. A bare `true` anywhere a schema *object* is
|
|
69
|
+
* expected is a different matter.
|
|
70
|
+
*/
|
|
71
|
+
const BOOLEAN_VALUED_KEYWORDS = new Set([
|
|
72
|
+
'additionalProperties',
|
|
73
|
+
'unevaluatedProperties',
|
|
74
|
+
'additionalItems',
|
|
75
|
+
'unevaluatedItems',
|
|
76
|
+
]);
|
|
77
|
+
const SUBSCHEMA_KEYWORDS = [
|
|
78
|
+
'items',
|
|
79
|
+
'contains',
|
|
80
|
+
'not',
|
|
81
|
+
'propertyNames',
|
|
82
|
+
'if',
|
|
83
|
+
'then',
|
|
84
|
+
'else',
|
|
85
|
+
'additionalProperties',
|
|
86
|
+
'unevaluatedProperties',
|
|
87
|
+
'additionalItems',
|
|
88
|
+
'unevaluatedItems',
|
|
89
|
+
'contentSchema',
|
|
90
|
+
];
|
|
91
|
+
const SUBSCHEMA_ARRAY_KEYWORDS = ['allOf', 'anyOf', 'oneOf', 'prefixItems'];
|
|
92
|
+
const SUBSCHEMA_MAP_KEYWORDS = [
|
|
93
|
+
'properties',
|
|
94
|
+
'patternProperties',
|
|
95
|
+
'dependentSchemas',
|
|
96
|
+
'dependencies',
|
|
97
|
+
'$defs',
|
|
98
|
+
'definitions',
|
|
99
|
+
];
|
|
100
|
+
const JSON_SCHEMA_TYPES = new Set([
|
|
101
|
+
'null',
|
|
102
|
+
'boolean',
|
|
103
|
+
'object',
|
|
104
|
+
'array',
|
|
105
|
+
'number',
|
|
106
|
+
'string',
|
|
107
|
+
'integer',
|
|
108
|
+
]);
|
|
109
|
+
/** Guards against a `$ref` cycle turning the walk into a hang. */
|
|
110
|
+
const MAX_DEPTH = 64;
|
|
111
|
+
function isRecord(value) {
|
|
112
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
113
|
+
}
|
|
114
|
+
function constrainsInstance(node) {
|
|
115
|
+
if (Object.keys(node).some((key) => CONSTRAINING_KEYWORDS.has(key))) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
// `if` alone constrains nothing; with a branch beside it, it does.
|
|
119
|
+
return 'if' in node && ('then' in node || 'else' in node);
|
|
120
|
+
}
|
|
121
|
+
function isPlainTypeUnion(type) {
|
|
122
|
+
if (!Array.isArray(type) || type.length === 0)
|
|
123
|
+
return false;
|
|
124
|
+
return type.every((entry) => typeof entry === 'string' && JSON_SCHEMA_TYPES.has(entry));
|
|
125
|
+
}
|
|
126
|
+
function walk(node, path, keyword, depth, emit) {
|
|
127
|
+
if (depth > MAX_DEPTH)
|
|
128
|
+
return;
|
|
129
|
+
if (typeof node === 'boolean') {
|
|
130
|
+
if (keyword !== undefined && BOOLEAN_VALUED_KEYWORDS.has(keyword))
|
|
131
|
+
return;
|
|
132
|
+
emit('boolean-schema', path, `bare \`${String(node)}\` where a schema object is expected. Write ` +
|
|
133
|
+
(node
|
|
134
|
+
? 'the object form — `{}` under `not`, or the type the value really has.'
|
|
135
|
+
: '`{"not": {}}`; deleting the entry allows the property instead of forbidding it.'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!isRecord(node))
|
|
139
|
+
return;
|
|
140
|
+
if (isPlainTypeUnion(node.type)) {
|
|
141
|
+
emit('type-union', path, `\`type\` is an array (${JSON.stringify(node.type)}). Several clients ` +
|
|
142
|
+
'read `type` as a single string and drop the constraint or refuse the ' +
|
|
143
|
+
'tool. Split it into `anyOf` branches — in zod, put `.describe()` on ' +
|
|
144
|
+
'the inner type *before* `.nullable()`, which stops it folding the two ' +
|
|
145
|
+
'into one `type` array.');
|
|
146
|
+
}
|
|
147
|
+
const ref = node.$ref;
|
|
148
|
+
if (typeof ref === 'string' && ref !== '' && !ref.startsWith('#')) {
|
|
149
|
+
emit('remote-ref', path, `\`$ref\` points outside this document (\`${ref}\`). Clients do not ` +
|
|
150
|
+
'fetch remote schemas. Inline it, or move it into `$defs`.');
|
|
151
|
+
}
|
|
152
|
+
if (!constrainsInstance(node) && keyword !== 'not') {
|
|
153
|
+
emit('untyped-schema', path, 'no validation keyword at all, so this accepts any value — the object ' +
|
|
154
|
+
'spelling of a bare `true`, which some clients refuse or mishandle. ' +
|
|
155
|
+
'In zod: `.meta({ additionalProperties: true })` on a `looseObject` or ' +
|
|
156
|
+
'a `catchall`, and the real type instead of `z.unknown()`.');
|
|
157
|
+
}
|
|
158
|
+
for (const key of SUBSCHEMA_MAP_KEYWORDS) {
|
|
159
|
+
const map = node[key];
|
|
160
|
+
if (!isRecord(map))
|
|
161
|
+
continue;
|
|
162
|
+
for (const [name, child] of Object.entries(map)) {
|
|
163
|
+
walk(child, `${path}.${key}.${name}`, key, depth + 1, emit);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const key of SUBSCHEMA_ARRAY_KEYWORDS) {
|
|
167
|
+
const list = node[key];
|
|
168
|
+
if (!Array.isArray(list))
|
|
169
|
+
continue;
|
|
170
|
+
list.forEach((child, index) => {
|
|
171
|
+
walk(child, `${path}.${key}[${index}]`, key, depth + 1, emit);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
for (const key of SUBSCHEMA_KEYWORDS) {
|
|
175
|
+
if (!(key in node))
|
|
176
|
+
continue;
|
|
177
|
+
const child = node[key];
|
|
178
|
+
if (key === 'items' && Array.isArray(child)) {
|
|
179
|
+
child.forEach((entry, index) => {
|
|
180
|
+
walk(entry, `${path}.${key}[${index}]`, key, depth + 1, emit);
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
walk(child, `${path}.${key}`, key, depth + 1, emit);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Lints the advertised schemas, without asserting.
|
|
189
|
+
*
|
|
190
|
+
* Separate from the assertion for the reason {@link toolCoverage} is: the count
|
|
191
|
+
* is worth a line in a CI log on a green run too.
|
|
192
|
+
*/
|
|
193
|
+
export function schemaPortability(tools) {
|
|
194
|
+
const findings = [];
|
|
195
|
+
for (const tool of tools) {
|
|
196
|
+
for (const kind of ['inputSchema', 'outputSchema']) {
|
|
197
|
+
const schema = tool[kind];
|
|
198
|
+
if (schema === undefined)
|
|
199
|
+
continue;
|
|
200
|
+
walk(schema, kind, undefined, 0, (rule, path, message) => {
|
|
201
|
+
findings.push({ rule, tool: tool.name, path, message });
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return findings;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Fails unless every advertised schema is one every client can read.
|
|
209
|
+
*
|
|
210
|
+
* No exemption map, unlike the checks in `coverage.ts`. Those excuse what a
|
|
211
|
+
* *backend* cannot provide, which is a fact about the world and needs writing
|
|
212
|
+
* down. There is nothing to excuse here: every finding has an equivalent
|
|
213
|
+
* spelling that says the same thing, so a reason could only ever read "not
|
|
214
|
+
* fixed yet".
|
|
215
|
+
*/
|
|
216
|
+
export function expectPortableToolSchemas(tools) {
|
|
217
|
+
const findings = schemaPortability(tools);
|
|
218
|
+
if (findings.length === 0)
|
|
219
|
+
return;
|
|
220
|
+
const lines = findings.map((finding) => ` ${finding.tool} — ${finding.path}: ${finding.message}`);
|
|
221
|
+
throw new Error(`${findings.length} schema portability problem(s) across ` +
|
|
222
|
+
`${tools.length} tool(s):\n\n${lines.join('\n\n')}`);
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=schema-lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-lint.js","sourceRoot":"","sources":["../src/schema-lint.ts"],"names":[],"mappings":"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"}
|