@frontmcp/testing 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1358 -0
  3. package/jest-preset.js +61 -0
  4. package/package.json +94 -0
  5. package/src/assertions/index.d.ts +5 -0
  6. package/src/assertions/index.js +18 -0
  7. package/src/assertions/index.js.map +1 -0
  8. package/src/assertions/mcp-assertions.d.ts +81 -0
  9. package/src/assertions/mcp-assertions.js +220 -0
  10. package/src/assertions/mcp-assertions.js.map +1 -0
  11. package/src/auth/auth-headers.d.ts +29 -0
  12. package/src/auth/auth-headers.js +62 -0
  13. package/src/auth/auth-headers.js.map +1 -0
  14. package/src/auth/index.d.ts +9 -0
  15. package/src/auth/index.js +15 -0
  16. package/src/auth/index.js.map +1 -0
  17. package/src/auth/token-factory.d.ts +94 -0
  18. package/src/auth/token-factory.js +181 -0
  19. package/src/auth/token-factory.js.map +1 -0
  20. package/src/auth/user-fixtures.d.ts +26 -0
  21. package/src/auth/user-fixtures.js +92 -0
  22. package/src/auth/user-fixtures.js.map +1 -0
  23. package/src/client/index.d.ts +7 -0
  24. package/src/client/index.js +12 -0
  25. package/src/client/index.js.map +1 -0
  26. package/src/client/mcp-test-client.builder.d.ts +72 -0
  27. package/src/client/mcp-test-client.builder.js +111 -0
  28. package/src/client/mcp-test-client.builder.js.map +1 -0
  29. package/src/client/mcp-test-client.d.ts +360 -0
  30. package/src/client/mcp-test-client.js +929 -0
  31. package/src/client/mcp-test-client.js.map +1 -0
  32. package/src/client/mcp-test-client.types.d.ts +216 -0
  33. package/src/client/mcp-test-client.types.js +7 -0
  34. package/src/client/mcp-test-client.types.js.map +1 -0
  35. package/src/errors/index.d.ts +45 -0
  36. package/src/errors/index.js +85 -0
  37. package/src/errors/index.js.map +1 -0
  38. package/src/expect.d.ts +67 -0
  39. package/src/expect.js +31 -0
  40. package/src/expect.js.map +1 -0
  41. package/src/fixtures/fixture-types.d.ts +166 -0
  42. package/src/fixtures/fixture-types.js +7 -0
  43. package/src/fixtures/fixture-types.js.map +1 -0
  44. package/src/fixtures/index.d.ts +7 -0
  45. package/src/fixtures/index.js +16 -0
  46. package/src/fixtures/index.js.map +1 -0
  47. package/src/fixtures/test-fixture.d.ts +41 -0
  48. package/src/fixtures/test-fixture.js +280 -0
  49. package/src/fixtures/test-fixture.js.map +1 -0
  50. package/src/http-mock/http-mock.d.ts +84 -0
  51. package/src/http-mock/http-mock.js +544 -0
  52. package/src/http-mock/http-mock.js.map +1 -0
  53. package/src/http-mock/http-mock.types.d.ts +124 -0
  54. package/src/http-mock/http-mock.types.js +10 -0
  55. package/src/http-mock/http-mock.types.js.map +1 -0
  56. package/src/http-mock/index.d.ts +6 -0
  57. package/src/http-mock/index.js +11 -0
  58. package/src/http-mock/index.js.map +1 -0
  59. package/src/index.d.ts +65 -0
  60. package/src/index.js +128 -0
  61. package/src/index.js.map +1 -0
  62. package/src/interceptor/index.d.ts +7 -0
  63. package/src/interceptor/index.js +15 -0
  64. package/src/interceptor/index.js.map +1 -0
  65. package/src/interceptor/interceptor-chain.d.ts +77 -0
  66. package/src/interceptor/interceptor-chain.js +207 -0
  67. package/src/interceptor/interceptor-chain.js.map +1 -0
  68. package/src/interceptor/interceptor.types.d.ts +131 -0
  69. package/src/interceptor/interceptor.types.js +7 -0
  70. package/src/interceptor/interceptor.types.js.map +1 -0
  71. package/src/interceptor/mock-registry.d.ts +82 -0
  72. package/src/interceptor/mock-registry.js +189 -0
  73. package/src/interceptor/mock-registry.js.map +1 -0
  74. package/src/matchers/index.d.ts +7 -0
  75. package/src/matchers/index.js +12 -0
  76. package/src/matchers/index.js.map +1 -0
  77. package/src/matchers/matcher-types.d.ts +266 -0
  78. package/src/matchers/matcher-types.js +10 -0
  79. package/src/matchers/matcher-types.js.map +1 -0
  80. package/src/matchers/mcp-matchers.d.ts +47 -0
  81. package/src/matchers/mcp-matchers.js +391 -0
  82. package/src/matchers/mcp-matchers.js.map +1 -0
  83. package/src/playwright/index.d.ts +37 -0
  84. package/src/playwright/index.js +49 -0
  85. package/src/playwright/index.js.map +1 -0
  86. package/src/server/index.d.ts +6 -0
  87. package/src/server/index.js +10 -0
  88. package/src/server/index.js.map +1 -0
  89. package/src/server/test-server.d.ts +99 -0
  90. package/src/server/test-server.js +286 -0
  91. package/src/server/test-server.js.map +1 -0
  92. package/src/setup.d.ts +22 -0
  93. package/src/setup.js +30 -0
  94. package/src/setup.js.map +1 -0
  95. package/src/transport/index.d.ts +6 -0
  96. package/src/transport/index.js +10 -0
  97. package/src/transport/index.js.map +1 -0
  98. package/src/transport/streamable-http.transport.d.ts +65 -0
  99. package/src/transport/streamable-http.transport.js +432 -0
  100. package/src/transport/streamable-http.transport.js.map +1 -0
  101. package/src/transport/transport.interface.d.ts +124 -0
  102. package/src/transport/transport.interface.js +7 -0
  103. package/src/transport/transport.interface.js.map +1 -0
  104. package/src/ui/index.d.ts +17 -0
  105. package/src/ui/index.js +23 -0
  106. package/src/ui/index.js.map +1 -0
  107. package/src/ui/ui-assertions.d.ts +94 -0
  108. package/src/ui/ui-assertions.js +215 -0
  109. package/src/ui/ui-assertions.js.map +1 -0
  110. package/src/ui/ui-matchers.d.ts +39 -0
  111. package/src/ui/ui-matchers.js +275 -0
  112. package/src/ui/ui-matchers.js.map +1 -0
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ /**
3
+ * @file interceptor-chain.ts
4
+ * @description Interceptor chain for request/response interception
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.interceptors = exports.DefaultInterceptorChain = void 0;
8
+ const mock_registry_1 = require("./mock-registry");
9
+ /**
10
+ * Default implementation of InterceptorChain
11
+ */
12
+ class DefaultInterceptorChain {
13
+ request = [];
14
+ response = [];
15
+ mocks;
16
+ constructor() {
17
+ this.mocks = new mock_registry_1.DefaultMockRegistry();
18
+ }
19
+ /**
20
+ * Add a request interceptor
21
+ */
22
+ addRequestInterceptor(interceptor) {
23
+ this.request.push(interceptor);
24
+ return () => {
25
+ const index = this.request.indexOf(interceptor);
26
+ if (index !== -1) {
27
+ this.request.splice(index, 1);
28
+ }
29
+ };
30
+ }
31
+ /**
32
+ * Add a response interceptor
33
+ */
34
+ addResponseInterceptor(interceptor) {
35
+ this.response.push(interceptor);
36
+ return () => {
37
+ const index = this.response.indexOf(interceptor);
38
+ if (index !== -1) {
39
+ this.response.splice(index, 1);
40
+ }
41
+ };
42
+ }
43
+ /**
44
+ * Process a request through the interceptor chain
45
+ * Returns either:
46
+ * - { type: 'continue', request } - continue with (possibly modified) request
47
+ * - { type: 'mock', response } - return mock response immediately
48
+ * - { type: 'error', error } - throw error
49
+ */
50
+ async processRequest(request, meta) {
51
+ let currentRequest = request;
52
+ // 1. Check mocks first
53
+ const mockDef = this.mocks.match(request);
54
+ if (mockDef) {
55
+ // Apply delay if specified
56
+ if (mockDef.delay && mockDef.delay > 0) {
57
+ await sleep(mockDef.delay);
58
+ }
59
+ // Get mock response
60
+ let mockResponse;
61
+ if (typeof mockDef.response === 'function') {
62
+ mockResponse = await mockDef.response(request);
63
+ }
64
+ else {
65
+ mockResponse = mockDef.response;
66
+ }
67
+ // Ensure the response ID matches the request ID
68
+ return {
69
+ type: 'mock',
70
+ response: { ...mockResponse, id: request.id ?? mockResponse.id },
71
+ };
72
+ }
73
+ // 2. Run request interceptors
74
+ for (const interceptor of this.request) {
75
+ const ctx = {
76
+ request: currentRequest,
77
+ meta,
78
+ };
79
+ const result = await interceptor(ctx);
80
+ switch (result.action) {
81
+ case 'passthrough':
82
+ // Continue with current request
83
+ break;
84
+ case 'modify':
85
+ // Use modified request
86
+ currentRequest = result.request;
87
+ break;
88
+ case 'mock':
89
+ // Return mock response immediately
90
+ return {
91
+ type: 'mock',
92
+ response: { ...result.response, id: request.id ?? result.response.id },
93
+ };
94
+ case 'error':
95
+ return { type: 'error', error: result.error };
96
+ }
97
+ }
98
+ return { type: 'continue', request: currentRequest };
99
+ }
100
+ /**
101
+ * Process a response through the interceptor chain
102
+ */
103
+ async processResponse(request, response, durationMs) {
104
+ let currentResponse = response;
105
+ for (const interceptor of this.response) {
106
+ const ctx = {
107
+ request,
108
+ response: currentResponse,
109
+ durationMs,
110
+ };
111
+ const result = await interceptor(ctx);
112
+ switch (result.action) {
113
+ case 'passthrough':
114
+ // Continue with current response
115
+ break;
116
+ case 'modify':
117
+ // Use modified response
118
+ currentResponse = result.response;
119
+ break;
120
+ }
121
+ }
122
+ return currentResponse;
123
+ }
124
+ /**
125
+ * Clear all interceptors and mocks
126
+ */
127
+ clear() {
128
+ this.request = [];
129
+ this.response = [];
130
+ this.mocks.clear();
131
+ }
132
+ }
133
+ exports.DefaultInterceptorChain = DefaultInterceptorChain;
134
+ /**
135
+ * Sleep helper
136
+ */
137
+ function sleep(ms) {
138
+ return new Promise((resolve) => setTimeout(resolve, ms));
139
+ }
140
+ /**
141
+ * Convenience interceptor creators
142
+ */
143
+ exports.interceptors = {
144
+ /**
145
+ * Create an interceptor that logs all requests
146
+ */
147
+ logger(logFn = console.log) {
148
+ return (ctx) => {
149
+ logFn(`[MCP Request] ${ctx.request.method}`, ctx.request.params);
150
+ return { action: 'passthrough' };
151
+ };
152
+ },
153
+ /**
154
+ * Create an interceptor that adds latency to all requests
155
+ */
156
+ delay(ms) {
157
+ return async () => {
158
+ await sleep(ms);
159
+ return { action: 'passthrough' };
160
+ };
161
+ },
162
+ /**
163
+ * Create an interceptor that fails requests matching a condition
164
+ */
165
+ failWhen(condition, error) {
166
+ return (ctx) => {
167
+ if (condition(ctx)) {
168
+ const err = typeof error === 'string' ? new Error(error) : error;
169
+ return { action: 'error', error: err };
170
+ }
171
+ return { action: 'passthrough' };
172
+ };
173
+ },
174
+ /**
175
+ * Create an interceptor that modifies specific methods
176
+ */
177
+ modifyMethod(method, modifier) {
178
+ return (ctx) => {
179
+ if (ctx.request.method === method) {
180
+ return { action: 'modify', request: modifier(ctx.request) };
181
+ }
182
+ return { action: 'passthrough' };
183
+ };
184
+ },
185
+ /**
186
+ * Create a response interceptor that logs responses
187
+ */
188
+ responseLogger(logFn = console.log) {
189
+ return (ctx) => {
190
+ const status = ctx.response.error ? 'ERROR' : 'OK';
191
+ logFn(`[MCP Response] ${ctx.request.method} ${status} (${ctx.durationMs}ms)`, ctx.response);
192
+ return { action: 'passthrough' };
193
+ };
194
+ },
195
+ /**
196
+ * Create a response interceptor that modifies specific responses
197
+ */
198
+ modifyResponse(method, modifier) {
199
+ return (ctx) => {
200
+ if (ctx.request.method === method) {
201
+ return { action: 'modify', response: modifier(ctx.response) };
202
+ }
203
+ return { action: 'passthrough' };
204
+ };
205
+ },
206
+ };
207
+ //# sourceMappingURL=interceptor-chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor-chain.js","sourceRoot":"","sources":["../../../src/interceptor/interceptor-chain.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file interceptor-chain.ts\n * @description Interceptor chain for request/response interception\n */\n\nimport type { JsonRpcRequest, JsonRpcResponse } from '../transport/transport.interface';\nimport type {\n InterceptorChain,\n RequestInterceptor,\n ResponseInterceptor,\n InterceptorContext,\n InterceptorResult,\n ResponseInterceptorContext,\n ResponseInterceptorResult,\n MockRegistry,\n} from './interceptor.types';\nimport { DefaultMockRegistry } from './mock-registry';\n\n/**\n * Default implementation of InterceptorChain\n */\nexport class DefaultInterceptorChain implements InterceptorChain {\n request: RequestInterceptor[] = [];\n response: ResponseInterceptor[] = [];\n mocks: MockRegistry;\n\n constructor() {\n this.mocks = new DefaultMockRegistry();\n }\n\n /**\n * Add a request interceptor\n */\n addRequestInterceptor(interceptor: RequestInterceptor): () => void {\n this.request.push(interceptor);\n return () => {\n const index = this.request.indexOf(interceptor);\n if (index !== -1) {\n this.request.splice(index, 1);\n }\n };\n }\n\n /**\n * Add a response interceptor\n */\n addResponseInterceptor(interceptor: ResponseInterceptor): () => void {\n this.response.push(interceptor);\n return () => {\n const index = this.response.indexOf(interceptor);\n if (index !== -1) {\n this.response.splice(index, 1);\n }\n };\n }\n\n /**\n * Process a request through the interceptor chain\n * Returns either:\n * - { type: 'continue', request } - continue with (possibly modified) request\n * - { type: 'mock', response } - return mock response immediately\n * - { type: 'error', error } - throw error\n */\n async processRequest(\n request: JsonRpcRequest,\n meta: InterceptorContext['meta'],\n ): Promise<\n | { type: 'continue'; request: JsonRpcRequest }\n | { type: 'mock'; response: JsonRpcResponse }\n | { type: 'error'; error: Error }\n > {\n let currentRequest = request;\n\n // 1. Check mocks first\n const mockDef = this.mocks.match(request);\n if (mockDef) {\n // Apply delay if specified\n if (mockDef.delay && mockDef.delay > 0) {\n await sleep(mockDef.delay);\n }\n\n // Get mock response\n let mockResponse: JsonRpcResponse;\n if (typeof mockDef.response === 'function') {\n mockResponse = await mockDef.response(request);\n } else {\n mockResponse = mockDef.response;\n }\n\n // Ensure the response ID matches the request ID\n return {\n type: 'mock',\n response: { ...mockResponse, id: request.id ?? mockResponse.id },\n };\n }\n\n // 2. Run request interceptors\n for (const interceptor of this.request) {\n const ctx: InterceptorContext = {\n request: currentRequest,\n meta,\n };\n\n const result = await interceptor(ctx);\n\n switch (result.action) {\n case 'passthrough':\n // Continue with current request\n break;\n case 'modify':\n // Use modified request\n currentRequest = result.request;\n break;\n case 'mock':\n // Return mock response immediately\n return {\n type: 'mock',\n response: { ...result.response, id: request.id ?? result.response.id },\n };\n case 'error':\n return { type: 'error', error: result.error };\n }\n }\n\n return { type: 'continue', request: currentRequest };\n }\n\n /**\n * Process a response through the interceptor chain\n */\n async processResponse(\n request: JsonRpcRequest,\n response: JsonRpcResponse,\n durationMs: number,\n ): Promise<JsonRpcResponse> {\n let currentResponse = response;\n\n for (const interceptor of this.response) {\n const ctx: ResponseInterceptorContext = {\n request,\n response: currentResponse,\n durationMs,\n };\n\n const result = await interceptor(ctx);\n\n switch (result.action) {\n case 'passthrough':\n // Continue with current response\n break;\n case 'modify':\n // Use modified response\n currentResponse = result.response;\n break;\n }\n }\n\n return currentResponse;\n }\n\n /**\n * Clear all interceptors and mocks\n */\n clear(): void {\n this.request = [];\n this.response = [];\n this.mocks.clear();\n }\n}\n\n/**\n * Sleep helper\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Convenience interceptor creators\n */\nexport const interceptors = {\n /**\n * Create an interceptor that logs all requests\n */\n logger(logFn: (message: string, data?: unknown) => void = console.log): RequestInterceptor {\n return (ctx) => {\n logFn(`[MCP Request] ${ctx.request.method}`, ctx.request.params);\n return { action: 'passthrough' };\n };\n },\n\n /**\n * Create an interceptor that adds latency to all requests\n */\n delay(ms: number): RequestInterceptor {\n return async () => {\n await sleep(ms);\n return { action: 'passthrough' };\n };\n },\n\n /**\n * Create an interceptor that fails requests matching a condition\n */\n failWhen(condition: (ctx: InterceptorContext) => boolean, error: Error | string): RequestInterceptor {\n return (ctx) => {\n if (condition(ctx)) {\n const err = typeof error === 'string' ? new Error(error) : error;\n return { action: 'error', error: err };\n }\n return { action: 'passthrough' };\n };\n },\n\n /**\n * Create an interceptor that modifies specific methods\n */\n modifyMethod(method: string, modifier: (request: JsonRpcRequest) => JsonRpcRequest): RequestInterceptor {\n return (ctx) => {\n if (ctx.request.method === method) {\n return { action: 'modify', request: modifier(ctx.request) };\n }\n return { action: 'passthrough' };\n };\n },\n\n /**\n * Create a response interceptor that logs responses\n */\n responseLogger(logFn: (message: string, data?: unknown) => void = console.log): ResponseInterceptor {\n return (ctx) => {\n const status = ctx.response.error ? 'ERROR' : 'OK';\n logFn(`[MCP Response] ${ctx.request.method} ${status} (${ctx.durationMs}ms)`, ctx.response);\n return { action: 'passthrough' };\n };\n },\n\n /**\n * Create a response interceptor that modifies specific responses\n */\n modifyResponse(method: string, modifier: (response: JsonRpcResponse) => JsonRpcResponse): ResponseInterceptor {\n return (ctx) => {\n if (ctx.request.method === method) {\n return { action: 'modify', response: modifier(ctx.response) };\n }\n return { action: 'passthrough' };\n };\n },\n};\n"]}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @file interceptor.types.ts
3
+ * @description Types for request/response interception and mocking
4
+ */
5
+ import type { JsonRpcRequest, JsonRpcResponse } from '../transport/transport.interface';
6
+ /**
7
+ * Interceptor context passed to handler functions
8
+ */
9
+ export interface InterceptorContext {
10
+ /** The original request */
11
+ request: JsonRpcRequest;
12
+ /** Request metadata */
13
+ meta: {
14
+ /** Timestamp when request was made */
15
+ timestamp: Date;
16
+ /** Transport type being used */
17
+ transport: string;
18
+ /** Session ID if available */
19
+ sessionId?: string;
20
+ };
21
+ }
22
+ /**
23
+ * Result of an interceptor - can modify, mock, or pass through
24
+ */
25
+ export type InterceptorResult = {
26
+ action: 'passthrough';
27
+ } | {
28
+ action: 'modify';
29
+ request: JsonRpcRequest;
30
+ } | {
31
+ action: 'mock';
32
+ response: JsonRpcResponse;
33
+ } | {
34
+ action: 'error';
35
+ error: Error;
36
+ };
37
+ /**
38
+ * Function signature for request interceptors
39
+ */
40
+ export type RequestInterceptor = (ctx: InterceptorContext) => InterceptorResult | Promise<InterceptorResult>;
41
+ /**
42
+ * Response interceptor context
43
+ */
44
+ export interface ResponseInterceptorContext {
45
+ /** The original request */
46
+ request: JsonRpcRequest;
47
+ /** The response from the server */
48
+ response: JsonRpcResponse;
49
+ /** Duration of the request in ms */
50
+ durationMs: number;
51
+ }
52
+ /**
53
+ * Response interceptor result
54
+ */
55
+ export type ResponseInterceptorResult = {
56
+ action: 'passthrough';
57
+ } | {
58
+ action: 'modify';
59
+ response: JsonRpcResponse;
60
+ };
61
+ /**
62
+ * Function signature for response interceptors
63
+ */
64
+ export type ResponseInterceptor = (ctx: ResponseInterceptorContext) => ResponseInterceptorResult | Promise<ResponseInterceptorResult>;
65
+ /**
66
+ * Mock definition for a specific method
67
+ */
68
+ export interface MockDefinition {
69
+ /** Method name to mock (e.g., 'tools/call', 'resources/read') */
70
+ method: string;
71
+ /** Optional matcher for params - if provided, only matches when params match */
72
+ params?: Record<string, unknown> | ((params: Record<string, unknown>) => boolean);
73
+ /** The mock response to return */
74
+ response: JsonRpcResponse | ((request: JsonRpcRequest) => JsonRpcResponse | Promise<JsonRpcResponse>);
75
+ /** Number of times this mock should be used (default: Infinity) */
76
+ times?: number;
77
+ /** Delay in ms before returning the response (simulates latency) */
78
+ delay?: number;
79
+ }
80
+ /**
81
+ * Mock registry for managing active mocks
82
+ */
83
+ export interface MockRegistry {
84
+ /** Add a mock */
85
+ add(mock: MockDefinition): MockHandle;
86
+ /** Remove all mocks */
87
+ clear(): void;
88
+ /** Get all active mocks */
89
+ getAll(): MockDefinition[];
90
+ /** Check if a request matches any mock */
91
+ match(request: JsonRpcRequest): MockDefinition | undefined;
92
+ }
93
+ /**
94
+ * Handle returned when adding a mock - allows removal
95
+ */
96
+ export interface MockHandle {
97
+ /** Remove this specific mock */
98
+ remove(): void;
99
+ /** Check how many times this mock was called */
100
+ callCount(): number;
101
+ /** Get all calls made to this mock */
102
+ calls(): JsonRpcRequest[];
103
+ }
104
+ /**
105
+ * Interceptor chain configuration
106
+ */
107
+ export interface InterceptorChain {
108
+ /** Request interceptors (run before request) */
109
+ request: RequestInterceptor[];
110
+ /** Response interceptors (run after response) */
111
+ response: ResponseInterceptor[];
112
+ /** Mock registry */
113
+ mocks: MockRegistry;
114
+ /**
115
+ * Process a request through the interceptor chain
116
+ */
117
+ processRequest(request: JsonRpcRequest, meta: InterceptorContext['meta']): Promise<{
118
+ type: 'continue';
119
+ request: JsonRpcRequest;
120
+ } | {
121
+ type: 'mock';
122
+ response: JsonRpcResponse;
123
+ } | {
124
+ type: 'error';
125
+ error: Error;
126
+ }>;
127
+ /**
128
+ * Process a response through the interceptor chain
129
+ */
130
+ processResponse(request: JsonRpcRequest, response: JsonRpcResponse, durationMs: number): Promise<JsonRpcResponse>;
131
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * @file interceptor.types.ts
4
+ * @description Types for request/response interception and mocking
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=interceptor.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.types.js","sourceRoot":"","sources":["../../../src/interceptor/interceptor.types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * @file interceptor.types.ts\n * @description Types for request/response interception and mocking\n */\n\nimport type { JsonRpcRequest, JsonRpcResponse } from '../transport/transport.interface';\n\n/**\n * Interceptor context passed to handler functions\n */\nexport interface InterceptorContext {\n /** The original request */\n request: JsonRpcRequest;\n /** Request metadata */\n meta: {\n /** Timestamp when request was made */\n timestamp: Date;\n /** Transport type being used */\n transport: string;\n /** Session ID if available */\n sessionId?: string;\n };\n}\n\n/**\n * Result of an interceptor - can modify, mock, or pass through\n */\nexport type InterceptorResult =\n | { action: 'passthrough' }\n | { action: 'modify'; request: JsonRpcRequest }\n | { action: 'mock'; response: JsonRpcResponse }\n | { action: 'error'; error: Error };\n\n/**\n * Function signature for request interceptors\n */\nexport type RequestInterceptor = (ctx: InterceptorContext) => InterceptorResult | Promise<InterceptorResult>;\n\n/**\n * Response interceptor context\n */\nexport interface ResponseInterceptorContext {\n /** The original request */\n request: JsonRpcRequest;\n /** The response from the server */\n response: JsonRpcResponse;\n /** Duration of the request in ms */\n durationMs: number;\n}\n\n/**\n * Response interceptor result\n */\nexport type ResponseInterceptorResult = { action: 'passthrough' } | { action: 'modify'; response: JsonRpcResponse };\n\n/**\n * Function signature for response interceptors\n */\nexport type ResponseInterceptor = (\n ctx: ResponseInterceptorContext,\n) => ResponseInterceptorResult | Promise<ResponseInterceptorResult>;\n\n/**\n * Mock definition for a specific method\n */\nexport interface MockDefinition {\n /** Method name to mock (e.g., 'tools/call', 'resources/read') */\n method: string;\n /** Optional matcher for params - if provided, only matches when params match */\n params?: Record<string, unknown> | ((params: Record<string, unknown>) => boolean);\n /** The mock response to return */\n response: JsonRpcResponse | ((request: JsonRpcRequest) => JsonRpcResponse | Promise<JsonRpcResponse>);\n /** Number of times this mock should be used (default: Infinity) */\n times?: number;\n /** Delay in ms before returning the response (simulates latency) */\n delay?: number;\n}\n\n/**\n * Mock registry for managing active mocks\n */\nexport interface MockRegistry {\n /** Add a mock */\n add(mock: MockDefinition): MockHandle;\n /** Remove all mocks */\n clear(): void;\n /** Get all active mocks */\n getAll(): MockDefinition[];\n /** Check if a request matches any mock */\n match(request: JsonRpcRequest): MockDefinition | undefined;\n}\n\n/**\n * Handle returned when adding a mock - allows removal\n */\nexport interface MockHandle {\n /** Remove this specific mock */\n remove(): void;\n /** Check how many times this mock was called */\n callCount(): number;\n /** Get all calls made to this mock */\n calls(): JsonRpcRequest[];\n}\n\n/**\n * Interceptor chain configuration\n */\nexport interface InterceptorChain {\n /** Request interceptors (run before request) */\n request: RequestInterceptor[];\n /** Response interceptors (run after response) */\n response: ResponseInterceptor[];\n /** Mock registry */\n mocks: MockRegistry;\n\n /**\n * Process a request through the interceptor chain\n */\n processRequest(\n request: JsonRpcRequest,\n meta: InterceptorContext['meta'],\n ): Promise<\n | { type: 'continue'; request: JsonRpcRequest }\n | { type: 'mock'; response: JsonRpcResponse }\n | { type: 'error'; error: Error }\n >;\n\n /**\n * Process a response through the interceptor chain\n */\n processResponse(request: JsonRpcRequest, response: JsonRpcResponse, durationMs: number): Promise<JsonRpcResponse>;\n}\n"]}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @file mock-registry.ts
3
+ * @description Registry for managing request mocks
4
+ */
5
+ import type { JsonRpcRequest, JsonRpcResponse } from '../transport/transport.interface';
6
+ import type { MockDefinition, MockRegistry, MockHandle } from './interceptor.types';
7
+ /**
8
+ * Default implementation of MockRegistry
9
+ */
10
+ export declare class DefaultMockRegistry implements MockRegistry {
11
+ private mocks;
12
+ add(mock: MockDefinition): MockHandle;
13
+ clear(): void;
14
+ getAll(): MockDefinition[];
15
+ match(request: JsonRpcRequest): MockDefinition | undefined;
16
+ /**
17
+ * Check if request params match the mock params definition
18
+ */
19
+ private paramsMatch;
20
+ }
21
+ /**
22
+ * Helper to create mock responses
23
+ */
24
+ export declare const mockResponse: {
25
+ /**
26
+ * Create a successful JSON-RPC response
27
+ */
28
+ success<T>(result: T, id?: string | number): JsonRpcResponse;
29
+ /**
30
+ * Create an error JSON-RPC response
31
+ */
32
+ error(code: number, message: string, data?: unknown, id?: string | number | null): JsonRpcResponse;
33
+ /**
34
+ * Create a tool result response
35
+ */
36
+ toolResult(content: Array<{
37
+ type: "text";
38
+ text: string;
39
+ } | {
40
+ type: "image";
41
+ data: string;
42
+ mimeType: string;
43
+ }>, id?: string | number): JsonRpcResponse;
44
+ /**
45
+ * Create a tools/list response
46
+ */
47
+ toolsList(tools: Array<{
48
+ name: string;
49
+ description?: string;
50
+ inputSchema?: Record<string, unknown>;
51
+ }>, id?: string | number): JsonRpcResponse;
52
+ /**
53
+ * Create a resources/list response
54
+ */
55
+ resourcesList(resources: Array<{
56
+ uri: string;
57
+ name?: string;
58
+ description?: string;
59
+ mimeType?: string;
60
+ }>, id?: string | number): JsonRpcResponse;
61
+ /**
62
+ * Create a resources/read response
63
+ */
64
+ resourceRead(contents: Array<{
65
+ uri: string;
66
+ text?: string;
67
+ blob?: string;
68
+ mimeType?: string;
69
+ }>, id?: string | number): JsonRpcResponse;
70
+ /**
71
+ * Common MCP errors
72
+ */
73
+ errors: {
74
+ methodNotFound: (method: string, id?: string | number | null) => JsonRpcResponse;
75
+ invalidParams: (message: string, id?: string | number | null) => JsonRpcResponse;
76
+ internalError: (message: string, id?: string | number | null) => JsonRpcResponse;
77
+ resourceNotFound: (uri: string, id?: string | number | null) => JsonRpcResponse;
78
+ toolNotFound: (name: string, id?: string | number | null) => JsonRpcResponse;
79
+ unauthorized: (id?: string | number | null) => JsonRpcResponse;
80
+ forbidden: (id?: string | number | null) => JsonRpcResponse;
81
+ };
82
+ };
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ /**
3
+ * @file mock-registry.ts
4
+ * @description Registry for managing request mocks
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.mockResponse = exports.DefaultMockRegistry = void 0;
8
+ /**
9
+ * Default implementation of MockRegistry
10
+ */
11
+ class DefaultMockRegistry {
12
+ mocks = [];
13
+ add(mock) {
14
+ const entry = {
15
+ definition: mock,
16
+ callCount: 0,
17
+ calls: [],
18
+ remainingUses: mock.times ?? Infinity,
19
+ };
20
+ this.mocks.push(entry);
21
+ return {
22
+ remove: () => {
23
+ const index = this.mocks.indexOf(entry);
24
+ if (index !== -1) {
25
+ this.mocks.splice(index, 1);
26
+ }
27
+ },
28
+ callCount: () => entry.callCount,
29
+ calls: () => [...entry.calls],
30
+ };
31
+ }
32
+ clear() {
33
+ this.mocks = [];
34
+ }
35
+ getAll() {
36
+ return this.mocks.map((e) => e.definition);
37
+ }
38
+ match(request) {
39
+ for (const entry of this.mocks) {
40
+ if (entry.remainingUses <= 0)
41
+ continue;
42
+ const { definition } = entry;
43
+ // Check method match
44
+ if (definition.method !== request.method)
45
+ continue;
46
+ // Check params match if specified
47
+ if (definition.params !== undefined) {
48
+ const params = request.params ?? {};
49
+ if (typeof definition.params === 'function') {
50
+ // Custom matcher function
51
+ if (!definition.params(params))
52
+ continue;
53
+ }
54
+ else {
55
+ // Object deep equality check
56
+ if (!this.paramsMatch(definition.params, params))
57
+ continue;
58
+ }
59
+ }
60
+ // Found a match - update tracking
61
+ entry.callCount++;
62
+ entry.calls.push(request);
63
+ entry.remainingUses--;
64
+ return definition;
65
+ }
66
+ return undefined;
67
+ }
68
+ /**
69
+ * Check if request params match the mock params definition
70
+ */
71
+ paramsMatch(expected, actual) {
72
+ for (const [key, value] of Object.entries(expected)) {
73
+ if (!(key in actual))
74
+ return false;
75
+ const actualValue = actual[key];
76
+ // Handle arrays explicitly
77
+ if (Array.isArray(value)) {
78
+ if (!Array.isArray(actualValue))
79
+ return false;
80
+ if (value.length !== actualValue.length)
81
+ return false;
82
+ for (let i = 0; i < value.length; i++) {
83
+ const expectedItem = value[i];
84
+ const actualItem = actualValue[i];
85
+ if (typeof expectedItem === 'object' && expectedItem !== null) {
86
+ if (typeof actualItem !== 'object' || actualItem === null)
87
+ return false;
88
+ if (!this.paramsMatch(expectedItem, actualItem)) {
89
+ return false;
90
+ }
91
+ }
92
+ else if (actualItem !== expectedItem) {
93
+ return false;
94
+ }
95
+ }
96
+ }
97
+ else if (typeof value === 'object' && value !== null) {
98
+ if (typeof actualValue !== 'object' || actualValue === null)
99
+ return false;
100
+ if (!this.paramsMatch(value, actualValue)) {
101
+ return false;
102
+ }
103
+ }
104
+ else if (actualValue !== value) {
105
+ return false;
106
+ }
107
+ }
108
+ return true;
109
+ }
110
+ }
111
+ exports.DefaultMockRegistry = DefaultMockRegistry;
112
+ /**
113
+ * Helper to create mock responses
114
+ */
115
+ exports.mockResponse = {
116
+ /**
117
+ * Create a successful JSON-RPC response
118
+ */
119
+ success(result, id = 1) {
120
+ return {
121
+ jsonrpc: '2.0',
122
+ id,
123
+ result,
124
+ };
125
+ },
126
+ /**
127
+ * Create an error JSON-RPC response
128
+ */
129
+ error(code, message, data, id = 1) {
130
+ return {
131
+ jsonrpc: '2.0',
132
+ id,
133
+ error: { code, message, data },
134
+ };
135
+ },
136
+ /**
137
+ * Create a tool result response
138
+ */
139
+ toolResult(content, id = 1) {
140
+ return {
141
+ jsonrpc: '2.0',
142
+ id,
143
+ result: { content },
144
+ };
145
+ },
146
+ /**
147
+ * Create a tools/list response
148
+ */
149
+ toolsList(tools, id = 1) {
150
+ return {
151
+ jsonrpc: '2.0',
152
+ id,
153
+ result: { tools },
154
+ };
155
+ },
156
+ /**
157
+ * Create a resources/list response
158
+ */
159
+ resourcesList(resources, id = 1) {
160
+ return {
161
+ jsonrpc: '2.0',
162
+ id,
163
+ result: { resources },
164
+ };
165
+ },
166
+ /**
167
+ * Create a resources/read response
168
+ */
169
+ resourceRead(contents, id = 1) {
170
+ return {
171
+ jsonrpc: '2.0',
172
+ id,
173
+ result: { contents },
174
+ };
175
+ },
176
+ /**
177
+ * Common MCP errors
178
+ */
179
+ errors: {
180
+ methodNotFound: (method, id = 1) => exports.mockResponse.error(-32601, `Method not found: ${method}`, undefined, id),
181
+ invalidParams: (message, id = 1) => exports.mockResponse.error(-32602, message, undefined, id),
182
+ internalError: (message, id = 1) => exports.mockResponse.error(-32603, message, undefined, id),
183
+ resourceNotFound: (uri, id = 1) => exports.mockResponse.error(-32002, `Resource not found: ${uri}`, { uri }, id),
184
+ toolNotFound: (name, id = 1) => exports.mockResponse.error(-32601, `Tool not found: ${name}`, { name }, id),
185
+ unauthorized: (id = 1) => exports.mockResponse.error(-32001, 'Unauthorized', undefined, id),
186
+ forbidden: (id = 1) => exports.mockResponse.error(-32003, 'Forbidden', undefined, id),
187
+ },
188
+ };
189
+ //# sourceMappingURL=mock-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-registry.js","sourceRoot":"","sources":["../../../src/interceptor/mock-registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file mock-registry.ts\n * @description Registry for managing request mocks\n */\n\nimport type { JsonRpcRequest, JsonRpcResponse } from '../transport/transport.interface';\nimport type { MockDefinition, MockRegistry, MockHandle } from './interceptor.types';\n\n/**\n * Internal mock entry with tracking\n */\ninterface MockEntry {\n definition: MockDefinition;\n callCount: number;\n calls: JsonRpcRequest[];\n remainingUses: number;\n}\n\n/**\n * Default implementation of MockRegistry\n */\nexport class DefaultMockRegistry implements MockRegistry {\n private mocks: MockEntry[] = [];\n\n add(mock: MockDefinition): MockHandle {\n const entry: MockEntry = {\n definition: mock,\n callCount: 0,\n calls: [],\n remainingUses: mock.times ?? Infinity,\n };\n\n this.mocks.push(entry);\n\n return {\n remove: () => {\n const index = this.mocks.indexOf(entry);\n if (index !== -1) {\n this.mocks.splice(index, 1);\n }\n },\n callCount: () => entry.callCount,\n calls: () => [...entry.calls],\n };\n }\n\n clear(): void {\n this.mocks = [];\n }\n\n getAll(): MockDefinition[] {\n return this.mocks.map((e) => e.definition);\n }\n\n match(request: JsonRpcRequest): MockDefinition | undefined {\n for (const entry of this.mocks) {\n if (entry.remainingUses <= 0) continue;\n\n const { definition } = entry;\n\n // Check method match\n if (definition.method !== request.method) continue;\n\n // Check params match if specified\n if (definition.params !== undefined) {\n const params = request.params ?? {};\n\n if (typeof definition.params === 'function') {\n // Custom matcher function\n if (!definition.params(params)) continue;\n } else {\n // Object deep equality check\n if (!this.paramsMatch(definition.params, params)) continue;\n }\n }\n\n // Found a match - update tracking\n entry.callCount++;\n entry.calls.push(request);\n entry.remainingUses--;\n\n return definition;\n }\n\n return undefined;\n }\n\n /**\n * Check if request params match the mock params definition\n */\n private paramsMatch(expected: Record<string, unknown>, actual: Record<string, unknown>): boolean {\n for (const [key, value] of Object.entries(expected)) {\n if (!(key in actual)) return false;\n\n const actualValue = actual[key];\n\n // Handle arrays explicitly\n if (Array.isArray(value)) {\n if (!Array.isArray(actualValue)) return false;\n if (value.length !== actualValue.length) return false;\n for (let i = 0; i < value.length; i++) {\n const expectedItem = value[i];\n const actualItem = actualValue[i];\n if (typeof expectedItem === 'object' && expectedItem !== null) {\n if (typeof actualItem !== 'object' || actualItem === null) return false;\n if (!this.paramsMatch(expectedItem as Record<string, unknown>, actualItem as Record<string, unknown>)) {\n return false;\n }\n } else if (actualItem !== expectedItem) {\n return false;\n }\n }\n } else if (typeof value === 'object' && value !== null) {\n if (typeof actualValue !== 'object' || actualValue === null) return false;\n if (!this.paramsMatch(value as Record<string, unknown>, actualValue as Record<string, unknown>)) {\n return false;\n }\n } else if (actualValue !== value) {\n return false;\n }\n }\n\n return true;\n }\n}\n\n/**\n * Helper to create mock responses\n */\nexport const mockResponse = {\n /**\n * Create a successful JSON-RPC response\n */\n success<T>(result: T, id: string | number = 1): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result,\n };\n },\n\n /**\n * Create an error JSON-RPC response\n */\n error(code: number, message: string, data?: unknown, id: string | number | null = 1): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n error: { code, message, data },\n };\n },\n\n /**\n * Create a tool result response\n */\n toolResult(\n content: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { content },\n };\n },\n\n /**\n * Create a tools/list response\n */\n toolsList(\n tools: Array<{ name: string; description?: string; inputSchema?: Record<string, unknown> }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { tools },\n };\n },\n\n /**\n * Create a resources/list response\n */\n resourcesList(\n resources: Array<{ uri: string; name?: string; description?: string; mimeType?: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { resources },\n };\n },\n\n /**\n * Create a resources/read response\n */\n resourceRead(\n contents: Array<{ uri: string; text?: string; blob?: string; mimeType?: string }>,\n id: string | number = 1,\n ): JsonRpcResponse {\n return {\n jsonrpc: '2.0',\n id,\n result: { contents },\n };\n },\n\n /**\n * Common MCP errors\n */\n errors: {\n methodNotFound: (method: string, id: string | number | null = 1) =>\n mockResponse.error(-32601, `Method not found: ${method}`, undefined, id),\n\n invalidParams: (message: string, id: string | number | null = 1) =>\n mockResponse.error(-32602, message, undefined, id),\n\n internalError: (message: string, id: string | number | null = 1) =>\n mockResponse.error(-32603, message, undefined, id),\n\n resourceNotFound: (uri: string, id: string | number | null = 1) =>\n mockResponse.error(-32002, `Resource not found: ${uri}`, { uri }, id),\n\n toolNotFound: (name: string, id: string | number | null = 1) =>\n mockResponse.error(-32601, `Tool not found: ${name}`, { name }, id),\n\n unauthorized: (id: string | number | null = 1) => mockResponse.error(-32001, 'Unauthorized', undefined, id),\n\n forbidden: (id: string | number | null = 1) => mockResponse.error(-32003, 'Forbidden', undefined, id),\n },\n};\n"]}