@vurb/testing 3.14.5 → 3.14.6

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 (2) hide show
  1. package/README.md +258 -258
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,258 +1,258 @@
1
- <p align="center">
2
- <h1 align="center">@vurb/testing</h1>
3
- <p align="center">
4
- <strong>MCP Server Testing Framework — Vurb.ts</strong> — A framework for testing MCP servers in-memory<br/>
5
- Full MVA pipeline · Egress Firewall audit · PII redaction checks · Middleware guards · Vitest · Jest · Mocha
6
- </p>
7
- </p>
8
-
9
- <p align="center">
10
- <a href="https://www.npmjs.com/package/@vurb/testing"><img src="https://img.shields.io/npm/v/@vurb/testing?color=blue" alt="npm" /></a>
11
- <a href="https://github.com/vinkius-labs/vurb.ts/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="License" /></a>
12
- <img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node" />
13
- <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Dependencies" />
14
- <a href="https://modelcontextprotocol.io/"><img src="https://img.shields.io/badge/MCP-compatible-purple" alt="MCP" /></a>
15
- <a href="https://vurb.vinkius.com/"><img src="https://img.shields.io/badge/Vurb.ts-framework-0ea5e9" alt="Vurb.ts" /></a>
16
- </p>
17
-
18
- ---
19
-
20
- > **MCP Server Testing Framework — Vurb.ts**, the Model Context Protocol framework for building production MCP servers. In-memory MVA lifecycle emulator — runs the full execution pipeline without network transport. Zero runtime dependencies. Runner agnostic (Vitest, Jest, Mocha, `node:test`).
21
-
22
- ## Why
23
-
24
- Every MCP server today is tested with HTTP mocks, raw `JSON.stringify` assertions, and string matching. That's like testing a REST API by reading TCP packets.
25
-
26
- **Vurb.ts** applications have **five auditable layers** (Zod Validation → Middleware Chain → Handler → Presenter Egress Firewall → System Rules). The `VurbTester` lets you assert each layer independently, in-memory, without starting a server.
27
-
28
- ```
29
- ┌─────────────────────────────────────────────────────────┐
30
- │ VurbTester │
31
- │ │
32
- │ ┌──────────┐ ┌────────────┐ ┌─────────┐ │
33
- │ │ Zod │──▶│ Middleware │──▶│ Handler │ │
34
- │ │ Input │ │ Chain │ │ │ │
35
- │ └──────────┘ └────────────┘ └────┬────┘ │
36
- │ │ │
37
- │ ┌────▼────┐ │
38
- │ │Presenter│ │
39
- │ │ (Egress │ │
40
- │ │Firewall)│ │
41
- │ └────┬────┘ │
42
- │ │ │
43
- │ ┌────────────────────────────────────▼──────────────┐ │
44
- │ │ MvaTestResult │ │
45
- │ │ ┌──────┐ ┌───────────┐ ┌────────┐ ┌───────────┐ │ │
46
- │ │ │ data │ │systemRules│ │uiBlocks│ │rawResponse│ │ │
47
- │ │ └──────┘ └───────────┘ └────────┘ └───────────┘ │ │
48
- │ └───────────────────────────────────────────────────┘ │
49
- └─────────────────────────────────────────────────────────┘
50
- ```
51
-
52
- ## What You Can Audit
53
-
54
- | MVA Layer | What VurbTester asserts | SOC2 Relevance |
55
- |---|---|---|
56
- | **Egress Firewall** | Hidden fields (`passwordHash`, `tenantId`) are physically absent from `result.data` | Data leak prevention |
57
- | **OOM Guard** | Zod rejects `take: 10000` before it reaches the handler | Memory exhaustion protection |
58
- | **System Rules** | `result.systemRules` contains the expected domain rules | Deterministic LLM governance |
59
- | **UI Blocks** | SSR blocks (echarts, summaries) are correctly generated | Agent response quality |
60
- | **Middleware** | Auth guards block unauthorized calls, isError is true | Access control verification |
61
- | **Agent Limit** | Collections are truncated at cognitive guardrail bounds | Context window protection |
62
- | **HATEOAS** | `suggestActions` produces correct next-step affordances | Agent navigation safety |
63
-
64
- ## Quick Start
65
-
66
- ```typescript
67
- import { describe, it, expect } from 'vitest';
68
- import { createVurbTester } from '@vurb/testing';
69
- import { registry } from './server/registry.js';
70
-
71
- const tester = createVurbTester(registry, {
72
- contextFactory: () => ({
73
- prisma: mockPrisma,
74
- tenantId: 't_enterprise_42',
75
- role: 'ADMIN',
76
- }),
77
- });
78
-
79
- describe('User MVA Audit', () => {
80
- it('Egress Firewall strips sensitive fields', async () => {
81
- const result = await tester.callAction('db_user', 'find_many', { take: 10 });
82
-
83
- expect(result.data[0]).not.toHaveProperty('passwordHash');
84
- expect(result.data[0]).not.toHaveProperty('tenantId');
85
- expect(result.data[0].email).toBe('ceo@acme.com');
86
- });
87
-
88
- it('System rules are injected by Presenter', async () => {
89
- const result = await tester.callAction('db_user', 'find_many', { take: 5 });
90
-
91
- expect(result.systemRules).toContain(
92
- 'Data originates from the database via Prisma ORM.'
93
- );
94
- });
95
-
96
- it('OOM Guard rejects unbounded queries', async () => {
97
- const result = await tester.callAction('db_user', 'find_many', { take: 99999 });
98
- expect(result.isError).toBe(true);
99
- });
100
- });
101
- ```
102
-
103
- ## API Reference
104
-
105
- ### `createVurbTester(registry, options)`
106
-
107
- Factory function — creates a `VurbTester` instance.
108
-
109
- ```typescript
110
- function createVurbTester<TContext>(
111
- registry: ToolRegistry<TContext>,
112
- options: TesterOptions<TContext>,
113
- ): VurbTester<TContext>;
114
- ```
115
-
116
- | Parameter | Type | Description |
117
- |---|---|---|
118
- | `registry` | `ToolRegistry<TContext>` | Your application's tool registry — the same one wired to the MCP server |
119
- | `options` | `TesterOptions<TContext>` | Configuration object |
120
-
121
- ### `TesterOptions<TContext>`
122
-
123
- ```typescript
124
- interface TesterOptions<TContext> {
125
- contextFactory: () => TContext | Promise<TContext>;
126
- }
127
- ```
128
-
129
- ### `tester.callAction(toolName, actionName, args?, overrideContext?)`
130
-
131
- Executes a single tool action through the **full MVA pipeline** and returns a decomposed result.
132
-
133
- ```typescript
134
- async callAction<TArgs>(
135
- toolName: string,
136
- actionName: string,
137
- args?: TArgs,
138
- overrideContext?: Partial<TContext>,
139
- ): Promise<MvaTestResult>;
140
- ```
141
-
142
- | Parameter | Type | Required | Description |
143
- |---|---|---|---|
144
- | `toolName` | `string` | ✅ | The registered tool name (e.g. `'db_user'`, `'analytics'`) |
145
- | `actionName` | `string` | ✅ | The action discriminator (e.g. `'find_many'`, `'create'`) |
146
- | `args` | `object` | ❌ | Arguments for the action — omit the `action` discriminator |
147
- | `overrideContext` | `Partial<TContext>` | ❌ | Per-test context overrides. Shallow-merged with `contextFactory()` output |
148
-
149
- ### `MvaTestResult<TData>`
150
-
151
- Decomposed MVA response — each field maps to a specific pipeline layer.
152
-
153
- | Field | Type | Source | Description |
154
- |---|---|---|---|
155
- | `data` | `TData` | Presenter Zod schema | Validated data **after** the Egress Firewall. Hidden fields are physically absent. |
156
- | `systemRules` | `string[]` | Presenter `.systemRules()` | JIT domain rules injected by the Presenter. Empty array if no Presenter. |
157
- | `uiBlocks` | `unknown[]` | Presenter `.uiBlocks()` | SSR UI blocks (charts, summaries, markdown). Empty array if no Presenter. |
158
- | `isError` | `boolean` | Pipeline | `true` if Zod rejected, middleware blocked, or handler returned `error()`. |
159
- | `rawResponse` | `unknown` | Pipeline | The raw MCP `ToolResponse` for protocol-level inspection. |
160
-
161
- ## Cookbook
162
-
163
- ### Egress Firewall Audit
164
-
165
- ```typescript
166
- it('strips PII from response', async () => {
167
- const result = await tester.callAction('db_user', 'find_many', { take: 5 });
168
-
169
- const users = result.data as Array<Record<string, unknown>>;
170
- for (const user of users) {
171
- expect(user).not.toHaveProperty('passwordHash');
172
- expect(user).not.toHaveProperty('tenantId');
173
- }
174
- });
175
- ```
176
-
177
- ### Middleware Guards (RBAC)
178
-
179
- ```typescript
180
- it('blocks GUEST role', async () => {
181
- const result = await tester.callAction(
182
- 'db_user', 'find_many', { take: 5 },
183
- { role: 'GUEST' },
184
- );
185
- expect(result.isError).toBe(true);
186
- });
187
-
188
- it('allows ADMIN role', async () => {
189
- const result = await tester.callAction(
190
- 'db_user', 'find_many', { take: 5 },
191
- { role: 'ADMIN' },
192
- );
193
- expect(result.isError).toBe(false);
194
- });
195
- ```
196
-
197
- ### Agent Limit (Cognitive Guardrail)
198
-
199
- ```typescript
200
- it('truncates at agentLimit', async () => {
201
- const result = await tester.callAction('analytics', 'list', { limit: 100 });
202
- expect((result.data as any[]).length).toBe(20);
203
- });
204
- ```
205
-
206
- ### Protocol-Level Inspection
207
-
208
- ```typescript
209
- it('raw response follows MCP shape', async () => {
210
- const result = await tester.callAction('db_user', 'find_many', { take: 1 });
211
- const raw = result.rawResponse as { content: Array<{ type: string; text: string }> };
212
-
213
- expect(raw.content).toBeInstanceOf(Array);
214
- expect(raw.content[0].type).toBe('text');
215
- });
216
- ```
217
-
218
- ## How It Works
219
-
220
- The `VurbTester` runs the **real** execution pipeline — the exact same code path as your production MCP server:
221
-
222
- ```
223
- ToolRegistry.routeCall()
224
- → Concurrency Semaphore
225
- → Discriminator Parsing
226
- → Zod Input Validation
227
- → Compiled Middleware Chain
228
- → Handler Execution
229
- → PostProcessor (Presenter auto-application)
230
- → Egress Guard
231
- ```
232
-
233
- The key insight: `ResponseBuilder.build()` attaches structured MVA metadata via a **global Symbol** (`MVA_META_SYMBOL`). Symbols are ignored by `JSON.stringify`, so the MCP transport never sees them — but the `VurbTester` reads them in RAM.
234
-
235
- **No XML regex. No string parsing. Zero coupling to response formatting.**
236
-
237
- ## Installation
238
-
239
- ```bash
240
- npm install @vurb/testing
241
- ```
242
-
243
- ### Peer Dependencies
244
-
245
- | Package | Version |
246
- |---------|---------|
247
- | `vurb` | `^2.0.0` |
248
- | `zod` | `^3.25.1 \|\| ^4.0.0` |
249
-
250
- ## Requirements
251
-
252
- - **Node.js** ≥ 18.0.0
253
- - **TypeScript** 5.7+
254
- - **Vurb.ts** ≥ 2.0.0 (peer dependency)
255
-
256
- ## License
257
-
258
- [Apache-2.0](https://github.com/vinkius-labs/vurb.ts/blob/main/LICENSE)
1
+ <p align="center">
2
+ <h1 align="center">@vurb/testing</h1>
3
+ <p align="center">
4
+ <strong>MCP Server Testing Framework — Vurb.ts</strong> — A framework for testing MCP servers in-memory<br/>
5
+ Full MVA pipeline · Egress Firewall audit · PII redaction checks · Middleware guards · Vitest · Jest · Mocha
6
+ </p>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@vurb/testing"><img src="https://img.shields.io/npm/v/@vurb/testing?color=blue" alt="npm" /></a>
11
+ <a href="https://github.com/vinkius-labs/vurb.ts/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="License" /></a>
12
+ <img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node" />
13
+ <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Dependencies" />
14
+ <a href="https://modelcontextprotocol.io/"><img src="https://img.shields.io/badge/MCP-compatible-purple" alt="MCP" /></a>
15
+ <a href="https://vurb.vinkius.com/"><img src="https://img.shields.io/badge/Vurb.ts-framework-0ea5e9" alt="Vurb.ts" /></a>
16
+ </p>
17
+
18
+ ---
19
+
20
+ > **MCP Server Testing Framework — Vurb.ts**, the Model Context Protocol framework for building production MCP servers. In-memory MVA lifecycle emulator — runs the full execution pipeline without network transport. Zero runtime dependencies. Runner agnostic (Vitest, Jest, Mocha, `node:test`).
21
+
22
+ ## Why
23
+
24
+ Every MCP server today is tested with HTTP mocks, raw `JSON.stringify` assertions, and string matching. That's like testing a REST API by reading TCP packets.
25
+
26
+ **Vurb.ts** applications have **five auditable layers** (Zod Validation → Middleware Chain → Handler → Presenter Egress Firewall → System Rules). The `VurbTester` lets you assert each layer independently, in-memory, without starting a server.
27
+
28
+ ```
29
+ ┌─────────────────────────────────────────────────────────┐
30
+ │ VurbTester │
31
+ │ │
32
+ │ ┌──────────┐ ┌────────────┐ ┌─────────┐ │
33
+ │ │ Zod │──▶│ Middleware │──▶│ Handler │ │
34
+ │ │ Input │ │ Chain │ │ │ │
35
+ │ └──────────┘ └────────────┘ └────┬────┘ │
36
+ │ │ │
37
+ │ ┌────▼────┐ │
38
+ │ │Presenter│ │
39
+ │ │ (Egress │ │
40
+ │ │Firewall)│ │
41
+ │ └────┬────┘ │
42
+ │ │ │
43
+ │ ┌────────────────────────────────────▼──────────────┐ │
44
+ │ │ MvaTestResult │ │
45
+ │ │ ┌──────┐ ┌───────────┐ ┌────────┐ ┌───────────┐ │ │
46
+ │ │ │ data │ │systemRules│ │uiBlocks│ │rawResponse│ │ │
47
+ │ │ └──────┘ └───────────┘ └────────┘ └───────────┘ │ │
48
+ │ └───────────────────────────────────────────────────┘ │
49
+ └─────────────────────────────────────────────────────────┘
50
+ ```
51
+
52
+ ## What You Can Audit
53
+
54
+ | MVA Layer | What VurbTester asserts | SOC2 Relevance |
55
+ |---|---|---|
56
+ | **Egress Firewall** | Hidden fields (`passwordHash`, `tenantId`) are physically absent from `result.data` | Data leak prevention |
57
+ | **OOM Guard** | Zod rejects `take: 10000` before it reaches the handler | Memory exhaustion protection |
58
+ | **System Rules** | `result.systemRules` contains the expected domain rules | Deterministic LLM governance |
59
+ | **UI Blocks** | SSR blocks (echarts, summaries) are correctly generated | Agent response quality |
60
+ | **Middleware** | Auth guards block unauthorized calls, isError is true | Access control verification |
61
+ | **Agent Limit** | Collections are truncated at cognitive guardrail bounds | Context window protection |
62
+ | **HATEOAS** | `suggestActions` produces correct next-step affordances | Agent navigation safety |
63
+
64
+ ## Quick Start
65
+
66
+ ```typescript
67
+ import { describe, it, expect } from 'vitest';
68
+ import { createVurbTester } from '@vurb/testing';
69
+ import { registry } from './server/registry.js';
70
+
71
+ const tester = createVurbTester(registry, {
72
+ contextFactory: () => ({
73
+ prisma: mockPrisma,
74
+ tenantId: 't_enterprise_42',
75
+ role: 'ADMIN',
76
+ }),
77
+ });
78
+
79
+ describe('User MVA Audit', () => {
80
+ it('Egress Firewall strips sensitive fields', async () => {
81
+ const result = await tester.callAction('db_user', 'find_many', { take: 10 });
82
+
83
+ expect(result.data[0]).not.toHaveProperty('passwordHash');
84
+ expect(result.data[0]).not.toHaveProperty('tenantId');
85
+ expect(result.data[0].email).toBe('ceo@acme.com');
86
+ });
87
+
88
+ it('System rules are injected by Presenter', async () => {
89
+ const result = await tester.callAction('db_user', 'find_many', { take: 5 });
90
+
91
+ expect(result.systemRules).toContain(
92
+ 'Data originates from the database via Prisma ORM.'
93
+ );
94
+ });
95
+
96
+ it('OOM Guard rejects unbounded queries', async () => {
97
+ const result = await tester.callAction('db_user', 'find_many', { take: 99999 });
98
+ expect(result.isError).toBe(true);
99
+ });
100
+ });
101
+ ```
102
+
103
+ ## API Reference
104
+
105
+ ### `createVurbTester(registry, options)`
106
+
107
+ Factory function — creates a `VurbTester` instance.
108
+
109
+ ```typescript
110
+ function createVurbTester<TContext>(
111
+ registry: ToolRegistry<TContext>,
112
+ options: TesterOptions<TContext>,
113
+ ): VurbTester<TContext>;
114
+ ```
115
+
116
+ | Parameter | Type | Description |
117
+ |---|---|---|
118
+ | `registry` | `ToolRegistry<TContext>` | Your application's tool registry — the same one wired to the MCP server |
119
+ | `options` | `TesterOptions<TContext>` | Configuration object |
120
+
121
+ ### `TesterOptions<TContext>`
122
+
123
+ ```typescript
124
+ interface TesterOptions<TContext> {
125
+ contextFactory: () => TContext | Promise<TContext>;
126
+ }
127
+ ```
128
+
129
+ ### `tester.callAction(toolName, actionName, args?, overrideContext?)`
130
+
131
+ Executes a single tool action through the **full MVA pipeline** and returns a decomposed result.
132
+
133
+ ```typescript
134
+ async callAction<TArgs>(
135
+ toolName: string,
136
+ actionName: string,
137
+ args?: TArgs,
138
+ overrideContext?: Partial<TContext>,
139
+ ): Promise<MvaTestResult>;
140
+ ```
141
+
142
+ | Parameter | Type | Required | Description |
143
+ |---|---|---|---|
144
+ | `toolName` | `string` | ✅ | The registered tool name (e.g. `'db_user'`, `'analytics'`) |
145
+ | `actionName` | `string` | ✅ | The action discriminator (e.g. `'find_many'`, `'create'`) |
146
+ | `args` | `object` | ❌ | Arguments for the action — omit the `action` discriminator |
147
+ | `overrideContext` | `Partial<TContext>` | ❌ | Per-test context overrides. Shallow-merged with `contextFactory()` output |
148
+
149
+ ### `MvaTestResult<TData>`
150
+
151
+ Decomposed MVA response — each field maps to a specific pipeline layer.
152
+
153
+ | Field | Type | Source | Description |
154
+ |---|---|---|---|
155
+ | `data` | `TData` | Presenter Zod schema | Validated data **after** the Egress Firewall. Hidden fields are physically absent. |
156
+ | `systemRules` | `string[]` | Presenter `.systemRules()` | JIT domain rules injected by the Presenter. Empty array if no Presenter. |
157
+ | `uiBlocks` | `unknown[]` | Presenter `.uiBlocks()` | SSR UI blocks (charts, summaries, markdown). Empty array if no Presenter. |
158
+ | `isError` | `boolean` | Pipeline | `true` if Zod rejected, middleware blocked, or handler returned `error()`. |
159
+ | `rawResponse` | `unknown` | Pipeline | The raw MCP `ToolResponse` for protocol-level inspection. |
160
+
161
+ ## Cookbook
162
+
163
+ ### Egress Firewall Audit
164
+
165
+ ```typescript
166
+ it('strips PII from response', async () => {
167
+ const result = await tester.callAction('db_user', 'find_many', { take: 5 });
168
+
169
+ const users = result.data as Array<Record<string, unknown>>;
170
+ for (const user of users) {
171
+ expect(user).not.toHaveProperty('passwordHash');
172
+ expect(user).not.toHaveProperty('tenantId');
173
+ }
174
+ });
175
+ ```
176
+
177
+ ### Middleware Guards (RBAC)
178
+
179
+ ```typescript
180
+ it('blocks GUEST role', async () => {
181
+ const result = await tester.callAction(
182
+ 'db_user', 'find_many', { take: 5 },
183
+ { role: 'GUEST' },
184
+ );
185
+ expect(result.isError).toBe(true);
186
+ });
187
+
188
+ it('allows ADMIN role', async () => {
189
+ const result = await tester.callAction(
190
+ 'db_user', 'find_many', { take: 5 },
191
+ { role: 'ADMIN' },
192
+ );
193
+ expect(result.isError).toBe(false);
194
+ });
195
+ ```
196
+
197
+ ### Agent Limit (Cognitive Guardrail)
198
+
199
+ ```typescript
200
+ it('truncates at agentLimit', async () => {
201
+ const result = await tester.callAction('analytics', 'list', { limit: 100 });
202
+ expect((result.data as any[]).length).toBe(20);
203
+ });
204
+ ```
205
+
206
+ ### Protocol-Level Inspection
207
+
208
+ ```typescript
209
+ it('raw response follows MCP shape', async () => {
210
+ const result = await tester.callAction('db_user', 'find_many', { take: 1 });
211
+ const raw = result.rawResponse as { content: Array<{ type: string; text: string }> };
212
+
213
+ expect(raw.content).toBeInstanceOf(Array);
214
+ expect(raw.content[0].type).toBe('text');
215
+ });
216
+ ```
217
+
218
+ ## How It Works
219
+
220
+ The `VurbTester` runs the **real** execution pipeline — the exact same code path as your production MCP server:
221
+
222
+ ```
223
+ ToolRegistry.routeCall()
224
+ → Concurrency Semaphore
225
+ → Discriminator Parsing
226
+ → Zod Input Validation
227
+ → Compiled Middleware Chain
228
+ → Handler Execution
229
+ → PostProcessor (Presenter auto-application)
230
+ → Egress Guard
231
+ ```
232
+
233
+ The key insight: `ResponseBuilder.build()` attaches structured MVA metadata via a **global Symbol** (`MVA_META_SYMBOL`). Symbols are ignored by `JSON.stringify`, so the MCP transport never sees them — but the `VurbTester` reads them in RAM.
234
+
235
+ **No XML regex. No string parsing. Zero coupling to response formatting.**
236
+
237
+ ## Installation
238
+
239
+ ```bash
240
+ npm install @vurb/testing
241
+ ```
242
+
243
+ ### Peer Dependencies
244
+
245
+ | Package | Version |
246
+ |---------|---------|
247
+ | `vurb` | `^2.0.0` |
248
+ | `zod` | `^3.25.1 \|\| ^4.0.0` |
249
+
250
+ ## Requirements
251
+
252
+ - **Node.js** ≥ 18.0.0
253
+ - **TypeScript** 5.7+
254
+ - **Vurb.ts** ≥ 2.0.0 (peer dependency)
255
+
256
+ ## License
257
+
258
+ [Apache-2.0](https://github.com/vinkius-labs/vurb.ts/blob/main/LICENSE)
package/package.json CHANGED
@@ -54,5 +54,5 @@
54
54
  "devDependencies": {
55
55
  "@vurb/core": ">=3.8.0"
56
56
  },
57
- "version": "3.14.5"
57
+ "version": "3.14.6"
58
58
  }