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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +266 -0
  3. package/bin/mcproof.js +12 -0
  4. package/dist/assertions.d.ts +81 -0
  5. package/dist/assertions.d.ts.map +1 -0
  6. package/dist/assertions.js +300 -0
  7. package/dist/assertions.js.map +1 -0
  8. package/dist/autoSetup.d.ts +2 -0
  9. package/dist/autoSetup.d.ts.map +1 -0
  10. package/dist/autoSetup.js +6 -0
  11. package/dist/autoSetup.js.map +1 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +346 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/env.d.ts +3 -0
  17. package/dist/env.d.ts.map +1 -0
  18. package/dist/env.js +100 -0
  19. package/dist/env.js.map +1 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +22 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/jestPreset.d.ts +18 -0
  25. package/dist/jestPreset.d.ts.map +1 -0
  26. package/dist/jestPreset.js +26 -0
  27. package/dist/jestPreset.js.map +1 -0
  28. package/dist/mcpTestClient.d.ts +40 -0
  29. package/dist/mcpTestClient.d.ts.map +1 -0
  30. package/dist/mcpTestClient.js +500 -0
  31. package/dist/mcpTestClient.js.map +1 -0
  32. package/dist/protocol.d.ts +8 -0
  33. package/dist/protocol.d.ts.map +1 -0
  34. package/dist/protocol.js +133 -0
  35. package/dist/protocol.js.map +1 -0
  36. package/dist/reporting.d.ts +53 -0
  37. package/dist/reporting.d.ts.map +1 -0
  38. package/dist/reporting.js +239 -0
  39. package/dist/reporting.js.map +1 -0
  40. package/dist/sharedClient.d.ts +9 -0
  41. package/dist/sharedClient.d.ts.map +1 -0
  42. package/dist/sharedClient.js +101 -0
  43. package/dist/sharedClient.js.map +1 -0
  44. package/dist/types.d.ts +83 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +3 -0
  47. package/dist/types.js.map +1 -0
  48. 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
+ ![MCProof Logging Screenshot](resources/images/MCProof_Logging_Screenshot.png)
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
+ ![MCProof Report Screenshot 1](resources/images/MCProof_Report_Screenshot_1.png)
122
+ ![MCProof Report Screenshot 2](resources/images/MCProof_Report_Screenshot_2.png)
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