@frontmcp/testing 0.6.1 → 0.6.3

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