@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,432 @@
1
+ "use strict";
2
+ /**
3
+ * @file streamable-http.transport.ts
4
+ * @description StreamableHTTP transport implementation for MCP Test Client
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.StreamableHttpTransport = void 0;
8
+ const DEFAULT_TIMEOUT = 30000;
9
+ /**
10
+ * StreamableHTTP transport for MCP communication
11
+ *
12
+ * This transport uses HTTP POST requests for all communication,
13
+ * following the MCP StreamableHTTP specification.
14
+ */
15
+ class StreamableHttpTransport {
16
+ config;
17
+ state = 'disconnected';
18
+ sessionId;
19
+ authToken;
20
+ connectionCount = 0;
21
+ reconnectCount = 0;
22
+ lastRequestHeaders = {};
23
+ interceptors;
24
+ publicMode;
25
+ constructor(config) {
26
+ this.config = {
27
+ baseUrl: config.baseUrl.replace(/\/$/, ''), // Remove trailing slash
28
+ timeout: config.timeout ?? DEFAULT_TIMEOUT,
29
+ auth: config.auth ?? {},
30
+ publicMode: config.publicMode ?? false,
31
+ debug: config.debug ?? false,
32
+ interceptors: config.interceptors,
33
+ };
34
+ this.authToken = config.auth?.token;
35
+ this.interceptors = config.interceptors;
36
+ this.publicMode = config.publicMode ?? false;
37
+ }
38
+ async connect() {
39
+ this.state = 'connecting';
40
+ this.connectionCount++;
41
+ try {
42
+ // Public mode: Skip all authentication - connect without any token
43
+ if (this.publicMode) {
44
+ this.log('Public mode: connecting without authentication');
45
+ this.state = 'connected';
46
+ return;
47
+ }
48
+ // If no auth token provided, request anonymous token from FrontMCP SDK
49
+ if (!this.authToken) {
50
+ await this.requestAnonymousToken();
51
+ }
52
+ // StreamableHTTP doesn't require an explicit connection step
53
+ // The session is established on the first request
54
+ this.state = 'connected';
55
+ this.log('Connected to StreamableHTTP transport');
56
+ }
57
+ catch (error) {
58
+ this.state = 'error';
59
+ throw error;
60
+ }
61
+ }
62
+ /**
63
+ * Request an anonymous token from the FrontMCP OAuth endpoint
64
+ * This allows the test client to authenticate without user interaction
65
+ */
66
+ async requestAnonymousToken() {
67
+ const clientId = crypto.randomUUID();
68
+ const tokenUrl = `${this.config.baseUrl}/oauth/token`;
69
+ this.log(`Requesting anonymous token from ${tokenUrl}`);
70
+ try {
71
+ const response = await fetch(tokenUrl, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ },
76
+ body: JSON.stringify({
77
+ grant_type: 'anonymous',
78
+ client_id: clientId,
79
+ resource: this.config.baseUrl,
80
+ }),
81
+ });
82
+ if (!response.ok) {
83
+ const errorText = await response.text();
84
+ this.log(`Failed to get anonymous token: ${response.status} ${errorText}`);
85
+ // Continue without token - server may allow unauthenticated access
86
+ return;
87
+ }
88
+ const tokenResponse = await response.json();
89
+ if (tokenResponse.access_token) {
90
+ this.authToken = tokenResponse.access_token;
91
+ this.log('Anonymous token acquired successfully');
92
+ }
93
+ }
94
+ catch (error) {
95
+ this.log(`Error requesting anonymous token: ${error}`);
96
+ // Continue without token - server may allow unauthenticated access
97
+ }
98
+ }
99
+ async request(message) {
100
+ this.ensureConnected();
101
+ const startTime = Date.now();
102
+ // Process through interceptors if available
103
+ if (this.interceptors) {
104
+ const interceptResult = await this.interceptors.processRequest(message, {
105
+ timestamp: new Date(),
106
+ transport: 'streamable-http',
107
+ sessionId: this.sessionId,
108
+ });
109
+ switch (interceptResult.type) {
110
+ case 'mock': {
111
+ // Return mock response directly, run through response interceptors
112
+ const mockResponse = await this.interceptors.processResponse(message, interceptResult.response, Date.now() - startTime);
113
+ return mockResponse;
114
+ }
115
+ case 'error':
116
+ throw interceptResult.error;
117
+ case 'continue':
118
+ // Use possibly modified request
119
+ message = interceptResult.request;
120
+ break;
121
+ }
122
+ }
123
+ const headers = this.buildHeaders();
124
+ this.lastRequestHeaders = headers;
125
+ const url = `${this.config.baseUrl}/`;
126
+ this.log(`POST ${url}`, message);
127
+ const controller = new AbortController();
128
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
129
+ try {
130
+ const response = await fetch(url, {
131
+ method: 'POST',
132
+ headers,
133
+ body: JSON.stringify(message),
134
+ signal: controller.signal,
135
+ });
136
+ clearTimeout(timeoutId);
137
+ // Check for session ID in response headers
138
+ const newSessionId = response.headers.get('mcp-session-id');
139
+ if (newSessionId) {
140
+ this.sessionId = newSessionId;
141
+ }
142
+ let jsonResponse;
143
+ if (!response.ok) {
144
+ // Handle HTTP errors
145
+ const errorText = await response.text();
146
+ this.log(`HTTP Error ${response.status}: ${errorText}`);
147
+ jsonResponse = {
148
+ jsonrpc: '2.0',
149
+ id: message.id ?? null,
150
+ error: {
151
+ code: -32000,
152
+ message: `HTTP ${response.status}: ${response.statusText}`,
153
+ data: errorText,
154
+ },
155
+ };
156
+ }
157
+ else {
158
+ // Parse response - may be JSON or SSE
159
+ const contentType = response.headers.get('content-type') ?? '';
160
+ const text = await response.text();
161
+ this.log('Response:', text);
162
+ // Handle empty response (for notifications)
163
+ if (!text.trim()) {
164
+ jsonResponse = {
165
+ jsonrpc: '2.0',
166
+ id: message.id ?? null,
167
+ result: undefined,
168
+ };
169
+ }
170
+ else if (contentType.includes('text/event-stream')) {
171
+ // Parse SSE response - extract data and session ID from event stream
172
+ const { response: sseResponse, sseSessionId } = this.parseSSEResponseWithSession(text, message.id);
173
+ jsonResponse = sseResponse;
174
+ // Store session ID from SSE id field (format: sessionId:messageId)
175
+ if (sseSessionId && !this.sessionId) {
176
+ this.sessionId = sseSessionId;
177
+ this.log('Session ID from SSE:', this.sessionId);
178
+ }
179
+ }
180
+ else {
181
+ jsonResponse = JSON.parse(text);
182
+ }
183
+ }
184
+ // Process response through interceptors
185
+ if (this.interceptors) {
186
+ jsonResponse = await this.interceptors.processResponse(message, jsonResponse, Date.now() - startTime);
187
+ }
188
+ return jsonResponse;
189
+ }
190
+ catch (error) {
191
+ clearTimeout(timeoutId);
192
+ if (error instanceof Error && error.name === 'AbortError') {
193
+ return {
194
+ jsonrpc: '2.0',
195
+ id: message.id ?? null,
196
+ error: {
197
+ code: -32000,
198
+ message: `Request timeout after ${this.config.timeout}ms`,
199
+ },
200
+ };
201
+ }
202
+ throw error;
203
+ }
204
+ }
205
+ async notify(message) {
206
+ this.ensureConnected();
207
+ const headers = this.buildHeaders();
208
+ this.lastRequestHeaders = headers;
209
+ const url = `${this.config.baseUrl}/`;
210
+ this.log(`POST ${url} (notification)`, message);
211
+ const controller = new AbortController();
212
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
213
+ try {
214
+ const response = await fetch(url, {
215
+ method: 'POST',
216
+ headers,
217
+ body: JSON.stringify(message),
218
+ signal: controller.signal,
219
+ });
220
+ clearTimeout(timeoutId);
221
+ // Update session ID if present
222
+ const newSessionId = response.headers.get('mcp-session-id');
223
+ if (newSessionId) {
224
+ this.sessionId = newSessionId;
225
+ }
226
+ if (!response.ok) {
227
+ const errorText = await response.text();
228
+ this.log(`HTTP Error ${response.status} on notification: ${errorText}`);
229
+ }
230
+ }
231
+ catch (error) {
232
+ clearTimeout(timeoutId);
233
+ if (error instanceof Error && error.name !== 'AbortError') {
234
+ throw error;
235
+ }
236
+ }
237
+ }
238
+ async sendRaw(data) {
239
+ this.ensureConnected();
240
+ const headers = this.buildHeaders();
241
+ this.lastRequestHeaders = headers;
242
+ const url = `${this.config.baseUrl}/`;
243
+ this.log(`POST ${url} (raw)`, data);
244
+ const controller = new AbortController();
245
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
246
+ try {
247
+ const response = await fetch(url, {
248
+ method: 'POST',
249
+ headers,
250
+ body: data,
251
+ signal: controller.signal,
252
+ });
253
+ clearTimeout(timeoutId);
254
+ const text = await response.text();
255
+ if (!text.trim()) {
256
+ return {
257
+ jsonrpc: '2.0',
258
+ id: null,
259
+ error: {
260
+ code: -32700,
261
+ message: 'Parse error',
262
+ },
263
+ };
264
+ }
265
+ return JSON.parse(text);
266
+ }
267
+ catch (error) {
268
+ clearTimeout(timeoutId);
269
+ return {
270
+ jsonrpc: '2.0',
271
+ id: null,
272
+ error: {
273
+ code: -32700,
274
+ message: 'Parse error',
275
+ data: error instanceof Error ? error.message : 'Unknown error',
276
+ },
277
+ };
278
+ }
279
+ }
280
+ async close() {
281
+ this.state = 'disconnected';
282
+ this.sessionId = undefined;
283
+ this.log('StreamableHTTP transport closed');
284
+ }
285
+ isConnected() {
286
+ return this.state === 'connected';
287
+ }
288
+ getState() {
289
+ return this.state;
290
+ }
291
+ getSessionId() {
292
+ return this.sessionId;
293
+ }
294
+ setAuthToken(token) {
295
+ this.authToken = token;
296
+ }
297
+ setTimeout(ms) {
298
+ this.config.timeout = ms;
299
+ }
300
+ setInterceptors(interceptors) {
301
+ this.interceptors = interceptors;
302
+ }
303
+ getInterceptors() {
304
+ return this.interceptors;
305
+ }
306
+ getConnectionCount() {
307
+ return this.connectionCount;
308
+ }
309
+ getReconnectCount() {
310
+ return this.reconnectCount;
311
+ }
312
+ getLastRequestHeaders() {
313
+ return { ...this.lastRequestHeaders };
314
+ }
315
+ async simulateDisconnect() {
316
+ this.state = 'disconnected';
317
+ this.sessionId = undefined;
318
+ }
319
+ async waitForReconnect(timeoutMs) {
320
+ const deadline = Date.now() + timeoutMs;
321
+ // Auto-reconnect
322
+ this.reconnectCount++;
323
+ await this.connect();
324
+ while (Date.now() < deadline) {
325
+ if (this.state === 'connected') {
326
+ return;
327
+ }
328
+ await new Promise((r) => setTimeout(r, 50));
329
+ }
330
+ throw new Error('Timeout waiting for reconnection');
331
+ }
332
+ // ═══════════════════════════════════════════════════════════════════
333
+ // PRIVATE HELPERS
334
+ // ═══════════════════════════════════════════════════════════════════
335
+ buildHeaders() {
336
+ const headers = {
337
+ 'Content-Type': 'application/json',
338
+ Accept: 'application/json, text/event-stream',
339
+ };
340
+ // Only add Authorization header if we have a token AND not in public mode
341
+ // Public mode explicitly skips auth headers for CI/CD and public docs testing
342
+ if (this.authToken && !this.publicMode) {
343
+ headers['Authorization'] = `Bearer ${this.authToken}`;
344
+ }
345
+ // Always send session ID if we have one (even in public mode - server creates sessions)
346
+ if (this.sessionId) {
347
+ headers['mcp-session-id'] = this.sessionId;
348
+ }
349
+ // Add custom headers from config (allow override even in public mode)
350
+ if (this.config.auth.headers) {
351
+ Object.assign(headers, this.config.auth.headers);
352
+ }
353
+ return headers;
354
+ }
355
+ ensureConnected() {
356
+ if (this.state !== 'connected') {
357
+ throw new Error('Transport not connected. Call connect() first.');
358
+ }
359
+ }
360
+ log(message, data) {
361
+ if (this.config.debug) {
362
+ console.log(`[StreamableHTTP] ${message}`, data ?? '');
363
+ }
364
+ }
365
+ /**
366
+ * Parse SSE (Server-Sent Events) response format with session ID extraction
367
+ * SSE format is:
368
+ * event: message
369
+ * id: sessionId:messageId
370
+ * data: {"jsonrpc":"2.0",...}
371
+ *
372
+ * The id field contains the session ID followed by a colon and the message ID.
373
+ *
374
+ * @param text - The raw SSE response text
375
+ * @param requestId - The original request ID
376
+ * @returns Object with parsed JSON-RPC response and session ID (if found)
377
+ */
378
+ parseSSEResponseWithSession(text, requestId) {
379
+ const lines = text.split('\n');
380
+ const dataLines = [];
381
+ let sseSessionId;
382
+ // Collect all data lines and extract session ID from id: field
383
+ for (const line of lines) {
384
+ if (line.startsWith('data: ')) {
385
+ dataLines.push(line.slice(6)); // Remove 'data: ' prefix
386
+ }
387
+ else if (line === 'data:') {
388
+ // Empty data line represents a newline in the data
389
+ dataLines.push('');
390
+ }
391
+ else if (line.startsWith('id: ')) {
392
+ // Extract session ID from id field (format: sessionId:messageId)
393
+ const idValue = line.slice(4);
394
+ const colonIndex = idValue.lastIndexOf(':');
395
+ if (colonIndex > 0) {
396
+ sseSessionId = idValue.substring(0, colonIndex);
397
+ }
398
+ else {
399
+ // No colon, use the whole id as session ID
400
+ sseSessionId = idValue;
401
+ }
402
+ }
403
+ }
404
+ if (dataLines.length > 0) {
405
+ const jsonData = dataLines.join('\n');
406
+ try {
407
+ return {
408
+ response: JSON.parse(jsonData),
409
+ sseSessionId,
410
+ };
411
+ }
412
+ catch {
413
+ this.log('Failed to parse SSE data as JSON:', jsonData);
414
+ }
415
+ }
416
+ // Fallback: return error response
417
+ return {
418
+ response: {
419
+ jsonrpc: '2.0',
420
+ id: requestId ?? null,
421
+ error: {
422
+ code: -32700,
423
+ message: 'Failed to parse SSE response',
424
+ data: text,
425
+ },
426
+ },
427
+ sseSessionId,
428
+ };
429
+ }
430
+ }
431
+ exports.StreamableHttpTransport = StreamableHttpTransport;
432
+ //# sourceMappingURL=streamable-http.transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamable-http.transport.js","sourceRoot":"","sources":["../../../src/transport/streamable-http.transport.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file streamable-http.transport.ts\n * @description StreamableHTTP transport implementation for MCP Test Client\n */\n\nimport type {\n McpTransport,\n TransportConfig,\n TransportState,\n JsonRpcRequest,\n JsonRpcResponse,\n} from './transport.interface';\nimport type { InterceptorChain } from '../interceptor';\n\nconst DEFAULT_TIMEOUT = 30000;\n\n/**\n * StreamableHTTP transport for MCP communication\n *\n * This transport uses HTTP POST requests for all communication,\n * following the MCP StreamableHTTP specification.\n */\nexport class StreamableHttpTransport implements McpTransport {\n private readonly config: Required<Omit<TransportConfig, 'interceptors'>> & { interceptors?: InterceptorChain };\n private state: TransportState = 'disconnected';\n private sessionId: string | undefined;\n private authToken: string | undefined;\n private connectionCount = 0;\n private reconnectCount = 0;\n private lastRequestHeaders: Record<string, string> = {};\n private interceptors?: InterceptorChain;\n private readonly publicMode: boolean;\n\n constructor(config: TransportConfig) {\n this.config = {\n baseUrl: config.baseUrl.replace(/\\/$/, ''), // Remove trailing slash\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n auth: config.auth ?? {},\n publicMode: config.publicMode ?? false,\n debug: config.debug ?? false,\n interceptors: config.interceptors,\n };\n\n this.authToken = config.auth?.token;\n this.interceptors = config.interceptors;\n this.publicMode = config.publicMode ?? false;\n }\n\n async connect(): Promise<void> {\n this.state = 'connecting';\n this.connectionCount++;\n\n try {\n // Public mode: Skip all authentication - connect without any token\n if (this.publicMode) {\n this.log('Public mode: connecting without authentication');\n this.state = 'connected';\n return;\n }\n\n // If no auth token provided, request anonymous token from FrontMCP SDK\n if (!this.authToken) {\n await this.requestAnonymousToken();\n }\n\n // StreamableHTTP doesn't require an explicit connection step\n // The session is established on the first request\n this.state = 'connected';\n this.log('Connected to StreamableHTTP transport');\n } catch (error) {\n this.state = 'error';\n throw error;\n }\n }\n\n /**\n * Request an anonymous token from the FrontMCP OAuth endpoint\n * This allows the test client to authenticate without user interaction\n */\n private async requestAnonymousToken(): Promise<void> {\n const clientId = crypto.randomUUID();\n const tokenUrl = `${this.config.baseUrl}/oauth/token`;\n\n this.log(`Requesting anonymous token from ${tokenUrl}`);\n\n try {\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n grant_type: 'anonymous',\n client_id: clientId,\n resource: this.config.baseUrl,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n this.log(`Failed to get anonymous token: ${response.status} ${errorText}`);\n // Continue without token - server may allow unauthenticated access\n return;\n }\n\n const tokenResponse = await response.json();\n if (tokenResponse.access_token) {\n this.authToken = tokenResponse.access_token;\n this.log('Anonymous token acquired successfully');\n }\n } catch (error) {\n this.log(`Error requesting anonymous token: ${error}`);\n // Continue without token - server may allow unauthenticated access\n }\n }\n\n async request<T = unknown>(message: JsonRpcRequest): Promise<JsonRpcResponse & { result?: T }> {\n this.ensureConnected();\n\n const startTime = Date.now();\n\n // Process through interceptors if available\n if (this.interceptors) {\n const interceptResult = await this.interceptors.processRequest(message, {\n timestamp: new Date(),\n transport: 'streamable-http',\n sessionId: this.sessionId,\n });\n\n switch (interceptResult.type) {\n case 'mock': {\n // Return mock response directly, run through response interceptors\n const mockResponse = await this.interceptors.processResponse(\n message,\n interceptResult.response,\n Date.now() - startTime,\n );\n return mockResponse as JsonRpcResponse & { result?: T };\n }\n\n case 'error':\n throw interceptResult.error;\n\n case 'continue':\n // Use possibly modified request\n message = interceptResult.request;\n break;\n }\n }\n\n const headers = this.buildHeaders();\n this.lastRequestHeaders = headers;\n\n const url = `${this.config.baseUrl}/`;\n this.log(`POST ${url}`, message);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(message),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Check for session ID in response headers\n const newSessionId = response.headers.get('mcp-session-id');\n if (newSessionId) {\n this.sessionId = newSessionId;\n }\n\n let jsonResponse: JsonRpcResponse;\n\n if (!response.ok) {\n // Handle HTTP errors\n const errorText = await response.text();\n this.log(`HTTP Error ${response.status}: ${errorText}`);\n\n jsonResponse = {\n jsonrpc: '2.0',\n id: message.id ?? null,\n error: {\n code: -32000,\n message: `HTTP ${response.status}: ${response.statusText}`,\n data: errorText,\n },\n };\n } else {\n // Parse response - may be JSON or SSE\n const contentType = response.headers.get('content-type') ?? '';\n const text = await response.text();\n this.log('Response:', text);\n\n // Handle empty response (for notifications)\n if (!text.trim()) {\n jsonResponse = {\n jsonrpc: '2.0',\n id: message.id ?? null,\n result: undefined,\n };\n } else if (contentType.includes('text/event-stream')) {\n // Parse SSE response - extract data and session ID from event stream\n const { response: sseResponse, sseSessionId } = this.parseSSEResponseWithSession(text, message.id);\n jsonResponse = sseResponse;\n // Store session ID from SSE id field (format: sessionId:messageId)\n if (sseSessionId && !this.sessionId) {\n this.sessionId = sseSessionId;\n this.log('Session ID from SSE:', this.sessionId);\n }\n } else {\n jsonResponse = JSON.parse(text) as JsonRpcResponse;\n }\n }\n\n // Process response through interceptors\n if (this.interceptors) {\n jsonResponse = await this.interceptors.processResponse(message, jsonResponse, Date.now() - startTime);\n }\n\n return jsonResponse as JsonRpcResponse & { result?: T };\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof Error && error.name === 'AbortError') {\n return {\n jsonrpc: '2.0',\n id: message.id ?? null,\n error: {\n code: -32000,\n message: `Request timeout after ${this.config.timeout}ms`,\n },\n };\n }\n\n throw error;\n }\n }\n\n async notify(message: JsonRpcRequest): Promise<void> {\n this.ensureConnected();\n\n const headers = this.buildHeaders();\n this.lastRequestHeaders = headers;\n\n const url = `${this.config.baseUrl}/`;\n this.log(`POST ${url} (notification)`, message);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(message),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n // Update session ID if present\n const newSessionId = response.headers.get('mcp-session-id');\n if (newSessionId) {\n this.sessionId = newSessionId;\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n this.log(`HTTP Error ${response.status} on notification: ${errorText}`);\n }\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof Error && error.name !== 'AbortError') {\n throw error;\n }\n }\n }\n\n async sendRaw(data: string): Promise<JsonRpcResponse> {\n this.ensureConnected();\n\n const headers = this.buildHeaders();\n this.lastRequestHeaders = headers;\n\n const url = `${this.config.baseUrl}/`;\n this.log(`POST ${url} (raw)`, data);\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: data,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const text = await response.text();\n\n if (!text.trim()) {\n return {\n jsonrpc: '2.0',\n id: null,\n error: {\n code: -32700,\n message: 'Parse error',\n },\n };\n }\n\n return JSON.parse(text) as JsonRpcResponse;\n } catch (error) {\n clearTimeout(timeoutId);\n\n return {\n jsonrpc: '2.0',\n id: null,\n error: {\n code: -32700,\n message: 'Parse error',\n data: error instanceof Error ? error.message : 'Unknown error',\n },\n };\n }\n }\n\n async close(): Promise<void> {\n this.state = 'disconnected';\n this.sessionId = undefined;\n this.log('StreamableHTTP transport closed');\n }\n\n isConnected(): boolean {\n return this.state === 'connected';\n }\n\n getState(): TransportState {\n return this.state;\n }\n\n getSessionId(): string | undefined {\n return this.sessionId;\n }\n\n setAuthToken(token: string): void {\n this.authToken = token;\n }\n\n setTimeout(ms: number): void {\n this.config.timeout = ms;\n }\n\n setInterceptors(interceptors: InterceptorChain): void {\n this.interceptors = interceptors;\n }\n\n getInterceptors(): InterceptorChain | undefined {\n return this.interceptors;\n }\n\n getConnectionCount(): number {\n return this.connectionCount;\n }\n\n getReconnectCount(): number {\n return this.reconnectCount;\n }\n\n getLastRequestHeaders(): Record<string, string> {\n return { ...this.lastRequestHeaders };\n }\n\n async simulateDisconnect(): Promise<void> {\n this.state = 'disconnected';\n this.sessionId = undefined;\n }\n\n async waitForReconnect(timeoutMs: number): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n\n // Auto-reconnect\n this.reconnectCount++;\n await this.connect();\n\n while (Date.now() < deadline) {\n if (this.state === 'connected') {\n return;\n }\n await new Promise((r) => setTimeout(r, 50));\n }\n\n throw new Error('Timeout waiting for reconnection');\n }\n\n // ═══════════════════════════════════════════════════════════════════\n // PRIVATE HELPERS\n // ═══════════════════════════════════════════════════════════════════\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json, text/event-stream',\n };\n\n // Only add Authorization header if we have a token AND not in public mode\n // Public mode explicitly skips auth headers for CI/CD and public docs testing\n if (this.authToken && !this.publicMode) {\n headers['Authorization'] = `Bearer ${this.authToken}`;\n }\n\n // Always send session ID if we have one (even in public mode - server creates sessions)\n if (this.sessionId) {\n headers['mcp-session-id'] = this.sessionId;\n }\n\n // Add custom headers from config (allow override even in public mode)\n if (this.config.auth.headers) {\n Object.assign(headers, this.config.auth.headers);\n }\n\n return headers;\n }\n\n private ensureConnected(): void {\n if (this.state !== 'connected') {\n throw new Error('Transport not connected. Call connect() first.');\n }\n }\n\n private log(message: string, data?: unknown): void {\n if (this.config.debug) {\n console.log(`[StreamableHTTP] ${message}`, data ?? '');\n }\n }\n\n /**\n * Parse SSE (Server-Sent Events) response format with session ID extraction\n * SSE format is:\n * event: message\n * id: sessionId:messageId\n * data: {\"jsonrpc\":\"2.0\",...}\n *\n * The id field contains the session ID followed by a colon and the message ID.\n *\n * @param text - The raw SSE response text\n * @param requestId - The original request ID\n * @returns Object with parsed JSON-RPC response and session ID (if found)\n */\n private parseSSEResponseWithSession(\n text: string,\n requestId: string | number | undefined,\n ): { response: JsonRpcResponse; sseSessionId?: string } {\n const lines = text.split('\\n');\n const dataLines: string[] = [];\n let sseSessionId: string | undefined;\n\n // Collect all data lines and extract session ID from id: field\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n dataLines.push(line.slice(6)); // Remove 'data: ' prefix\n } else if (line === 'data:') {\n // Empty data line represents a newline in the data\n dataLines.push('');\n } else if (line.startsWith('id: ')) {\n // Extract session ID from id field (format: sessionId:messageId)\n const idValue = line.slice(4);\n const colonIndex = idValue.lastIndexOf(':');\n if (colonIndex > 0) {\n sseSessionId = idValue.substring(0, colonIndex);\n } else {\n // No colon, use the whole id as session ID\n sseSessionId = idValue;\n }\n }\n }\n\n if (dataLines.length > 0) {\n const jsonData = dataLines.join('\\n');\n try {\n return {\n response: JSON.parse(jsonData) as JsonRpcResponse,\n sseSessionId,\n };\n } catch {\n this.log('Failed to parse SSE data as JSON:', jsonData);\n }\n }\n\n // Fallback: return error response\n return {\n response: {\n jsonrpc: '2.0',\n id: requestId ?? null,\n error: {\n code: -32700,\n message: 'Failed to parse SSE response',\n data: text,\n },\n },\n sseSessionId,\n };\n }\n}\n"]}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @file transport.interface.ts
3
+ * @description Interface for MCP transport implementations
4
+ */
5
+ export interface JsonRpcRequest {
6
+ jsonrpc: '2.0';
7
+ id?: string | number;
8
+ method: string;
9
+ params?: Record<string, unknown>;
10
+ }
11
+ export interface JsonRpcResponse {
12
+ jsonrpc: '2.0';
13
+ id: string | number | null;
14
+ result?: unknown;
15
+ error?: {
16
+ code: number;
17
+ message: string;
18
+ data?: unknown;
19
+ };
20
+ }
21
+ export type TransportState = 'disconnected' | 'connecting' | 'connected' | 'error';
22
+ /**
23
+ * Interface that all MCP transports must implement
24
+ */
25
+ export interface McpTransport {
26
+ /**
27
+ * Connect to the MCP server
28
+ */
29
+ connect(): Promise<void>;
30
+ /**
31
+ * Send a JSON-RPC request and wait for response
32
+ */
33
+ request<T = unknown>(message: JsonRpcRequest): Promise<JsonRpcResponse & {
34
+ result?: T;
35
+ }>;
36
+ /**
37
+ * Send a notification (no response expected)
38
+ */
39
+ notify(message: JsonRpcRequest): Promise<void>;
40
+ /**
41
+ * Send raw string data (for error testing)
42
+ */
43
+ sendRaw(data: string): Promise<JsonRpcResponse>;
44
+ /**
45
+ * Close the connection
46
+ */
47
+ close(): Promise<void>;
48
+ /**
49
+ * Check if transport is connected
50
+ */
51
+ isConnected(): boolean;
52
+ /**
53
+ * Get current transport state
54
+ */
55
+ getState(): TransportState;
56
+ /**
57
+ * Get the session ID (if applicable)
58
+ */
59
+ getSessionId(): string | undefined;
60
+ /**
61
+ * Set the authentication token
62
+ */
63
+ setAuthToken(token: string): void;
64
+ /**
65
+ * Set the request timeout
66
+ */
67
+ setTimeout(ms: number): void;
68
+ /**
69
+ * Get the message endpoint URL (SSE transport)
70
+ */
71
+ getMessageEndpoint?(): string | undefined;
72
+ /**
73
+ * Get number of connections made
74
+ */
75
+ getConnectionCount?(): number;
76
+ /**
77
+ * Get number of reconnections
78
+ */
79
+ getReconnectCount?(): number;
80
+ /**
81
+ * Get the headers from the last request
82
+ */
83
+ getLastRequestHeaders?(): Record<string, string>;
84
+ /**
85
+ * Simulate a disconnect (for testing reconnection)
86
+ */
87
+ simulateDisconnect?(): Promise<void>;
88
+ /**
89
+ * Wait for reconnection to complete
90
+ */
91
+ waitForReconnect?(timeoutMs: number): Promise<void>;
92
+ /**
93
+ * Set interceptor chain for request/response interception
94
+ */
95
+ setInterceptors?(interceptors: import('../interceptor').InterceptorChain): void;
96
+ /**
97
+ * Get the current interceptor chain
98
+ */
99
+ getInterceptors?(): import('../interceptor').InterceptorChain | undefined;
100
+ }
101
+ /**
102
+ * Configuration for transport implementations
103
+ */
104
+ export interface TransportConfig {
105
+ /** Base URL of the MCP server */
106
+ baseUrl: string;
107
+ /** Request timeout in milliseconds */
108
+ timeout?: number;
109
+ /** Authentication configuration */
110
+ auth?: {
111
+ token?: string;
112
+ headers?: Record<string, string>;
113
+ };
114
+ /**
115
+ * Enable public mode - skip authentication entirely.
116
+ * When true, no Authorization header is sent and anonymous token is not requested.
117
+ * Use this for testing public/unauthenticated endpoints in CI/CD pipelines.
118
+ */
119
+ publicMode?: boolean;
120
+ /** Enable debug logging */
121
+ debug?: boolean;
122
+ /** Interceptor chain for request/response interception */
123
+ interceptors?: import('../interceptor').InterceptorChain;
124
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * @file transport.interface.ts
4
+ * @description Interface for MCP transport implementations
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=transport.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.interface.js","sourceRoot":"","sources":["../../../src/transport/transport.interface.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * @file transport.interface.ts\n * @description Interface for MCP transport implementations\n */\n\n// Simplified JSON-RPC types for transport layer\nexport interface JsonRpcRequest {\n jsonrpc: '2.0';\n id?: string | number;\n method: string;\n params?: Record<string, unknown>;\n}\n\nexport interface JsonRpcResponse {\n jsonrpc: '2.0';\n id: string | number | null;\n result?: unknown;\n error?: {\n code: number;\n message: string;\n data?: unknown;\n };\n}\n\nexport type TransportState = 'disconnected' | 'connecting' | 'connected' | 'error';\n\n/**\n * Interface that all MCP transports must implement\n */\nexport interface McpTransport {\n /**\n * Connect to the MCP server\n */\n connect(): Promise<void>;\n\n /**\n * Send a JSON-RPC request and wait for response\n */\n request<T = unknown>(message: JsonRpcRequest): Promise<JsonRpcResponse & { result?: T }>;\n\n /**\n * Send a notification (no response expected)\n */\n notify(message: JsonRpcRequest): Promise<void>;\n\n /**\n * Send raw string data (for error testing)\n */\n sendRaw(data: string): Promise<JsonRpcResponse>;\n\n /**\n * Close the connection\n */\n close(): Promise<void>;\n\n /**\n * Check if transport is connected\n */\n isConnected(): boolean;\n\n /**\n * Get current transport state\n */\n getState(): TransportState;\n\n /**\n * Get the session ID (if applicable)\n */\n getSessionId(): string | undefined;\n\n /**\n * Set the authentication token\n */\n setAuthToken(token: string): void;\n\n /**\n * Set the request timeout\n */\n setTimeout(ms: number): void;\n\n // Optional methods for testing\n\n /**\n * Get the message endpoint URL (SSE transport)\n */\n getMessageEndpoint?(): string | undefined;\n\n /**\n * Get number of connections made\n */\n getConnectionCount?(): number;\n\n /**\n * Get number of reconnections\n */\n getReconnectCount?(): number;\n\n /**\n * Get the headers from the last request\n */\n getLastRequestHeaders?(): Record<string, string>;\n\n /**\n * Simulate a disconnect (for testing reconnection)\n */\n simulateDisconnect?(): Promise<void>;\n\n /**\n * Wait for reconnection to complete\n */\n waitForReconnect?(timeoutMs: number): Promise<void>;\n\n /**\n * Set interceptor chain for request/response interception\n */\n setInterceptors?(interceptors: import('../interceptor').InterceptorChain): void;\n\n /**\n * Get the current interceptor chain\n */\n getInterceptors?(): import('../interceptor').InterceptorChain | undefined;\n}\n\n/**\n * Configuration for transport implementations\n */\nexport interface TransportConfig {\n /** Base URL of the MCP server */\n baseUrl: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Authentication configuration */\n auth?: {\n token?: string;\n headers?: Record<string, string>;\n };\n /**\n * Enable public mode - skip authentication entirely.\n * When true, no Authorization header is sent and anonymous token is not requested.\n * Use this for testing public/unauthenticated endpoints in CI/CD pipelines.\n */\n publicMode?: boolean;\n /** Enable debug logging */\n debug?: boolean;\n /** Interceptor chain for request/response interception */\n interceptors?: import('../interceptor').InterceptorChain;\n}\n"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @file index.ts
3
+ * @description Barrel exports for UI testing utilities
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * import { uiMatchers, UIAssertions } from '@frontmcp/testing';
8
+ *
9
+ * // Use matchers with expect.extend
10
+ * expect.extend(uiMatchers);
11
+ *
12
+ * // Or use assertion helpers directly
13
+ * const html = UIAssertions.assertRenderedUI(result);
14
+ * ```
15
+ */
16
+ export { uiMatchers } from './ui-matchers';
17
+ export { UIAssertions } from './ui-assertions';
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * @file index.ts
4
+ * @description Barrel exports for UI testing utilities
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { uiMatchers, UIAssertions } from '@frontmcp/testing';
9
+ *
10
+ * // Use matchers with expect.extend
11
+ * expect.extend(uiMatchers);
12
+ *
13
+ * // Or use assertion helpers directly
14
+ * const html = UIAssertions.assertRenderedUI(result);
15
+ * ```
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.UIAssertions = exports.uiMatchers = void 0;
19
+ var ui_matchers_1 = require("./ui-matchers");
20
+ Object.defineProperty(exports, "uiMatchers", { enumerable: true, get: function () { return ui_matchers_1.uiMatchers; } });
21
+ var ui_assertions_1 = require("./ui-assertions");
22
+ Object.defineProperty(exports, "UIAssertions", { enumerable: true, get: function () { return ui_assertions_1.UIAssertions; } });
23
+ //# sourceMappingURL=index.js.map