@fragno-dev/mcp-fragment 0.0.0-stage → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE.md +16 -0
  2. package/README.md +312 -2
  3. package/bin/run.js +7 -0
  4. package/dist/browser/client/react.d.ts +253 -0
  5. package/dist/browser/client/react.d.ts.map +1 -0
  6. package/dist/browser/client/react.js +194 -0
  7. package/dist/browser/client/react.js.map +1 -0
  8. package/dist/browser/client/solid.d.ts +256 -0
  9. package/dist/browser/client/solid.d.ts.map +1 -0
  10. package/dist/browser/client/solid.js +129 -0
  11. package/dist/browser/client/solid.js.map +1 -0
  12. package/dist/browser/client/svelte.d.ts +256 -0
  13. package/dist/browser/client/svelte.d.ts.map +1 -0
  14. package/dist/browser/client/svelte.js +131 -0
  15. package/dist/browser/client/svelte.js.map +1 -0
  16. package/dist/browser/client/vanilla.d.ts +283 -0
  17. package/dist/browser/client/vanilla.d.ts.map +1 -0
  18. package/dist/browser/client/vanilla.js +159 -0
  19. package/dist/browser/client/vanilla.js.map +1 -0
  20. package/dist/browser/client/vue.d.ts +256 -0
  21. package/dist/browser/client/vue.d.ts.map +1 -0
  22. package/dist/browser/client/vue.js +127 -0
  23. package/dist/browser/client/vue.js.map +1 -0
  24. package/dist/browser/client-BCzNDSlW.d.ts +711 -0
  25. package/dist/browser/client-BCzNDSlW.d.ts.map +1 -0
  26. package/dist/browser/index.d.ts +813 -0
  27. package/dist/browser/index.d.ts.map +1 -0
  28. package/dist/browser/index.js +1 -0
  29. package/dist/browser/src-B6dHZXqB.js +2303 -0
  30. package/dist/browser/src-B6dHZXqB.js.map +1 -0
  31. package/dist/cli/cli.d.ts +6 -0
  32. package/dist/cli/cli.d.ts.map +1 -0
  33. package/dist/cli/cli.js +227 -0
  34. package/dist/cli/cli.js.map +1 -0
  35. package/dist/cli/definition.js +330 -0
  36. package/dist/cli/definition.js.map +1 -0
  37. package/dist/cli/diagnostics.js +93 -0
  38. package/dist/cli/diagnostics.js.map +1 -0
  39. package/dist/cli/index.js +13 -0
  40. package/dist/cli/index.js.map +1 -0
  41. package/dist/cli/mcp-api.js +87 -0
  42. package/dist/cli/mcp-api.js.map +1 -0
  43. package/dist/cli/mcp-types.js +100 -0
  44. package/dist/cli/mcp-types.js.map +1 -0
  45. package/dist/cli/oauth-provider.js +113 -0
  46. package/dist/cli/oauth-provider.js.map +1 -0
  47. package/dist/cli/routes.js +743 -0
  48. package/dist/cli/routes.js.map +1 -0
  49. package/dist/cli/schema.js +13 -0
  50. package/dist/cli/schema.js.map +1 -0
  51. package/dist/cli/services.js +330 -0
  52. package/dist/cli/services.js.map +1 -0
  53. package/dist/node/definition.d.ts +260 -0
  54. package/dist/node/definition.d.ts.map +1 -0
  55. package/dist/node/definition.js +330 -0
  56. package/dist/node/definition.js.map +1 -0
  57. package/dist/node/diagnostics.js +93 -0
  58. package/dist/node/diagnostics.js.map +1 -0
  59. package/dist/node/index.d.ts +694 -0
  60. package/dist/node/index.d.ts.map +1 -0
  61. package/dist/node/index.js +26 -0
  62. package/dist/node/index.js.map +1 -0
  63. package/dist/node/mcp-api.d.ts +2 -0
  64. package/dist/node/mcp-api.js +87 -0
  65. package/dist/node/mcp-api.js.map +1 -0
  66. package/dist/node/mcp-types.d.ts +146 -0
  67. package/dist/node/mcp-types.d.ts.map +1 -0
  68. package/dist/node/mcp-types.js +100 -0
  69. package/dist/node/mcp-types.js.map +1 -0
  70. package/dist/node/oauth-provider.d.ts +24 -0
  71. package/dist/node/oauth-provider.d.ts.map +1 -0
  72. package/dist/node/oauth-provider.js +113 -0
  73. package/dist/node/oauth-provider.js.map +1 -0
  74. package/dist/node/routes.d.ts +466 -0
  75. package/dist/node/routes.d.ts.map +1 -0
  76. package/dist/node/routes.js +743 -0
  77. package/dist/node/routes.js.map +1 -0
  78. package/dist/node/schema.d.ts +9 -0
  79. package/dist/node/schema.d.ts.map +1 -0
  80. package/dist/node/schema.js +13 -0
  81. package/dist/node/schema.js.map +1 -0
  82. package/dist/node/services.d.ts +40 -0
  83. package/dist/node/services.d.ts.map +1 -0
  84. package/dist/node/services.js +330 -0
  85. package/dist/node/services.js.map +1 -0
  86. package/dist/node/testing/mcp-test-server.d.ts +42 -0
  87. package/dist/node/testing/mcp-test-server.d.ts.map +1 -0
  88. package/dist/node/testing/mcp-test-server.js +218 -0
  89. package/dist/node/testing/mcp-test-server.js.map +1 -0
  90. package/package.json +130 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.js","names":[],"sources":["../../src/services.ts"],"sourcesContent":["import { auth, type OAuthTokens } from \"@modelcontextprotocol/client\";\n\nimport { parseSecretPayload, stringifySecretPayload, type McpClientAuth } from \"./mcp-api\";\nimport type { AuthConfig, McpAuthStatus, McpFragmentConfig } from \"./mcp-types\";\nimport {\n BufferedOAuthClientProvider,\n type OAuthProviderChanges,\n type OAuthProviderSnapshot,\n} from \"./oauth-provider\";\n\n/** A buffered OAuth secret is kept, replaced, or removed because the server rejected it. */\ntype OAuthSecretChange =\n | { action: \"keep\" }\n | { action: \"save\"; payload: string }\n | { action: \"remove\" };\n\nfunction oauthSecretChange(value: unknown, invalidated: boolean | undefined): OAuthSecretChange {\n if (value) {\n return { action: \"save\", payload: stringifySecretPayload(value) };\n }\n return invalidated ? { action: \"remove\" } : { action: \"keep\" };\n}\n\nexport async function prepareOAuthChanges(changes: OAuthProviderChanges) {\n return {\n ...changes,\n clientChange: oauthSecretChange(changes.clientInformation, changes.clientInvalidated),\n discoveryChange: oauthSecretChange(changes.discoveryState, changes.discoveryInvalidated),\n };\n}\n\n/** Applies the client and discovery changes through a write site's own secret writers. */\nexport function writeOAuthSecretChanges(\n changes: Pick<AuthPersistenceChanges, \"clientChange\" | \"discoveryChange\">,\n writers: { upsert(kind: string, payload: string): void; remove(kind: string): void },\n) {\n for (const [kind, change] of [\n [\"oauth-client\", changes.clientChange],\n [\"oauth-discovery\", changes.discoveryChange],\n ] as const) {\n switch (change.action) {\n case \"keep\":\n break;\n case \"save\":\n writers.upsert(kind, change.payload);\n break;\n case \"remove\":\n writers.remove(kind);\n break;\n default:\n throw new Error(\"Unknown OAuth secret change.\", { cause: change satisfies never });\n }\n }\n}\n\nexport function tokenExpiry(tokens: NonNullable<OAuthProviderChanges[\"tokens\"]>): Date | null {\n return typeof tokens.expires_in === \"number\"\n ? new Date(Date.now() + tokens.expires_in * 1000)\n : null;\n}\n\nexport type AuthPersistenceChanges = Awaited<ReturnType<typeof prepareOAuthChanges>> & {\n authPayload?: string;\n authExpiresAt?: Date | null;\n};\n\nexport type McpOperationAuth = McpClientAuth<AuthPersistenceChanges>;\n\nexport type SecretRecord = {\n id: { toString(): string } | string;\n kind: string;\n payload: string;\n expiresAt?: Date | string | null;\n};\n\n// OAuth payloads hold the creation-time configuration until consent adds tokens and a redirect URI.\ntype StoredAuthPayload =\n | { type: \"bearer\"; token: string }\n | (Omit<Extract<AuthConfig, { type: \"oauth\" }>, \"type\"> & {\n type: \"oauth\";\n tokens?: OAuthTokens;\n redirectUri?: string;\n })\n | {\n type: \"client_credentials\";\n clientId: string;\n clientSecret: string;\n scopes?: string[];\n tokens?: NonNullable<OAuthProviderChanges[\"tokens\"]>;\n };\n\nfunction secretByKind<TSecret extends { kind: string }>(secrets: TSecret[], kind: string) {\n return secrets.find((secret) => secret.kind === kind);\n}\n\nconst TOKEN_EXPIRY_SKEW_MS = 30_000;\n\nfunction secretExpiresInFuture(secret: Pick<SecretRecord, \"expiresAt\"> | undefined) {\n if (!secret?.expiresAt) {\n return false;\n }\n return new Date(secret.expiresAt).getTime() > Date.now() + TOKEN_EXPIRY_SKEW_MS;\n}\n\nfunction secretIsUnexpired(secret: Pick<SecretRecord, \"expiresAt\"> | undefined) {\n return !secret?.expiresAt || secretExpiresInFuture(secret);\n}\n\n/** Projects stored auth without secrets; whether credentials work can only be learned by using them. */\nexport function projectMcpAuthStatus(args: {\n authMode: string;\n authSecret: Pick<SecretRecord, \"payload\" | \"expiresAt\"> | undefined;\n hasPendingOAuth: boolean;\n}): McpAuthStatus {\n // Routes write authMode only from authConfigSchema discriminators.\n const mode = args.authMode as AuthConfig[\"type\"];\n if (mode === \"none\") {\n return { mode };\n }\n if (mode !== \"oauth\") {\n return { mode, credentials: args.authSecret ? \"present\" : \"missing\" };\n }\n const auth = args.authSecret\n ? parseSecretPayload<StoredAuthPayload>(args.authSecret.payload)\n : undefined;\n const tokens = auth?.type === \"oauth\" ? auth.tokens : undefined;\n // Matches operation-time resolution: a refresh token renews an expired access token.\n if (tokens?.access_token && (tokens.refresh_token || secretIsUnexpired(args.authSecret))) {\n return { mode, state: \"authorized\" };\n }\n // Expired tokens without a refresh token are unusable, so a pending link is the actionable state.\n if (args.hasPendingOAuth) {\n return { mode, state: \"consent-pending\" };\n }\n return { mode, state: tokens?.access_token ? \"expired\" : \"consent-required\" };\n}\n\n/** Whether stored auth can authorize operations without new consent or credentials. */\nexport function isMcpAuthReady(\n authMode: string,\n authSecret: Pick<SecretRecord, \"payload\" | \"expiresAt\"> | undefined,\n): boolean {\n // Pending consent only distinguishes states that are not ready.\n const status = projectMcpAuthStatus({ authMode, authSecret, hasPendingOAuth: false });\n switch (status.mode) {\n case \"none\":\n return true;\n case \"oauth\":\n return status.state === \"authorized\";\n case \"bearer\":\n case \"client_credentials\":\n return status.credentials === \"present\";\n default:\n throw new Error(\"Unknown MCP auth mode.\", { cause: status satisfies never });\n }\n}\n\n/**\n * When stored auth stops authorizing operations because its access token expires without renewal,\n * whether or not that moment has passed. Null when a refresh token renews it or nothing expires.\n */\nexport function mcpAuthReadyUntil(\n authMode: string,\n authSecret: Pick<SecretRecord, \"payload\" | \"expiresAt\">,\n): Date | null {\n if (authMode !== \"oauth\" || !authSecret.expiresAt) {\n return null;\n }\n const auth = parseSecretPayload<StoredAuthPayload>(authSecret.payload);\n if (auth.type !== \"oauth\" || !auth.tokens?.access_token || auth.tokens.refresh_token) {\n return null;\n }\n return new Date(new Date(authSecret.expiresAt).getTime() - TOKEN_EXPIRY_SKEW_MS);\n}\n\n/**\n * Projects the newest unexpired, unconsumed state; any such state completes the same server.\n * States started before links were retained have no URL and cannot be resumed.\n */\nexport function pendingOAuthLink(\n state: { authorizationUrl: string | null; expiresAt: Date } | null,\n): { authorizationUrl: string; expiresAt: Date } | null {\n return state?.authorizationUrl\n ? { authorizationUrl: state.authorizationUrl, expiresAt: state.expiresAt }\n : null;\n}\n\n/** Start requests may override the configured client; otherwise the stored configuration applies. */\nexport function resolveOAuthStartClient(\n secrets: Pick<SecretRecord, \"kind\" | \"payload\">[],\n input: { scope?: string; clientId?: string; clientSecret?: string },\n) {\n const authSecret = secretByKind(secrets, \"auth\");\n const stored = authSecret ? parseSecretPayload<StoredAuthPayload>(authSecret.payload) : undefined;\n const configured = stored?.type === \"oauth\" ? stored : undefined;\n return {\n scope: input.scope ?? configured?.scopes?.join(\" \"),\n // A client ID and secret belong together; never pair an override with a stored secret.\n ...(input.clientId\n ? { clientId: input.clientId, clientSecret: input.clientSecret }\n : { clientId: configured?.clientId, clientSecret: configured?.clientSecret }),\n };\n}\n\n/** Clearing OAuth consent keeps the configured client and scopes for the next start. */\nexport function withoutOAuthConsent(payload: string) {\n const {\n tokens: _tokens,\n redirectUri: _redirectUri,\n ...configuration\n } = parseSecretPayload<Extract<StoredAuthPayload, { type: \"oauth\" }>>(payload);\n return stringifySecretPayload(configuration);\n}\n\nasync function parseSecretKind<T>(\n secrets: Pick<SecretRecord, \"kind\" | \"payload\">[],\n kind: string,\n): Promise<T | undefined> {\n const secret = secretByKind(secrets, kind);\n return secret ? parseSecretPayload<T>(secret.payload) : undefined;\n}\n\nexport async function createOAuthStartSnapshot(args: {\n config: McpFragmentConfig;\n server: { id: { toString(): string } | string; endpointUrl: string };\n secrets: Pick<SecretRecord, \"kind\" | \"payload\">[];\n stateId: string;\n redirectUri: string;\n scope?: string;\n clientId?: string;\n clientSecret?: string;\n}): Promise<{\n authorizationUrl: URL;\n changes: Awaited<ReturnType<typeof prepareOAuthChanges>>;\n}> {\n const snapshot: OAuthProviderSnapshot = {\n serverId: args.server.id.toString(),\n endpointUrl: args.server.endpointUrl,\n redirectUrl: args.redirectUri,\n scope: args.scope,\n stateId: args.stateId,\n clientId: args.clientId,\n clientSecret: args.clientSecret,\n clientInformation: await parseSecretKind(args.secrets, \"oauth-client\"),\n discoveryState: await parseSecretKind(args.secrets, \"oauth-discovery\"),\n };\n const provider = new BufferedOAuthClientProvider(snapshot);\n await auth(provider, {\n serverUrl: snapshot.endpointUrl,\n scope: args.scope,\n fetchFn: args.config.fetch,\n });\n const authorizationUrl = provider.changes.authorizationUrl;\n if (!authorizationUrl) {\n throw new Error(\"OAuth authorization did not produce a redirect URL\");\n }\n return { authorizationUrl, changes: await prepareOAuthChanges(provider.changes) };\n}\n\nexport async function createOAuthCallbackSnapshot(args: {\n config: McpFragmentConfig;\n stateId: string;\n code: string;\n iss: string | undefined;\n state: {\n id: { toString(): string } | string;\n codeVerifier: string;\n redirectUri: string;\n scope?: string | null;\n expiresAt: Date | string;\n consumedAt?: Date | string | null;\n };\n server: { id: { toString(): string } | string; endpointUrl: string };\n secrets: Pick<SecretRecord, \"kind\" | \"payload\">[];\n}): Promise<AuthPersistenceChanges> {\n const authPayload = await parseSecretKind<Extract<StoredAuthPayload, { type: \"oauth\" }>>(\n args.secrets,\n \"auth\",\n );\n const snapshot: OAuthProviderSnapshot = {\n serverId: args.server.id.toString(),\n endpointUrl: args.server.endpointUrl,\n redirectUrl: args.state.redirectUri,\n scope: args.state.scope ?? undefined,\n stateId: args.stateId,\n clientInformation: await parseSecretKind(args.secrets, \"oauth-client\"),\n tokens: authPayload?.tokens,\n discoveryState: await parseSecretKind(args.secrets, \"oauth-discovery\"),\n oauthState: {\n id: args.state.id.toString(),\n codeVerifier: args.state.codeVerifier,\n redirectUri: args.state.redirectUri,\n scope: args.state.scope ?? undefined,\n expiresAt: new Date(args.state.expiresAt),\n consumedAt: args.state.consumedAt ? new Date(args.state.consumedAt) : null,\n },\n };\n const provider = new BufferedOAuthClientProvider(snapshot);\n await auth(provider, {\n serverUrl: snapshot.endpointUrl,\n authorizationCode: args.code,\n iss: args.iss,\n scope: snapshot.scope,\n fetchFn: args.config.fetch,\n });\n provider.consumeState();\n const changes = await prepareOAuthChanges(provider.changes);\n return {\n ...changes,\n ...(changes.tokens\n ? {\n // Keep the configured client and scopes so consent can be started again later.\n authPayload: stringifySecretPayload({\n ...authPayload,\n type: \"oauth\",\n redirectUri: args.state.redirectUri,\n tokens: changes.tokens,\n }),\n authExpiresAt: tokenExpiry(changes.tokens),\n }\n : {}),\n };\n}\n\nconst providerHasChanges = (changes: OAuthProviderChanges) =>\n Boolean(\n changes.clientInformation ||\n changes.discoveryState ||\n changes.tokens ||\n changes.tokensInvalidated ||\n changes.clientInvalidated ||\n changes.discoveryInvalidated,\n );\n\nfunction authChangesReader(\n provider: BufferedOAuthClientProvider,\n authPayload: Exclude<StoredAuthPayload, { type: \"bearer\" }>,\n) {\n return async (): Promise<AuthPersistenceChanges | undefined> => {\n if (!providerHasChanges(provider.changes)) {\n return undefined;\n }\n const tokens = provider.tokens();\n return {\n ...(await prepareOAuthChanges(provider.changes)),\n ...(provider.changes.tokens || provider.changes.tokensInvalidated\n ? {\n authPayload: stringifySecretPayload({ ...authPayload, tokens }),\n authExpiresAt: tokens ? tokenExpiry(tokens) : null,\n }\n : {}),\n };\n };\n}\n\nexport async function createMcpOperationAuth(args: {\n config: McpFragmentConfig;\n server: { id: { toString(): string } | string; endpointUrl: string };\n secrets: SecretRecord[];\n}): Promise<McpOperationAuth | undefined> {\n const authSecret = secretByKind(args.secrets, \"auth\");\n const authPayload = authSecret\n ? parseSecretPayload<StoredAuthPayload>(authSecret.payload)\n : undefined;\n if (!authPayload) {\n return undefined;\n }\n if (authPayload.type === \"bearer\") {\n return { type: \"bearer\", token: authPayload.token };\n }\n if (authPayload.type === \"oauth\") {\n if (!authPayload.tokens?.access_token) {\n return undefined;\n }\n const clientSecret = secretByKind(args.secrets, \"oauth-client\");\n const discoverySecret = secretByKind(args.secrets, \"oauth-discovery\");\n const provider = new BufferedOAuthClientProvider({\n serverId: args.server.id.toString(),\n endpointUrl: args.server.endpointUrl,\n redirectUrl: authPayload.redirectUri,\n scope: authPayload.tokens.scope,\n clientInformation: clientSecret ? parseSecretPayload(clientSecret.payload) : undefined,\n tokens: authPayload.tokens,\n discoveryState: discoverySecret ? parseSecretPayload(discoverySecret.payload) : undefined,\n });\n return {\n type: \"provider\",\n provider,\n readAuthChanges: authChangesReader(provider, authPayload),\n };\n }\n\n const discoverySecret = secretByKind(args.secrets, \"oauth-discovery\");\n const provider = new BufferedOAuthClientProvider({\n serverId: args.server.id.toString(),\n endpointUrl: args.server.endpointUrl,\n scope: authPayload.scopes?.join(\" \"),\n clientId: authPayload.clientId,\n clientSecret: authPayload.clientSecret,\n tokens: authPayload.tokens,\n discoveryState: discoverySecret ? parseSecretPayload(discoverySecret.payload) : undefined,\n flow: \"client_credentials\",\n });\n return {\n type: \"provider\",\n provider,\n readAuthChanges: authChangesReader(provider, authPayload),\n };\n}\n\nexport async function resolveMcpOperationAuth(args: {\n config: McpFragmentConfig;\n server: { id: { toString(): string } | string; endpointUrl: string };\n secrets: SecretRecord[];\n}): Promise<{\n token?: string;\n authChanges?: AuthPersistenceChanges;\n}> {\n const authSecret = secretByKind(args.secrets, \"auth\");\n const authPayload = authSecret\n ? parseSecretPayload<StoredAuthPayload>(authSecret.payload)\n : undefined;\n if (!authPayload || authPayload.type === \"bearer\") {\n return { token: authPayload?.token };\n }\n if (authPayload.type === \"oauth\") {\n if (!authPayload.tokens?.access_token) {\n return {};\n }\n if (secretIsUnexpired(authSecret)) {\n return { token: authPayload.tokens.access_token };\n }\n if (!authPayload.tokens.refresh_token) {\n throw new Error(\"OAuth access token expired and no refresh token is available\");\n }\n\n const clientSecret = secretByKind(args.secrets, \"oauth-client\");\n const discoverySecret = secretByKind(args.secrets, \"oauth-discovery\");\n const provider = new BufferedOAuthClientProvider({\n serverId: args.server.id.toString(),\n endpointUrl: args.server.endpointUrl,\n redirectUrl: authPayload.redirectUri,\n scope: authPayload.tokens.scope,\n clientInformation: clientSecret ? parseSecretPayload(clientSecret.payload) : undefined,\n tokens: authPayload.tokens,\n discoveryState: discoverySecret ? parseSecretPayload(discoverySecret.payload) : undefined,\n });\n await auth(provider, {\n serverUrl: args.server.endpointUrl,\n scope: authPayload.tokens.scope,\n fetchFn: args.config.fetch,\n });\n const tokens = provider.changes.tokens ?? authPayload.tokens;\n if (!tokens.access_token) {\n throw new Error(\"OAuth refresh did not return an access token\");\n }\n return {\n token: tokens.access_token,\n authChanges: {\n ...(await prepareOAuthChanges(provider.changes)),\n authPayload: stringifySecretPayload({ ...authPayload, tokens }),\n authExpiresAt: tokenExpiry(tokens),\n },\n };\n }\n\n if (authPayload.tokens?.access_token && secretExpiresInFuture(authSecret)) {\n return { token: authPayload.tokens.access_token };\n }\n\n const discoverySecret = secretByKind(args.secrets, \"oauth-discovery\");\n const provider = new BufferedOAuthClientProvider({\n serverId: args.server.id.toString(),\n endpointUrl: args.server.endpointUrl,\n scope: authPayload.scopes?.join(\" \"),\n clientId: authPayload.clientId,\n clientSecret: authPayload.clientSecret,\n tokens: authPayload.tokens,\n discoveryState: discoverySecret ? parseSecretPayload(discoverySecret.payload) : undefined,\n flow: \"client_credentials\",\n });\n await auth(provider, {\n serverUrl: args.server.endpointUrl,\n scope: authPayload.scopes?.join(\" \"),\n fetchFn: args.config.fetch,\n });\n const tokens = provider.changes.tokens ?? authPayload.tokens;\n if (!tokens?.access_token) {\n throw new Error(\"Client credentials flow did not return an access token\");\n }\n return {\n token: tokens.access_token,\n authChanges: {\n ...(await prepareOAuthChanges(provider.changes)),\n authPayload: stringifySecretPayload({ ...authPayload, tokens }),\n authExpiresAt: tokenExpiry(tokens),\n },\n };\n}\n"],"mappings":";;;;AAgBA,SAAS,kBAAkB,OAAgB,aAAqD;CAC9F,IAAI,OACF,OAAO;EAAE,QAAQ;EAAQ,SAAS,uBAAuB,KAAK;CAAE;CAElE,OAAO,cAAc,EAAE,QAAQ,SAAS,IAAI,EAAE,QAAQ,OAAO;AAC/D;AAEA,eAAsB,oBAAoB,SAA+B;CACvE,OAAO;EACL,GAAG;EACH,cAAc,kBAAkB,QAAQ,mBAAmB,QAAQ,iBAAiB;EACpF,iBAAiB,kBAAkB,QAAQ,gBAAgB,QAAQ,oBAAoB;CACzF;AACF;;AAGA,SAAgB,wBACd,SACA,SACA;CACA,KAAK,MAAM,CAAC,MAAM,WAAW,CAC3B,CAAC,gBAAgB,QAAQ,YAAY,GACrC,CAAC,mBAAmB,QAAQ,eAAe,CAC7C,GACE,QAAQ,OAAO,QAAf;EACE,KAAK,QACH;EACF,KAAK;GACH,QAAQ,OAAO,MAAM,OAAO,OAAO;GACnC;EACF,KAAK;GACH,QAAQ,OAAO,IAAI;GACnB;EACF,SACE,MAAM,IAAI,MAAM,gCAAgC,EAAE,OAAO,OAAuB,CAAC;CACrF;AAEJ;AAEA,SAAgB,YAAY,QAAkE;CAC5F,OAAO,OAAO,OAAO,eAAe,WAChC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,aAAa,GAAI,IAC9C;AACN;AAgCA,SAAS,aAA+C,SAAoB,MAAc;CACxF,OAAO,QAAQ,MAAM,WAAW,OAAO,SAAS,IAAI;AACtD;AAEA,MAAM,uBAAuB;AAE7B,SAAS,sBAAsB,QAAqD;CAClF,IAAI,CAAC,QAAQ,WACX,OAAO;CAET,OAAO,IAAI,KAAK,OAAO,SAAS,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI,IAAI;AAC7D;AAEA,SAAS,kBAAkB,QAAqD;CAC9E,OAAO,CAAC,QAAQ,aAAa,sBAAsB,MAAM;AAC3D;;AAGA,SAAgB,qBAAqB,MAInB;CAEhB,MAAM,OAAO,KAAK;CAClB,IAAI,SAAS,QACX,OAAO,EAAE,KAAK;CAEhB,IAAI,SAAS,SACX,OAAO;EAAE;EAAM,aAAa,KAAK,aAAa,YAAY;CAAU;CAEtE,MAAM,OAAO,KAAK,aACd,mBAAsC,KAAK,WAAW,OAAO,IAC7D,KAAA;CACJ,MAAM,SAAS,MAAM,SAAS,UAAU,KAAK,SAAS,KAAA;CAEtD,IAAI,QAAQ,iBAAiB,OAAO,iBAAiB,kBAAkB,KAAK,UAAU,IACpF,OAAO;EAAE;EAAM,OAAO;CAAa;CAGrC,IAAI,KAAK,iBACP,OAAO;EAAE;EAAM,OAAO;CAAkB;CAE1C,OAAO;EAAE;EAAM,OAAO,QAAQ,eAAe,YAAY;CAAmB;AAC9E;;AAGA,SAAgB,eACd,UACA,YACS;CAET,MAAM,SAAS,qBAAqB;EAAE;EAAU;EAAY,iBAAiB;CAAM,CAAC;CACpF,QAAQ,OAAO,MAAf;EACE,KAAK,QACH,OAAO;EACT,KAAK,SACH,OAAO,OAAO,UAAU;EAC1B,KAAK;EACL,KAAK,sBACH,OAAO,OAAO,gBAAgB;EAChC,SACE,MAAM,IAAI,MAAM,0BAA0B,EAAE,OAAO,OAAuB,CAAC;CAC/E;AACF;;;;;AAMA,SAAgB,kBACd,UACA,YACa;CACb,IAAI,aAAa,WAAW,CAAC,WAAW,WACtC,OAAO;CAET,MAAM,OAAO,mBAAsC,WAAW,OAAO;CACrE,IAAI,KAAK,SAAS,WAAW,CAAC,KAAK,QAAQ,gBAAgB,KAAK,OAAO,eACrE,OAAO;CAET,uBAAO,IAAI,KAAK,IAAI,KAAK,WAAW,SAAS,CAAC,CAAC,QAAQ,IAAI,oBAAoB;AACjF;;;;;AAMA,SAAgB,iBACd,OACsD;CACtD,OAAO,OAAO,mBACV;EAAE,kBAAkB,MAAM;EAAkB,WAAW,MAAM;CAAU,IACvE;AACN;;AAGA,SAAgB,wBACd,SACA,OACA;CACA,MAAM,aAAa,aAAa,SAAS,MAAM;CAC/C,MAAM,SAAS,aAAa,mBAAsC,WAAW,OAAO,IAAI,KAAA;CACxF,MAAM,aAAa,QAAQ,SAAS,UAAU,SAAS,KAAA;CACvD,OAAO;EACL,OAAO,MAAM,SAAS,YAAY,QAAQ,KAAK,GAAG;EAElD,GAAI,MAAM,WACN;GAAE,UAAU,MAAM;GAAU,cAAc,MAAM;EAAa,IAC7D;GAAE,UAAU,YAAY;GAAU,cAAc,YAAY;EAAa;CAC/E;AACF;;AAGA,SAAgB,oBAAoB,SAAiB;CACnD,MAAM,EACJ,QAAQ,SACR,aAAa,cACb,GAAG,kBACD,mBAAkE,OAAO;CAC7E,OAAO,uBAAuB,aAAa;AAC7C;AAEA,eAAe,gBACb,SACA,MACwB;CACxB,MAAM,SAAS,aAAa,SAAS,IAAI;CACzC,OAAO,SAAS,mBAAsB,OAAO,OAAO,IAAI,KAAA;AAC1D;AAEA,eAAsB,yBAAyB,MAY5C;CACD,MAAM,WAAkC;EACtC,UAAU,KAAK,OAAO,GAAG,SAAS;EAClC,aAAa,KAAK,OAAO;EACzB,aAAa,KAAK;EAClB,OAAO,KAAK;EACZ,SAAS,KAAK;EACd,UAAU,KAAK;EACf,cAAc,KAAK;EACnB,mBAAmB,MAAM,gBAAgB,KAAK,SAAS,cAAc;EACrE,gBAAgB,MAAM,gBAAgB,KAAK,SAAS,iBAAiB;CACvE;CACA,MAAM,WAAW,IAAI,4BAA4B,QAAQ;CACzD,MAAM,KAAK,UAAU;EACnB,WAAW,SAAS;EACpB,OAAO,KAAK;EACZ,SAAS,KAAK,OAAO;CACvB,CAAC;CACD,MAAM,mBAAmB,SAAS,QAAQ;CAC1C,IAAI,CAAC,kBACH,MAAM,IAAI,MAAM,oDAAoD;CAEtE,OAAO;EAAE;EAAkB,SAAS,MAAM,oBAAoB,SAAS,OAAO;CAAE;AAClF;AAEA,eAAsB,4BAA4B,MAed;CAClC,MAAM,cAAc,MAAM,gBACxB,KAAK,SACL,MACF;CACA,MAAM,WAAkC;EACtC,UAAU,KAAK,OAAO,GAAG,SAAS;EAClC,aAAa,KAAK,OAAO;EACzB,aAAa,KAAK,MAAM;EACxB,OAAO,KAAK,MAAM,SAAS,KAAA;EAC3B,SAAS,KAAK;EACd,mBAAmB,MAAM,gBAAgB,KAAK,SAAS,cAAc;EACrE,QAAQ,aAAa;EACrB,gBAAgB,MAAM,gBAAgB,KAAK,SAAS,iBAAiB;EACrE,YAAY;GACV,IAAI,KAAK,MAAM,GAAG,SAAS;GAC3B,cAAc,KAAK,MAAM;GACzB,aAAa,KAAK,MAAM;GACxB,OAAO,KAAK,MAAM,SAAS,KAAA;GAC3B,WAAW,IAAI,KAAK,KAAK,MAAM,SAAS;GACxC,YAAY,KAAK,MAAM,aAAa,IAAI,KAAK,KAAK,MAAM,UAAU,IAAI;EACxE;CACF;CACA,MAAM,WAAW,IAAI,4BAA4B,QAAQ;CACzD,MAAM,KAAK,UAAU;EACnB,WAAW,SAAS;EACpB,mBAAmB,KAAK;EACxB,KAAK,KAAK;EACV,OAAO,SAAS;EAChB,SAAS,KAAK,OAAO;CACvB,CAAC;CACD,SAAS,aAAa;CACtB,MAAM,UAAU,MAAM,oBAAoB,SAAS,OAAO;CAC1D,OAAO;EACL,GAAG;EACH,GAAI,QAAQ,SACR;GAEE,aAAa,uBAAuB;IAClC,GAAG;IACH,MAAM;IACN,aAAa,KAAK,MAAM;IACxB,QAAQ,QAAQ;GAClB,CAAC;GACD,eAAe,YAAY,QAAQ,MAAM;EAC3C,IACA,CAAC;CACP;AACF;AAEA,MAAM,sBAAsB,YAC1B,QACE,QAAQ,qBACR,QAAQ,kBACR,QAAQ,UACR,QAAQ,qBACR,QAAQ,qBACR,QAAQ,oBACV;AAEF,SAAS,kBACP,UACA,aACA;CACA,OAAO,YAAyD;EAC9D,IAAI,CAAC,mBAAmB,SAAS,OAAO,GACtC;EAEF,MAAM,SAAS,SAAS,OAAO;EAC/B,OAAO;GACL,GAAI,MAAM,oBAAoB,SAAS,OAAO;GAC9C,GAAI,SAAS,QAAQ,UAAU,SAAS,QAAQ,oBAC5C;IACE,aAAa,uBAAuB;KAAE,GAAG;KAAa;IAAO,CAAC;IAC9D,eAAe,SAAS,YAAY,MAAM,IAAI;GAChD,IACA,CAAC;EACP;CACF;AACF;AAEA,eAAsB,uBAAuB,MAIH;CACxC,MAAM,aAAa,aAAa,KAAK,SAAS,MAAM;CACpD,MAAM,cAAc,aAChB,mBAAsC,WAAW,OAAO,IACxD,KAAA;CACJ,IAAI,CAAC,aACH;CAEF,IAAI,YAAY,SAAS,UACvB,OAAO;EAAE,MAAM;EAAU,OAAO,YAAY;CAAM;CAEpD,IAAI,YAAY,SAAS,SAAS;EAChC,IAAI,CAAC,YAAY,QAAQ,cACvB;EAEF,MAAM,eAAe,aAAa,KAAK,SAAS,cAAc;EAC9D,MAAM,kBAAkB,aAAa,KAAK,SAAS,iBAAiB;EACpE,MAAM,WAAW,IAAI,4BAA4B;GAC/C,UAAU,KAAK,OAAO,GAAG,SAAS;GAClC,aAAa,KAAK,OAAO;GACzB,aAAa,YAAY;GACzB,OAAO,YAAY,OAAO;GAC1B,mBAAmB,eAAe,mBAAmB,aAAa,OAAO,IAAI,KAAA;GAC7E,QAAQ,YAAY;GACpB,gBAAgB,kBAAkB,mBAAmB,gBAAgB,OAAO,IAAI,KAAA;EAClF,CAAC;EACD,OAAO;GACL,MAAM;GACN;GACA,iBAAiB,kBAAkB,UAAU,WAAW;EAC1D;CACF;CAEA,MAAM,kBAAkB,aAAa,KAAK,SAAS,iBAAiB;CACpE,MAAM,WAAW,IAAI,4BAA4B;EAC/C,UAAU,KAAK,OAAO,GAAG,SAAS;EAClC,aAAa,KAAK,OAAO;EACzB,OAAO,YAAY,QAAQ,KAAK,GAAG;EACnC,UAAU,YAAY;EACtB,cAAc,YAAY;EAC1B,QAAQ,YAAY;EACpB,gBAAgB,kBAAkB,mBAAmB,gBAAgB,OAAO,IAAI,KAAA;EAChF,MAAM;CACR,CAAC;CACD,OAAO;EACL,MAAM;EACN;EACA,iBAAiB,kBAAkB,UAAU,WAAW;CAC1D;AACF;AAEA,eAAsB,wBAAwB,MAO3C;CACD,MAAM,aAAa,aAAa,KAAK,SAAS,MAAM;CACpD,MAAM,cAAc,aAChB,mBAAsC,WAAW,OAAO,IACxD,KAAA;CACJ,IAAI,CAAC,eAAe,YAAY,SAAS,UACvC,OAAO,EAAE,OAAO,aAAa,MAAM;CAErC,IAAI,YAAY,SAAS,SAAS;EAChC,IAAI,CAAC,YAAY,QAAQ,cACvB,OAAO,CAAC;EAEV,IAAI,kBAAkB,UAAU,GAC9B,OAAO,EAAE,OAAO,YAAY,OAAO,aAAa;EAElD,IAAI,CAAC,YAAY,OAAO,eACtB,MAAM,IAAI,MAAM,8DAA8D;EAGhF,MAAM,eAAe,aAAa,KAAK,SAAS,cAAc;EAC9D,MAAM,kBAAkB,aAAa,KAAK,SAAS,iBAAiB;EACpE,MAAM,WAAW,IAAI,4BAA4B;GAC/C,UAAU,KAAK,OAAO,GAAG,SAAS;GAClC,aAAa,KAAK,OAAO;GACzB,aAAa,YAAY;GACzB,OAAO,YAAY,OAAO;GAC1B,mBAAmB,eAAe,mBAAmB,aAAa,OAAO,IAAI,KAAA;GAC7E,QAAQ,YAAY;GACpB,gBAAgB,kBAAkB,mBAAmB,gBAAgB,OAAO,IAAI,KAAA;EAClF,CAAC;EACD,MAAM,KAAK,UAAU;GACnB,WAAW,KAAK,OAAO;GACvB,OAAO,YAAY,OAAO;GAC1B,SAAS,KAAK,OAAO;EACvB,CAAC;EACD,MAAM,SAAS,SAAS,QAAQ,UAAU,YAAY;EACtD,IAAI,CAAC,OAAO,cACV,MAAM,IAAI,MAAM,8CAA8C;EAEhE,OAAO;GACL,OAAO,OAAO;GACd,aAAa;IACX,GAAI,MAAM,oBAAoB,SAAS,OAAO;IAC9C,aAAa,uBAAuB;KAAE,GAAG;KAAa;IAAO,CAAC;IAC9D,eAAe,YAAY,MAAM;GACnC;EACF;CACF;CAEA,IAAI,YAAY,QAAQ,gBAAgB,sBAAsB,UAAU,GACtE,OAAO,EAAE,OAAO,YAAY,OAAO,aAAa;CAGlD,MAAM,kBAAkB,aAAa,KAAK,SAAS,iBAAiB;CACpE,MAAM,WAAW,IAAI,4BAA4B;EAC/C,UAAU,KAAK,OAAO,GAAG,SAAS;EAClC,aAAa,KAAK,OAAO;EACzB,OAAO,YAAY,QAAQ,KAAK,GAAG;EACnC,UAAU,YAAY;EACtB,cAAc,YAAY;EAC1B,QAAQ,YAAY;EACpB,gBAAgB,kBAAkB,mBAAmB,gBAAgB,OAAO,IAAI,KAAA;EAChF,MAAM;CACR,CAAC;CACD,MAAM,KAAK,UAAU;EACnB,WAAW,KAAK,OAAO;EACvB,OAAO,YAAY,QAAQ,KAAK,GAAG;EACnC,SAAS,KAAK,OAAO;CACvB,CAAC;CACD,MAAM,SAAS,SAAS,QAAQ,UAAU,YAAY;CACtD,IAAI,CAAC,QAAQ,cACX,MAAM,IAAI,MAAM,wDAAwD;CAE1E,OAAO;EACL,OAAO,OAAO;EACd,aAAa;GACX,GAAI,MAAM,oBAAoB,SAAS,OAAO;GAC9C,aAAa,uBAAuB;IAAE,GAAG;IAAa;GAAO,CAAC;GAC9D,eAAe,YAAY,MAAM;EACnC;CACF;AACF"}
