@slbdn/mcp-tester 1.4.3 → 1.5.3

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 (66) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +49 -0
  3. package/dist/ai/provider.d.ts +145 -0
  4. package/dist/ai/provider.d.ts.map +1 -0
  5. package/dist/ai/provider.js +428 -0
  6. package/dist/ai/provider.js.map +1 -0
  7. package/dist/cli/index.js +214 -14
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/generate-cases.d.ts +113 -0
  10. package/dist/generate-cases.d.ts.map +1 -0
  11. package/dist/generate-cases.js +565 -0
  12. package/dist/generate-cases.js.map +1 -0
  13. package/dist/generate-tests.d.ts +52 -1
  14. package/dist/generate-tests.d.ts.map +1 -1
  15. package/dist/generate-tests.js +208 -81
  16. package/dist/generate-tests.js.map +1 -1
  17. package/dist/index.d.ts +3 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/matchers.d.ts +31 -21
  22. package/dist/matchers.d.ts.map +1 -1
  23. package/dist/matchers.js.map +1 -1
  24. package/dist/templates/full-stack/.github/workflows/test.yml +34 -0
  25. package/dist/templates/full-stack/README.md +73 -0
  26. package/dist/templates/full-stack/examples/mock-server.js +115 -0
  27. package/dist/templates/full-stack/jest.config.js +39 -0
  28. package/dist/templates/full-stack/package.json +31 -0
  29. package/dist/templates/full-stack/scripts/generate-tests.ts +32 -0
  30. package/dist/templates/full-stack/scripts/generate-types.ts +28 -0
  31. package/dist/templates/full-stack/src/server.ts +78 -0
  32. package/dist/templates/full-stack/tests/helpers.ts +29 -0
  33. package/dist/templates/full-stack/tests/parallel.test.ts +44 -0
  34. package/dist/templates/full-stack/tests/server.test.ts +36 -0
  35. package/dist/templates/full-stack/tests/tools.test.ts +61 -0
  36. package/dist/templates/full-stack/tsconfig.json +19 -0
  37. package/dist/templates/minimal-jest/README.md +42 -0
  38. package/dist/templates/minimal-jest/examples/mock-server.js +62 -0
  39. package/dist/templates/minimal-jest/jest.config.js +24 -0
  40. package/dist/templates/minimal-jest/package.json +24 -0
  41. package/dist/templates/minimal-jest/tests/server.test.ts +64 -0
  42. package/dist/templates/minimal-jest/tsconfig.json +19 -0
  43. package/dist/templates/standard-jest/.github/workflows/test.yml +32 -0
  44. package/dist/templates/standard-jest/README.md +44 -0
  45. package/dist/templates/standard-jest/examples/mock-server.js +122 -0
  46. package/dist/templates/standard-jest/jest.config.js +39 -0
  47. package/dist/templates/standard-jest/package.json +27 -0
  48. package/dist/templates/standard-jest/tests/helpers.ts +43 -0
  49. package/dist/templates/standard-jest/tests/prompts.test.ts +37 -0
  50. package/dist/templates/standard-jest/tests/resources.test.ts +37 -0
  51. package/dist/templates/standard-jest/tests/server.test.ts +30 -0
  52. package/dist/templates/standard-jest/tests/tools.test.ts +87 -0
  53. package/dist/templates/standard-jest/tsconfig.json +19 -0
  54. package/docs/advanced.md +424 -0
  55. package/docs/ai-generation.md +211 -0
  56. package/docs/api-reference.md +819 -0
  57. package/docs/cicd.md +289 -0
  58. package/docs/cli.md +225 -0
  59. package/docs/examples.md +300 -0
  60. package/docs/nodejs-compatibility.md +81 -0
  61. package/docs/releases.md +413 -0
  62. package/docs/starter-templates.md +110 -0
  63. package/docs/testing.md +443 -0
  64. package/docs/troubleshooting.md +139 -0
  65. package/jest.d.ts +43 -0
  66. package/package.json +5 -2
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Resource tests.
3
+ */
4
+
5
+ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
6
+ import type { MCPClient } from '@slbdn/mcp-tester';
7
+ import { contentText, createConnectedClient, promptText, resourceText } from './helpers.js';
8
+
9
+ describe('Resources', () => {
10
+ let client: MCPClient;
11
+
12
+ beforeAll(async () => {
13
+ client = await createConnectedClient('resources-test');
14
+ });
15
+
16
+ afterAll(async () => {
17
+ if (client?.isConnected()) {
18
+ await client.stop();
19
+ }
20
+ });
21
+
22
+ it('lists available resources', async () => {
23
+ const resources = await client.listResources();
24
+ expect(resources.length).toBeGreaterThan(0);
25
+ expect(resources.map((r) => r.uri)).toContain('config://version');
26
+ });
27
+
28
+ it('reads the version resource', async () => {
29
+ const result = await client.readResource('config://version');
30
+ expect(result.contents.length).toBe(1);
31
+ expect(resourceText(result)).toBe('1.0.0');
32
+ });
33
+
34
+ it('rejects unknown resources', async () => {
35
+ await expect(client.readResource('config://does-not-exist')).rejects.toThrow();
36
+ });
37
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Server lifecycle tests.
3
+ */
4
+
5
+ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
6
+ import type { MCPClient } from '@slbdn/mcp-tester';
7
+ import { createConnectedClient } from './helpers.js';
8
+
9
+ describe('Server lifecycle', () => {
10
+ let client: MCPClient;
11
+
12
+ beforeAll(async () => {
13
+ client = await createConnectedClient('lifecycle-test');
14
+ });
15
+
16
+ afterAll(async () => {
17
+ if (client?.isConnected()) {
18
+ await client.stop();
19
+ }
20
+ });
21
+
22
+ it('connects to the mock server', () => {
23
+ expect(client.isConnected()).toBe(true);
24
+ });
25
+
26
+ it('reports a healthy server', async () => {
27
+ const health = await client.isHealthy();
28
+ expect(health.healthy).toBe(true);
29
+ });
30
+ });
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Tool tests — covers listing, schema validation, and calling.
3
+ */
4
+
5
+ import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
6
+ import type { MCPClient } from '@slbdn/mcp-tester';
7
+ import { contentText, createConnectedClient } from './helpers.js';
8
+
9
+ describe('Tools', () => {
10
+ let client: MCPClient;
11
+
12
+ beforeAll(async () => {
13
+ client = await createConnectedClient('tools-test');
14
+ });
15
+
16
+ afterAll(async () => {
17
+ if (client?.isConnected()) {
18
+ await client.stop();
19
+ }
20
+ });
21
+
22
+ it('lists all expected tools', async () => {
23
+ const tools = await client.listTools();
24
+ const names = tools.map((t) => t.name);
25
+ expect(names).toEqual(expect.arrayContaining(['echo', 'add', 'slow']));
26
+ });
27
+
28
+ it('validates tool input schemas', async () => {
29
+ const tools = await client.listTools();
30
+ for (const tool of tools) {
31
+ expect(tool.inputSchema).toBeDefined();
32
+ expect(tool.inputSchema.type).toBe('object');
33
+ }
34
+ });
35
+
36
+ describe('echo', () => {
37
+ it('returns the message', async () => {
38
+ const result = await client.callTool({
39
+ name: 'echo',
40
+ arguments: { message: 'hello' },
41
+ });
42
+
43
+ expect(contentText(result)).toBe('hello');
44
+ });
45
+ });
46
+
47
+ describe('add', () => {
48
+ it('adds two positive integers', async () => {
49
+ const result = await client.callTool({
50
+ name: 'add',
51
+ arguments: { a: 2, b: 3 },
52
+ });
53
+ expect(contentText(result)).toBe('5');
54
+ });
55
+
56
+ it('adds negative numbers', async () => {
57
+ const result = await client.callTool({
58
+ name: 'add',
59
+ arguments: { a: -5, b: 10 },
60
+ });
61
+ expect(contentText(result)).toBe('5');
62
+ });
63
+ });
64
+
65
+ describe('slow', () => {
66
+ it('respects the requested delay', async () => {
67
+ const start = Date.now();
68
+ const result = await client.callTool({
69
+ name: 'slow',
70
+ arguments: { ms: 50 },
71
+ });
72
+ const elapsed = Date.now() - start;
73
+ expect(contentText(result)).toBe('waited 50ms');
74
+ expect(elapsed).toBeGreaterThanOrEqual(45);
75
+ });
76
+
77
+ it('times out when requested', async () => {
78
+ await expect(
79
+ client.callTool({
80
+ name: 'slow',
81
+ arguments: { ms: 5000 },
82
+ timeout: 200,
83
+ })
84
+ ).rejects.toThrow(/timed out/i);
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "node",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "outDir": "./dist",
12
+ "rootDir": "./",
13
+ "declaration": true,
14
+ "sourceMap": true,
15
+ "allowSyntheticDefaultImports": true
16
+ },
17
+ "include": ["tests/**/*.ts", "fixtures/**/*.ts", "examples/**/*.js"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
@@ -0,0 +1,424 @@
1
+ # Advanced Usage
2
+
3
+ Advanced patterns and features of MCP Tester.
4
+
5
+ ## Concurrent Operations
6
+
7
+ Run multiple tool calls in parallel:
8
+
9
+ ```typescript
10
+ const results = await Promise.all([
11
+ client.callTool({ name: 'tool1', arguments: {} }),
12
+ client.callTool({ name: 'tool2', arguments: {} }),
13
+ client.callTool({ name: 'tool3', arguments: {} }),
14
+ ]);
15
+
16
+ results.forEach((result, index) => {
17
+ console.log(`Tool ${index + 1}:`, result.content[0]?.text);
18
+ });
19
+ ```
20
+
21
+ ## Timeout Handling
22
+
23
+ Customize timeouts at two levels:
24
+
25
+ **Global timeout** — applies to all requests:
26
+
27
+ ```typescript
28
+ const client = new MCPClient({ timeout: 60000 });
29
+ ```
30
+
31
+ **Per-call timeout** — overrides the global default:
32
+
33
+ ```typescript
34
+ const result = await client.callTool({
35
+ name: 'slow-operation',
36
+ arguments: { duration: 5000 },
37
+ timeout: 10000,
38
+ });
39
+ ```
40
+
41
+ ## Retry Logic
42
+
43
+ Configure automatic retries with exponential backoff:
44
+
45
+ ```typescript
46
+ const client = new MCPClient({
47
+ retries: 3,
48
+ retryDelay: 1000, // base delay in ms
49
+ });
50
+ ```
51
+
52
+ Per-call override:
53
+
54
+ ```typescript
55
+ await client.callTool({
56
+ name: 'flaky-tool',
57
+ arguments: {},
58
+ retries: 5,
59
+ });
60
+ ```
61
+
62
+ Retry behavior:
63
+ - Exponential backoff: `delay * backoffMultiplier^(attempt - 1)`
64
+ - Default multiplier: 2
65
+ - Max delay cap: 10,000ms
66
+ - Retries only on thrown errors (not on successful responses with error content)
67
+
68
+ ## Environment Variables
69
+
70
+ Pass environment variables to the MCP server process:
71
+
72
+ ```typescript
73
+ await client.start({
74
+ command: 'node',
75
+ args: ['./server.js'],
76
+ env: {
77
+ NODE_ENV: 'test',
78
+ API_ENDPOINT: process.env.API_ENDPOINT || 'http://localhost:3000',
79
+ LOG_LEVEL: 'debug',
80
+ },
81
+ });
82
+ ```
83
+
84
+ `undefined` values are automatically filtered out, so conditional env vars work cleanly:
85
+
86
+ ```typescript
87
+ env: {
88
+ NODE_ENV: 'production',
89
+ API_KEY: process.env.API_KEY, // won't be passed if undefined
90
+ }
91
+ ```
92
+
93
+ ## Notification Handling
94
+
95
+ Monitor server-initiated notifications:
96
+
97
+ ```typescript
98
+ client.setNotificationHandlers({
99
+ onLoggingMessage: (level, data) => {
100
+ console.log(`[${level}] ${data}`);
101
+ },
102
+ onResourceListChanged: async () => {
103
+ console.log('Resources changed, refreshing...');
104
+ const resources = await client.listResources();
105
+ console.log(`Now have ${resources.length} resources`);
106
+ },
107
+ });
108
+ ```
109
+
110
+ ## Elicitation Handling
111
+
112
+ Respond to server requests for user input:
113
+
114
+ ```typescript
115
+ await client.setElicitationHandler(async (request) => {
116
+ console.log('Elicitation request:', request.params.mode);
117
+
118
+ if (request.params.mode === 'form') {
119
+ return {
120
+ action: 'accept',
121
+ content: {
122
+ username: 'test-user',
123
+ email: 'test@example.com',
124
+ },
125
+ };
126
+ }
127
+
128
+ return { action: 'decline' };
129
+ });
130
+ ```
131
+
132
+ Handler return values:
133
+ - `action: 'accept'` — provide `content` with the collected input
134
+ - `action: 'decline'` — user declined the request
135
+ - `action: 'cancel'` — user cancelled the operation
136
+
137
+ ## Logging
138
+
139
+ Configure logging at construction or dynamically:
140
+
141
+ ```typescript
142
+ const client = new MCPClient({
143
+ logLevel: 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'none'
144
+ });
145
+
146
+ // Change at runtime
147
+ client.setLogLevel('warn');
148
+ ```
149
+
150
+ Enable protocol-level logging to see raw JSON messages:
151
+
152
+ ```typescript
153
+ const client = new MCPClient({
154
+ enableProtocolLogging: true,
155
+ logLevel: 'debug',
156
+ });
157
+ ```
158
+
159
+ All log output goes to stderr so it doesn't interfere with stdout.
160
+
161
+ ## Mock Server for Unit Testing
162
+
163
+ Use the in-memory mock server for tests that don't need a real server process:
164
+
165
+ ```typescript
166
+ import { MockMCPServer } from './fixtures/mock-server.js';
167
+
168
+ describe('My Feature', () => {
169
+ let mockServer: MockMCPServer;
170
+
171
+ beforeEach(() => {
172
+ mockServer = new MockMCPServer();
173
+ });
174
+
175
+ it('should handle tool call', async () => {
176
+ const result = await mockServer.handleToolCall('echo', { message: 'test' });
177
+ expect(result.content[0].text).toBe('Echo: test');
178
+ });
179
+ });
180
+ ```
181
+
182
+ ### Built-in Tools
183
+
184
+ | Tool | Description |
185
+ |------|-------------|
186
+ | `echo` | Echo back the input message |
187
+ | `add` | Add two numbers |
188
+ | `delay` | Delay for specified milliseconds |
189
+ | `error_tool` | Always throws an error |
190
+ | `counter` | Stateful counter: increment, get, reset |
191
+ | `items` | Stateful item list: add, list, remove, clear |
192
+ | `transform` | Transform text: upper, lower, reverse, length |
193
+
194
+ ### Built-in Resources: `text://example`, `config://settings`
195
+ ### Built-in Prompts: `greet`, `summarize`
196
+
197
+ ### Testing Retry Logic
198
+
199
+ ```typescript
200
+ const server = new MockMCPServer({ failureRate: 0.5 });
201
+ // 50% of tool calls will throw randomly
202
+ // Great for testing retry logic
203
+ ```
204
+
205
+ ### Testing with Delays
206
+
207
+ ```typescript
208
+ const server = new MockMCPServer({ defaultDelay: 100 });
209
+ // All tool calls take ≥100ms — test timeout handling
210
+ ```
211
+
212
+ ### Input Validation Testing
213
+
214
+ ```typescript
215
+ const server = new MockMCPServer({ validateSchemas: true });
216
+ await expect(
217
+ server.handleToolCall('echo', {})
218
+ ).rejects.toThrow('Missing required field');
219
+ ```
220
+
221
+ ### Stateful Testing
222
+
223
+ ```typescript
224
+ await server.handleToolCall('counter', { action: 'increment', by: 5 });
225
+ await server.handleToolCall('counter', { action: 'increment', by: 3 });
226
+ const result = await server.handleToolCall('counter', { action: 'get' });
227
+ // result.content[0].text === 'Counter: 8'
228
+
229
+ server.resetState(); // clears counters, items, call history
230
+ ```
231
+
232
+ ### Custom Handlers
233
+
234
+ ```typescript
235
+ server.registerToolHandler('echo', (args) => [
236
+ { type: 'text', text: `Override: ${args.message}` },
237
+ ]);
238
+ server.registerResourceHandler('text://example', (uri) => [
239
+ { uri, mimeType: 'text/plain', text: 'Custom content' },
240
+ ]);
241
+ server.registerPromptHandler('greet', (args) => [
242
+ { role: 'assistant', content: { type: 'text', text: `Hi ${args.name}!` } },
243
+ ]);
244
+ ```
245
+
246
+ ### Call History Assertions
247
+
248
+ ```typescript
249
+ await server.handleToolCall('echo', { message: 'test' });
250
+ server.getCallCount('echo'); // 1
251
+ server.getCallHistory()[0].args; // { message: 'test' }
252
+ ```
253
+
254
+ See [Testing Guide](./testing.md#mock-server) for the full mock server API reference.
255
+
256
+ ## TypeScript Type Generation
257
+
258
+ Generate typed `.d.ts` declarations from any MCP server's tool schemas:
259
+
260
+ ```bash
261
+ npx mcp-tester generate-types node ./server.js -o server.d.ts
262
+ ```
263
+
264
+ Or use the API:
265
+
266
+ ```typescript
267
+ import { generateTypes } from '@slbdn/mcp-tester';
268
+ import { writeFileSync } from 'fs';
269
+
270
+ const types = await generateTypes({
271
+ command: 'node',
272
+ args: ['./server.js'],
273
+ });
274
+
275
+ writeFileSync('server.d.ts', types);
276
+ ```
277
+
278
+ ### Generated Output
279
+
280
+ ```typescript
281
+ /** Echo back the input */
282
+ export interface EchoArgs {
283
+ /** Message to echo */
284
+ message: string;
285
+ }
286
+
287
+ /** Add two numbers */
288
+ export interface AddArgs {
289
+ a: number;
290
+ b: number;
291
+ }
292
+
293
+ /** Union of all tool names */
294
+ export type ToolName = 'echo' | 'add' | 'delay' | 'error_tool';
295
+
296
+ /** Look up the argument type for a tool by name */
297
+ export interface ToolArgsMap {
298
+ 'echo': EchoArgs;
299
+ 'add': AddArgs;
300
+ }
301
+
302
+ /** Discriminated union for typed callTool() */
303
+ export type ToolCall =
304
+ | { name: 'echo'; arguments: EchoArgs }
305
+ | { name: 'add'; arguments: AddArgs };
306
+ ```
307
+
308
+ ### Using Generated Types
309
+
310
+ ```typescript
311
+ import type { ToolArgsMap, ToolName } from './server.d.ts';
312
+
313
+ // Type-safe arguments
314
+ const result = await client.callTool({
315
+ name: 'add',
316
+ arguments: { a: 1, b: 2 } as ToolArgsMap['add'],
317
+ });
318
+ ```
319
+
320
+ ### Supported JSON Schema Features
321
+
322
+ | Feature | TypeScript Output |
323
+ |---------|------------------|
324
+ | `{ type: 'string' }` | `string` |
325
+ | `{ type: 'number' }` | `number` |
326
+ | `{ type: 'boolean' }` | `boolean` |
327
+ | `{ enum: ['a', 'b'] }` | `'a' \| 'b'` |
328
+ | `{ const: 'fixed' }` | `'fixed'` |
329
+ | `{ type: 'array', items: ... }` | `T[]` or `Array<T>` |
330
+ | `{ type: 'object', properties: ... }` | `{ prop: Type }` |
331
+ | `{ oneOf: [...] }` | Union type |
332
+ | `{ anyOf: [...] }` | Union type |
333
+ | `{ allOf: [...] }` | Intersection type |
334
+ | `{ $ref: '#/definitions/X' }` | `X` |
335
+ | Optional fields (not in `required`) | `field?: Type` |
336
+ | `description` | JSDoc comments |
337
+
338
+ ## Server Health Checks
339
+
340
+ Detect zombie processes and monitor server health during long-running test suites.
341
+
342
+ ### One-Time Check
343
+
344
+ ```typescript
345
+ const health = await client.isHealthy();
346
+ if (!health.healthy) {
347
+ console.error(`Server unhealthy: ${health.message}`);
348
+ console.error(`PID ${health.pid}, latency: ${health.latencyMs}ms`);
349
+ }
350
+ ```
351
+
352
+ ### Periodic Monitoring
353
+
354
+ ```typescript
355
+ client.startHealthMonitor({
356
+ interval: 3000,
357
+ onUnhealthy: (status) => {
358
+ console.error(`Server died: ${status.message}`);
359
+ // Optionally restart or fail the test
360
+ },
361
+ onRecovery: (status) => {
362
+ console.log(`Server recovered after crash`);
363
+ },
364
+ });
365
+
366
+ // Clean up
367
+ client.stopHealthMonitor(); // explicit
368
+ await client.stop(); // also stops the monitor
369
+ ```
370
+
371
+ ### Zombie Detection
372
+
373
+ Uses `process.kill(pid, 0)` to check if the server process is still alive. Works on macOS, Linux, and Windows. Detects crashed, killed, or orphaned processes that `isConnected()` cannot detect (the client object still exists but the process is gone).
374
+
375
+ ### HealthStatus Fields
376
+
377
+ | Field | Type | Description |
378
+ |-------|------|-------------|
379
+ | `healthy` | `boolean` | Is the server responsive |
380
+ | `checkedAt` | `number` | Unix timestamp of the check |
381
+ | `latencyMs` | `number` | Round-trip time, or -1 if failed |
382
+ | `pid` | `number \| null` | Server process ID |
383
+ | `message` | `string` | Human-readable status |
384
+
385
+ ## Performance Benchmarks
386
+
387
+ Run the benchmark suite to measure MCP Tester's performance characteristics:
388
+
389
+ ```bash
390
+ npm run benchmark
391
+ ```
392
+
393
+ This runs 19 benchmarks against the mock server (50 runs each, 3 warmup), measuring:
394
+
395
+ | Category | Benchmarks |
396
+ |----------|------------|
397
+ | **Connection** | Reconnect, disconnect |
398
+ | **Tool calls** | Echo, add, delay (10ms simulated) |
399
+ | **Sequential** | 5 and 10 tools in a row |
400
+ | **Parallel** | 5, 10, and 20 tools concurrently |
401
+ | **Payloads** | 10KB, 100KB, 1MB echo payloads |
402
+ | **Metadata** | List tools, resources, prompts; get prompt with args |
403
+ | **Lifecycle** | Full start → list → call → stop patterns |
404
+
405
+ ### Reference Numbers (Node.js 24, Apple Silicon)
406
+
407
+ | Operation | Mean Latency | Throughput |
408
+ |----------|-------------|------------|
409
+ | Single tool call (echo) | **0.19 ms** | ~5,130 ops/s |
410
+ | Add tool (numbers) | **0.19 ms** | ~5,360 ops/s |
411
+ | 10 parallel tool calls | **0.55 ms total** | ~18,200 ops/s |
412
+ | 20 parallel tool calls | **0.86 ms total** | ~23,300 ops/s |
413
+ | List tools / resources / prompts | **0.15 ms** | ~6,700 ops/s |
414
+ | 1MB payload echo | **7.79 ms** | ~1,080 Mbps |
415
+ | Server reconnect | **165 ms** | ~6 ops/s |
416
+ | Full lifecycle (start+call+stop) | **167 ms** | ~6 ops/s |
417
+
418
+ ### Key Takeaways
419
+
420
+ - **Parallelization wins** — 10 parallel calls take the same time as 1 (~0.5ms total), giving ~18x throughput vs sequential
421
+ - **Connection overhead dominates single-call cost** — once connected, tool calls are sub-millisecond
422
+ - **Payload size has minimal impact** — going from 10KB to 1MB adds only ~7ms of overhead
423
+ - **Metadata listings are cheap** — listing tools, resources, or prompts costs ~0.15ms
424
+ - **The stdio transport is fast** — most latency comes from process spawn, not the protocol itself