@frontmcp/testing 0.5.1 → 0.6.1

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 (57) hide show
  1. package/package.json +4 -4
  2. package/src/auth/mock-api-server.d.ts +99 -0
  3. package/src/auth/mock-api-server.js +200 -0
  4. package/src/auth/mock-api-server.js.map +1 -0
  5. package/src/auth/mock-oauth-server.d.ts +85 -0
  6. package/src/auth/mock-oauth-server.js +253 -0
  7. package/src/auth/mock-oauth-server.js.map +1 -0
  8. package/src/client/mcp-test-client.builder.d.ts +43 -1
  9. package/src/client/mcp-test-client.builder.js +52 -0
  10. package/src/client/mcp-test-client.builder.js.map +1 -1
  11. package/src/client/mcp-test-client.js +22 -14
  12. package/src/client/mcp-test-client.js.map +1 -1
  13. package/src/client/mcp-test-client.types.d.ts +67 -6
  14. package/src/client/mcp-test-client.types.js +9 -0
  15. package/src/client/mcp-test-client.types.js.map +1 -1
  16. package/src/example-tools/index.d.ts +19 -0
  17. package/src/example-tools/index.js +40 -0
  18. package/src/example-tools/index.js.map +1 -0
  19. package/src/example-tools/tool-configs.d.ts +170 -0
  20. package/src/example-tools/tool-configs.js +222 -0
  21. package/src/example-tools/tool-configs.js.map +1 -0
  22. package/src/expect.d.ts +6 -5
  23. package/src/expect.js.map +1 -1
  24. package/src/fixtures/fixture-types.d.ts +19 -0
  25. package/src/fixtures/fixture-types.js.map +1 -1
  26. package/src/fixtures/test-fixture.d.ts +3 -1
  27. package/src/fixtures/test-fixture.js +35 -4
  28. package/src/fixtures/test-fixture.js.map +1 -1
  29. package/src/index.d.ts +7 -0
  30. package/src/index.js +40 -1
  31. package/src/index.js.map +1 -1
  32. package/src/matchers/matcher-types.js.map +1 -1
  33. package/src/matchers/mcp-matchers.d.ts +7 -0
  34. package/src/matchers/mcp-matchers.js +8 -4
  35. package/src/matchers/mcp-matchers.js.map +1 -1
  36. package/src/platform/index.d.ts +28 -0
  37. package/src/platform/index.js +47 -0
  38. package/src/platform/index.js.map +1 -0
  39. package/src/platform/platform-client-info.d.ts +97 -0
  40. package/src/platform/platform-client-info.js +155 -0
  41. package/src/platform/platform-client-info.js.map +1 -0
  42. package/src/platform/platform-types.d.ts +72 -0
  43. package/src/platform/platform-types.js +110 -0
  44. package/src/platform/platform-types.js.map +1 -0
  45. package/src/server/test-server.d.ts +4 -0
  46. package/src/server/test-server.js +58 -3
  47. package/src/server/test-server.js.map +1 -1
  48. package/src/transport/streamable-http.transport.js +6 -0
  49. package/src/transport/streamable-http.transport.js.map +1 -1
  50. package/src/transport/transport.interface.d.ts +3 -0
  51. package/src/transport/transport.interface.js.map +1 -1
  52. package/src/ui/ui-assertions.d.ts +59 -0
  53. package/src/ui/ui-assertions.js +152 -0
  54. package/src/ui/ui-assertions.js.map +1 -1
  55. package/src/ui/ui-matchers.d.ts +8 -0
  56. package/src/ui/ui-matchers.js +218 -0
  57. package/src/ui/ui-matchers.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"test-fixture.js","sourceRoot":"","sources":["../../../src/fixtures/test-fixture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file test-fixture.ts\n * @description Jest-based fixture system for MCP testing\n *\n * Provides a Playwright-like fixture API for testing FrontMCP servers:\n *\n * @example\n * ```typescript\n * import { test, expect } from '@frontmcp/testing';\n *\n * test.use({\n * server: './src/main.ts',\n * port: 3003,\n * });\n *\n * test('server exposes tools', async ({ mcp }) => {\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * });\n * ```\n */\n\nimport { McpTestClient } from '../client/mcp-test-client';\nimport { TestTokenFactory } from '../auth/token-factory';\nimport { TestServer } from '../server/test-server';\nimport type {\n TestConfig,\n TestFixtures,\n AuthFixture,\n ServerFixture,\n TestFn,\n TestWithFixtures,\n TestUser,\n} from './fixture-types';\n\n// ═══════════════════════════════════════════════════════════════════\n// GLOBAL STATE\n// ═══════════════════════════════════════════════════════════════════\n\n/** Current test configuration (set via test.use()) */\nlet currentConfig: TestConfig = {};\n\n/** Server instance (shared across tests in a file) */\nlet serverInstance: TestServer | null = null;\n\n/** Token factory instance (shared across tests in a file) */\nlet tokenFactory: TestTokenFactory | null = null;\n\n/** Track if server was started by us (vs external) */\nlet serverStartedByUs = false;\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURE SETUP/TEARDOWN\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Initialize shared resources (server, token factory) once per test file\n */\nasync function initializeSharedResources(): Promise<void> {\n // Create token factory if not exists\n if (!tokenFactory) {\n tokenFactory = new TestTokenFactory();\n }\n\n // Start or connect to server if not exists\n if (!serverInstance) {\n if (currentConfig.baseUrl) {\n // Connect to existing external server\n serverInstance = TestServer.connect(currentConfig.baseUrl);\n serverStartedByUs = false;\n } else if (currentConfig.server) {\n // Start new server\n serverInstance = await TestServer.start({\n port: currentConfig.port,\n command: resolveServerCommand(currentConfig.server),\n env: currentConfig.env,\n startupTimeout: currentConfig.startupTimeout ?? 30000,\n debug: currentConfig.logLevel === 'debug',\n });\n serverStartedByUs = true;\n } else {\n throw new Error(\n 'test.use() requires either \"server\" (entry file path) or \"baseUrl\" (for external server) option',\n );\n }\n }\n}\n\n/**\n * Create fixtures for a single test\n */\nasync function createTestFixtures(): Promise<TestFixtures> {\n // Ensure shared resources are initialized\n await initializeSharedResources();\n\n // Create MCP client for this test\n // Pass publicMode if configured to skip authentication\n const clientInstance = await McpTestClient.create({\n baseUrl: serverInstance!.info.baseUrl,\n transport: currentConfig.transport ?? 'streamable-http',\n publicMode: currentConfig.publicMode,\n }).buildAndConnect();\n\n // Build fixtures\n const auth = createAuthFixture(tokenFactory!);\n const server = createServerFixture(serverInstance!);\n\n return {\n mcp: clientInstance,\n auth,\n server,\n };\n}\n\n/**\n * Clean up fixtures after a single test\n */\nasync function cleanupTestFixtures(fixtures: TestFixtures): Promise<void> {\n // Disconnect client\n if (fixtures.mcp.isConnected()) {\n await fixtures.mcp.disconnect();\n }\n}\n\n/**\n * Clean up shared resources after all tests in a file\n */\nasync function cleanupSharedResources(): Promise<void> {\n // Only stop server if we started it\n if (serverInstance && serverStartedByUs) {\n await serverInstance.stop();\n }\n serverInstance = null;\n tokenFactory = null;\n serverStartedByUs = false;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURE FACTORIES\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Create the auth fixture from token factory\n */\nfunction createAuthFixture(factory: TestTokenFactory): AuthFixture {\n const users: Record<string, TestUser> = {\n admin: {\n sub: 'admin-001',\n scopes: ['admin:*', 'read', 'write', 'delete'],\n email: 'admin@test.local',\n name: 'Test Admin',\n },\n user: {\n sub: 'user-001',\n scopes: ['read', 'write'],\n email: 'user@test.local',\n name: 'Test User',\n },\n readOnly: {\n sub: 'readonly-001',\n scopes: ['read'],\n email: 'readonly@test.local',\n name: 'Read Only User',\n },\n };\n\n return {\n createToken: (opts) =>\n factory.createTestToken({\n sub: opts.sub,\n scopes: opts.scopes,\n claims: {\n email: opts.email,\n name: opts.name,\n ...opts.claims,\n },\n exp: opts.expiresIn,\n }),\n\n createExpiredToken: (opts) => factory.createExpiredToken(opts),\n\n createInvalidToken: (opts) => factory.createTokenWithInvalidSignature(opts),\n\n users: {\n admin: users['admin'],\n user: users['user'],\n readOnly: users['readOnly'],\n },\n\n getJwks: () => factory.getPublicJwks(),\n\n getIssuer: () => factory.getIssuer(),\n\n getAudience: () => factory.getAudience(),\n };\n}\n\n/**\n * Create the server fixture from test server\n */\nfunction createServerFixture(server: TestServer): ServerFixture {\n return {\n info: server.info,\n\n createClient: async (opts) => {\n return McpTestClient.create({\n baseUrl: server.info.baseUrl,\n transport: opts?.transport ?? 'streamable-http',\n auth: opts?.token ? { token: opts.token } : undefined,\n }).buildAndConnect();\n },\n\n restart: () => server.restart(),\n\n getLogs: () => server.getLogs(),\n\n clearLogs: () => server.clearLogs(),\n };\n}\n\n/**\n * Resolve server entry to a command\n */\nfunction resolveServerCommand(server: string): string {\n // If it's already a command (contains spaces), use as-is\n if (server.includes(' ')) {\n return server;\n }\n // Otherwise, run with tsx\n return `npx tsx ${server}`;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// TEST FUNCTION WITH FIXTURES\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Enhanced test function that provides fixtures\n */\nfunction testWithFixtures(name: string, fn: TestFn): void {\n it(name, async () => {\n const fixtures = await createTestFixtures();\n try {\n await fn(fixtures);\n } finally {\n await cleanupTestFixtures(fixtures);\n }\n });\n}\n\n/**\n * Configure test fixtures for the current test file/suite\n */\nfunction use(config: TestConfig): void {\n // Merge with existing config\n currentConfig = { ...currentConfig, ...config };\n\n // Register cleanup hook if not already done\n // This ensures server is stopped after all tests in the file\n afterAll(async () => {\n await cleanupSharedResources();\n });\n}\n\n/**\n * Skip a test\n */\nfunction skip(name: string, fn: TestFn): void {\n it.skip(name, async () => {\n const fixtures = await createTestFixtures();\n try {\n await fn(fixtures);\n } finally {\n await cleanupTestFixtures(fixtures);\n }\n });\n}\n\n/**\n * Run only this test\n */\nfunction only(name: string, fn: TestFn): void {\n it.only(name, async () => {\n const fixtures = await createTestFixtures();\n try {\n await fn(fixtures);\n } finally {\n await cleanupTestFixtures(fixtures);\n }\n });\n}\n\n/**\n * Mark test as todo\n */\nfunction todo(name: string): void {\n it.todo(name);\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// ATTACH STATIC METHODS\n// ═══════════════════════════════════════════════════════════════════\n\n// Cast to the full interface type\nconst test = testWithFixtures as TestWithFixtures;\n\n// Attach configuration method\ntest.use = use;\n\n// Attach Jest lifecycle methods\ntest.describe = describe;\ntest.beforeAll = beforeAll;\ntest.beforeEach = beforeEach;\ntest.afterEach = afterEach;\ntest.afterAll = afterAll;\n\n// Attach test modifiers\ntest.skip = skip;\ntest.only = only;\ntest.todo = todo;\n\n// ═══════════════════════════════════════════════════════════════════\n// EXPORTS\n// ═══════════════════════════════════════════════════════════════════\n\nexport { test };\n\n// Also export for advanced use cases\nexport { createTestFixtures, cleanupTestFixtures, initializeSharedResources, cleanupSharedResources };\n"]}
