mcproof 0.3.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/bin/mcproof.js +12 -0
- package/dist/assertions.d.ts +81 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/assertions.js +300 -0
- package/dist/assertions.js.map +1 -0
- package/dist/autoSetup.d.ts +2 -0
- package/dist/autoSetup.d.ts.map +1 -0
- package/dist/autoSetup.js +6 -0
- package/dist/autoSetup.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +346 -0
- package/dist/cli.js.map +1 -0
- package/dist/env.d.ts +3 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +100 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/jestPreset.d.ts +18 -0
- package/dist/jestPreset.d.ts.map +1 -0
- package/dist/jestPreset.js +26 -0
- package/dist/jestPreset.js.map +1 -0
- package/dist/mcpTestClient.d.ts +40 -0
- package/dist/mcpTestClient.d.ts.map +1 -0
- package/dist/mcpTestClient.js +500 -0
- package/dist/mcpTestClient.js.map +1 -0
- package/dist/protocol.d.ts +8 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +133 -0
- package/dist/protocol.js.map +1 -0
- package/dist/reporting.d.ts +53 -0
- package/dist/reporting.d.ts.map +1 -0
- package/dist/reporting.js +239 -0
- package/dist/reporting.js.map +1 -0
- package/dist/sharedClient.d.ts +9 -0
- package/dist/sharedClient.d.ts.map +1 -0
- package/dist/sharedClient.js +101 -0
- package/dist/sharedClient.js.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nathaniel Caron
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# MCProof 🛡️
|
|
2
|
+
|
|
3
|
+
MCProof: A framework for testing MCP servers
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install mcproof
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
Create a `.env.mcproof` file in your project root:
|
|
14
|
+
|
|
15
|
+
```env
|
|
16
|
+
MCPROOF_BASE_URL=http://localhost:36719
|
|
17
|
+
MCPROOF_TIMEOUT_MS=10000
|
|
18
|
+
MCPROOF_HEADERS='{ "Authorization": "Bearer integration-token", "x-api-key":"demo-key" }'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Write granular test files without any local client setup:
|
|
22
|
+
|
|
23
|
+
`tools.test.ts`
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { expectTool, expectToolCallContent, expectToolCallSuccess, getSharedMcpTestClient } from 'mcproof';
|
|
27
|
+
|
|
28
|
+
describe('get_current_time', () => {
|
|
29
|
+
const client = getSharedMcpTestClient();
|
|
30
|
+
|
|
31
|
+
test('tool is present', async () => {
|
|
32
|
+
await expectTool(client, 'get_current_time');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('responds successfully', async () => {
|
|
36
|
+
const result = await client.invokeTool({
|
|
37
|
+
name: 'get_current_time',
|
|
38
|
+
input: { timezone: 'America/New_York' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expectToolCallSuccess(result);
|
|
42
|
+
expectToolCallContent(result, expect.objectContaining({
|
|
43
|
+
timezone: 'America/New_York',
|
|
44
|
+
}));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('fails when required input is missing', async () => {
|
|
48
|
+
await expectToolCallError(
|
|
49
|
+
client.invokeTool({ name: 'get_current_time', input: {} })
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`resources.test.ts`
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import {
|
|
59
|
+
expectResource,
|
|
60
|
+
expectResourceReadContent,
|
|
61
|
+
expectResourceReadSuccess,
|
|
62
|
+
getSharedMcpTestClient,
|
|
63
|
+
} from 'mcproof';
|
|
64
|
+
|
|
65
|
+
describe('time_tool_app resource', () => {
|
|
66
|
+
const client = getSharedMcpTestClient();
|
|
67
|
+
|
|
68
|
+
test('is available', async () => {
|
|
69
|
+
await expectResource(client, 'ui://time-tool/mcp-app.html', {
|
|
70
|
+
mimeType: 'text/html;profile=mcp-app',
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('returns content', async () => {
|
|
75
|
+
const result = await client.readResource({ uri: 'ui://time-tool/mcp-app.html' });
|
|
76
|
+
expectResourceReadSuccess(result);
|
|
77
|
+
expectResourceReadContent(result, expect.any(Array));
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`prompts.test.ts`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import {
|
|
86
|
+
expectPrompt,
|
|
87
|
+
expectPromptGetContent,
|
|
88
|
+
expectPromptGetSuccess,
|
|
89
|
+
getSharedMcpTestClient,
|
|
90
|
+
} from 'mcproof';
|
|
91
|
+
|
|
92
|
+
describe('time_expert_prompt', () => {
|
|
93
|
+
const client = getSharedMcpTestClient();
|
|
94
|
+
|
|
95
|
+
test('is available', async () => {
|
|
96
|
+
await expectPrompt(client, 'time_expert_prompt', {
|
|
97
|
+
argumentCount: 0,
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('returns messages', async () => {
|
|
102
|
+
const result = await client.getPrompt({ name: 'time_expert_prompt' });
|
|
103
|
+
expectPromptGetSuccess(result);
|
|
104
|
+
expectPromptGetContent(result, expect.any(Array));
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Run the suite with the framework CLI:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx mcproof test
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
e.g.,
|
|
116
|
+

|
|
117
|
+
|
|
118
|
+
Each CLI test run also writes a simple HTML report under `mcproof-reports/` in your current working directory.
|
|
119
|
+
|
|
120
|
+
e.g.,
|
|
121
|
+

|
|
122
|
+

|
|
123
|
+
|
|
124
|
+
Show the installed package version:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx mcproof --version
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Testing for Errors
|
|
131
|
+
|
|
132
|
+
Error assertions support two patterns, but the **promise pattern** is the default and safest option.
|
|
133
|
+
|
|
134
|
+
**Promise pattern** — pass the promise directly WITHOUT awaiting to catch validation errors:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
test('fails when required input is missing', async () => {
|
|
138
|
+
await expectToolCallError(
|
|
139
|
+
client.invokeTool({ name: 'get_current_time', input: {} })
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('fails for invalid URI', async () => {
|
|
144
|
+
await expectResourceReadError(
|
|
145
|
+
client.readResource({ uri: 'invalid://uri' })
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('fails for unknown name', async () => {
|
|
150
|
+
await expectPromptGetError(
|
|
151
|
+
client.getPrompt({ name: 'unknown_prompt' })
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Sync result pattern** — only use this when the SDK returns an error result object (does not throw):
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
test('tool call returns error', async () => {
|
|
160
|
+
const result = await client.invokeTool({ name: 'some_tool', input: { bad: 'input' } });
|
|
161
|
+
expectToolCallError(result, 'MCP error -32602: validation failed');
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Try/catch pattern** — use this when you intentionally want to assert on a thrown error directly:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
test('tool throws when required input is missing', async () => {
|
|
169
|
+
try {
|
|
170
|
+
await client.invokeTool({ name: 'get_current_time', input: {} });
|
|
171
|
+
throw new Error('Expected invokeTool to throw');
|
|
172
|
+
} catch (error) {
|
|
173
|
+
expect(String(error)).toContain('MCP error -32602');
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Env Configuration
|
|
179
|
+
|
|
180
|
+
- `MCPROOF_BASE_URL`: required MCP server base URL
|
|
181
|
+
- `MCPROOF_TIMEOUT_MS`: optional timeout in milliseconds
|
|
182
|
+
- `MCPROOF_HEADERS`: optional JSON object of default headers
|
|
183
|
+
- `MCPROOF_HEADER_*`: optional per-header overrides, e.g. `MCPROOF_HEADER_AUTHORIZATION`
|
|
184
|
+
- `MCPROOF_ENV_FILE`: optional path to a non-default env file
|
|
185
|
+
|
|
186
|
+
When both `MCPROOF_HEADERS` and `MCPROOF_HEADER_*` are present, the individual header vars win.
|
|
187
|
+
|
|
188
|
+
## Advanced Usage
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import {McpTestClient, validateMcpToolCall, expectToolCallSuccess} from 'mcproof';
|
|
192
|
+
|
|
193
|
+
const client = new McpTestClient({ baseUrl: 'http://localhost:36719', timeoutMs: 10000 });
|
|
194
|
+
client.setAuthHeaders({ Authorization: 'Bearer token', 'x-api-key': 'key' });
|
|
195
|
+
|
|
196
|
+
const validation = validateMcpToolCall({ name: 'ping', requestId: '1' });
|
|
197
|
+
if (!validation.isValid) {
|
|
198
|
+
throw new Error(validation.message);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const result = await client.invokeTool({ name: 'ping', requestId: '1' });
|
|
202
|
+
expectToolCallSuccess(result);
|
|
203
|
+
|
|
204
|
+
const resourceResult = await client.readResource({ uri: 'resource://weather/current', requestId: '2' });
|
|
205
|
+
const promptResult = await client.getPrompt({
|
|
206
|
+
name: 'summarize.weather',
|
|
207
|
+
arguments: { city: 'Montreal' },
|
|
208
|
+
requestId: '3',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
console.log('result output', result.output);
|
|
212
|
+
console.log('resource output', resourceResult.output);
|
|
213
|
+
console.log('prompt output', promptResult.output);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## API
|
|
217
|
+
|
|
218
|
+
- `McpTestClient` - core test client with HTTP MCP integration
|
|
219
|
+
- `connect()`
|
|
220
|
+
- `disconnect()`
|
|
221
|
+
- `getAuthHeaders()`
|
|
222
|
+
- `setAuthHeaders(headers)`
|
|
223
|
+
- `clearAuthHeaders()`
|
|
224
|
+
- `listTools()`
|
|
225
|
+
- `listResources()`
|
|
226
|
+
- `listPrompts()`
|
|
227
|
+
- `invokeTool(toolCall)`
|
|
228
|
+
- `readResource(resourceRead)`
|
|
229
|
+
- `getPrompt(promptGet)`
|
|
230
|
+
|
|
231
|
+
- `installSharedMcpTestClient(config)`
|
|
232
|
+
- `configureSharedMcpTestClient(config)`
|
|
233
|
+
- `initializeSharedMcpTestClient()`
|
|
234
|
+
- `getSharedMcpTestClient()`
|
|
235
|
+
- `disconnectSharedMcpTestClient()`
|
|
236
|
+
- `resetSharedMcpTestClient()`
|
|
237
|
+
|
|
238
|
+
- `validateMcpToolCall(call)` - returns `McpProtocolValidationResult`
|
|
239
|
+
- `validateMcpToolResult(result)` - returns `McpProtocolValidationResult`
|
|
240
|
+
- `validateMcpResourceRead(read)` - returns `McpProtocolValidationResult`
|
|
241
|
+
- `validateMcpResourceResult(result)` - returns `McpProtocolValidationResult`
|
|
242
|
+
- `validateMcpPromptGet(get)` - returns `McpProtocolValidationResult`
|
|
243
|
+
- `validateMcpPromptResult(result)` - returns `McpProtocolValidationResult`
|
|
244
|
+
- `expectTool(client, toolName)`
|
|
245
|
+
- `expectResource(client, resourceUri, expected?)`
|
|
246
|
+
- `expectPrompt(client, promptName, expected?)`
|
|
247
|
+
- `expectToolCallSuccess(result)`
|
|
248
|
+
- `expectToolCallError(resultOrInvocation[, expectedMessage])`
|
|
249
|
+
- `expectToolCallContent(result, expected)`
|
|
250
|
+
- `expectToolCallMeta(result, expected?)`
|
|
251
|
+
- `expectResourceReadSuccess(result)`
|
|
252
|
+
- `expectResourceReadError(resultOrInvocation[, expectedMessage])`
|
|
253
|
+
- `expectResourceReadContent(result, expected)`
|
|
254
|
+
- `expectResourceReadMeta(result, expected?)`
|
|
255
|
+
- `expectPromptGetSuccess(result)`
|
|
256
|
+
- `expectPromptGetError(resultOrInvocation[, expectedMessage])`
|
|
257
|
+
- `expectPromptGetContent(result, expected)`
|
|
258
|
+
- `expectPromptGetMeta(result, expected?)`
|
|
259
|
+
|
|
260
|
+
## Notes
|
|
261
|
+
|
|
262
|
+
- Uses the official MCP TypeScript SDK client and streamable HTTP transport.
|
|
263
|
+
- Designed for stateless HTTP MCP tooling.
|
|
264
|
+
- The default `mcproof test` command enforces sequential Jest execution for the shared-client workflow.
|
|
265
|
+
- `mcproof test` writes a timestamped HTML summary to `mcproof-reports/` with preflight discovery details and Jest results.
|
|
266
|
+
- The manual shared-client APIs are still available for advanced or non-standard integrations.
|
package/bin/mcproof.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { runCli } = require('../dist/cli.js');
|
|
4
|
+
|
|
5
|
+
runCli(process.argv.slice(2))
|
|
6
|
+
.then(code => {
|
|
7
|
+
process.exit(code);
|
|
8
|
+
})
|
|
9
|
+
.catch(error => {
|
|
10
|
+
console.error(error && error.message ? error.message : String(error));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { McpPromptDescriptor, McpPromptResult, McpResourceDescriptor, McpResourceResult, McpToolResult } from './types';
|
|
2
|
+
type AsymmetricMatcher = {
|
|
3
|
+
asymmetricMatch?: (value: unknown) => boolean;
|
|
4
|
+
};
|
|
5
|
+
type ToolLookupClient = {
|
|
6
|
+
listAvailableTools: () => Promise<string[]>;
|
|
7
|
+
};
|
|
8
|
+
type ResourceLookupClient = {
|
|
9
|
+
listResources: () => Promise<McpResourceDescriptor[]>;
|
|
10
|
+
};
|
|
11
|
+
type PromptLookupClient = {
|
|
12
|
+
listPrompts: () => Promise<McpPromptDescriptor[]>;
|
|
13
|
+
};
|
|
14
|
+
export declare function expectTool(client: ToolLookupClient, toolName: string): Promise<void>;
|
|
15
|
+
export declare function expectToolCallSuccess(result: McpToolResult): void;
|
|
16
|
+
/**
|
|
17
|
+
* Assert that a tool call failed.
|
|
18
|
+
*
|
|
19
|
+
* Two patterns are supported:
|
|
20
|
+
*
|
|
21
|
+
* 1. **Promise pattern** (to catch SDK validation errors):
|
|
22
|
+
* Pass the promise directly WITHOUT awaiting to catch errors thrown during input validation.
|
|
23
|
+
* Example: `await expectToolCallError(client.invokeTool({ name: 'tool', input: {} }))`
|
|
24
|
+
*
|
|
25
|
+
* 2. **Sync result pattern** (only for non-throwing error responses):
|
|
26
|
+
* Await the invocation first, then assert the error result object.
|
|
27
|
+
* If invocation throws, use the promise pattern above or a try/catch in your test.
|
|
28
|
+
* Example: `const result = await client.invokeTool(...); expectToolCallError(result, 'expected error')`
|
|
29
|
+
*
|
|
30
|
+
* When `expectedMessage` is provided, it must match the full error message exactly.
|
|
31
|
+
*/
|
|
32
|
+
export declare function expectToolCallError(result: McpToolResult, expectedMessage?: string): void;
|
|
33
|
+
export declare function expectToolCallError(result: Promise<McpToolResult>, expectedMessage?: string): Promise<void>;
|
|
34
|
+
export declare function expectToolCallContent(result: McpToolResult, expected: unknown): void;
|
|
35
|
+
export declare function expectToolCallMeta(result: McpToolResult, expected?: unknown): void;
|
|
36
|
+
export declare function expectResource(client: ResourceLookupClient, resourceUri: string, expected?: Partial<McpResourceDescriptor> | AsymmetricMatcher): Promise<void>;
|
|
37
|
+
export declare function expectResourceReadSuccess(result: McpResourceResult): void;
|
|
38
|
+
/**
|
|
39
|
+
* Assert that a resource read failed.
|
|
40
|
+
*
|
|
41
|
+
* Two patterns are supported:
|
|
42
|
+
*
|
|
43
|
+
* 1. **Promise pattern** (to catch SDK validation errors):
|
|
44
|
+
* Pass the promise directly WITHOUT awaiting to catch errors thrown during request validation.
|
|
45
|
+
* Example: `await expectResourceReadError(client.readResource({ uri: 'resource://missing' }))`
|
|
46
|
+
*
|
|
47
|
+
* 2. **Sync result pattern** (only for non-throwing error responses):
|
|
48
|
+
* Await the read first, then assert the error result object.
|
|
49
|
+
* If invocation throws, use the promise pattern above or a try/catch in your test.
|
|
50
|
+
* Example: `const result = await client.readResource(...); expectResourceReadError(result, 'expected error')`
|
|
51
|
+
*
|
|
52
|
+
* When `expectedMessage` is provided, it must match the full error message exactly.
|
|
53
|
+
*/
|
|
54
|
+
export declare function expectResourceReadError(result: McpResourceResult, expectedMessage?: string): void;
|
|
55
|
+
export declare function expectResourceReadError(result: Promise<McpResourceResult>, expectedMessage?: string): Promise<void>;
|
|
56
|
+
export declare function expectResourceReadContent(result: McpResourceResult, expected: unknown): void;
|
|
57
|
+
export declare function expectResourceReadMeta(result: McpResourceResult, expected?: unknown): void;
|
|
58
|
+
export declare function expectPrompt(client: PromptLookupClient, promptName: string, expected?: Partial<McpPromptDescriptor> | AsymmetricMatcher): Promise<void>;
|
|
59
|
+
export declare function expectPromptGetSuccess(result: McpPromptResult): void;
|
|
60
|
+
/**
|
|
61
|
+
* Assert that a prompt get failed.
|
|
62
|
+
*
|
|
63
|
+
* Two patterns are supported:
|
|
64
|
+
*
|
|
65
|
+
* 1. **Promise pattern** (to catch SDK validation errors):
|
|
66
|
+
* Pass the promise directly WITHOUT awaiting to catch errors thrown during request validation.
|
|
67
|
+
* Example: `await expectPromptGetError(client.getPrompt({ name: 'unknown' }))`
|
|
68
|
+
*
|
|
69
|
+
* 2. **Sync result pattern** (only for non-throwing error responses):
|
|
70
|
+
* Await the get first, then assert the error result object.
|
|
71
|
+
* If invocation throws, use the promise pattern above or a try/catch in your test.
|
|
72
|
+
* Example: `const result = await client.getPrompt(...); expectPromptGetError(result, 'expected error')`
|
|
73
|
+
*
|
|
74
|
+
* When `expectedMessage` is provided, it must match the full error message exactly.
|
|
75
|
+
*/
|
|
76
|
+
export declare function expectPromptGetError(result: McpPromptResult, expectedMessage?: string): void;
|
|
77
|
+
export declare function expectPromptGetError(result: Promise<McpPromptResult>, expectedMessage?: string): Promise<void>;
|
|
78
|
+
export declare function expectPromptGetContent(result: McpPromptResult, expected: unknown): void;
|
|
79
|
+
export declare function expectPromptGetMeta(result: McpPromptResult, expected?: unknown): void;
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=assertions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../src/assertions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,mBAAmB,EAAE,eAAe,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,aAAa,EAAC,MAAM,SAAS,CAAC;AAEtH,KAAK,iBAAiB,GAAG;IACvB,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;CAC/C,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,kBAAkB,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC7C,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,aAAa,EAAE,MAAM,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC;CACvD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,WAAW,EAAE,MAAM,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;CACnD,CAAC;AAeF,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ1F;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAEjE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;AAC3F,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAqB7G,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAYpF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAYlF;AAGD,wBAAsB,cAAc,CAClC,MAAM,EAAE,oBAAoB,EAC5B,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,iBAAiB,GAC5D,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAEzE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;AACnG,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAwBrH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAY5F;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAY1F;AAGD,wBAAsB,YAAY,CAChC,MAAM,EAAE,kBAAkB,EAC1B,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,iBAAiB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAEpE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;AAC9F,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAwBhH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAYvF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAYrF"}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expectTool = expectTool;
|
|
4
|
+
exports.expectToolCallSuccess = expectToolCallSuccess;
|
|
5
|
+
exports.expectToolCallError = expectToolCallError;
|
|
6
|
+
exports.expectToolCallContent = expectToolCallContent;
|
|
7
|
+
exports.expectToolCallMeta = expectToolCallMeta;
|
|
8
|
+
exports.expectResource = expectResource;
|
|
9
|
+
exports.expectResourceReadSuccess = expectResourceReadSuccess;
|
|
10
|
+
exports.expectResourceReadError = expectResourceReadError;
|
|
11
|
+
exports.expectResourceReadContent = expectResourceReadContent;
|
|
12
|
+
exports.expectResourceReadMeta = expectResourceReadMeta;
|
|
13
|
+
exports.expectPrompt = expectPrompt;
|
|
14
|
+
exports.expectPromptGetSuccess = expectPromptGetSuccess;
|
|
15
|
+
exports.expectPromptGetError = expectPromptGetError;
|
|
16
|
+
exports.expectPromptGetContent = expectPromptGetContent;
|
|
17
|
+
exports.expectPromptGetMeta = expectPromptGetMeta;
|
|
18
|
+
// Tool assertions
|
|
19
|
+
async function expectTool(client, toolName) {
|
|
20
|
+
const availableTools = await client.listAvailableTools();
|
|
21
|
+
if (availableTools.includes(toolName)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const discovered = availableTools.length > 0 ? availableTools.join(', ') : '(none)';
|
|
25
|
+
throw new Error(`Expected MCP server to expose tool '${toolName}', but discovered: ${discovered}`);
|
|
26
|
+
}
|
|
27
|
+
function expectToolCallSuccess(result) {
|
|
28
|
+
assertSuccess(result.status, result.error, 'tool call');
|
|
29
|
+
}
|
|
30
|
+
function expectToolCallError(result, expectedMessage) {
|
|
31
|
+
if (isPromiseLikeToolResult(result)) {
|
|
32
|
+
return Promise.resolve(result).then(toolResult => {
|
|
33
|
+
assertErrored(toolResult.status, toolResult.error, 'tool call', expectedMessage);
|
|
34
|
+
}, (error) => {
|
|
35
|
+
if (!expectedMessage) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const message = formatUnknownErrorMessage(error);
|
|
39
|
+
assertThrownMessageEquals(message, expectedMessage);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
assertErrored(result.status, result.error, 'tool call', expectedMessage);
|
|
43
|
+
}
|
|
44
|
+
function expectToolCallContent(result, expected) {
|
|
45
|
+
var _a;
|
|
46
|
+
const normalizedOutput = normalizeToolOutput(result.output);
|
|
47
|
+
if (isAsymmetricMatcher(expected)) {
|
|
48
|
+
if (!((_a = expected.asymmetricMatch) === null || _a === void 0 ? void 0 : _a.call(expected, normalizedOutput))) {
|
|
49
|
+
expect(normalizedOutput).toEqual(expected);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
expect(normalizedOutput).toEqual(expected);
|
|
54
|
+
}
|
|
55
|
+
function expectToolCallMeta(result, expected) {
|
|
56
|
+
const meta = extractToolMeta(result.output);
|
|
57
|
+
if (expected === undefined) {
|
|
58
|
+
if (meta === undefined) {
|
|
59
|
+
throw new Error('Expected tool call result output to contain a _meta field');
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
expect(meta).toEqual(expected);
|
|
64
|
+
}
|
|
65
|
+
// Resource assertions
|
|
66
|
+
async function expectResource(client, resourceUri, expected) {
|
|
67
|
+
const resources = await client.listResources();
|
|
68
|
+
const resource = resources.find(entry => entry.uri === resourceUri);
|
|
69
|
+
if (!resource) {
|
|
70
|
+
const discovered = resources.length > 0 ? resources.map(entry => entry.uri).join(', ') : '(none)';
|
|
71
|
+
throw new Error(`Expected MCP server to expose resource '${resourceUri}', but discovered: ${discovered}`);
|
|
72
|
+
}
|
|
73
|
+
if (expected !== undefined) {
|
|
74
|
+
expect(resource).toEqual(normalizeExpectedDescriptor(expected));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function expectResourceReadSuccess(result) {
|
|
78
|
+
assertSuccess(result.status, result.error, 'resource read');
|
|
79
|
+
}
|
|
80
|
+
function expectResourceReadError(result, expectedMessage) {
|
|
81
|
+
if (isPromiseLikeResourceResult(result)) {
|
|
82
|
+
return Promise.resolve(result).then(resourceResult => {
|
|
83
|
+
assertErrored(resourceResult.status, resourceResult.error, 'resource read', expectedMessage);
|
|
84
|
+
}, (error) => {
|
|
85
|
+
if (!expectedMessage) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const message = formatUnknownErrorMessage(error);
|
|
89
|
+
assertThrownMessageEquals(message, expectedMessage);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
assertErrored(result.status, result.error, 'resource read', expectedMessage);
|
|
93
|
+
}
|
|
94
|
+
function expectResourceReadContent(result, expected) {
|
|
95
|
+
var _a;
|
|
96
|
+
const normalizedOutput = normalizeResourceOutput(result.output);
|
|
97
|
+
if (isAsymmetricMatcher(expected)) {
|
|
98
|
+
if (!((_a = expected.asymmetricMatch) === null || _a === void 0 ? void 0 : _a.call(expected, normalizedOutput))) {
|
|
99
|
+
expect(normalizedOutput).toEqual(expected);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
expect(normalizedOutput).toEqual(expected);
|
|
104
|
+
}
|
|
105
|
+
function expectResourceReadMeta(result, expected) {
|
|
106
|
+
const meta = extractToolMeta(result.output);
|
|
107
|
+
if (expected === undefined) {
|
|
108
|
+
if (meta === undefined) {
|
|
109
|
+
throw new Error('Expected resource read output to contain a _meta field');
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
expect(meta).toEqual(expected);
|
|
114
|
+
}
|
|
115
|
+
// Prompt assertions
|
|
116
|
+
async function expectPrompt(client, promptName, expected) {
|
|
117
|
+
const prompts = await client.listPrompts();
|
|
118
|
+
const prompt = prompts.find(entry => entry.name === promptName);
|
|
119
|
+
if (!prompt) {
|
|
120
|
+
const discovered = prompts.length > 0 ? prompts.map(entry => entry.name).join(', ') : '(none)';
|
|
121
|
+
throw new Error(`Expected MCP server to expose prompt '${promptName}', but discovered: ${discovered}`);
|
|
122
|
+
}
|
|
123
|
+
if (expected !== undefined) {
|
|
124
|
+
expect(prompt).toEqual(normalizeExpectedDescriptor(expected));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function expectPromptGetSuccess(result) {
|
|
128
|
+
assertSuccess(result.status, result.error, 'prompt get');
|
|
129
|
+
}
|
|
130
|
+
function expectPromptGetError(result, expectedMessage) {
|
|
131
|
+
if (isPromiseLikePromptResult(result)) {
|
|
132
|
+
return Promise.resolve(result).then(promptResult => {
|
|
133
|
+
assertErrored(promptResult.status, promptResult.error, 'prompt get', expectedMessage);
|
|
134
|
+
}, (error) => {
|
|
135
|
+
if (!expectedMessage) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const message = formatUnknownErrorMessage(error);
|
|
139
|
+
assertThrownMessageEquals(message, expectedMessage);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
assertErrored(result.status, result.error, 'prompt get', expectedMessage);
|
|
143
|
+
}
|
|
144
|
+
function expectPromptGetContent(result, expected) {
|
|
145
|
+
var _a;
|
|
146
|
+
const normalizedOutput = normalizePromptOutput(result.output);
|
|
147
|
+
if (isAsymmetricMatcher(expected)) {
|
|
148
|
+
if (!((_a = expected.asymmetricMatch) === null || _a === void 0 ? void 0 : _a.call(expected, normalizedOutput))) {
|
|
149
|
+
expect(normalizedOutput).toEqual(expected);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
expect(normalizedOutput).toEqual(expected);
|
|
154
|
+
}
|
|
155
|
+
function expectPromptGetMeta(result, expected) {
|
|
156
|
+
const meta = extractToolMeta(result.output);
|
|
157
|
+
if (expected === undefined) {
|
|
158
|
+
if (meta === undefined) {
|
|
159
|
+
throw new Error('Expected prompt get output to contain a _meta field');
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
expect(meta).toEqual(expected);
|
|
164
|
+
}
|
|
165
|
+
function safeParseJson(value) {
|
|
166
|
+
try {
|
|
167
|
+
return JSON.parse(value);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function extractSseDataPayload(raw) {
|
|
174
|
+
const payloads = raw
|
|
175
|
+
.split(/\r?\n/)
|
|
176
|
+
.map(line => line.trim())
|
|
177
|
+
.filter(line => line.startsWith('data:'))
|
|
178
|
+
.map(line => line.replace(/^data:\s?/, ''))
|
|
179
|
+
.filter(line => line.length > 0)
|
|
180
|
+
.map(line => safeParseJson(line))
|
|
181
|
+
.filter((value) => value !== undefined);
|
|
182
|
+
if (payloads.length === 0) {
|
|
183
|
+
return raw;
|
|
184
|
+
}
|
|
185
|
+
return payloads[payloads.length - 1];
|
|
186
|
+
}
|
|
187
|
+
function normalizeToolOutput(value) {
|
|
188
|
+
if (typeof value === 'string') {
|
|
189
|
+
const ssePayload = extractSseDataPayload(value);
|
|
190
|
+
if (ssePayload !== value) {
|
|
191
|
+
return normalizeToolOutput(ssePayload);
|
|
192
|
+
}
|
|
193
|
+
const parsedJson = safeParseJson(value);
|
|
194
|
+
if (parsedJson !== undefined) {
|
|
195
|
+
return normalizeToolOutput(parsedJson);
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
if (!value || typeof value !== 'object') {
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
const candidate = value;
|
|
203
|
+
if (candidate.result !== undefined) {
|
|
204
|
+
return normalizeToolOutput(candidate.result);
|
|
205
|
+
}
|
|
206
|
+
if (candidate.output !== undefined) {
|
|
207
|
+
return normalizeToolOutput(candidate.output);
|
|
208
|
+
}
|
|
209
|
+
if (candidate.structuredContent !== undefined) {
|
|
210
|
+
return candidate.structuredContent;
|
|
211
|
+
}
|
|
212
|
+
if (Array.isArray(candidate.content)) {
|
|
213
|
+
const texts = candidate.content
|
|
214
|
+
.map(item => (typeof (item === null || item === void 0 ? void 0 : item.text) === 'string' ? item.text : undefined))
|
|
215
|
+
.filter((text) => text !== undefined);
|
|
216
|
+
if (texts.length === 1) {
|
|
217
|
+
const parsed = safeParseJson(texts[0]);
|
|
218
|
+
return parsed !== undefined ? parsed : texts[0];
|
|
219
|
+
}
|
|
220
|
+
if (texts.length > 1) {
|
|
221
|
+
return texts.join('\n');
|
|
222
|
+
}
|
|
223
|
+
return candidate.content;
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
function normalizeResourceOutput(value) {
|
|
228
|
+
if (!value || typeof value !== 'object') {
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
const candidate = value;
|
|
232
|
+
if (candidate.contents !== undefined) {
|
|
233
|
+
return candidate.contents;
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
function normalizePromptOutput(value) {
|
|
238
|
+
if (!value || typeof value !== 'object') {
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
const candidate = value;
|
|
242
|
+
if (candidate.messages !== undefined) {
|
|
243
|
+
return candidate.messages;
|
|
244
|
+
}
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
function isAsymmetricMatcher(value) {
|
|
248
|
+
return Boolean(value && typeof value === 'object' && typeof value.asymmetricMatch === 'function');
|
|
249
|
+
}
|
|
250
|
+
function extractToolMeta(value) {
|
|
251
|
+
if (!value || typeof value !== 'object') {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const candidate = value;
|
|
255
|
+
if (candidate._meta !== undefined) {
|
|
256
|
+
return candidate._meta;
|
|
257
|
+
}
|
|
258
|
+
if (candidate.result !== undefined) {
|
|
259
|
+
return extractToolMeta(candidate.result);
|
|
260
|
+
}
|
|
261
|
+
if (candidate.output !== undefined) {
|
|
262
|
+
return extractToolMeta(candidate.output);
|
|
263
|
+
}
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
function isPromiseLikeToolResult(value) {
|
|
267
|
+
return Boolean(value && typeof value === 'object' && typeof value.then === 'function');
|
|
268
|
+
}
|
|
269
|
+
function isPromiseLikeResourceResult(value) {
|
|
270
|
+
return Boolean(value && typeof value === 'object' && typeof value.then === 'function');
|
|
271
|
+
}
|
|
272
|
+
function isPromiseLikePromptResult(value) {
|
|
273
|
+
return Boolean(value && typeof value === 'object' && typeof value.then === 'function');
|
|
274
|
+
}
|
|
275
|
+
function formatUnknownErrorMessage(error) {
|
|
276
|
+
return error instanceof Error ? error.message : String(error);
|
|
277
|
+
}
|
|
278
|
+
function assertThrownMessageEquals(actualMessage, expectedMessage) {
|
|
279
|
+
expect(actualMessage).toBe(expectedMessage);
|
|
280
|
+
}
|
|
281
|
+
function assertErrored(status, error, label, expectedMessage) {
|
|
282
|
+
if (status !== 'error') {
|
|
283
|
+
throw new Error(`Expected ${label} to fail with error status`);
|
|
284
|
+
}
|
|
285
|
+
if (expectedMessage !== undefined) {
|
|
286
|
+
expect(error).toBe(expectedMessage);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function assertSuccess(status, error, label) {
|
|
290
|
+
if (status !== 'success') {
|
|
291
|
+
throw new Error(`Expected ${label} success status but got ${status}: ${error !== null && error !== void 0 ? error : 'no error info'}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function normalizeExpectedDescriptor(expected) {
|
|
295
|
+
if (isAsymmetricMatcher(expected)) {
|
|
296
|
+
return expected;
|
|
297
|
+
}
|
|
298
|
+
return expect.objectContaining((expected !== null && expected !== void 0 ? expected : {}));
|
|
299
|
+
}
|
|
300
|
+
//# sourceMappingURL=assertions.js.map
|