@@ -0,0 +1,42 @@
1
+ import { CallToolResult, Tool } from "@modelcontextprotocol/server";
2
+ //#region src/testing/mcp-test-server.d.ts
3
+ /** Tools declare raw JSON Schemas so tests can publish any dialect a real server might. */
4
+ export interface TestMcpTool {
5
+ name: string;
6
+ title?: string;
7
+ description?: string;
8
+ inputSchema: Tool["inputSchema"];
9
+ outputSchema?: Tool["outputSchema"];
10
+ call: (args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>;
11
+ }
12
+ export interface TestMcpServerOptions {
13
+ /** 2026-07-28 serves stateless requests only; 2025-11-25 serves sessions only. */
14
+ protocolVersion: "2025-11-25" | "2026-07-28";
15
+ requiredBearerToken?: string;
16
+ enableJsonResponse?: boolean;
17
+ tools?: readonly TestMcpTool[];
18
+ oauth?: boolean;
19
+ disableDynamicRegistration?: boolean;
20
+ requiredClientId?: string;
21
+ requiredClientSecret?: string;
22
+ refreshedRefreshToken?: string;
23
+ omitRefreshTokenOnRefresh?: boolean;
24
+ /** Consent grants only an access token, which stops working when it expires. */
25
+ omitRefreshTokenOnConsent?: boolean;
26
+ failAuthorizedMcpRequests?: boolean;
27
+ }
28
+ /** An in-process streamable HTTP MCP server, with optional OAuth, served from one origin. */
29
+ export interface TestMcpServer {
30
+ fetch: (request: Request) => Promise<Response>;
31
+ getMcpRequestCount: () => number;
32
+ getTokenRequestCount: () => number;
33
+ /** Rejects every client registered so far, as a server that lost its registrations would. */
34
+ forgetRegisteredClients: () => void;
35
+ /** Sets how long access tokens issued from now on last; one hour until changed. */
36
+ setTokenLifetimeSeconds: (seconds: number) => void;
37
+ close: () => Promise<void>;
38
+ }
39
+ /** Serves MCP at `<origin>/mcp`; metadata URLs derive from each request's origin. */
40
+ export declare function createTestMcpServer(options?: TestMcpServerOptions): TestMcpServer;
41
+ //#endregion
42
+ //# sourceMappingURL=mcp-test-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-test-server.d.ts","names":[],"sources":["../../../src/testing/mcp-test-server.ts"],"mappings":";;;iBASiB;EACf;EACA;EACA;EACA,aAAa;EACb,eAAe;EACf,OAAO,MAAM,4BAA4B,iBAAiB,QAAQ;;iBAGnD;;EAEf;EACA;EACA;EACA,iBAAiB;EACjB;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;;;iBAIe;EACf,QAAQ,SAAS,YAAY,QAAQ;EACrC;EACA;;EAEA;;EAEA,0BAA0B;EAC1B,aAAa;;;wBAgJC,oBACd,UAAS,uBACR"}
@@ -0,0 +1,218 @@
1
+ import { Server, WebStandardStreamableHTTPServerTransport, createMcpHandler } from "@modelcontextprotocol/server";
2
+ //#region src/testing/mcp-test-server.ts
3
+ const echoTool = {
4
+ name: "echo",
5
+ title: "Echo",
6
+ description: "Echo a message",
7
+ inputSchema: {
8
+ $schema: "http://json-schema.org/draft-07/schema#",
9
+ type: "object",
10
+ properties: { text: { type: "string" } },
11
+ required: ["text"],
12
+ additionalProperties: false
13
+ },
14
+ outputSchema: {
15
+ $schema: "http://json-schema.org/draft-07/schema#",
16
+ type: "object",
17
+ properties: { echoed: { type: "string" } },
18
+ required: ["echoed"],
19
+ additionalProperties: false
20
+ },
21
+ call: ({ text }) => ({
22
+ content: [{
23
+ type: "text",
24
+ text: String(text)
25
+ }],
26
+ structuredContent: { echoed: String(text) }
27
+ })
28
+ };
29
+ function json(body, status = 200) {
30
+ return Response.json(body, { status });
31
+ }
32
+ function requestClientId(request, form) {
33
+ const authorization = request.headers.get("authorization");
34
+ if (authorization?.startsWith("Basic ")) return atob(authorization.slice(6)).split(":")[0];
35
+ return form.get("client_id");
36
+ }
37
+ function hasExpectedClientAuth(request, form, options, registeredClients) {
38
+ if (!options.requiredClientId) return options.disableDynamicRegistration === true || registeredClients.has(requestClientId(request, form) ?? "");
39
+ const authorization = request.headers.get("authorization");
40
+ if (authorization?.startsWith("Basic ")) return atob(authorization.slice(6)) === `${options.requiredClientId}:${options.requiredClientSecret ?? ""}`;
41
+ return form.get("client_id") === options.requiredClientId && form.get("client_secret") === (options.requiredClientSecret ?? "");
42
+ }
43
+ function createMcpProtocolServer(tools) {
44
+ const server = new Server({
45
+ name: "fragno-test-mcp-server",
46
+ version: "1.0.0"
47
+ }, { capabilities: tools.length > 0 ? { tools: {} } : {} });
48
+ if (tools.length > 0) {
49
+ server.setRequestHandler("tools/list", () => ({ tools: tools.map(({ call: _call, ...tool }) => tool) }));
50
+ server.setRequestHandler("tools/call", async ({ params }) => {
51
+ const tool = tools.find((candidate) => candidate.name === params.name);
52
+ if (!tool) return {
53
+ isError: true,
54
+ content: [{
55
+ type: "text",
56
+ text: `Tool ${params.name} not found`
57
+ }]
58
+ };
59
+ return server.projectCallToolResult(await tool.call(params.arguments ?? {}), tool.outputSchema);
60
+ });
61
+ }
62
+ return server;
63
+ }
64
+ async function handleToken(request, options, registeredClients, tokenLifetimeSeconds) {
65
+ const form = new URLSearchParams(await request.text());
66
+ if (!hasExpectedClientAuth(request, form, options, registeredClients)) return json({ error: "invalid_client" }, 401);
67
+ const grantType = form.get("grant_type");
68
+ if (grantType === "client_credentials") return json({
69
+ access_token: options.requiredBearerToken ?? "client-credentials-access-token",
70
+ token_type: "Bearer",
71
+ expires_in: tokenLifetimeSeconds,
72
+ scope: form.get("scope") ?? "tools"
73
+ });
74
+ if (grantType === "refresh_token") {
75
+ if (form.get("refresh_token") !== "oauth-refresh-token") return json({ error: "invalid_grant" }, 400);
76
+ return json({
77
+ access_token: options.requiredBearerToken ?? "oauth-access-token",
78
+ ...options.omitRefreshTokenOnRefresh ? {} : { refresh_token: options.refreshedRefreshToken ?? "oauth-refresh-token" },
79
+ token_type: "Bearer",
80
+ expires_in: tokenLifetimeSeconds,
81
+ scope: "tools"
82
+ });
83
+ }
84
+ if (grantType !== "authorization_code" || form.get("code") !== "valid-code") return json({ error: "invalid_grant" }, 400);
85
+ if (!form.get("code_verifier")) return json({ error: "invalid_request" }, 400);
86
+ return json({
87
+ access_token: options.requiredBearerToken ?? "oauth-access-token",
88
+ ...options.omitRefreshTokenOnConsent ? {} : { refresh_token: "oauth-refresh-token" },
89
+ token_type: "Bearer",
90
+ expires_in: tokenLifetimeSeconds,
91
+ scope: "tools"
92
+ });
93
+ }
94
+ /** Serves MCP at `<origin>/mcp`; metadata URLs derive from each request's origin. */
95
+ function createTestMcpServer(options = { protocolVersion: "2026-07-28" }) {
96
+ const tools = options.tools ?? [echoTool];
97
+ const modernHandler = createMcpHandler(() => createMcpProtocolServer(tools), {
98
+ legacy: "reject",
99
+ responseMode: options.enableJsonResponse ? "auto" : "sse"
100
+ });
101
+ const sessions = /* @__PURE__ */ new Map();
102
+ let mcpRequestCount = 0;
103
+ let tokenRequestCount = 0;
104
+ let registrationCount = 0;
105
+ let tokenLifetimeSeconds = 3600;
106
+ const registeredClients = /* @__PURE__ */ new Set();
107
+ async function createSession() {
108
+ const server = createMcpProtocolServer(tools);
109
+ const transport = new WebStandardStreamableHTTPServerTransport({
110
+ sessionIdGenerator: () => crypto.randomUUID(),
111
+ onsessioninitialized: (sessionId) => {
112
+ sessions.set(sessionId, {
113
+ server,
114
+ transport
115
+ });
116
+ },
117
+ enableJsonResponse: options.enableJsonResponse ?? false
118
+ });
119
+ await server.connect(transport);
120
+ return {
121
+ server,
122
+ transport
123
+ };
124
+ }
125
+ async function handle(request) {
126
+ const url = new URL(request.url);
127
+ const baseUrl = url.origin;
128
+ if (options.oauth && url.pathname.startsWith("/.well-known/oauth-protected-resource")) return json({
129
+ resource: `${baseUrl}/mcp`,
130
+ authorization_servers: [baseUrl],
131
+ scopes_supported: ["tools"]
132
+ });
133
+ if (options.oauth && url.pathname === "/.well-known/oauth-authorization-server") return json({
134
+ issuer: baseUrl,
135
+ authorization_response_iss_parameter_supported: true,
136
+ authorization_endpoint: `${baseUrl}/authorize`,
137
+ token_endpoint: `${baseUrl}/token`,
138
+ ...options.disableDynamicRegistration ? {} : { registration_endpoint: `${baseUrl}/register` },
139
+ response_types_supported: ["code"],
140
+ grant_types_supported: [
141
+ "authorization_code",
142
+ "refresh_token",
143
+ "client_credentials"
144
+ ],
145
+ token_endpoint_auth_methods_supported: ["client_secret_post", "none"],
146
+ code_challenge_methods_supported: ["S256"]
147
+ });
148
+ if (options.oauth && !options.disableDynamicRegistration && url.pathname === "/register" && request.method === "POST") {
149
+ const rawBody = await request.text();
150
+ const metadata = rawBody ? JSON.parse(rawBody) : {};
151
+ registrationCount += 1;
152
+ const clientId = `test-client-${registrationCount}`;
153
+ registeredClients.add(clientId);
154
+ return json({
155
+ ...metadata,
156
+ client_id: clientId,
157
+ client_secret: "test-secret"
158
+ });
159
+ }
160
+ if (options.oauth && url.pathname === "/authorize") {
161
+ const redirectUri = url.searchParams.get("redirect_uri");
162
+ if (!redirectUri) return new Response("Missing redirect_uri", { status: 400 });
163
+ if (!options.disableDynamicRegistration && !options.requiredClientId && !registeredClients.has(url.searchParams.get("client_id") ?? "")) return new Response("Unknown client", { status: 400 });
164
+ const redirect = new URL(redirectUri);
165
+ redirect.searchParams.set("code", "valid-code");
166
+ redirect.searchParams.set("iss", baseUrl);
167
+ const state = url.searchParams.get("state");
168
+ if (state) redirect.searchParams.set("state", state);
169
+ return new Response(null, {
170
+ status: 302,
171
+ headers: { Location: redirect.toString() }
172
+ });
173
+ }
174
+ if (options.oauth && url.pathname === "/token" && request.method === "POST") {
175
+ tokenRequestCount += 1;
176
+ return await handleToken(request, options, registeredClients, tokenLifetimeSeconds);
177
+ }
178
+ if (url.pathname !== "/mcp") return new Response("Not found", { status: 404 });
179
+ mcpRequestCount += 1;
180
+ if (options.requiredBearerToken && request.headers.get("authorization") !== `Bearer ${options.requiredBearerToken}`) return new Response("Unauthorized", {
181
+ status: 401,
182
+ headers: options.oauth ? { "WWW-Authenticate": `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource/mcp"` } : {}
183
+ });
184
+ if (options.failAuthorizedMcpRequests) return new Response("MCP operation failed", { status: 500 });
185
+ if (options.protocolVersion === "2026-07-28") return await modernHandler.fetch(request);
186
+ const sessionId = request.headers.get("mcp-session-id");
187
+ return await ((sessionId ? sessions.get(sessionId) : void 0) ?? await createSession()).transport.handleRequest(request);
188
+ }
189
+ return {
190
+ fetch: async (request) => {
191
+ try {
192
+ return await handle(request);
193
+ } catch (error) {
194
+ return new Response(error instanceof Error ? error.message : "MCP transport error", { status: 500 });
195
+ }
196
+ },
197
+ getMcpRequestCount: () => mcpRequestCount,
198
+ getTokenRequestCount: () => tokenRequestCount,
199
+ forgetRegisteredClients: () => {
200
+ registeredClients.clear();
201
+ },
202
+ setTokenLifetimeSeconds: (seconds) => {
203
+ tokenLifetimeSeconds = seconds;
204
+ },
205
+ close: async () => {
206
+ await modernHandler.close();
207
+ for (const session of sessions.values()) {
208
+ await session.server.close();
209
+ await session.transport.close();
210
+ }
211
+ sessions.clear();
212
+ }
213
+ };
214
+ }
215
+ //#endregion
216
+ export { createTestMcpServer };
217
+
218
+ //# sourceMappingURL=mcp-test-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-test-server.js","names":[],"sources":["../../../src/testing/mcp-test-server.ts"],"sourcesContent":["import {\n createMcpHandler,\n Server,\n WebStandardStreamableHTTPServerTransport,\n type CallToolResult,\n type Tool,\n} from \"@modelcontextprotocol/server\";\n\n/** Tools declare raw JSON Schemas so tests can publish any dialect a real server might. */\nexport interface TestMcpTool {\n name: string;\n title?: string;\n description?: string;\n inputSchema: Tool[\"inputSchema\"];\n outputSchema?: Tool[\"outputSchema\"];\n call: (args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>;\n}\n\nexport interface TestMcpServerOptions {\n /** 2026-07-28 serves stateless requests only; 2025-11-25 serves sessions only. */\n protocolVersion: \"2025-11-25\" | \"2026-07-28\";\n requiredBearerToken?: string;\n enableJsonResponse?: boolean;\n tools?: readonly TestMcpTool[];\n oauth?: boolean;\n disableDynamicRegistration?: boolean;\n requiredClientId?: string;\n requiredClientSecret?: string;\n refreshedRefreshToken?: string;\n omitRefreshTokenOnRefresh?: boolean;\n /** Consent grants only an access token, which stops working when it expires. */\n omitRefreshTokenOnConsent?: boolean;\n failAuthorizedMcpRequests?: boolean;\n}\n\n/** An in-process streamable HTTP MCP server, with optional OAuth, served from one origin. */\nexport interface TestMcpServer {\n fetch: (request: Request) => Promise<Response>;\n getMcpRequestCount: () => number;\n getTokenRequestCount: () => number;\n /** Rejects every client registered so far, as a server that lost its registrations would. */\n forgetRegisteredClients: () => void;\n /** Sets how long access tokens issued from now on last; one hour until changed. */\n setTokenLifetimeSeconds: (seconds: number) => void;\n close: () => Promise<void>;\n}\n\n// Servers built on the v1 SDK emit draft-07 schemas; this echo tool publishes the same dialect.\nconst echoTool: TestMcpTool = {\n name: \"echo\",\n title: \"Echo\",\n description: \"Echo a message\",\n inputSchema: {\n $schema: \"http://json-schema.org/draft-07/schema#\",\n type: \"object\",\n properties: { text: { type: \"string\" } },\n required: [\"text\"],\n additionalProperties: false,\n },\n outputSchema: {\n $schema: \"http://json-schema.org/draft-07/schema#\",\n type: \"object\",\n properties: { echoed: { type: \"string\" } },\n required: [\"echoed\"],\n additionalProperties: false,\n },\n call: ({ text }) => ({\n content: [{ type: \"text\", text: String(text) }],\n structuredContent: { echoed: String(text) },\n }),\n};\n\nfunction json(body: unknown, status = 200) {\n return Response.json(body, { status });\n}\n\nfunction requestClientId(request: Request, form: URLSearchParams) {\n const authorization = request.headers.get(\"authorization\");\n if (authorization?.startsWith(\"Basic \")) {\n return atob(authorization.slice(\"Basic \".length)).split(\":\")[0];\n }\n return form.get(\"client_id\");\n}\n\nfunction hasExpectedClientAuth(\n request: Request,\n form: URLSearchParams,\n options: TestMcpServerOptions,\n registeredClients: ReadonlySet<string>,\n) {\n if (!options.requiredClientId) {\n // Registered clients authenticate only while the server still knows them.\n return (\n options.disableDynamicRegistration === true ||\n registeredClients.has(requestClientId(request, form) ?? \"\")\n );\n }\n const authorization = request.headers.get(\"authorization\");\n if (authorization?.startsWith(\"Basic \")) {\n return (\n atob(authorization.slice(\"Basic \".length)) ===\n `${options.requiredClientId}:${options.requiredClientSecret ?? \"\"}`\n );\n }\n return (\n form.get(\"client_id\") === options.requiredClientId &&\n form.get(\"client_secret\") === (options.requiredClientSecret ?? \"\")\n );\n}\n\nfunction createMcpProtocolServer(tools: readonly TestMcpTool[]) {\n // oxlint-disable-next-line typescript/no-deprecated -- Raw JSON Schemas need the low-level server.\n const server = new Server(\n { name: \"fragno-test-mcp-server\", version: \"1.0.0\" },\n { capabilities: tools.length > 0 ? { tools: {} } : {} },\n );\n if (tools.length > 0) {\n server.setRequestHandler(\"tools/list\", () => ({\n tools: tools.map(({ call: _call, ...tool }) => tool),\n }));\n server.setRequestHandler(\"tools/call\", async ({ params }) => {\n const tool = tools.find((candidate) => candidate.name === params.name);\n if (!tool) {\n // Mirrors McpServer: an unknown tool is a tool error, not a protocol failure.\n return {\n isError: true,\n content: [{ type: \"text\", text: `Tool ${params.name} not found` }],\n };\n }\n // Low-level handlers project results themselves to match the negotiated protocol's wire shape.\n return server.projectCallToolResult(\n await tool.call(params.arguments ?? {}),\n tool.outputSchema,\n );\n });\n }\n return server;\n}\n\nasync function handleToken(\n request: Request,\n options: TestMcpServerOptions,\n registeredClients: ReadonlySet<string>,\n tokenLifetimeSeconds: number,\n) {\n const form = new URLSearchParams(await request.text());\n if (!hasExpectedClientAuth(request, form, options, registeredClients)) {\n return json({ error: \"invalid_client\" }, 401);\n }\n const grantType = form.get(\"grant_type\");\n if (grantType === \"client_credentials\") {\n return json({\n access_token: options.requiredBearerToken ?? \"client-credentials-access-token\",\n token_type: \"Bearer\",\n expires_in: tokenLifetimeSeconds,\n scope: form.get(\"scope\") ?? \"tools\",\n });\n }\n if (grantType === \"refresh_token\") {\n if (form.get(\"refresh_token\") !== \"oauth-refresh-token\") {\n return json({ error: \"invalid_grant\" }, 400);\n }\n return json({\n access_token: options.requiredBearerToken ?? \"oauth-access-token\",\n ...(options.omitRefreshTokenOnRefresh\n ? {}\n : { refresh_token: options.refreshedRefreshToken ?? \"oauth-refresh-token\" }),\n token_type: \"Bearer\",\n expires_in: tokenLifetimeSeconds,\n scope: \"tools\",\n });\n }\n if (grantType !== \"authorization_code\" || form.get(\"code\") !== \"valid-code\") {\n return json({ error: \"invalid_grant\" }, 400);\n }\n if (!form.get(\"code_verifier\")) {\n return json({ error: \"invalid_request\" }, 400);\n }\n return json({\n access_token: options.requiredBearerToken ?? \"oauth-access-token\",\n ...(options.omitRefreshTokenOnConsent ? {} : { refresh_token: \"oauth-refresh-token\" }),\n token_type: \"Bearer\",\n expires_in: tokenLifetimeSeconds,\n scope: \"tools\",\n });\n}\n\n/** Serves MCP at `<origin>/mcp`; metadata URLs derive from each request's origin. */\nexport function createTestMcpServer(\n options: TestMcpServerOptions = { protocolVersion: \"2026-07-28\" },\n): TestMcpServer {\n const tools = options.tools ?? [echoTool];\n const modernHandler = createMcpHandler(() => createMcpProtocolServer(tools), {\n legacy: \"reject\",\n responseMode: options.enableJsonResponse ? \"auto\" : \"sse\",\n });\n const sessions = new Map<\n string,\n {\n // oxlint-disable-next-line typescript/no-deprecated -- See createMcpProtocolServer.\n server: Server;\n transport: WebStandardStreamableHTTPServerTransport;\n }\n >();\n let mcpRequestCount = 0;\n let tokenRequestCount = 0;\n let registrationCount = 0;\n let tokenLifetimeSeconds = 3600;\n const registeredClients = new Set<string>();\n\n async function createSession() {\n const server = createMcpProtocolServer(tools);\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: () => crypto.randomUUID(),\n onsessioninitialized: (sessionId) => {\n sessions.set(sessionId, { server, transport });\n },\n enableJsonResponse: options.enableJsonResponse ?? false,\n });\n await server.connect(transport);\n return { server, transport };\n }\n\n async function handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const baseUrl = url.origin;\n\n if (options.oauth && url.pathname.startsWith(\"/.well-known/oauth-protected-resource\")) {\n return json({\n resource: `${baseUrl}/mcp`,\n authorization_servers: [baseUrl],\n scopes_supported: [\"tools\"],\n });\n }\n if (options.oauth && url.pathname === \"/.well-known/oauth-authorization-server\") {\n return json({\n issuer: baseUrl,\n authorization_response_iss_parameter_supported: true,\n authorization_endpoint: `${baseUrl}/authorize`,\n token_endpoint: `${baseUrl}/token`,\n ...(options.disableDynamicRegistration\n ? {}\n : { registration_endpoint: `${baseUrl}/register` }),\n response_types_supported: [\"code\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\", \"client_credentials\"],\n token_endpoint_auth_methods_supported: [\"client_secret_post\", \"none\"],\n code_challenge_methods_supported: [\"S256\"],\n });\n }\n if (\n options.oauth &&\n !options.disableDynamicRegistration &&\n url.pathname === \"/register\" &&\n request.method === \"POST\"\n ) {\n const rawBody = await request.text();\n const metadata = rawBody ? (JSON.parse(rawBody) as Record<string, unknown>) : {};\n registrationCount += 1;\n const clientId = `test-client-${registrationCount}`;\n registeredClients.add(clientId);\n return json({ ...metadata, client_id: clientId, client_secret: \"test-secret\" });\n }\n if (options.oauth && url.pathname === \"/authorize\") {\n // Consent is immediate: the redirect carries a fixed code and the caller's state.\n const redirectUri = url.searchParams.get(\"redirect_uri\");\n if (!redirectUri) {\n return new Response(\"Missing redirect_uri\", { status: 400 });\n }\n if (\n !options.disableDynamicRegistration &&\n !options.requiredClientId &&\n !registeredClients.has(url.searchParams.get(\"client_id\") ?? \"\")\n ) {\n return new Response(\"Unknown client\", { status: 400 });\n }\n const redirect = new URL(redirectUri);\n redirect.searchParams.set(\"code\", \"valid-code\");\n redirect.searchParams.set(\"iss\", baseUrl);\n const state = url.searchParams.get(\"state\");\n if (state) {\n redirect.searchParams.set(\"state\", state);\n }\n return new Response(null, { status: 302, headers: { Location: redirect.toString() } });\n }\n if (options.oauth && url.pathname === \"/token\" && request.method === \"POST\") {\n tokenRequestCount += 1;\n return await handleToken(request, options, registeredClients, tokenLifetimeSeconds);\n }\n if (url.pathname !== \"/mcp\") {\n return new Response(\"Not found\", { status: 404 });\n }\n\n mcpRequestCount += 1;\n if (\n options.requiredBearerToken &&\n request.headers.get(\"authorization\") !== `Bearer ${options.requiredBearerToken}`\n ) {\n return new Response(\"Unauthorized\", {\n status: 401,\n headers: options.oauth\n ? {\n \"WWW-Authenticate\": `Bearer resource_metadata=\"${baseUrl}/.well-known/oauth-protected-resource/mcp\"`,\n }\n : {},\n });\n }\n if (options.failAuthorizedMcpRequests) {\n return new Response(\"MCP operation failed\", { status: 500 });\n }\n if (options.protocolVersion === \"2026-07-28\") {\n return await modernHandler.fetch(request);\n }\n const sessionId = request.headers.get(\"mcp-session-id\");\n const session = (sessionId ? sessions.get(sessionId) : undefined) ?? (await createSession());\n return await session.transport.handleRequest(request);\n }\n\n return {\n fetch: async (request) => {\n try {\n return await handle(request);\n } catch (error) {\n return new Response(error instanceof Error ? error.message : \"MCP transport error\", {\n status: 500,\n });\n }\n },\n getMcpRequestCount: () => mcpRequestCount,\n getTokenRequestCount: () => tokenRequestCount,\n forgetRegisteredClients: () => {\n registeredClients.clear();\n },\n setTokenLifetimeSeconds: (seconds) => {\n tokenLifetimeSeconds = seconds;\n },\n close: async () => {\n await modernHandler.close();\n for (const session of sessions.values()) {\n await session.server.close();\n await session.transport.close();\n }\n sessions.clear();\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,133 @@
1
1
  {
2
2
  "name": "@fragno-dev/mcp-fragment",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.3",
4
+ "description": "A Fragno fragment",
5
+ "keywords": [
6
+ "fragno",
7
+ "react",
8
+ "solidjs",
9
+ "svelte",
10
+ "typescript",
11
+ "vue"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/rejot-dev/fragno.git",
16
+ "directory": "packages/mcp-fragment"
17
+ },
18
+ "bin": {
19
+ "fragno-mcp": "./bin/run.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "bin"
24
+ ],
25
+ "type": "module",
26
+ "main": "./dist/node/index.js",
27
+ "module": "./dist/node/index.js",
28
+ "types": "./dist/node/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/node/index.d.ts",
32
+ "default": "./dist/node/index.js"
33
+ },
34
+ "./cli": {
35
+ "types": "./dist/cli/cli.d.ts",
36
+ "default": "./dist/cli/cli.js"
37
+ },
38
+ "./definition": {
39
+ "types": "./dist/node/definition.d.ts",
40
+ "default": "./dist/node/definition.js"
41
+ },
42
+ "./dist/node/definition.js": {
43
+ "types": "./dist/node/definition.d.ts",
44
+ "default": "./dist/node/definition.js"
45
+ },
46
+ "./routes": {
47
+ "types": "./dist/node/routes.d.ts",
48
+ "default": "./dist/node/routes.js"
49
+ },
50
+ "./dist/node/routes.js": {
51
+ "types": "./dist/node/routes.d.ts",
52
+ "default": "./dist/node/routes.js"
53
+ },
54
+ "./schema": {
55
+ "types": "./dist/node/schema.d.ts",
56
+ "default": "./dist/node/schema.js"
57
+ },
58
+ "./dist/node/schema.js": {
59
+ "types": "./dist/node/schema.d.ts",
60
+ "default": "./dist/node/schema.js"
61
+ },
62
+ "./types": {
63
+ "types": "./dist/node/mcp-types.d.ts",
64
+ "default": "./dist/node/mcp-types.js"
65
+ },
66
+ "./dist/node/mcp-types.js": {
67
+ "types": "./dist/node/mcp-types.d.ts",
68
+ "default": "./dist/node/mcp-types.js"
69
+ },
70
+ "./testing": {
71
+ "types": "./dist/node/testing/mcp-test-server.d.ts",
72
+ "default": "./dist/node/testing/mcp-test-server.js"
73
+ },
74
+ "./react": {
75
+ "types": "./dist/browser/client/react.d.ts",
76
+ "default": "./dist/browser/client/react.js"
77
+ },
78
+ "./vue": {
79
+ "types": "./dist/browser/client/vue.d.ts",
80
+ "default": "./dist/browser/client/vue.js"
81
+ },
82
+ "./svelte": {
83
+ "types": "./dist/browser/client/svelte.d.ts",
84
+ "default": "./dist/browser/client/svelte.js"
85
+ },
86
+ "./solid": {
87
+ "types": "./dist/browser/client/solid.d.ts",
88
+ "default": "./dist/browser/client/solid.js"
89
+ },
90
+ "./vanilla": {
91
+ "types": "./dist/browser/client/vanilla.d.ts",
92
+ "default": "./dist/browser/client/vanilla.js"
93
+ }
94
+ },
95
+ "dependencies": {
96
+ "@modelcontextprotocol/client": "2.3.1",
97
+ "@modelcontextprotocol/server": "2.3.1",
98
+ "@standard-schema/spec": "^1.1.0",
99
+ "zod": "^4.6.7",
100
+ "@fragno-dev/core": "0.2.2",
101
+ "@fragno-dev/db": "0.4.1",
102
+ "@fragno-dev/node": "0.0.9"
103
+ },
104
+ "devDependencies": {
105
+ "@types/node": "^26.6.5",
106
+ "@vitest/coverage-istanbul": "^5.0.4",
107
+ "tsdown": "^0.23.1",
108
+ "vitest": "^5.0.4",
109
+ "@fragno-dev/test": "2.0.2",
110
+ "@fragno-dev/unplugin-fragno": "0.0.9",
111
+ "@fragno-private/typescript-config": "0.0.1",
112
+ "@fragno-dev/cli": "0.2.3",
113
+ "@fragno-private/vitest-config": "0.0.0"
114
+ },
115
+ "peerDependencies": {
116
+ "react": ">=18.0.0",
117
+ "solid-js": ">=1.0.0",
118
+ "svelte": ">=4.0.0",
119
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
120
+ "vue": ">=3.0.0"
121
+ },
122
+ "scripts": {
123
+ "types:check": "tsc --noEmit",
124
+ "build": "tsdown",
125
+ "cli": "tsx src/cli/cli.ts",
126
+ "test": "vitest run",
127
+ "test:watch": "vitest --watch",
128
+ "format:package": "node ../../scripts/run-package-quality.mjs format",
129
+ "format:check:package": "node ../../scripts/run-package-quality.mjs format:check",
130
+ "lint:package": "node ../../scripts/run-package-quality.mjs lint",
131
+ "lint:fix:package": "node ../../scripts/run-package-quality.mjs lint:fix"
132
+ }
6
133
  }