1
+ {"version":3,"file":"test-fixture.js","sourceRoot":"","sources":["../../../src/fixtures/test-fixture.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAiVM,gDAAkB;AAAE,kDAAmB;AAAE,8DAAyB;AAAE,wDAAsB;AA/UnG,+DAA0D;AAC1D,+EAAyE;AACzE,yDAAyD;AACzD,uDAAmD;AAWnD,sEAAsE;AACtE,eAAe;AACf,sEAAsE;AAEtE,sDAAsD;AACtD,IAAI,aAAa,GAAe,EAAE,CAAC;AAEnC,sDAAsD;AACtD,IAAI,cAAc,GAAsB,IAAI,CAAC;AAE7C,6DAA6D;AAC7D,IAAI,YAAY,GAA4B,IAAI,CAAC;AAEjD,sDAAsD;AACtD,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B,sEAAsE;AACtE,yBAAyB;AACzB,sEAAsE;AAEtE;;GAEG;AACH,KAAK,UAAU,yBAAyB;IACtC,qCAAqC;IACrC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,IAAI,gCAAgB,EAAE,CAAC;IACxC,CAAC;IAED,2CAA2C;IAC3C,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;YAC1B,sCAAsC;YACtC,cAAc,GAAG,wBAAU,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;YAC3D,iBAAiB,GAAG,KAAK,CAAC;QAC5B,CAAC;aAAM,IAAI,aAAa,CAAC,MAAM,EAAE,CAAC;YAChC,mBAAmB;YACnB,cAAc,GAAG,MAAM,wBAAU,CAAC,KAAK,CAAC;gBACtC,IAAI,EAAE,aAAa,CAAC,IAAI;gBACxB,OAAO,EAAE,oBAAoB,CAAC,aAAa,CAAC,MAAM,CAAC;gBACnD,GAAG,EAAE,aAAa,CAAC,GAAG;gBACtB,cAAc,EAAE,aAAa,CAAC,cAAc,IAAI,KAAK;gBACrD,KAAK,EAAE,aAAa,CAAC,QAAQ,KAAK,OAAO;aAC1C,CAAC,CAAC;YACH,iBAAiB,GAAG,IAAI,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CACb,iGAAiG,CAClG,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB;IAC/B,0CAA0C;IAC1C,MAAM,yBAAyB,EAAE,CAAC;IAElC,kCAAkC;IAClC,uDAAuD;IACvD,MAAM,cAAc,GAAG,MAAM,+BAAa,CAAC,MAAM,CAAC;QAChD,OAAO,EAAE,cAAe,CAAC,IAAI,CAAC,OAAO;QACrC,SAAS,EAAE,aAAa,CAAC,SAAS,IAAI,iBAAiB;QACvD,UAAU,EAAE,aAAa,CAAC,UAAU;KACrC,CAAC,CAAC,eAAe,EAAE,CAAC;IAErB,iBAAiB;IACjB,MAAM,IAAI,GAAG,iBAAiB,CAAC,YAAa,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,mBAAmB,CAAC,cAAe,CAAC,CAAC;IAEpD,OAAO;QACL,GAAG,EAAE,cAAc;QACnB,IAAI;QACJ,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,mBAAmB,CAAC,QAAsB,EAAE,WAAW,GAAG,KAAK;IAC5E,oBAAoB;IACpB,IAAI,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/B,MAAM,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;IAClC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,sBAAsB;IACnC,oCAAoC;IACpC,IAAI,cAAc,IAAI,iBAAiB,EAAE,CAAC;QACxC,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC;IACD,cAAc,GAAG,IAAI,CAAC;IACtB,YAAY,GAAG,IAAI,CAAC;IACpB,iBAAiB,GAAG,KAAK,CAAC;AAC5B,CAAC;AAED,sEAAsE;AACtE,oBAAoB;AACpB,sEAAsE;AAEtE;;GAEG;AACH,SAAS,iBAAiB,CAAC,OAAyB;IAClD,MAAM,KAAK,GAA6B;QACtC,KAAK,EAAE;YACL,GAAG,EAAE,WAAW;YAChB,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC;YAC9C,KAAK,EAAE,kBAAkB;YACzB,IAAI,EAAE,YAAY;SACnB;QACD,IAAI,EAAE;YACJ,GAAG,EAAE,UAAU;YACf,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YACzB,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,WAAW;SAClB;QACD,QAAQ,EAAE;YACR,GAAG,EAAE,cAAc;YACnB,MAAM,EAAE,CAAC,MAAM,CAAC;YAChB,KAAK,EAAE,qBAAqB;YAC5B,IAAI,EAAE,gBAAgB;SACvB;KACF,CAAC;IAEF,OAAO;QACL,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CACpB,OAAO,CAAC,eAAe,CAAC;YACtB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE;gBACN,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,IAAI,CAAC,MAAM;aACf;YACD,GAAG,EAAE,IAAI,CAAC,SAAS;SACpB,CAAC;QAEJ,kBAAkB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC;QAE9D,kBAAkB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,+BAA+B,CAAC,IAAI,CAAC;QAE3E,KAAK,EAAE;YACL,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;YACrB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC;SAC5B;QAED,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE;QAEtC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE;QAEpC,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE;KACzC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,MAAkB;IAC7C,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI;QAEjB,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YAC3B,0EAA0E;YAC1E,gEAAgE;YAChE,OAAO,+BAAa,CAAC,MAAM,CAAC;gBAC1B,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO;gBAC5B,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,iBAAiB;gBAC/C,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS;gBACrD,UAAU,EAAE,IAAI,EAAE,UAAU;gBAC5B,UAAU,EAAE,aAAa,CAAC,UAAU;aACrC,CAAC,CAAC,eAAe,EAAE,CAAC;QACvB,CAAC;QAED,mBAAmB,EAAE,GAAG,EAAE;YACxB,4EAA4E;YAC5E,0EAA0E;YAC1E,MAAM,OAAO,GAAG,IAAI,8CAAoB,CAAC;gBACvC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO;gBAC5B,UAAU,EAAE,aAAa,CAAC,UAAU;aACrC,CAAC,CAAC;YACH,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE;QAE/B,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE;QAE/B,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE;KACpC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,MAAc;IAC1C,yDAAyD;IACzD,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,0BAA0B;IAC1B,OAAO,WAAW,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED,sEAAsE;AACtE,8BAA8B;AAC9B,sEAAsE;AAEtE;;GAEG;AACH,SAAS,gBAAgB,CAAC,IAAY,EAAE,EAAU;IAChD,EAAE,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;QAClB,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;QAC5C,IAAI,UAAU,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,UAAU,GAAG,IAAI,CAAC;YAClB,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,MAAM,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,GAAG,CAAC,MAAkB;IAC7B,6BAA6B;IAC7B,aAAa,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,EAAE,CAAC;IAEhD,4CAA4C;IAC5C,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,IAAI,EAAE;QAClB,MAAM,sBAAsB,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,IAAI,CAAC,IAAY,EAAE,EAAU;IACpC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;QACvB,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;QAC5C,IAAI,UAAU,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,UAAU,GAAG,IAAI,CAAC;YAClB,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,MAAM,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,IAAI,CAAC,IAAY,EAAE,EAAU;IACpC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;QACvB,MAAM,QAAQ,GAAG,MAAM,kBAAkB,EAAE,CAAC;QAC5C,IAAI,UAAU,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,UAAU,GAAG,IAAI,CAAC;YAClB,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,MAAM,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,SAAS,IAAI,CAAC,IAAY;IACxB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,sEAAsE;AACtE,wBAAwB;AACxB,sEAAsE;AAEtE,kCAAkC;AAClC,MAAM,IAAI,GAAG,gBAAoC,CAAC;AAqBzC,oBAAI;AAnBb,8BAA8B;AAC9B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;AAEf,gCAAgC;AAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;AACzB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;AAC3B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;AAC7B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;AAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAEzB,wBAAwB;AACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;AACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;AACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC","sourcesContent":["/**\n * @file test-fixture.ts\n * @description Jest-based fixture system for MCP testing\n *\n * Provides a Playwright-like fixture API for testing FrontMCP servers:\n *\n * @example\n * ```typescript\n * import { test, expect } from '@frontmcp/testing';\n *\n * test.use({\n * server: './src/main.ts',\n * port: 3003,\n * });\n *\n * test('server exposes tools', async ({ mcp }) => {\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * });\n * ```\n */\n\nimport { McpTestClient } from '../client/mcp-test-client';\nimport { McpTestClientBuilder } from '../client/mcp-test-client.builder';\nimport { TestTokenFactory } from '../auth/token-factory';\nimport { TestServer } from '../server/test-server';\nimport type {\n TestConfig,\n TestFixtures,\n AuthFixture,\n ServerFixture,\n TestFn,\n TestWithFixtures,\n TestUser,\n} from './fixture-types';\n\n// ═══════════════════════════════════════════════════════════════════\n// GLOBAL STATE\n// ═══════════════════════════════════════════════════════════════════\n\n/** Current test configuration (set via test.use()) */\nlet currentConfig: TestConfig = {};\n\n/** Server instance (shared across tests in a file) */\nlet serverInstance: TestServer | null = null;\n\n/** Token factory instance (shared across tests in a file) */\nlet tokenFactory: TestTokenFactory | null = null;\n\n/** Track if server was started by us (vs external) */\nlet serverStartedByUs = false;\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURE SETUP/TEARDOWN\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Initialize shared resources (server, token factory) once per test file\n */\nasync function initializeSharedResources(): Promise<void> {\n // Create token factory if not exists\n if (!tokenFactory) {\n tokenFactory = new TestTokenFactory();\n }\n\n // Start or connect to server if not exists\n if (!serverInstance) {\n if (currentConfig.baseUrl) {\n // Connect to existing external server\n serverInstance = TestServer.connect(currentConfig.baseUrl);\n serverStartedByUs = false;\n } else if (currentConfig.server) {\n // Start new server\n serverInstance = await TestServer.start({\n port: currentConfig.port,\n command: resolveServerCommand(currentConfig.server),\n env: currentConfig.env,\n startupTimeout: currentConfig.startupTimeout ?? 30000,\n debug: currentConfig.logLevel === 'debug',\n });\n serverStartedByUs = true;\n } else {\n throw new Error(\n 'test.use() requires either \"server\" (entry file path) or \"baseUrl\" (for external server) option',\n );\n }\n }\n}\n\n/**\n * Create fixtures for a single test\n */\nasync function createTestFixtures(): Promise<TestFixtures> {\n // Ensure shared resources are initialized\n await initializeSharedResources();\n\n // Create MCP client for this test\n // Pass publicMode if configured to skip authentication\n const clientInstance = await McpTestClient.create({\n baseUrl: serverInstance!.info.baseUrl,\n transport: currentConfig.transport ?? 'streamable-http',\n publicMode: currentConfig.publicMode,\n }).buildAndConnect();\n\n // Build fixtures\n const auth = createAuthFixture(tokenFactory!);\n const server = createServerFixture(serverInstance!);\n\n return {\n mcp: clientInstance,\n auth,\n server,\n };\n}\n\n/**\n * Clean up fixtures after a single test\n * @param fixtures - The test fixtures to clean up\n * @param testFailed - Whether the test failed (to output server logs)\n */\nasync function cleanupTestFixtures(fixtures: TestFixtures, _testFailed = false): Promise<void> {\n // Disconnect client\n if (fixtures.mcp.isConnected()) {\n await fixtures.mcp.disconnect();\n }\n}\n\n/**\n * Clean up shared resources after all tests in a file\n */\nasync function cleanupSharedResources(): Promise<void> {\n // Only stop server if we started it\n if (serverInstance && serverStartedByUs) {\n await serverInstance.stop();\n }\n serverInstance = null;\n tokenFactory = null;\n serverStartedByUs = false;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURE FACTORIES\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Create the auth fixture from token factory\n */\nfunction createAuthFixture(factory: TestTokenFactory): AuthFixture {\n const users: Record<string, TestUser> = {\n admin: {\n sub: 'admin-001',\n scopes: ['admin:*', 'read', 'write', 'delete'],\n email: 'admin@test.local',\n name: 'Test Admin',\n },\n user: {\n sub: 'user-001',\n scopes: ['read', 'write'],\n email: 'user@test.local',\n name: 'Test User',\n },\n readOnly: {\n sub: 'readonly-001',\n scopes: ['read'],\n email: 'readonly@test.local',\n name: 'Read Only User',\n },\n };\n\n return {\n createToken: (opts) =>\n factory.createTestToken({\n sub: opts.sub,\n scopes: opts.scopes,\n claims: {\n email: opts.email,\n name: opts.name,\n ...opts.claims,\n },\n exp: opts.expiresIn,\n }),\n\n createExpiredToken: (opts) => factory.createExpiredToken(opts),\n\n createInvalidToken: (opts) => factory.createTokenWithInvalidSignature(opts),\n\n users: {\n admin: users['admin'],\n user: users['user'],\n readOnly: users['readOnly'],\n },\n\n getJwks: () => factory.getPublicJwks(),\n\n getIssuer: () => factory.getIssuer(),\n\n getAudience: () => factory.getAudience(),\n };\n}\n\n/**\n * Create the server fixture from test server\n */\nfunction createServerFixture(server: TestServer): ServerFixture {\n return {\n info: server.info,\n\n createClient: async (opts) => {\n // Inherit publicMode from current config when creating additional clients\n // This ensures all clients connect to a public server correctly\n return McpTestClient.create({\n baseUrl: server.info.baseUrl,\n transport: opts?.transport ?? 'streamable-http',\n auth: opts?.token ? { token: opts.token } : undefined,\n clientInfo: opts?.clientInfo,\n publicMode: currentConfig.publicMode,\n }).buildAndConnect();\n },\n\n createClientBuilder: () => {\n // Return a pre-configured builder with the server's base URL and publicMode\n // This allows full customization including platform-specific capabilities\n const builder = new McpTestClientBuilder({\n baseUrl: server.info.baseUrl,\n publicMode: currentConfig.publicMode,\n });\n return builder;\n },\n\n restart: () => server.restart(),\n\n getLogs: () => server.getLogs(),\n\n clearLogs: () => server.clearLogs(),\n };\n}\n\n/**\n * Resolve server entry to a command\n */\nfunction resolveServerCommand(server: string): string {\n // If it's already a command (contains spaces), use as-is\n if (server.includes(' ')) {\n return server;\n }\n // Otherwise, run with tsx\n return `npx tsx ${server}`;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// TEST FUNCTION WITH FIXTURES\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Enhanced test function that provides fixtures\n */\nfunction testWithFixtures(name: string, fn: TestFn): void {\n it(name, async () => {\n const fixtures = await createTestFixtures();\n let testFailed = false;\n try {\n await fn(fixtures);\n } catch (error) {\n testFailed = true;\n throw error;\n } finally {\n await cleanupTestFixtures(fixtures, testFailed);\n }\n });\n}\n\n/**\n * Configure test fixtures for the current test file/suite\n */\nfunction use(config: TestConfig): void {\n // Merge with existing config\n currentConfig = { ...currentConfig, ...config };\n\n // Register cleanup hook if not already done\n // This ensures server is stopped after all tests in the file\n afterAll(async () => {\n await cleanupSharedResources();\n });\n}\n\n/**\n * Skip a test\n */\nfunction skip(name: string, fn: TestFn): void {\n it.skip(name, async () => {\n const fixtures = await createTestFixtures();\n let testFailed = false;\n try {\n await fn(fixtures);\n } catch (error) {\n testFailed = true;\n throw error;\n } finally {\n await cleanupTestFixtures(fixtures, testFailed);\n }\n });\n}\n\n/**\n * Run only this test\n */\nfunction only(name: string, fn: TestFn): void {\n it.only(name, async () => {\n const fixtures = await createTestFixtures();\n let testFailed = false;\n try {\n await fn(fixtures);\n } catch (error) {\n testFailed = true;\n throw error;\n } finally {\n await cleanupTestFixtures(fixtures, testFailed);\n }\n });\n}\n\n/**\n * Mark test as todo\n */\nfunction todo(name: string): void {\n it.todo(name);\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// ATTACH STATIC METHODS\n// ═══════════════════════════════════════════════════════════════════\n\n// Cast to the full interface type\nconst test = testWithFixtures as TestWithFixtures;\n\n// Attach configuration method\ntest.use = use;\n\n// Attach Jest lifecycle methods\ntest.describe = describe;\ntest.beforeAll = beforeAll;\ntest.beforeEach = beforeEach;\ntest.afterEach = afterEach;\ntest.afterAll = afterAll;\n\n// Attach test modifiers\ntest.skip = skip;\ntest.only = only;\ntest.todo = todo;\n\n// ═══════════════════════════════════════════════════════════════════\n// EXPORTS\n// ═══════════════════════════════════════════════════════════════════\n\nexport { test };\n\n// Also export for advanced use cases\nexport { createTestFixtures, cleanupTestFixtures, initializeSharedResources, cleanupSharedResources };\n"]}
package/src/index.d.ts CHANGED
@@ -48,6 +48,10 @@ export type { CreateTokenOptions, TokenFactoryOptions } from './auth/token-facto
48
48
  export { AuthHeaders } from './auth/auth-headers';
49
49
  export { TestUsers, createTestUser } from './auth/user-fixtures';
50
50
  export type { TestUserFixture } from './auth/user-fixtures';
51
+ export { MockOAuthServer } from './auth/mock-oauth-server';
52
+ export type { MockOAuthServerOptions, MockOAuthServerInfo } from './auth/mock-oauth-server';
53
+ export { MockAPIServer } from './auth/mock-api-server';
54
+ export type { MockAPIServerOptions, MockAPIServerInfo, MockRoute, MockResponse } from './auth/mock-api-server';
51
55
  export { TestServer } from './server/test-server';
52
56
  export type { TestServerOptions, TestServerInfo } from './server/test-server';
53
57
  export { McpAssertions, containsTool, containsResource, containsResourceTemplate, containsPrompt, isSuccessful, isError, hasTextContent, hasMimeType, } from './assertions/mcp-assertions';
@@ -63,3 +67,6 @@ export type { InterceptorContext, InterceptorResult, RequestInterceptor, Respons
63
67
  export { httpMock, httpResponse } from './http-mock';
64
68
  export type { HttpMethod, HttpRequestMatcher, HttpMockResponse, HttpMockDefinition, HttpRequestInfo, HttpMockHandle, HttpInterceptor, HttpMockManager, } from './http-mock';
65
69
  export { uiMatchers, UIAssertions } from './ui';
70
+ export type { TestPlatformType, PlatformMetaNamespace, TestClientInfo } from './platform';
71
+ export { getPlatformMetaNamespace, getPlatformMimeType, isOpenAIPlatform, isExtAppsPlatform, isFrontmcpPlatform, getToolsListMetaPrefixes, getToolCallMetaPrefixes, getForbiddenMetaPrefixes, getPlatformClientInfo, buildUserAgent, getPlatformUserAgent, PLATFORM_DETECTION_PATTERNS, } from './platform';
72
+ export { BASIC_UI_TOOL_CONFIG, FULL_UI_TOOL_CONFIG, basicUIToolInputSchema, basicUIToolOutputSchema, fullUIToolInputSchema, fullUIToolOutputSchema, generateBasicUIToolOutput, generateFullUIToolOutput, EXPECTED_OPENAI_TOOLS_LIST_META_KEYS, EXPECTED_OPENAI_TOOL_CALL_META_KEYS, EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS, EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS, EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS, EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS, } from './example-tools';
package/src/index.js CHANGED
@@ -40,7 +40,8 @@
40
40
  * ```
41
41
  */
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
- exports.UIAssertions = exports.uiMatchers = exports.httpResponse = exports.httpMock = exports.interceptors = exports.mockResponse = exports.DefaultInterceptorChain = exports.DefaultMockRegistry = exports.mcpMatchers = exports.expect = exports.test = exports.AssertionError = exports.ServerStartError = exports.McpProtocolError = exports.TimeoutError = exports.ConnectionError = exports.TestClientError = exports.hasMimeType = exports.hasTextContent = exports.isError = exports.isSuccessful = exports.containsPrompt = exports.containsResourceTemplate = exports.containsResource = exports.containsTool = exports.McpAssertions = exports.TestServer = exports.createTestUser = exports.TestUsers = exports.AuthHeaders = exports.TestTokenFactory = exports.StreamableHttpTransport = exports.McpTestClientBuilder = exports.McpTestClient = void 0;
43
+ exports.FULL_UI_TOOL_CONFIG = exports.BASIC_UI_TOOL_CONFIG = exports.PLATFORM_DETECTION_PATTERNS = exports.getPlatformUserAgent = exports.buildUserAgent = exports.getPlatformClientInfo = exports.getForbiddenMetaPrefixes = exports.getToolCallMetaPrefixes = exports.getToolsListMetaPrefixes = exports.isFrontmcpPlatform = exports.isExtAppsPlatform = exports.isOpenAIPlatform = exports.getPlatformMimeType = exports.getPlatformMetaNamespace = exports.UIAssertions = exports.uiMatchers = exports.httpResponse = exports.httpMock = exports.interceptors = exports.mockResponse = exports.DefaultInterceptorChain = exports.DefaultMockRegistry = exports.mcpMatchers = exports.expect = exports.test = exports.AssertionError = exports.ServerStartError = exports.McpProtocolError = exports.TimeoutError = exports.ConnectionError = exports.TestClientError = exports.hasMimeType = exports.hasTextContent = exports.isError = exports.isSuccessful = exports.containsPrompt = exports.containsResourceTemplate = exports.containsResource = exports.containsTool = exports.McpAssertions = exports.TestServer = exports.MockAPIServer = exports.MockOAuthServer = exports.createTestUser = exports.TestUsers = exports.AuthHeaders = exports.TestTokenFactory = exports.StreamableHttpTransport = exports.McpTestClientBuilder = exports.McpTestClient = void 0;
44
+ exports.EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS = exports.EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS = exports.EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS = exports.EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS = exports.EXPECTED_OPENAI_TOOL_CALL_META_KEYS = exports.EXPECTED_OPENAI_TOOLS_LIST_META_KEYS = exports.generateFullUIToolOutput = exports.generateBasicUIToolOutput = exports.fullUIToolOutputSchema = exports.fullUIToolInputSchema = exports.basicUIToolOutputSchema = exports.basicUIToolInputSchema = void 0;
44
45
  // ═══════════════════════════════════════════════════════════════════
45
46
  // CLIENT
46
47
  // ═══════════════════════════════════════════════════════════════════
@@ -60,6 +61,10 @@ Object.defineProperty(exports, "AuthHeaders", { enumerable: true, get: function
60
61
  var user_fixtures_1 = require("./auth/user-fixtures");
61
62
  Object.defineProperty(exports, "TestUsers", { enumerable: true, get: function () { return user_fixtures_1.TestUsers; } });
62
63
  Object.defineProperty(exports, "createTestUser", { enumerable: true, get: function () { return user_fixtures_1.createTestUser; } });
64
+ var mock_oauth_server_1 = require("./auth/mock-oauth-server");
65
+ Object.defineProperty(exports, "MockOAuthServer", { enumerable: true, get: function () { return mock_oauth_server_1.MockOAuthServer; } });
66
+ var mock_api_server_1 = require("./auth/mock-api-server");
67
+ Object.defineProperty(exports, "MockAPIServer", { enumerable: true, get: function () { return mock_api_server_1.MockAPIServer; } });
63
68
  // ═══════════════════════════════════════════════════════════════════
64
69
  // SERVER
65
70
  // ═══════════════════════════════════════════════════════════════════
@@ -125,4 +130,38 @@ Object.defineProperty(exports, "httpResponse", { enumerable: true, get: function
125
130
  var ui_1 = require("./ui");
126
131
  Object.defineProperty(exports, "uiMatchers", { enumerable: true, get: function () { return ui_1.uiMatchers; } });
127
132
  Object.defineProperty(exports, "UIAssertions", { enumerable: true, get: function () { return ui_1.UIAssertions; } });
133
+ var platform_1 = require("./platform");
134
+ Object.defineProperty(exports, "getPlatformMetaNamespace", { enumerable: true, get: function () { return platform_1.getPlatformMetaNamespace; } });
135
+ Object.defineProperty(exports, "getPlatformMimeType", { enumerable: true, get: function () { return platform_1.getPlatformMimeType; } });
136
+ Object.defineProperty(exports, "isOpenAIPlatform", { enumerable: true, get: function () { return platform_1.isOpenAIPlatform; } });
137
+ Object.defineProperty(exports, "isExtAppsPlatform", { enumerable: true, get: function () { return platform_1.isExtAppsPlatform; } });
138
+ Object.defineProperty(exports, "isFrontmcpPlatform", { enumerable: true, get: function () { return platform_1.isFrontmcpPlatform; } });
139
+ Object.defineProperty(exports, "getToolsListMetaPrefixes", { enumerable: true, get: function () { return platform_1.getToolsListMetaPrefixes; } });
140
+ Object.defineProperty(exports, "getToolCallMetaPrefixes", { enumerable: true, get: function () { return platform_1.getToolCallMetaPrefixes; } });
141
+ Object.defineProperty(exports, "getForbiddenMetaPrefixes", { enumerable: true, get: function () { return platform_1.getForbiddenMetaPrefixes; } });
142
+ Object.defineProperty(exports, "getPlatformClientInfo", { enumerable: true, get: function () { return platform_1.getPlatformClientInfo; } });
143
+ Object.defineProperty(exports, "buildUserAgent", { enumerable: true, get: function () { return platform_1.buildUserAgent; } });
144
+ Object.defineProperty(exports, "getPlatformUserAgent", { enumerable: true, get: function () { return platform_1.getPlatformUserAgent; } });
145
+ Object.defineProperty(exports, "PLATFORM_DETECTION_PATTERNS", { enumerable: true, get: function () { return platform_1.PLATFORM_DETECTION_PATTERNS; } });
146
+ // ═══════════════════════════════════════════════════════════════════
147
+ // EXAMPLE TOOLS (for E2E platform testing)
148
+ // ═══════════════════════════════════════════════════════════════════
149
+ var example_tools_1 = require("./example-tools");
150
+ // Tool configurations
151
+ Object.defineProperty(exports, "BASIC_UI_TOOL_CONFIG", { enumerable: true, get: function () { return example_tools_1.BASIC_UI_TOOL_CONFIG; } });
152
+ Object.defineProperty(exports, "FULL_UI_TOOL_CONFIG", { enumerable: true, get: function () { return example_tools_1.FULL_UI_TOOL_CONFIG; } });
153
+ Object.defineProperty(exports, "basicUIToolInputSchema", { enumerable: true, get: function () { return example_tools_1.basicUIToolInputSchema; } });
154
+ Object.defineProperty(exports, "basicUIToolOutputSchema", { enumerable: true, get: function () { return example_tools_1.basicUIToolOutputSchema; } });
155
+ Object.defineProperty(exports, "fullUIToolInputSchema", { enumerable: true, get: function () { return example_tools_1.fullUIToolInputSchema; } });
156
+ Object.defineProperty(exports, "fullUIToolOutputSchema", { enumerable: true, get: function () { return example_tools_1.fullUIToolOutputSchema; } });
157
+ // Execution helpers
158
+ Object.defineProperty(exports, "generateBasicUIToolOutput", { enumerable: true, get: function () { return example_tools_1.generateBasicUIToolOutput; } });
159
+ Object.defineProperty(exports, "generateFullUIToolOutput", { enumerable: true, get: function () { return example_tools_1.generateFullUIToolOutput; } });
160
+ // Expected meta keys
161
+ Object.defineProperty(exports, "EXPECTED_OPENAI_TOOLS_LIST_META_KEYS", { enumerable: true, get: function () { return example_tools_1.EXPECTED_OPENAI_TOOLS_LIST_META_KEYS; } });
162
+ Object.defineProperty(exports, "EXPECTED_OPENAI_TOOL_CALL_META_KEYS", { enumerable: true, get: function () { return example_tools_1.EXPECTED_OPENAI_TOOL_CALL_META_KEYS; } });
163
+ Object.defineProperty(exports, "EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS", { enumerable: true, get: function () { return example_tools_1.EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS; } });
164
+ Object.defineProperty(exports, "EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS", { enumerable: true, get: function () { return example_tools_1.EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS; } });
165
+ Object.defineProperty(exports, "EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS", { enumerable: true, get: function () { return example_tools_1.EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS; } });
166
+ Object.defineProperty(exports, "EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS", { enumerable: true, get: function () { return example_tools_1.EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS; } });
128
167
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;;;AAEH,sEAAsE;AACtE,SAAS;AACT,sEAAsE;AAEtE,4DAAyD;AAAhD,gHAAA,aAAa,OAAA;AACtB,4EAAwE;AAA/D,+HAAA,oBAAoB,OAAA;AAwB7B,mFAAgF;AAAvE,oIAAA,uBAAuB,OAAA;AAEhC,sEAAsE;AACtE,OAAO;AACP,sEAAsE;AAEtE,sDAAwD;AAA/C,iHAAA,gBAAgB,OAAA;AAEzB,oDAAkD;AAAzC,2GAAA,WAAW,OAAA;AACpB,sDAAiE;AAAxD,0GAAA,SAAS,OAAA;AAAE,+GAAA,cAAc,OAAA;AAGlC,sEAAsE;AACtE,SAAS;AACT,sEAAsE;AAEtE,oDAAkD;AAAzC,yGAAA,UAAU,OAAA;AAGnB,sEAAsE;AACtE,aAAa;AACb,sEAAsE;AAEtE,8DAUqC;AATnC,+GAAA,aAAa,OAAA;AACb,8GAAA,YAAY,OAAA;AACZ,kHAAA,gBAAgB,OAAA;AAChB,0HAAA,wBAAwB,OAAA;AACxB,gHAAA,cAAc,OAAA;AACd,8GAAA,YAAY,OAAA;AACZ,yGAAA,OAAO,OAAA;AACP,gHAAA,cAAc,OAAA;AACd,6GAAA,WAAW,OAAA;AAGb,sEAAsE;AACtE,SAAS;AACT,sEAAsE;AAEtE,wCAOwB;AANtB,wGAAA,eAAe,OAAA;AACf,wGAAA,eAAe,OAAA;AACf,qGAAA,YAAY,OAAA;AACZ,yGAAA,gBAAgB,OAAA;AAChB,yGAAA,gBAAgB,OAAA;AAChB,uGAAA,cAAc,OAAA;AAyBhB,sEAAsE;AACtE,yBAAyB;AACzB,sEAAsE;AAEtE,uCAAkC;AAAzB,gGAAA,IAAI,OAAA;AAWb,sEAAsE;AACtE,uBAAuB;AACvB,sEAAsE;AAEtE,4EAA4E;AAC5E,+EAA+E;AAC/E,mCAAkC;AAAzB,gGAAA,MAAM,OAAA;AAEf,sEAAsE;AACtE,kBAAkB;AAClB,sEAAsE;AAEtE,uCAAyC;AAAhC,uGAAA,WAAW,OAAA;AAGpB,sEAAsE;AACtE,yBAAyB;AACzB,sEAAsE;AAEtE,6CAAyG;AAAhG,kHAAA,mBAAmB,OAAA;AAAE,sHAAA,uBAAuB,OAAA;AAAE,2GAAA,YAAY,OAAA;AAAE,2GAAA,YAAY,OAAA;AAejF,sEAAsE;AACtE,qCAAqC;AACrC,sEAAsE;AAEtE,yCAAqD;AAA5C,qGAAA,QAAQ,OAAA;AAAE,yGAAA,YAAY,OAAA;AAa/B,sEAAsE;AACtE,aAAa;AACb,sEAAsE;AAEtE,2BAAgD;AAAvC,gGAAA,UAAU,OAAA;AAAE,kGAAA,YAAY,OAAA","sourcesContent":["/**\n * @file index.ts\n * @description Main barrel exports for @frontmcp/testing\n *\n * @example Quick Start with Fixtures\n * ```typescript\n * import { test, expect } from '@frontmcp/testing';\n *\n * test.use({\n * server: './src/main.ts',\n * port: 3003,\n * });\n *\n * test('server exposes tools', async ({ mcp }) => {\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * });\n *\n * test('tool execution works', async ({ mcp }) => {\n * const result = await mcp.tools.call('my-tool', { input: 'test' });\n * expect(result).toBeSuccessful();\n * });\n * ```\n *\n * @example Manual Client Usage\n * ```typescript\n * import { McpTestClient, TestServer } from '@frontmcp/testing';\n *\n * const server = await TestServer.start({ command: 'npx tsx src/main.ts' });\n * const client = await McpTestClient.create({ baseUrl: server.info.baseUrl })\n * .withTransport('streamable-http')\n * .buildAndConnect();\n *\n * const tools = await client.tools.list();\n * console.log(tools);\n *\n * await client.disconnect();\n * await server.stop();\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════════\n// CLIENT\n// ═══════════════════════════════════════════════════════════════════\n\nexport { McpTestClient } from './client/mcp-test-client';\nexport { McpTestClientBuilder } from './client/mcp-test-client.builder';\nexport type {\n McpTestClientConfig,\n McpResponse,\n McpErrorInfo,\n TestTransportType,\n TestAuthConfig,\n ToolResultWrapper,\n ResourceContentWrapper,\n PromptResultWrapper,\n LogEntry,\n LogLevel,\n RequestTrace,\n NotificationEntry,\n ProgressUpdate,\n SessionInfo,\n AuthState,\n} from './client/mcp-test-client.types';\n\n// ═══════════════════════════════════════════════════════════════════\n// TRANSPORT\n// ═══════════════════════════════════════════════════════════════════\n\nexport type { McpTransport, TransportConfig, TransportState } from './transport/transport.interface';\nexport { StreamableHttpTransport } from './transport/streamable-http.transport';\n\n// ═══════════════════════════════════════════════════════════════════\n// AUTH\n// ═══════════════════════════════════════════════════════════════════\n\nexport { TestTokenFactory } from './auth/token-factory';\nexport type { CreateTokenOptions, TokenFactoryOptions } from './auth/token-factory';\nexport { AuthHeaders } from './auth/auth-headers';\nexport { TestUsers, createTestUser } from './auth/user-fixtures';\nexport type { TestUserFixture } from './auth/user-fixtures';\n\n// ═══════════════════════════════════════════════════════════════════\n// SERVER\n// ═══════════════════════════════════════════════════════════════════\n\nexport { TestServer } from './server/test-server';\nexport type { TestServerOptions, TestServerInfo } from './server/test-server';\n\n// ═══════════════════════════════════════════════════════════════════\n// ASSERTIONS\n// ═══════════════════════════════════════════════════════════════════\n\nexport {\n McpAssertions,\n containsTool,\n containsResource,\n containsResourceTemplate,\n containsPrompt,\n isSuccessful,\n isError,\n hasTextContent,\n hasMimeType,\n} from './assertions/mcp-assertions';\n\n// ═══════════════════════════════════════════════════════════════════\n// ERRORS\n// ═══════════════════════════════════════════════════════════════════\n\nexport {\n TestClientError,\n ConnectionError,\n TimeoutError,\n McpProtocolError,\n ServerStartError,\n AssertionError,\n} from './errors/index';\n\n// ═══════════════════════════════════════════════════════════════════\n// RE-EXPORTS FROM MCP SDK\n// ═══════════════════════════════════════════════════════════════════\n\nexport type {\n InitializeResult,\n ListToolsResult,\n CallToolResult,\n ListResourcesResult,\n ReadResourceResult,\n ListResourceTemplatesResult,\n ListPromptsResult,\n GetPromptResult,\n Tool,\n Resource,\n ResourceTemplate,\n Prompt,\n // JSON-RPC types\n JSONRPCRequest,\n JSONRPCResponse,\n} from './client/mcp-test-client.types';\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURES (Primary API)\n// ═══════════════════════════════════════════════════════════════════\n\nexport { test } from './fixtures';\nexport type {\n TestConfig,\n TestFixtures,\n AuthFixture,\n ServerFixture,\n TestFn,\n TestWithFixtures,\n TestUser,\n} from './fixtures';\n\n// ═══════════════════════════════════════════════════════════════════\n// EXPECT (Primary API)\n// ═══════════════════════════════════════════════════════════════════\n\n// Export the pre-typed expect with MCP matchers (Playwright-style approach)\n// This provides proper typing without relying on global namespace augmentation\nexport { expect } from './expect';\n\n// ═══════════════════════════════════════════════════════════════════\n// CUSTOM MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\nexport { mcpMatchers } from './matchers';\nexport type { McpMatchers } from './matchers';\n\n// ═══════════════════════════════════════════════════════════════════\n// INTERCEPTORS & MOCKING\n// ═══════════════════════════════════════════════════════════════════\n\nexport { DefaultMockRegistry, DefaultInterceptorChain, mockResponse, interceptors } from './interceptor';\n\nexport type {\n InterceptorContext,\n InterceptorResult,\n RequestInterceptor,\n ResponseInterceptorContext,\n ResponseInterceptorResult,\n ResponseInterceptor,\n MockDefinition,\n MockRegistry,\n MockHandle,\n InterceptorChain,\n} from './interceptor';\n\n// ═══════════════════════════════════════════════════════════════════\n// HTTP MOCKING (for offline testing)\n// ═══════════════════════════════════════════════════════════════════\n\nexport { httpMock, httpResponse } from './http-mock';\n\nexport type {\n HttpMethod,\n HttpRequestMatcher,\n HttpMockResponse,\n HttpMockDefinition,\n HttpRequestInfo,\n HttpMockHandle,\n HttpInterceptor,\n HttpMockManager,\n} from './http-mock';\n\n// ═══════════════════════════════════════════════════════════════════\n// UI TESTING\n// ═══════════════════════════════════════════════════════════════════\n\nexport { uiMatchers, UIAssertions } from './ui';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file index.ts\n * @description Main barrel exports for @frontmcp/testing\n *\n * @example Quick Start with Fixtures\n * ```typescript\n * import { test, expect } from '@frontmcp/testing';\n *\n * test.use({\n * server: './src/main.ts',\n * port: 3003,\n * });\n *\n * test('server exposes tools', async ({ mcp }) => {\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * });\n *\n * test('tool execution works', async ({ mcp }) => {\n * const result = await mcp.tools.call('my-tool', { input: 'test' });\n * expect(result).toBeSuccessful();\n * });\n * ```\n *\n * @example Manual Client Usage\n * ```typescript\n * import { McpTestClient, TestServer } from '@frontmcp/testing';\n *\n * const server = await TestServer.start({ command: 'npx tsx src/main.ts' });\n * const client = await McpTestClient.create({ baseUrl: server.info.baseUrl })\n * .withTransport('streamable-http')\n * .buildAndConnect();\n *\n * const tools = await client.tools.list();\n * console.log(tools);\n *\n * await client.disconnect();\n * await server.stop();\n * ```\n */\n\n// ═══════════════════════════════════════════════════════════════════\n// CLIENT\n// ═══════════════════════════════════════════════════════════════════\n\nexport { McpTestClient } from './client/mcp-test-client';\nexport { McpTestClientBuilder } from './client/mcp-test-client.builder';\nexport type {\n McpTestClientConfig,\n McpResponse,\n McpErrorInfo,\n TestTransportType,\n TestAuthConfig,\n ToolResultWrapper,\n ResourceContentWrapper,\n PromptResultWrapper,\n LogEntry,\n LogLevel,\n RequestTrace,\n NotificationEntry,\n ProgressUpdate,\n SessionInfo,\n AuthState,\n} from './client/mcp-test-client.types';\n\n// ═══════════════════════════════════════════════════════════════════\n// TRANSPORT\n// ═══════════════════════════════════════════════════════════════════\n\nexport type { McpTransport, TransportConfig, TransportState } from './transport/transport.interface';\nexport { StreamableHttpTransport } from './transport/streamable-http.transport';\n\n// ═══════════════════════════════════════════════════════════════════\n// AUTH\n// ═══════════════════════════════════════════════════════════════════\n\nexport { TestTokenFactory } from './auth/token-factory';\nexport type { CreateTokenOptions, TokenFactoryOptions } from './auth/token-factory';\nexport { AuthHeaders } from './auth/auth-headers';\nexport { TestUsers, createTestUser } from './auth/user-fixtures';\nexport type { TestUserFixture } from './auth/user-fixtures';\nexport { MockOAuthServer } from './auth/mock-oauth-server';\nexport type { MockOAuthServerOptions, MockOAuthServerInfo } from './auth/mock-oauth-server';\nexport { MockAPIServer } from './auth/mock-api-server';\nexport type { MockAPIServerOptions, MockAPIServerInfo, MockRoute, MockResponse } from './auth/mock-api-server';\n\n// ═══════════════════════════════════════════════════════════════════\n// SERVER\n// ═══════════════════════════════════════════════════════════════════\n\nexport { TestServer } from './server/test-server';\nexport type { TestServerOptions, TestServerInfo } from './server/test-server';\n\n// ═══════════════════════════════════════════════════════════════════\n// ASSERTIONS\n// ═══════════════════════════════════════════════════════════════════\n\nexport {\n McpAssertions,\n containsTool,\n containsResource,\n containsResourceTemplate,\n containsPrompt,\n isSuccessful,\n isError,\n hasTextContent,\n hasMimeType,\n} from './assertions/mcp-assertions';\n\n// ═══════════════════════════════════════════════════════════════════\n// ERRORS\n// ═══════════════════════════════════════════════════════════════════\n\nexport {\n TestClientError,\n ConnectionError,\n TimeoutError,\n McpProtocolError,\n ServerStartError,\n AssertionError,\n} from './errors/index';\n\n// ═══════════════════════════════════════════════════════════════════\n// RE-EXPORTS FROM MCP SDK\n// ═══════════════════════════════════════════════════════════════════\n\nexport type {\n InitializeResult,\n ListToolsResult,\n CallToolResult,\n ListResourcesResult,\n ReadResourceResult,\n ListResourceTemplatesResult,\n ListPromptsResult,\n GetPromptResult,\n Tool,\n Resource,\n ResourceTemplate,\n Prompt,\n // JSON-RPC types\n JSONRPCRequest,\n JSONRPCResponse,\n} from './client/mcp-test-client.types';\n\n// ═══════════════════════════════════════════════════════════════════\n// FIXTURES (Primary API)\n// ═══════════════════════════════════════════════════════════════════\n\nexport { test } from './fixtures';\nexport type {\n TestConfig,\n TestFixtures,\n AuthFixture,\n ServerFixture,\n TestFn,\n TestWithFixtures,\n TestUser,\n} from './fixtures';\n\n// ═══════════════════════════════════════════════════════════════════\n// EXPECT (Primary API)\n// ═══════════════════════════════════════════════════════════════════\n\n// Export the pre-typed expect with MCP matchers (Playwright-style approach)\n// This provides proper typing without relying on global namespace augmentation\nexport { expect } from './expect';\n\n// ═══════════════════════════════════════════════════════════════════\n// CUSTOM MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\nexport { mcpMatchers } from './matchers';\nexport type { McpMatchers } from './matchers';\n\n// ═══════════════════════════════════════════════════════════════════\n// INTERCEPTORS & MOCKING\n// ═══════════════════════════════════════════════════════════════════\n\nexport { DefaultMockRegistry, DefaultInterceptorChain, mockResponse, interceptors } from './interceptor';\n\nexport type {\n InterceptorContext,\n InterceptorResult,\n RequestInterceptor,\n ResponseInterceptorContext,\n ResponseInterceptorResult,\n ResponseInterceptor,\n MockDefinition,\n MockRegistry,\n MockHandle,\n InterceptorChain,\n} from './interceptor';\n\n// ═══════════════════════════════════════════════════════════════════\n// HTTP MOCKING (for offline testing)\n// ═══════════════════════════════════════════════════════════════════\n\nexport { httpMock, httpResponse } from './http-mock';\n\nexport type {\n HttpMethod,\n HttpRequestMatcher,\n HttpMockResponse,\n HttpMockDefinition,\n HttpRequestInfo,\n HttpMockHandle,\n HttpInterceptor,\n HttpMockManager,\n} from './http-mock';\n\n// ═══════════════════════════════════════════════════════════════════\n// UI TESTING\n// ═══════════════════════════════════════════════════════════════════\n\nexport { uiMatchers, UIAssertions } from './ui';\n\n// ═══════════════════════════════════════════════════════════════════\n// PLATFORM TESTING\n// ═══════════════════════════════════════════════════════════════════\n\nexport type { TestPlatformType, PlatformMetaNamespace, TestClientInfo } from './platform';\n\nexport {\n getPlatformMetaNamespace,\n getPlatformMimeType,\n isOpenAIPlatform,\n isExtAppsPlatform,\n isFrontmcpPlatform,\n getToolsListMetaPrefixes,\n getToolCallMetaPrefixes,\n getForbiddenMetaPrefixes,\n getPlatformClientInfo,\n buildUserAgent,\n getPlatformUserAgent,\n PLATFORM_DETECTION_PATTERNS,\n} from './platform';\n\n// ═══════════════════════════════════════════════════════════════════\n// EXAMPLE TOOLS (for E2E platform testing)\n// ═══════════════════════════════════════════════════════════════════\n\nexport {\n // Tool configurations\n BASIC_UI_TOOL_CONFIG,\n FULL_UI_TOOL_CONFIG,\n basicUIToolInputSchema,\n basicUIToolOutputSchema,\n fullUIToolInputSchema,\n fullUIToolOutputSchema,\n // Execution helpers\n generateBasicUIToolOutput,\n generateFullUIToolOutput,\n // Expected meta keys\n EXPECTED_OPENAI_TOOLS_LIST_META_KEYS,\n EXPECTED_OPENAI_TOOL_CALL_META_KEYS,\n EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS,\n EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS,\n EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS,\n EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS,\n} from './example-tools';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"matcher-types.js","sourceRoot":"","sources":["../../../src/matchers/matcher-types.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG","sourcesContent":["/**\n * @file matcher-types.ts\n * @description TypeScript declarations for custom MCP Jest matchers\n *\n * This file extends Jest's expect interface with MCP-specific matchers.\n * Import this file or ensure it's included in your tsconfig to get type checking.\n */\n\n// Note: These imports are used for documentation/JSDoc purposes in the interface comments\n// The actual runtime types are in mcp-matchers.ts\n\n/**\n * Custom MCP matchers for Jest\n */\nexport interface McpMatchers<R = unknown> {\n // ═══════════════════════════════════════════════════════════════════\n // TOOL MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if tools array contains a tool with the given name\n * @param toolName - The name of the tool to find\n *\n * @example\n * ```typescript\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * ```\n */\n toContainTool(toolName: string): R;\n\n /**\n * Check if result is successful (not an error)\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toBeSuccessful();\n * ```\n */\n toBeSuccessful(): R;\n\n /**\n * Check if result is an error, optionally with a specific error code\n * @param expectedCode - Optional specific MCP error code to match\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('unknown-tool', {});\n * expect(result).toBeError();\n * expect(result).toBeError(-32601); // Method not found\n * ```\n */\n toBeError(expectedCode?: number): R;\n\n /**\n * Check if tool result has text content, optionally containing specific text\n * @param expectedText - Optional text that should be contained in the result\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toHaveTextContent();\n * expect(result).toHaveTextContent('success');\n * ```\n */\n toHaveTextContent(expectedText?: string): R;\n\n /**\n * Check if tool result has image content\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('generate-image', {});\n * expect(result).toHaveImageContent();\n * ```\n */\n toHaveImageContent(): R;\n\n /**\n * Check if tool result has resource content\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('create-file', {});\n * expect(result).toHaveResourceContent();\n * ```\n */\n toHaveResourceContent(): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // RESOURCE MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if resources array contains a resource with the given URI\n * @param uri - The URI of the resource to find\n *\n * @example\n * ```typescript\n * const resources = await mcp.resources.list();\n * expect(resources).toContainResource('notes://all');\n * ```\n */\n toContainResource(uri: string): R;\n\n /**\n * Check if resource templates array contains a template with the given URI template\n * @param uriTemplate - The URI template to find\n *\n * @example\n * ```typescript\n * const templates = await mcp.resources.listTemplates();\n * expect(templates).toContainResourceTemplate('notes://note/{id}');\n * ```\n */\n toContainResourceTemplate(uriTemplate: string): R;\n\n /**\n * Check if resource content has a specific MIME type\n * @param mimeType - The expected MIME type\n *\n * @example\n * ```typescript\n * const content = await mcp.resources.read('notes://all');\n * expect(content).toHaveMimeType('application/json');\n * ```\n */\n toHaveMimeType(mimeType: string): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // PROMPT MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if prompts array contains a prompt with the given name\n * @param name - The name of the prompt to find\n *\n * @example\n * ```typescript\n * const prompts = await mcp.prompts.list();\n * expect(prompts).toContainPrompt('summarize');\n * ```\n */\n toContainPrompt(name: string): R;\n\n /**\n * Check if prompt result has a specific number of messages\n * @param count - The expected number of messages\n *\n * @example\n * ```typescript\n * const result = await mcp.prompts.get('summarize', {});\n * expect(result).toHaveMessages(2);\n * ```\n */\n toHaveMessages(count: number): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // PROTOCOL MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if response is valid JSON-RPC 2.0\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ ... });\n * expect(response).toBeValidJsonRpc();\n * ```\n */\n toBeValidJsonRpc(): R;\n\n /**\n * Check if JSON-RPC response has a result\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ ... });\n * expect(response).toHaveResult();\n * ```\n */\n toHaveResult(): R;\n\n /**\n * Check if JSON-RPC response has an error\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ method: 'unknown' });\n * expect(response).toHaveError();\n * ```\n */\n toHaveError(): R;\n\n /**\n * Check if JSON-RPC response has a specific error code\n * @param code - The expected JSON-RPC error code\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ method: 'unknown' });\n * expect(response).toHaveErrorCode(-32601);\n * ```\n */\n toHaveErrorCode(code: number): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // UI MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if tool result has rendered HTML in _meta['ui/html'].\n * Fails if the HTML is the mdx-fallback (escaped raw content).\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('get_weather', { location: 'London' });\n * expect(result).toHaveRenderedHtml();\n * ```\n */\n toHaveRenderedHtml(): R;\n\n /**\n * Check if HTML contains a specific HTML element tag.\n * @param tag - The HTML tag name to look for (e.g., 'div', 'h1', 'span')\n *\n * @example\n * ```typescript\n * expect(result).toContainHtmlElement('div');\n * expect(result).toContainHtmlElement('h1');\n * ```\n */\n toContainHtmlElement(tag: string): R;\n\n /**\n * Check if a bound value from tool output appears in the rendered HTML.\n * @param value - The value to look for (string or number)\n *\n * @example\n * ```typescript\n * const output = result.json();\n * expect(result).toContainBoundValue(output.location);\n * expect(result).toContainBoundValue(output.temperature);\n * ```\n */\n toContainBoundValue(value: string | number): R;\n\n /**\n * Check if HTML is XSS-safe (no script tags, event handlers, or javascript: URIs).\n *\n * @example\n * ```typescript\n * expect(result).toBeXssSafe();\n * ```\n */\n toBeXssSafe(): R;\n\n /**\n * Check if tool result has widget metadata (ui/resourceUri).\n *\n * @example\n * ```typescript\n * expect(result).toHaveWidgetMetadata();\n * ```\n */\n toHaveWidgetMetadata(): R;\n\n /**\n * Check if HTML has a specific CSS class.\n * @param className - The CSS class name to look for\n *\n * @example\n * ```typescript\n * expect(result).toHaveCssClass('weather-card');\n * ```\n */\n toHaveCssClass(className: string): R;\n\n /**\n * Check that HTML does NOT contain specific content (useful for fallback checks).\n * @param content - The content that should NOT be in the HTML\n *\n * @example\n * ```typescript\n * expect(result).toNotContainRawContent('mdx-fallback');\n * expect(result).toNotContainRawContent('<Alert'); // Custom component should be rendered\n * ```\n */\n toNotContainRawContent(content: string): R;\n\n /**\n * Check if HTML has proper structure (not just escaped text).\n *\n * @example\n * ```typescript\n * expect(result).toHaveProperHtmlStructure();\n * ```\n */\n toHaveProperHtmlStructure(): R;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// JEST TYPE AUGMENTATION\n// ═══════════════════════════════════════════════════════════════════\n\n/* eslint-disable @typescript-eslint/no-namespace */\n/* eslint-disable @typescript-eslint/no-empty-object-type */\ndeclare global {\n namespace jest {\n // Extend expect matchers\n interface Matchers<R> extends McpMatchers<R> {}\n\n // Extend asymmetric matchers (for expect.toContainTool etc.)\n interface Expect extends McpMatchers<void> {}\n\n // Extend inverse matchers (for expect.not.toContainTool etc.)\n interface InverseAsymmetricMatchers extends McpMatchers<void> {}\n }\n}\n/* eslint-enable @typescript-eslint/no-namespace */\n/* eslint-enable @typescript-eslint/no-empty-object-type */\n\n// This export is needed to make this a module\nexport {};\n"]}
1
+ {"version":3,"file":"matcher-types.js","sourceRoot":"","sources":["../../../src/matchers/matcher-types.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG","sourcesContent":["/**\n * @file matcher-types.ts\n * @description TypeScript declarations for custom MCP Jest matchers\n *\n * This file extends Jest's expect interface with MCP-specific matchers.\n * Import this file or ensure it's included in your tsconfig to get type checking.\n */\n\n// Note: These imports are used for documentation/JSDoc purposes in the interface comments\n// The actual runtime types are in mcp-matchers.ts\n\n/**\n * Custom MCP matchers for Jest\n */\nexport interface McpMatchers<R = unknown> {\n // ═══════════════════════════════════════════════════════════════════\n // TOOL MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if tools array contains a tool with the given name\n * @param toolName - The name of the tool to find\n *\n * @example\n * ```typescript\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n * ```\n */\n toContainTool(toolName: string): R;\n\n /**\n * Check if result is successful (not an error)\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toBeSuccessful();\n * ```\n */\n toBeSuccessful(): R;\n\n /**\n * Check if result is an error, optionally with a specific error code\n * @param expectedCode - Optional specific MCP error code to match\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('unknown-tool', {});\n * expect(result).toBeError();\n * expect(result).toBeError(-32601); // Method not found\n * ```\n */\n toBeError(expectedCode?: number): R;\n\n /**\n * Check if tool result has text content, optionally containing specific text\n * @param expectedText - Optional text that should be contained in the result\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toHaveTextContent();\n * expect(result).toHaveTextContent('success');\n * ```\n */\n toHaveTextContent(expectedText?: string): R;\n\n /**\n * Check if tool result has image content\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('generate-image', {});\n * expect(result).toHaveImageContent();\n * ```\n */\n toHaveImageContent(): R;\n\n /**\n * Check if tool result has resource content\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('create-file', {});\n * expect(result).toHaveResourceContent();\n * ```\n */\n toHaveResourceContent(): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // RESOURCE MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if resources array contains a resource with the given URI\n * @param uri - The URI of the resource to find\n *\n * @example\n * ```typescript\n * const resources = await mcp.resources.list();\n * expect(resources).toContainResource('notes://all');\n * ```\n */\n toContainResource(uri: string): R;\n\n /**\n * Check if resource templates array contains a template with the given URI template\n * @param uriTemplate - The URI template to find\n *\n * @example\n * ```typescript\n * const templates = await mcp.resources.listTemplates();\n * expect(templates).toContainResourceTemplate('notes://note/{id}');\n * ```\n */\n toContainResourceTemplate(uriTemplate: string): R;\n\n /**\n * Check if resource content has a specific MIME type\n * @param mimeType - The expected MIME type\n *\n * @example\n * ```typescript\n * const content = await mcp.resources.read('notes://all');\n * expect(content).toHaveMimeType('application/json');\n * ```\n */\n toHaveMimeType(mimeType: string): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // PROMPT MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if prompts array contains a prompt with the given name\n * @param name - The name of the prompt to find\n *\n * @example\n * ```typescript\n * const prompts = await mcp.prompts.list();\n * expect(prompts).toContainPrompt('summarize');\n * ```\n */\n toContainPrompt(name: string): R;\n\n /**\n * Check if prompt result has a specific number of messages\n * @param count - The expected number of messages\n *\n * @example\n * ```typescript\n * const result = await mcp.prompts.get('summarize', {});\n * expect(result).toHaveMessages(2);\n * ```\n */\n toHaveMessages(count: number): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // PROTOCOL MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if response is valid JSON-RPC 2.0\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ ... });\n * expect(response).toBeValidJsonRpc();\n * ```\n */\n toBeValidJsonRpc(): R;\n\n /**\n * Check if JSON-RPC response has a result\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ ... });\n * expect(response).toHaveResult();\n * ```\n */\n toHaveResult(): R;\n\n /**\n * Check if JSON-RPC response has an error\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ method: 'unknown' });\n * expect(response).toHaveError();\n * ```\n */\n toHaveError(): R;\n\n /**\n * Check if JSON-RPC response has a specific error code\n * @param code - The expected JSON-RPC error code\n *\n * @example\n * ```typescript\n * const response = await mcp.raw.request({ method: 'unknown' });\n * expect(response).toHaveErrorCode(-32601);\n * ```\n */\n toHaveErrorCode(code: number): R;\n\n // ═══════════════════════════════════════════════════════════════════\n // UI MATCHERS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Check if tool result has rendered HTML in _meta['ui/html'].\n * Fails if the HTML is the mdx-fallback (escaped raw content).\n *\n * @example\n * ```typescript\n * const result = await mcp.tools.call('get_weather', { location: 'London' });\n * expect(result).toHaveRenderedHtml();\n * ```\n */\n toHaveRenderedHtml(): R;\n\n /**\n * Check if HTML contains a specific HTML element tag.\n * @param tag - The HTML tag name to look for (e.g., 'div', 'h1', 'span')\n *\n * @example\n * ```typescript\n * expect(result).toContainHtmlElement('div');\n * expect(result).toContainHtmlElement('h1');\n * ```\n */\n toContainHtmlElement(tag: string): R;\n\n /**\n * Check if a bound value from tool output appears in the rendered HTML.\n * @param value - The value to look for (string or number)\n *\n * @example\n * ```typescript\n * const output = result.json();\n * expect(result).toContainBoundValue(output.location);\n * expect(result).toContainBoundValue(output.temperature);\n * ```\n */\n toContainBoundValue(value: string | number): R;\n\n /**\n * Check if HTML is XSS-safe (no script tags, event handlers, or javascript: URIs).\n *\n * @example\n * ```typescript\n * expect(result).toBeXssSafe();\n * ```\n */\n toBeXssSafe(): R;\n\n /**\n * Check if tool result has widget metadata (ui/resourceUri).\n *\n * @example\n * ```typescript\n * expect(result).toHaveWidgetMetadata();\n * ```\n */\n toHaveWidgetMetadata(): R;\n\n /**\n * Check if HTML has a specific CSS class.\n * @param className - The CSS class name to look for\n *\n * @example\n * ```typescript\n * expect(result).toHaveCssClass('weather-card');\n * ```\n */\n toHaveCssClass(className: string): R;\n\n /**\n * Check that HTML does NOT contain specific content (useful for fallback checks).\n * @param content - The content that should NOT be in the HTML\n *\n * @example\n * ```typescript\n * expect(result).toNotContainRawContent('mdx-fallback');\n * expect(result).toNotContainRawContent('<Alert'); // Custom component should be rendered\n * ```\n */\n toNotContainRawContent(content: string): R;\n\n /**\n * Check if HTML has proper structure (not just escaped text).\n *\n * @example\n * ```typescript\n * expect(result).toHaveProperHtmlStructure();\n * ```\n */\n toHaveProperHtmlStructure(): R;\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// JEST TYPE AUGMENTATION\n// ═══════════════════════════════════════════════════════════════════\n\n/* eslint-disable @typescript-eslint/no-namespace, @typescript-eslint/no-empty-interface */\n\ndeclare global {\n namespace jest {\n // Extend expect matchers\n interface Matchers<R> extends McpMatchers<R> {}\n\n // Extend asymmetric matchers (for expect.toContainTool etc.)\n interface Expect extends McpMatchers<void> {}\n\n // Extend inverse matchers (for expect.not.toContainTool etc.)\n interface InverseAsymmetricMatchers extends McpMatchers<void> {}\n }\n}\n/* eslint-enable @typescript-eslint/no-namespace, @typescript-eslint/no-empty-interface */\n\n// This export is needed to make this a module\nexport {};\n"]}
@@ -29,6 +29,13 @@ export declare const mcpMatchers: {
29
29
  toHaveCssClass: MatcherFunction<[className: string]>;
30
30
  toNotContainRawContent: MatcherFunction<[content: string]>;
31
31
  toHaveProperHtmlStructure: MatcherFunction<[]>;
32
+ toHavePlatformMeta: MatcherFunction<[platform: import("..").TestPlatformType]>;
33
+ toHaveMetaKey: MatcherFunction<[key: string]>;
34
+ toHaveMetaValue: MatcherFunction<[key: string, value: unknown]>;
35
+ toNotHaveMetaKey: MatcherFunction<[key: string]>;
36
+ toHaveOnlyNamespacedMeta: MatcherFunction<[namespace: string]>;
37
+ toHavePlatformMimeType: MatcherFunction<[platform: import("..").TestPlatformType]>;
38
+ toHavePlatformHtml: MatcherFunction<[platform: import("..").TestPlatformType]>;
32
39
  toContainTool: MatcherFunction<[toolName: string]>;
33
40
  toBeSuccessful: MatcherFunction<[]>;
34
41
  toBeError: MatcherFunction<[expectedCode?: number | undefined]>;
@@ -91,18 +91,22 @@ const toBeError = function (received, expectedCode) {
91
91
  };
92
92
  };
93
93
  /**
94
- * Check if tool result has text content, optionally containing specific text
94
+ * Check if tool result or resource content has text content, optionally containing specific text
95
+ * Works with both ToolResultWrapper and ResourceContentWrapper
95
96
  */
96
97
  const toHaveTextContent = function (received, expectedText) {
97
98
  const result = received;
98
- if (typeof result !== 'object' || result === null || !('hasTextContent' in result)) {
99
+ // Check if it's a valid wrapper object with text() method
100
+ if (typeof result !== 'object' || result === null || !('text' in result)) {
99
101
  return {
100
102
  pass: false,
101
- message: () => `Expected a ToolResultWrapper object with hasTextContent method`,
103
+ message: () => `Expected a ToolResultWrapper or ResourceContentWrapper object with text method`,
102
104
  };
103
105
  }
104
- const hasText = result.hasTextContent();
106
+ // Get text content - works for both wrapper types
105
107
  const text = result.text();
108
+ // Check if has text content - ToolResultWrapper has hasTextContent, ResourceContentWrapper uses text() !== undefined
109
+ const hasText = 'hasTextContent' in result ? result.hasTextContent() : text !== undefined;
106
110
  let pass = hasText;
107
111
  if (pass && expectedText !== undefined) {
108
112
  pass = text?.includes(expectedText) ?? false;
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-matchers.js","sourceRoot":"","sources":["../../../src/matchers/mcp-matchers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file mcp-matchers.ts\n * @description Custom Jest matchers for MCP testing\n *\n * @example\n * ```typescript\n * import { test, expect } from '@frontmcp/testing';\n *\n * test('tools work', async ({ mcp }) => {\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n *\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toBeSuccessful();\n * expect(result).toHaveTextContent();\n * });\n * ```\n */\n\nimport type { MatcherFunction } from 'expect';\nimport type { Tool, Resource, ResourceTemplate, Prompt } from '@modelcontextprotocol/sdk/types.js';\nimport type { ToolResultWrapper, ResourceContentWrapper, PromptResultWrapper } from '../client/mcp-test-client.types';\nimport { uiMatchers } from '../ui/ui-matchers';\n\n// ═══════════════════════════════════════════════════════════════════\n// HELPER TYPES\n// ═══════════════════════════════════════════════════════════════════\n\ntype ResultWrapper = ToolResultWrapper | ResourceContentWrapper;\n\n// ═══════════════════════════════════════════════════════════════════\n// TOOL MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if tools array contains a tool with the given name\n */\nconst toContainTool: MatcherFunction<[toolName: string]> = function (received, toolName) {\n const tools = received as Tool[];\n\n if (!Array.isArray(tools)) {\n return {\n pass: false,\n message: () => `Expected an array of tools, but received ${typeof received}`,\n };\n }\n\n const pass = tools.some((t) => t.name === toolName);\n const availableTools = tools.map((t) => t.name).join(', ');\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected tools not to contain \"${toolName}\"`\n : `Expected tools to contain \"${toolName}\", but got: [${availableTools}]`,\n };\n};\n\n/**\n * Check if result is successful (not an error)\n */\nconst toBeSuccessful: MatcherFunction<[]> = function (received) {\n const result = received as ResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('isSuccess' in result)) {\n return {\n pass: false,\n message: () => `Expected a result wrapper object with isSuccess property`,\n };\n }\n\n const pass = result.isSuccess;\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected result not to be successful'\n : `Expected result to be successful, but got error: ${result.error?.message ?? 'unknown error'}`,\n };\n};\n\n/**\n * Check if result is an error, optionally with a specific error code\n */\nconst toBeError: MatcherFunction<[expectedCode?: number]> = function (received, expectedCode) {\n const result = received as ResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('isError' in result)) {\n return {\n pass: false,\n message: () => `Expected a result wrapper object with isError property`,\n };\n }\n\n let pass = result.isError;\n\n if (pass && expectedCode !== undefined) {\n pass = result.error?.code === expectedCode;\n }\n\n return {\n pass,\n message: () => {\n if (!result.isError) {\n return 'Expected result to be an error, but it was successful';\n }\n if (expectedCode !== undefined && result.error?.code !== expectedCode) {\n return `Expected error code ${expectedCode}, but got ${result.error?.code}`;\n }\n return 'Expected result not to be an error';\n },\n };\n};\n\n/**\n * Check if tool result has text content, optionally containing specific text\n */\nconst toHaveTextContent: MatcherFunction<[expectedText?: string]> = function (received, expectedText) {\n const result = received as ToolResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('hasTextContent' in result)) {\n return {\n pass: false,\n message: () => `Expected a ToolResultWrapper object with hasTextContent method`,\n };\n }\n\n const hasText = result.hasTextContent();\n const text = result.text();\n let pass = hasText;\n\n if (pass && expectedText !== undefined) {\n pass = text?.includes(expectedText) ?? false;\n }\n\n return {\n pass,\n message: () => {\n if (!hasText) {\n return 'Expected result to have text content';\n }\n if (expectedText !== undefined && !text?.includes(expectedText)) {\n return `Expected text to contain \"${expectedText}\", but got: \"${text}\"`;\n }\n return 'Expected result not to have text content';\n },\n };\n};\n\n/**\n * Check if tool result has image content\n */\nconst toHaveImageContent: MatcherFunction<[]> = function (received) {\n const result = received as ToolResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('hasImageContent' in result)) {\n return {\n pass: false,\n message: () => `Expected a ToolResultWrapper object with hasImageContent method`,\n };\n }\n\n const pass = result.hasImageContent();\n\n return {\n pass,\n message: () => (pass ? 'Expected result not to have image content' : 'Expected result to have image content'),\n };\n};\n\n/**\n * Check if tool result has resource content\n */\nconst toHaveResourceContent: MatcherFunction<[]> = function (received) {\n const result = received as ToolResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('hasResourceContent' in result)) {\n return {\n pass: false,\n message: () => `Expected a ToolResultWrapper object with hasResourceContent method`,\n };\n }\n\n const pass = result.hasResourceContent();\n\n return {\n pass,\n message: () => (pass ? 'Expected result not to have resource content' : 'Expected result to have resource content'),\n };\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// RESOURCE MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if resources array contains a resource with the given URI\n */\nconst toContainResource: MatcherFunction<[uri: string]> = function (received, uri) {\n const resources = received as Resource[];\n\n if (!Array.isArray(resources)) {\n return {\n pass: false,\n message: () => `Expected an array of resources, but received ${typeof received}`,\n };\n }\n\n const pass = resources.some((r) => r.uri === uri);\n const availableUris = resources.map((r) => r.uri).join(', ');\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected resources not to contain \"${uri}\"`\n : `Expected resources to contain \"${uri}\", but got: [${availableUris}]`,\n };\n};\n\n/**\n * Check if resource templates array contains a template with the given URI template\n */\nconst toContainResourceTemplate: MatcherFunction<[uriTemplate: string]> = function (received, uriTemplate) {\n const templates = received as ResourceTemplate[];\n\n if (!Array.isArray(templates)) {\n return {\n pass: false,\n message: () => `Expected an array of resource templates, but received ${typeof received}`,\n };\n }\n\n const pass = templates.some((t) => t.uriTemplate === uriTemplate);\n const availableTemplates = templates.map((t) => t.uriTemplate).join(', ');\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected templates not to contain \"${uriTemplate}\"`\n : `Expected templates to contain \"${uriTemplate}\", but got: [${availableTemplates}]`,\n };\n};\n\n/**\n * Check if resource content has a specific MIME type\n */\nconst toHaveMimeType: MatcherFunction<[mimeType: string]> = function (received, mimeType) {\n const result = received as ResourceContentWrapper;\n\n if (typeof result !== 'object' || result === null || !('hasMimeType' in result)) {\n return {\n pass: false,\n message: () => `Expected a ResourceContentWrapper object with hasMimeType method`,\n };\n }\n\n const pass = result.hasMimeType(mimeType);\n const actualMimeType = result.mimeType();\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected content not to have MIME type \"${mimeType}\"`\n : `Expected MIME type \"${mimeType}\", but got \"${actualMimeType}\"`,\n };\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// PROMPT MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if prompts array contains a prompt with the given name\n */\nconst toContainPrompt: MatcherFunction<[name: string]> = function (received, name) {\n const prompts = received as Prompt[];\n\n if (!Array.isArray(prompts)) {\n return {\n pass: false,\n message: () => `Expected an array of prompts, but received ${typeof received}`,\n };\n }\n\n const pass = prompts.some((p) => p.name === name);\n const availablePrompts = prompts.map((p) => p.name).join(', ');\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected prompts not to contain \"${name}\"`\n : `Expected prompts to contain \"${name}\", but got: [${availablePrompts}]`,\n };\n};\n\n/**\n * Check if prompt result has a specific number of messages\n */\nconst toHaveMessages: MatcherFunction<[count: number]> = function (received, count) {\n const result = received as PromptResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('messages' in result)) {\n return {\n pass: false,\n message: () => `Expected a PromptResultWrapper object with messages property`,\n };\n }\n\n const actualCount = result.messages?.length ?? 0;\n const pass = actualCount === count;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected prompt not to have ${count} messages`\n : `Expected prompt to have ${count} messages, but got ${actualCount}`,\n };\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// PROTOCOL MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if response is valid JSON-RPC 2.0\n * A valid JSON-RPC 2.0 response must have:\n * - jsonrpc: \"2.0\"\n * - id (matching the request, can be null for notifications)\n * - Either result OR error (but not both)\n */\nconst toBeValidJsonRpc: MatcherFunction<[]> = function (received) {\n const response = received as Record<string, unknown>;\n\n if (typeof response !== 'object' || response === null) {\n return {\n pass: false,\n message: () => `Expected an object, but received ${typeof received}`,\n };\n }\n\n const hasJsonRpc = response['jsonrpc'] === '2.0';\n const hasId = 'id' in response;\n const hasResult = 'result' in response;\n const hasError = 'error' in response;\n const hasExactlyOneResultOrError = (hasResult || hasError) && !(hasResult && hasError);\n\n const pass = hasJsonRpc && hasId && hasExactlyOneResultOrError;\n\n return {\n pass,\n message: () => {\n if (pass) {\n return 'Expected response not to be valid JSON-RPC';\n }\n const issues: string[] = [];\n if (!hasJsonRpc) issues.push('missing or invalid \"jsonrpc\": \"2.0\"');\n if (!hasId) issues.push('missing \"id\" field');\n if (!hasExactlyOneResultOrError) {\n if (!hasResult && !hasError) issues.push('missing \"result\" or \"error\"');\n else issues.push('cannot have both \"result\" and \"error\"');\n }\n return `Expected valid JSON-RPC 2.0 response: ${issues.join(', ')}`;\n },\n };\n};\n\n/**\n * Check if JSON-RPC response has a result\n */\nconst toHaveResult: MatcherFunction<[]> = function (received) {\n const response = received as Record<string, unknown>;\n\n if (typeof response !== 'object' || response === null) {\n return {\n pass: false,\n message: () => `Expected an object, but received ${typeof received}`,\n };\n }\n\n const pass = 'result' in response;\n\n return {\n pass,\n message: () => (pass ? 'Expected response not to have result' : 'Expected response to have result'),\n };\n};\n\n/**\n * Check if JSON-RPC response has an error\n */\nconst toHaveError: MatcherFunction<[]> = function (received) {\n const response = received as Record<string, unknown>;\n\n if (typeof response !== 'object' || response === null) {\n return {\n pass: false,\n message: () => `Expected an object, but received ${typeof received}`,\n };\n }\n\n const pass = 'error' in response;\n\n return {\n pass,\n message: () => (pass ? 'Expected response not to have error' : 'Expected response to have error'),\n };\n};\n\n/**\n * Check if JSON-RPC response has a specific error code\n */\nconst toHaveErrorCode: MatcherFunction<[code: number]> = function (received, code) {\n const response = received as { error?: { code: number } };\n\n if (typeof response !== 'object' || response === null) {\n return {\n pass: false,\n message: () => `Expected an object, but received ${typeof received}`,\n };\n }\n\n const actualCode = response.error?.code;\n const pass = actualCode === code;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have error code ${code}`\n : `Expected error code ${code}, but got ${actualCode ?? 'no error'}`,\n };\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// EXPORTS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * All MCP matchers as an object for expect.extend()\n */\nexport const mcpMatchers = {\n // Tool matchers\n toContainTool,\n toBeSuccessful,\n toBeError,\n toHaveTextContent,\n toHaveImageContent,\n toHaveResourceContent,\n\n // Resource matchers\n toContainResource,\n toContainResourceTemplate,\n toHaveMimeType,\n\n // Prompt matchers\n toContainPrompt,\n toHaveMessages,\n\n // Protocol matchers\n toBeValidJsonRpc,\n toHaveResult,\n toHaveError,\n toHaveErrorCode,\n\n // UI matchers (for testing tool UI responses)\n ...uiMatchers,\n};\n"]}
1
+ {"version":3,"file":"mcp-matchers.js","sourceRoot":"","sources":["../../../src/matchers/mcp-matchers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @file mcp-matchers.ts\n * @description Custom Jest matchers for MCP testing\n *\n * @example\n * ```typescript\n * import { test, expect } from '@frontmcp/testing';\n *\n * test('tools work', async ({ mcp }) => {\n * const tools = await mcp.tools.list();\n * expect(tools).toContainTool('my-tool');\n *\n * const result = await mcp.tools.call('my-tool', {});\n * expect(result).toBeSuccessful();\n * expect(result).toHaveTextContent();\n * });\n * ```\n */\n\nimport type { MatcherFunction } from 'expect';\nimport type { Tool, Resource, ResourceTemplate, Prompt } from '@modelcontextprotocol/sdk/types.js';\nimport type { ToolResultWrapper, ResourceContentWrapper, PromptResultWrapper } from '../client/mcp-test-client.types';\nimport { uiMatchers } from '../ui/ui-matchers';\n\n// ═══════════════════════════════════════════════════════════════════\n// HELPER TYPES\n// ═══════════════════════════════════════════════════════════════════\n\ntype ResultWrapper = ToolResultWrapper | ResourceContentWrapper;\n\n// ═══════════════════════════════════════════════════════════════════\n// TOOL MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if tools array contains a tool with the given name\n */\nconst toContainTool: MatcherFunction<[toolName: string]> = function (received, toolName) {\n const tools = received as Tool[];\n\n if (!Array.isArray(tools)) {\n return {\n pass: false,\n message: () => `Expected an array of tools, but received ${typeof received}`,\n };\n }\n\n const pass = tools.some((t) => t.name === toolName);\n const availableTools = tools.map((t) => t.name).join(', ');\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected tools not to contain \"${toolName}\"`\n : `Expected tools to contain \"${toolName}\", but got: [${availableTools}]`,\n };\n};\n\n/**\n * Check if result is successful (not an error)\n */\nconst toBeSuccessful: MatcherFunction<[]> = function (received) {\n const result = received as ResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('isSuccess' in result)) {\n return {\n pass: false,\n message: () => `Expected a result wrapper object with isSuccess property`,\n };\n }\n\n const pass = result.isSuccess;\n\n return {\n pass,\n message: () =>\n pass\n ? 'Expected result not to be successful'\n : `Expected result to be successful, but got error: ${result.error?.message ?? 'unknown error'}`,\n };\n};\n\n/**\n * Check if result is an error, optionally with a specific error code\n */\nconst toBeError: MatcherFunction<[expectedCode?: number]> = function (received, expectedCode) {\n const result = received as ResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('isError' in result)) {\n return {\n pass: false,\n message: () => `Expected a result wrapper object with isError property`,\n };\n }\n\n let pass = result.isError;\n\n if (pass && expectedCode !== undefined) {\n pass = result.error?.code === expectedCode;\n }\n\n return {\n pass,\n message: () => {\n if (!result.isError) {\n return 'Expected result to be an error, but it was successful';\n }\n if (expectedCode !== undefined && result.error?.code !== expectedCode) {\n return `Expected error code ${expectedCode}, but got ${result.error?.code}`;\n }\n return 'Expected result not to be an error';\n },\n };\n};\n\n/**\n * Check if tool result or resource content has text content, optionally containing specific text\n * Works with both ToolResultWrapper and ResourceContentWrapper\n */\nconst toHaveTextContent: MatcherFunction<[expectedText?: string]> = function (received, expectedText) {\n const result = received as ToolResultWrapper | ResourceContentWrapper;\n\n // Check if it's a valid wrapper object with text() method\n if (typeof result !== 'object' || result === null || !('text' in result)) {\n return {\n pass: false,\n message: () => `Expected a ToolResultWrapper or ResourceContentWrapper object with text method`,\n };\n }\n\n // Get text content - works for both wrapper types\n const text = result.text();\n\n // Check if has text content - ToolResultWrapper has hasTextContent, ResourceContentWrapper uses text() !== undefined\n const hasText = 'hasTextContent' in result ? result.hasTextContent() : text !== undefined;\n\n let pass = hasText;\n\n if (pass && expectedText !== undefined) {\n pass = text?.includes(expectedText) ?? false;\n }\n\n return {\n pass,\n message: () => {\n if (!hasText) {\n return 'Expected result to have text content';\n }\n if (expectedText !== undefined && !text?.includes(expectedText)) {\n return `Expected text to contain \"${expectedText}\", but got: \"${text}\"`;\n }\n return 'Expected result not to have text content';\n },\n };\n};\n\n/**\n * Check if tool result has image content\n */\nconst toHaveImageContent: MatcherFunction<[]> = function (received) {\n const result = received as ToolResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('hasImageContent' in result)) {\n return {\n pass: false,\n message: () => `Expected a ToolResultWrapper object with hasImageContent method`,\n };\n }\n\n const pass = result.hasImageContent();\n\n return {\n pass,\n message: () => (pass ? 'Expected result not to have image content' : 'Expected result to have image content'),\n };\n};\n\n/**\n * Check if tool result has resource content\n */\nconst toHaveResourceContent: MatcherFunction<[]> = function (received) {\n const result = received as ToolResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('hasResourceContent' in result)) {\n return {\n pass: false,\n message: () => `Expected a ToolResultWrapper object with hasResourceContent method`,\n };\n }\n\n const pass = result.hasResourceContent();\n\n return {\n pass,\n message: () => (pass ? 'Expected result not to have resource content' : 'Expected result to have resource content'),\n };\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// RESOURCE MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if resources array contains a resource with the given URI\n */\nconst toContainResource: MatcherFunction<[uri: string]> = function (received, uri) {\n const resources = received as Resource[];\n\n if (!Array.isArray(resources)) {\n return {\n pass: false,\n message: () => `Expected an array of resources, but received ${typeof received}`,\n };\n }\n\n const pass = resources.some((r) => r.uri === uri);\n const availableUris = resources.map((r) => r.uri).join(', ');\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected resources not to contain \"${uri}\"`\n : `Expected resources to contain \"${uri}\", but got: [${availableUris}]`,\n };\n};\n\n/**\n * Check if resource templates array contains a template with the given URI template\n */\nconst toContainResourceTemplate: MatcherFunction<[uriTemplate: string]> = function (received, uriTemplate) {\n const templates = received as ResourceTemplate[];\n\n if (!Array.isArray(templates)) {\n return {\n pass: false,\n message: () => `Expected an array of resource templates, but received ${typeof received}`,\n };\n }\n\n const pass = templates.some((t) => t.uriTemplate === uriTemplate);\n const availableTemplates = templates.map((t) => t.uriTemplate).join(', ');\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected templates not to contain \"${uriTemplate}\"`\n : `Expected templates to contain \"${uriTemplate}\", but got: [${availableTemplates}]`,\n };\n};\n\n/**\n * Check if resource content has a specific MIME type\n */\nconst toHaveMimeType: MatcherFunction<[mimeType: string]> = function (received, mimeType) {\n const result = received as ResourceContentWrapper;\n\n if (typeof result !== 'object' || result === null || !('hasMimeType' in result)) {\n return {\n pass: false,\n message: () => `Expected a ResourceContentWrapper object with hasMimeType method`,\n };\n }\n\n const pass = result.hasMimeType(mimeType);\n const actualMimeType = result.mimeType();\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected content not to have MIME type \"${mimeType}\"`\n : `Expected MIME type \"${mimeType}\", but got \"${actualMimeType}\"`,\n };\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// PROMPT MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if prompts array contains a prompt with the given name\n */\nconst toContainPrompt: MatcherFunction<[name: string]> = function (received, name) {\n const prompts = received as Prompt[];\n\n if (!Array.isArray(prompts)) {\n return {\n pass: false,\n message: () => `Expected an array of prompts, but received ${typeof received}`,\n };\n }\n\n const pass = prompts.some((p) => p.name === name);\n const availablePrompts = prompts.map((p) => p.name).join(', ');\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected prompts not to contain \"${name}\"`\n : `Expected prompts to contain \"${name}\", but got: [${availablePrompts}]`,\n };\n};\n\n/**\n * Check if prompt result has a specific number of messages\n */\nconst toHaveMessages: MatcherFunction<[count: number]> = function (received, count) {\n const result = received as PromptResultWrapper;\n\n if (typeof result !== 'object' || result === null || !('messages' in result)) {\n return {\n pass: false,\n message: () => `Expected a PromptResultWrapper object with messages property`,\n };\n }\n\n const actualCount = result.messages?.length ?? 0;\n const pass = actualCount === count;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected prompt not to have ${count} messages`\n : `Expected prompt to have ${count} messages, but got ${actualCount}`,\n };\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// PROTOCOL MATCHERS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Check if response is valid JSON-RPC 2.0\n * A valid JSON-RPC 2.0 response must have:\n * - jsonrpc: \"2.0\"\n * - id (matching the request, can be null for notifications)\n * - Either result OR error (but not both)\n */\nconst toBeValidJsonRpc: MatcherFunction<[]> = function (received) {\n const response = received as Record<string, unknown>;\n\n if (typeof response !== 'object' || response === null) {\n return {\n pass: false,\n message: () => `Expected an object, but received ${typeof received}`,\n };\n }\n\n const hasJsonRpc = response['jsonrpc'] === '2.0';\n const hasId = 'id' in response;\n const hasResult = 'result' in response;\n const hasError = 'error' in response;\n const hasExactlyOneResultOrError = (hasResult || hasError) && !(hasResult && hasError);\n\n const pass = hasJsonRpc && hasId && hasExactlyOneResultOrError;\n\n return {\n pass,\n message: () => {\n if (pass) {\n return 'Expected response not to be valid JSON-RPC';\n }\n const issues: string[] = [];\n if (!hasJsonRpc) issues.push('missing or invalid \"jsonrpc\": \"2.0\"');\n if (!hasId) issues.push('missing \"id\" field');\n if (!hasExactlyOneResultOrError) {\n if (!hasResult && !hasError) issues.push('missing \"result\" or \"error\"');\n else issues.push('cannot have both \"result\" and \"error\"');\n }\n return `Expected valid JSON-RPC 2.0 response: ${issues.join(', ')}`;\n },\n };\n};\n\n/**\n * Check if JSON-RPC response has a result\n */\nconst toHaveResult: MatcherFunction<[]> = function (received) {\n const response = received as Record<string, unknown>;\n\n if (typeof response !== 'object' || response === null) {\n return {\n pass: false,\n message: () => `Expected an object, but received ${typeof received}`,\n };\n }\n\n const pass = 'result' in response;\n\n return {\n pass,\n message: () => (pass ? 'Expected response not to have result' : 'Expected response to have result'),\n };\n};\n\n/**\n * Check if JSON-RPC response has an error\n */\nconst toHaveError: MatcherFunction<[]> = function (received) {\n const response = received as Record<string, unknown>;\n\n if (typeof response !== 'object' || response === null) {\n return {\n pass: false,\n message: () => `Expected an object, but received ${typeof received}`,\n };\n }\n\n const pass = 'error' in response;\n\n return {\n pass,\n message: () => (pass ? 'Expected response not to have error' : 'Expected response to have error'),\n };\n};\n\n/**\n * Check if JSON-RPC response has a specific error code\n */\nconst toHaveErrorCode: MatcherFunction<[code: number]> = function (received, code) {\n const response = received as { error?: { code: number } };\n\n if (typeof response !== 'object' || response === null) {\n return {\n pass: false,\n message: () => `Expected an object, but received ${typeof received}`,\n };\n }\n\n const actualCode = response.error?.code;\n const pass = actualCode === code;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have error code ${code}`\n : `Expected error code ${code}, but got ${actualCode ?? 'no error'}`,\n };\n};\n\n// ═══════════════════════════════════════════════════════════════════\n// EXPORTS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * All MCP matchers as an object for expect.extend()\n */\nexport const mcpMatchers = {\n // Tool matchers\n toContainTool,\n toBeSuccessful,\n toBeError,\n toHaveTextContent,\n toHaveImageContent,\n toHaveResourceContent,\n\n // Resource matchers\n toContainResource,\n toContainResourceTemplate,\n toHaveMimeType,\n\n // Prompt matchers\n toContainPrompt,\n toHaveMessages,\n\n // Protocol matchers\n toBeValidJsonRpc,\n toHaveResult,\n toHaveError,\n toHaveErrorCode,\n\n // UI matchers (for testing tool UI responses)\n ...uiMatchers,\n};\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @file platform/index.ts
3
+ * @description Platform utilities barrel export.
4
+ *
5
+ * Provides types and helpers for platform-specific E2E testing.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import {
10
+ * TestPlatformType,
11
+ * getPlatformClientInfo,
12
+ * getPlatformMetaNamespace,
13
+ * getForbiddenMetaPrefixes,
14
+ * } from '@frontmcp/testing';
15
+ *
16
+ * // Get expected meta prefixes for a platform
17
+ * const prefixes = getToolsListMetaPrefixes('openai');
18
+ * // ['openai/']
19
+ *
20
+ * // Get forbidden prefixes (should NOT appear)
21
+ * const forbidden = getForbiddenMetaPrefixes('openai');
22
+ * // ['ui/', 'frontmcp/']
23
+ * ```
24
+ */
25
+ export type { TestPlatformType, PlatformMetaNamespace } from './platform-types';
26
+ export { getPlatformMetaNamespace, getPlatformMimeType, isOpenAIPlatform, isExtAppsPlatform, isFrontmcpPlatform, getToolsListMetaPrefixes, getToolCallMetaPrefixes, getForbiddenMetaPrefixes, } from './platform-types';
27
+ export type { TestClientInfo, TestClientCapabilities, ExperimentalCapabilities, McpAppsExtension, } from './platform-client-info';
28
+ export { getPlatformClientInfo, buildUserAgent, getPlatformUserAgent, PLATFORM_DETECTION_PATTERNS, MCP_APPS_EXTENSION_KEY, getPlatformCapabilities, requiresCapabilityDetection, } from './platform-client-info';
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ /**
3
+ * @file platform/index.ts
4
+ * @description Platform utilities barrel export.
5
+ *
6
+ * Provides types and helpers for platform-specific E2E testing.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import {
11
+ * TestPlatformType,
12
+ * getPlatformClientInfo,
13
+ * getPlatformMetaNamespace,
14
+ * getForbiddenMetaPrefixes,
15
+ * } from '@frontmcp/testing';
16
+ *
17
+ * // Get expected meta prefixes for a platform
18
+ * const prefixes = getToolsListMetaPrefixes('openai');
19
+ * // ['openai/']
20
+ *
21
+ * // Get forbidden prefixes (should NOT appear)
22
+ * const forbidden = getForbiddenMetaPrefixes('openai');
23
+ * // ['ui/', 'frontmcp/']
24
+ * ```
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.requiresCapabilityDetection = exports.getPlatformCapabilities = exports.MCP_APPS_EXTENSION_KEY = exports.PLATFORM_DETECTION_PATTERNS = exports.getPlatformUserAgent = exports.buildUserAgent = exports.getPlatformClientInfo = exports.getForbiddenMetaPrefixes = exports.getToolCallMetaPrefixes = exports.getToolsListMetaPrefixes = exports.isFrontmcpPlatform = exports.isExtAppsPlatform = exports.isOpenAIPlatform = exports.getPlatformMimeType = exports.getPlatformMetaNamespace = void 0;
28
+ // Platform type utilities
29
+ var platform_types_1 = require("./platform-types");
30
+ Object.defineProperty(exports, "getPlatformMetaNamespace", { enumerable: true, get: function () { return platform_types_1.getPlatformMetaNamespace; } });
31
+ Object.defineProperty(exports, "getPlatformMimeType", { enumerable: true, get: function () { return platform_types_1.getPlatformMimeType; } });
32
+ Object.defineProperty(exports, "isOpenAIPlatform", { enumerable: true, get: function () { return platform_types_1.isOpenAIPlatform; } });
33
+ Object.defineProperty(exports, "isExtAppsPlatform", { enumerable: true, get: function () { return platform_types_1.isExtAppsPlatform; } });
34
+ Object.defineProperty(exports, "isFrontmcpPlatform", { enumerable: true, get: function () { return platform_types_1.isFrontmcpPlatform; } });
35
+ Object.defineProperty(exports, "getToolsListMetaPrefixes", { enumerable: true, get: function () { return platform_types_1.getToolsListMetaPrefixes; } });
36
+ Object.defineProperty(exports, "getToolCallMetaPrefixes", { enumerable: true, get: function () { return platform_types_1.getToolCallMetaPrefixes; } });
37
+ Object.defineProperty(exports, "getForbiddenMetaPrefixes", { enumerable: true, get: function () { return platform_types_1.getForbiddenMetaPrefixes; } });
38
+ var platform_client_info_1 = require("./platform-client-info");
39
+ Object.defineProperty(exports, "getPlatformClientInfo", { enumerable: true, get: function () { return platform_client_info_1.getPlatformClientInfo; } });
40
+ Object.defineProperty(exports, "buildUserAgent", { enumerable: true, get: function () { return platform_client_info_1.buildUserAgent; } });
41
+ Object.defineProperty(exports, "getPlatformUserAgent", { enumerable: true, get: function () { return platform_client_info_1.getPlatformUserAgent; } });
42
+ Object.defineProperty(exports, "PLATFORM_DETECTION_PATTERNS", { enumerable: true, get: function () { return platform_client_info_1.PLATFORM_DETECTION_PATTERNS; } });
43
+ // Capability utilities for ext-apps platform detection
44
+ Object.defineProperty(exports, "MCP_APPS_EXTENSION_KEY", { enumerable: true, get: function () { return platform_client_info_1.MCP_APPS_EXTENSION_KEY; } });
45
+ Object.defineProperty(exports, "getPlatformCapabilities", { enumerable: true, get: function () { return platform_client_info_1.getPlatformCapabilities; } });
46
+ Object.defineProperty(exports, "requiresCapabilityDetection", { enumerable: true, get: function () { return platform_client_info_1.requiresCapabilityDetection; } });
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/platform/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;;;AAKH,0BAA0B;AAC1B,mDAS0B;AARxB,0HAAA,wBAAwB,OAAA;AACxB,qHAAA,mBAAmB,OAAA;AACnB,kHAAA,gBAAgB,OAAA;AAChB,mHAAA,iBAAiB,OAAA;AACjB,oHAAA,kBAAkB,OAAA;AAClB,0HAAA,wBAAwB,OAAA;AACxB,yHAAA,uBAAuB,OAAA;AACvB,0HAAA,wBAAwB,OAAA;AAW1B,+DASgC;AAR9B,6HAAA,qBAAqB,OAAA;AACrB,sHAAA,cAAc,OAAA;AACd,4HAAA,oBAAoB,OAAA;AACpB,mIAAA,2BAA2B,OAAA;AAC3B,uDAAuD;AACvD,8HAAA,sBAAsB,OAAA;AACtB,+HAAA,uBAAuB,OAAA;AACvB,mIAAA,2BAA2B,OAAA","sourcesContent":["/**\n * @file platform/index.ts\n * @description Platform utilities barrel export.\n *\n * Provides types and helpers for platform-specific E2E testing.\n *\n * @example\n * ```typescript\n * import {\n * TestPlatformType,\n * getPlatformClientInfo,\n * getPlatformMetaNamespace,\n * getForbiddenMetaPrefixes,\n * } from '@frontmcp/testing';\n *\n * // Get expected meta prefixes for a platform\n * const prefixes = getToolsListMetaPrefixes('openai');\n * // ['openai/']\n *\n * // Get forbidden prefixes (should NOT appear)\n * const forbidden = getForbiddenMetaPrefixes('openai');\n * // ['ui/', 'frontmcp/']\n * ```\n */\n\n// Platform types\nexport type { TestPlatformType, PlatformMetaNamespace } from './platform-types';\n\n// Platform type utilities\nexport {\n getPlatformMetaNamespace,\n getPlatformMimeType,\n isOpenAIPlatform,\n isExtAppsPlatform,\n isFrontmcpPlatform,\n getToolsListMetaPrefixes,\n getToolCallMetaPrefixes,\n getForbiddenMetaPrefixes,\n} from './platform-types';\n\n// Client info utilities\nexport type {\n TestClientInfo,\n TestClientCapabilities,\n ExperimentalCapabilities,\n McpAppsExtension,\n} from './platform-client-info';\n\nexport {\n getPlatformClientInfo,\n buildUserAgent,\n getPlatformUserAgent,\n PLATFORM_DETECTION_PATTERNS,\n // Capability utilities for ext-apps platform detection\n MCP_APPS_EXTENSION_KEY,\n getPlatformCapabilities,\n requiresCapabilityDetection,\n} from './platform-client-info';\n"]}