skybridge 0.0.0-dev.81ea105 → 0.0.0-dev.81fd90a

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 (222) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/build-helpers.d.ts +1 -1
  3. package/dist/cli/build-helpers.js +7 -2
  4. package/dist/cli/build-helpers.js.map +1 -1
  5. package/dist/cli/build-helpers.test.js +15 -2
  6. package/dist/cli/build-helpers.test.js.map +1 -1
  7. package/dist/cli/build-steps.js +3 -3
  8. package/dist/cli/build-steps.js.map +1 -1
  9. package/dist/cli/resolve-skybridge-config.d.ts +5 -0
  10. package/dist/cli/{resolve-views-dir.js → resolve-skybridge-config.js} +4 -3
  11. package/dist/cli/resolve-skybridge-config.js.map +1 -0
  12. package/dist/cli/use-execute-steps.d.ts +1 -1
  13. package/dist/commands/dev.js +2 -2
  14. package/dist/commands/dev.js.map +1 -1
  15. package/dist/context-warnings.d.ts +5 -0
  16. package/dist/context-warnings.js +31 -0
  17. package/dist/context-warnings.js.map +1 -0
  18. package/dist/context-warnings.test.js +28 -0
  19. package/dist/context-warnings.test.js.map +1 -0
  20. package/dist/server/auth/discovery.d.ts +1 -0
  21. package/dist/server/auth/discovery.js +1 -1
  22. package/dist/server/auth/discovery.js.map +1 -1
  23. package/dist/server/auth/index.d.ts +17 -5
  24. package/dist/server/auth/index.js.map +1 -1
  25. package/dist/server/auth/providers/auth0.d.ts +24 -2
  26. package/dist/server/auth/providers/auth0.js.map +1 -1
  27. package/dist/server/auth/providers/auth0.test.js +4 -2
  28. package/dist/server/auth/providers/auth0.test.js.map +1 -1
  29. package/dist/server/auth/providers/authplane.d.ts +49 -0
  30. package/dist/server/auth/providers/authplane.js +57 -0
  31. package/dist/server/auth/providers/authplane.js.map +1 -0
  32. package/dist/server/auth/providers/authplane.test.d.ts +1 -0
  33. package/dist/server/auth/providers/authplane.test.js +140 -0
  34. package/dist/server/auth/providers/authplane.test.js.map +1 -0
  35. package/dist/server/auth/providers/clerk.d.ts +39 -2
  36. package/dist/server/auth/providers/clerk.js +4 -1
  37. package/dist/server/auth/providers/clerk.js.map +1 -1
  38. package/dist/server/auth/providers/clerk.test.js +5 -3
  39. package/dist/server/auth/providers/clerk.test.js.map +1 -1
  40. package/dist/server/auth/providers/custom.d.ts +16 -6
  41. package/dist/server/auth/providers/custom.js +25 -9
  42. package/dist/server/auth/providers/custom.js.map +1 -1
  43. package/dist/server/auth/providers/custom.test.js +46 -16
  44. package/dist/server/auth/providers/custom.test.js.map +1 -1
  45. package/dist/server/auth/providers/descope.d.ts +31 -2
  46. package/dist/server/auth/providers/descope.js +12 -3
  47. package/dist/server/auth/providers/descope.js.map +1 -1
  48. package/dist/server/auth/providers/descope.test.js +42 -19
  49. package/dist/server/auth/providers/descope.test.js.map +1 -1
  50. package/dist/server/auth/providers/stytch.d.ts +15 -2
  51. package/dist/server/auth/providers/stytch.js +4 -1
  52. package/dist/server/auth/providers/stytch.js.map +1 -1
  53. package/dist/server/auth/providers/verify-spy.d.ts +12 -0
  54. package/dist/server/auth/providers/verify-spy.js +21 -0
  55. package/dist/server/auth/providers/verify-spy.js.map +1 -0
  56. package/dist/server/auth/providers/workos.d.ts +24 -2
  57. package/dist/server/auth/providers/workos.js +4 -1
  58. package/dist/server/auth/providers/workos.js.map +1 -1
  59. package/dist/server/auth/setup.d.ts +1 -1
  60. package/dist/server/auth/setup.js +4 -5
  61. package/dist/server/auth/setup.js.map +1 -1
  62. package/dist/server/auth/setup.test.js +39 -15
  63. package/dist/server/auth/setup.test.js.map +1 -1
  64. package/dist/server/auth/verify.d.ts +37 -3
  65. package/dist/server/auth/verify.js +27 -3
  66. package/dist/server/auth/verify.js.map +1 -1
  67. package/dist/server/auth/verify.test.js +49 -0
  68. package/dist/server/auth/verify.test.js.map +1 -1
  69. package/dist/server/auth-extra.test-d.d.ts +1 -0
  70. package/dist/server/auth-extra.test-d.js +71 -0
  71. package/dist/server/auth-extra.test-d.js.map +1 -0
  72. package/dist/server/auth.d.ts +42 -5
  73. package/dist/server/auth.js +3 -4
  74. package/dist/server/auth.js.map +1 -1
  75. package/dist/server/content-helpers.d.ts +1 -1
  76. package/dist/server/content-helpers.js.map +1 -1
  77. package/dist/server/express.js +9 -28
  78. package/dist/server/express.js.map +1 -1
  79. package/dist/server/express.test.js +42 -7
  80. package/dist/server/express.test.js.map +1 -1
  81. package/dist/server/index.d.ts +11 -7
  82. package/dist/server/index.js +5 -2
  83. package/dist/server/index.js.map +1 -1
  84. package/dist/server/mcp-errors.test.d.ts +1 -0
  85. package/dist/server/mcp-errors.test.js +37 -0
  86. package/dist/server/mcp-errors.test.js.map +1 -0
  87. package/dist/server/middleware.d.ts +35 -10
  88. package/dist/server/middleware.js +33 -0
  89. package/dist/server/middleware.js.map +1 -1
  90. package/dist/server/middleware.test-d.js +1 -1
  91. package/dist/server/middleware.test-d.js.map +1 -1
  92. package/dist/server/middleware.test.js +38 -3
  93. package/dist/server/middleware.test.js.map +1 -1
  94. package/dist/server/register-tool.test.d.ts +1 -0
  95. package/dist/server/register-tool.test.js +25 -0
  96. package/dist/server/register-tool.test.js.map +1 -0
  97. package/dist/server/server.d.ts +86 -50
  98. package/dist/server/server.js +101 -58
  99. package/dist/server/server.js.map +1 -1
  100. package/dist/server/skills-integration.test.js +31 -15
  101. package/dist/server/skills-integration.test.js.map +1 -1
  102. package/dist/server/skills.d.ts +14 -10
  103. package/dist/server/skills.js +48 -38
  104. package/dist/server/skills.js.map +1 -1
  105. package/dist/server/skills.test.js +77 -44
  106. package/dist/server/skills.test.js.map +1 -1
  107. package/dist/server/tool-extra.test.d.ts +1 -0
  108. package/dist/server/tool-extra.test.js +72 -0
  109. package/dist/server/tool-extra.test.js.map +1 -0
  110. package/dist/server/view-resource-resolution.test.js +1 -3
  111. package/dist/server/view-resource-resolution.test.js.map +1 -1
  112. package/dist/standard-schema.d.ts +3 -0
  113. package/dist/standard-schema.js +2 -0
  114. package/dist/standard-schema.js.map +1 -0
  115. package/dist/test/utils.d.ts +5 -8
  116. package/dist/test/utils.js +8 -4
  117. package/dist/test/utils.js.map +1 -1
  118. package/dist/test/view.test.js +17 -17
  119. package/dist/test/view.test.js.map +1 -1
  120. package/dist/web/bridges/adaptor.js +14 -17
  121. package/dist/web/bridges/adaptor.js.map +1 -1
  122. package/dist/web/bridges/adaptor.test.js +24 -7
  123. package/dist/web/bridges/adaptor.test.js.map +1 -1
  124. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +1 -1
  125. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +1 -1
  126. package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
  127. package/dist/web/bridges/mcp-app/bridge.js +5 -3
  128. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  129. package/dist/web/bridges/mcp-app/bridge.test.js +1 -1
  130. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -1
  131. package/dist/web/bridges/mcp-app/types.d.ts +2 -0
  132. package/dist/web/bridges/mcp-app/types.js.map +1 -1
  133. package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +1 -1
  134. package/dist/web/bridges/mcp-app/use-mcp-app-context.js +1 -1
  135. package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
  136. package/dist/web/bridges/types.d.ts +17 -9
  137. package/dist/web/bridges/types.js.map +1 -1
  138. package/dist/web/create-store.test.js +1 -1
  139. package/dist/web/create-store.test.js.map +1 -1
  140. package/dist/web/data-llm.test.js +4 -3
  141. package/dist/web/data-llm.test.js.map +1 -1
  142. package/dist/web/hooks/index.d.ts +2 -1
  143. package/dist/web/hooks/index.js +2 -1
  144. package/dist/web/hooks/index.js.map +1 -1
  145. package/dist/web/hooks/test/utils.d.ts +2 -1
  146. package/dist/web/hooks/test/utils.js +1 -1
  147. package/dist/web/hooks/test/utils.js.map +1 -1
  148. package/dist/web/hooks/use-call-tool.test.js +2 -2
  149. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  150. package/dist/web/hooks/use-display-mode.d.ts +1 -1
  151. package/dist/web/hooks/use-display-mode.js +1 -1
  152. package/dist/web/hooks/use-display-mode.js.map +1 -1
  153. package/dist/web/hooks/use-display-mode.test.js +1 -1
  154. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  155. package/dist/web/hooks/use-download.d.ts +1 -3
  156. package/dist/web/hooks/use-download.js +1 -2
  157. package/dist/web/hooks/use-download.js.map +1 -1
  158. package/dist/web/hooks/use-download.test.js +5 -5
  159. package/dist/web/hooks/use-download.test.js.map +1 -1
  160. package/dist/web/hooks/use-files.test.js +1 -1
  161. package/dist/web/hooks/use-files.test.js.map +1 -1
  162. package/dist/web/hooks/use-host.d.ts +22 -0
  163. package/dist/web/hooks/use-host.js +30 -0
  164. package/dist/web/hooks/use-host.js.map +1 -0
  165. package/dist/web/hooks/use-host.test.d.ts +1 -0
  166. package/dist/web/hooks/use-host.test.js +59 -0
  167. package/dist/web/hooks/use-host.test.js.map +1 -0
  168. package/dist/web/hooks/use-open-external.test.js +1 -1
  169. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  170. package/dist/web/hooks/use-register-view-tool.d.ts +2 -3
  171. package/dist/web/hooks/use-register-view-tool.js.map +1 -1
  172. package/dist/web/hooks/use-request-modal.test.js +1 -1
  173. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  174. package/dist/web/hooks/use-request-size.d.ts +1 -1
  175. package/dist/web/hooks/use-request-size.js +1 -1
  176. package/dist/web/hooks/use-request-size.js.map +1 -1
  177. package/dist/web/hooks/use-set-open-in-app-url.test.js +1 -1
  178. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  179. package/dist/web/hooks/use-tool-info.d.ts +1 -20
  180. package/dist/web/hooks/use-tool-info.js +0 -1
  181. package/dist/web/hooks/use-tool-info.js.map +1 -1
  182. package/dist/web/hooks/use-tool-info.test-d.js +0 -5
  183. package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
  184. package/dist/web/hooks/use-tool-info.test.js +1 -6
  185. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  186. package/dist/web/hooks/use-user.d.ts +3 -2
  187. package/dist/web/hooks/use-user.js +4 -3
  188. package/dist/web/hooks/use-user.js.map +1 -1
  189. package/dist/web/hooks/use-user.test.js +4 -2
  190. package/dist/web/hooks/use-user.test.js.map +1 -1
  191. package/dist/web/hooks/use-view-state.js +19 -9
  192. package/dist/web/hooks/use-view-state.js.map +1 -1
  193. package/dist/web/hooks/use-view-state.test.js +32 -1
  194. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  195. package/dist/web/hooks/use-viewport.d.ts +20 -0
  196. package/dist/web/hooks/use-viewport.js +21 -0
  197. package/dist/web/hooks/use-viewport.js.map +1 -0
  198. package/dist/web/hooks/use-viewport.test.d.ts +1 -0
  199. package/dist/web/hooks/{use-layout.test.js → use-viewport.test.js} +11 -31
  200. package/dist/web/hooks/use-viewport.test.js.map +1 -0
  201. package/dist/web/index.d.ts +1 -0
  202. package/dist/web/index.js.map +1 -1
  203. package/dist/web/mount-view.d.ts +3 -2
  204. package/dist/web/mount-view.js +3 -2
  205. package/dist/web/mount-view.js.map +1 -1
  206. package/dist/web/plugin/plugin-build.test.d.ts +1 -0
  207. package/dist/web/plugin/plugin-build.test.js +54 -0
  208. package/dist/web/plugin/plugin-build.test.js.map +1 -0
  209. package/dist/web/plugin/plugin.d.ts +6 -0
  210. package/dist/web/plugin/plugin.js +18 -6
  211. package/dist/web/plugin/plugin.js.map +1 -1
  212. package/dist/web/plugin/plugin.test.d.ts +1 -0
  213. package/dist/web/plugin/plugin.test.js +61 -0
  214. package/dist/web/plugin/plugin.test.js.map +1 -0
  215. package/package.json +10 -6
  216. package/dist/cli/resolve-views-dir.d.ts +0 -1
  217. package/dist/cli/resolve-views-dir.js.map +0 -1
  218. package/dist/web/hooks/use-layout.d.ts +0 -24
  219. package/dist/web/hooks/use-layout.js +0 -25
  220. package/dist/web/hooks/use-layout.js.map +0 -1
  221. package/dist/web/hooks/use-layout.test.js.map +0 -1
  222. /package/dist/{web/hooks/use-layout.test.d.ts → context-warnings.test.d.ts} +0 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ import { expectTypeOf, test } from "vitest";
2
+ import { z } from "zod";
3
+ import { customProvider } from "./auth/providers/custom.js";
4
+ import { workosProvider } from "./auth/providers/workos.js";
5
+ import { McpServer } from "./server.js";
6
+ const workosOAuth = await workosProvider({ domain: "d", audience: "a" });
7
+ const customOAuth = await workosProvider({
8
+ domain: "d",
9
+ audience: "a",
10
+ });
11
+ test("a server with no oauth keeps the untyped claim bag", () => {
12
+ new McpServer({ name: "t", version: "0" }).registerTool({ name: "plain", inputSchema: { q: z.string() } }, (_args, extra) => {
13
+ expectTypeOf(extra.http?.authInfo?.extra).toEqualTypeOf();
14
+ return { content: "ok" };
15
+ });
16
+ });
17
+ test("provider claims reach tool handlers", () => {
18
+ new McpServer({ name: "t", version: "0" }, {}, { oauth: workosOAuth })
19
+ .registerTool({ name: "a", inputSchema: {} }, (_args, extra) => {
20
+ expectTypeOf(extra.http?.authInfo?.extra?.org_id).toEqualTypeOf();
21
+ expectTypeOf(extra.http?.authInfo?.extra?.permissions).toEqualTypeOf();
22
+ // @ts-expect-error not a WorkOS claim
23
+ extra.http?.authInfo?.extra?.nope;
24
+ return { content: "a" };
25
+ })
26
+ .registerTool({ name: "b", inputSchema: {} }, (_args, extra) => {
27
+ expectTypeOf(extra.http?.authInfo?.extra?.sid).toEqualTypeOf();
28
+ return { content: "b" };
29
+ });
30
+ });
31
+ test("registered claims survive the mapping into extra", () => {
32
+ new McpServer({ name: "t", version: "0" }, {}, { oauth: workosOAuth }).registerTool({ name: "a", inputSchema: {} }, (_args, extra) => {
33
+ expectTypeOf(extra.http?.authInfo?.extra?.iss).toEqualTypeOf();
34
+ expectTypeOf(extra.http?.authInfo?.extra?.iat).toEqualTypeOf();
35
+ return { content: "a" };
36
+ });
37
+ });
38
+ test("a hand-written verifier carries its own claims", async () => {
39
+ const verifier = {
40
+ async verifyAccessToken(token) {
41
+ return { token, clientId: "c", scopes: [], expiresAt: 1, extra: {} };
42
+ },
43
+ };
44
+ const { oauthMetadata } = await customProvider({
45
+ issuer: "https://idp.example.com",
46
+ });
47
+ new McpServer({ name: "t", version: "0" }, {}, { oauth: { oauthMetadata, verifier } }).registerTool({ name: "a", inputSchema: {} }, (_args, extra) => {
48
+ expectTypeOf(extra.http?.authInfo?.extra?.email).toEqualTypeOf();
49
+ return { content: "a" };
50
+ });
51
+ });
52
+ test("a config must carry a verifier", () => {
53
+ // @ts-expect-error a verifier is required
54
+ const missing = {
55
+ oauthMetadata: workosOAuth.oauthMetadata,
56
+ };
57
+ void missing;
58
+ });
59
+ test("a provider override adds claims without dropping the provider's", () => {
60
+ new McpServer({ name: "t", version: "0" }, {}, { oauth: customOAuth })
61
+ .registerTool({ name: "a", inputSchema: {} }, (_args, extra) => {
62
+ expectTypeOf(extra.http?.authInfo?.extra?.tenant).toEqualTypeOf();
63
+ expectTypeOf(extra.http?.authInfo?.extra?.org_id).toEqualTypeOf();
64
+ return { content: "a" };
65
+ })
66
+ .mcpMiddleware("tools/call", (_request, extra, next) => {
67
+ expectTypeOf(extra.http?.authInfo?.extra?.tenant).toEqualTypeOf();
68
+ return next();
69
+ });
70
+ });
71
+ //# sourceMappingURL=auth-extra.test-d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-extra.test-d.js","sourceRoot":"","sources":["../../src/server/auth-extra.test-d.ts"],"names":[],"mappings":"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","sourcesContent":["import { expectTypeOf, test } from \"vitest\";\nimport { z } from \"zod\";\nimport type { OAuthConfig } from \"./auth/index.js\";\nimport { customProvider } from \"./auth/providers/custom.js\";\nimport { workosProvider } from \"./auth/providers/workos.js\";\nimport type { TokenVerifier } from \"./auth.js\";\nimport { McpServer } from \"./server.js\";\n\nconst workosOAuth = await workosProvider({ domain: \"d\", audience: \"a\" });\nconst customOAuth = await workosProvider<{ tenant: string }>({\n domain: \"d\",\n audience: \"a\",\n});\n\ntest(\"a server with no oauth keeps the untyped claim bag\", () => {\n new McpServer({ name: \"t\", version: \"0\" }).registerTool(\n { name: \"plain\", inputSchema: { q: z.string() } },\n (_args, extra) => {\n expectTypeOf(extra.http?.authInfo?.extra).toEqualTypeOf<\n Record<string, unknown> | undefined\n >();\n return { content: \"ok\" };\n },\n );\n});\n\ntest(\"provider claims reach tool handlers\", () => {\n new McpServer({ name: \"t\", version: \"0\" }, {}, { oauth: workosOAuth })\n .registerTool({ name: \"a\", inputSchema: {} }, (_args, extra) => {\n expectTypeOf(extra.http?.authInfo?.extra?.org_id).toEqualTypeOf<\n string | undefined\n >();\n expectTypeOf(extra.http?.authInfo?.extra?.permissions).toEqualTypeOf<\n string[] | undefined\n >();\n // @ts-expect-error not a WorkOS claim\n extra.http?.authInfo?.extra?.nope;\n return { content: \"a\" };\n })\n .registerTool({ name: \"b\", inputSchema: {} }, (_args, extra) => {\n expectTypeOf(extra.http?.authInfo?.extra?.sid).toEqualTypeOf<\n string | undefined\n >();\n return { content: \"b\" };\n });\n});\n\ntest(\"registered claims survive the mapping into extra\", () => {\n new McpServer(\n { name: \"t\", version: \"0\" },\n {},\n { oauth: workosOAuth },\n ).registerTool({ name: \"a\", inputSchema: {} }, (_args, extra) => {\n expectTypeOf(extra.http?.authInfo?.extra?.iss).toEqualTypeOf<\n string | undefined\n >();\n expectTypeOf(extra.http?.authInfo?.extra?.iat).toEqualTypeOf<\n number | undefined\n >();\n return { content: \"a\" };\n });\n});\n\ntest(\"a hand-written verifier carries its own claims\", async () => {\n type Claims = { subject?: string; email?: string };\n const verifier: TokenVerifier<Claims> = {\n async verifyAccessToken(token) {\n return { token, clientId: \"c\", scopes: [], expiresAt: 1, extra: {} };\n },\n };\n const { oauthMetadata } = await customProvider({\n issuer: \"https://idp.example.com\",\n });\n new McpServer(\n { name: \"t\", version: \"0\" },\n {},\n { oauth: { oauthMetadata, verifier } },\n ).registerTool({ name: \"a\", inputSchema: {} }, (_args, extra) => {\n expectTypeOf(extra.http?.authInfo?.extra?.email).toEqualTypeOf<\n string | undefined\n >();\n return { content: \"a\" };\n });\n});\n\ntest(\"a config must carry a verifier\", () => {\n // @ts-expect-error a verifier is required\n const missing: OAuthConfig = {\n oauthMetadata: workosOAuth.oauthMetadata,\n };\n void missing;\n});\n\ntest(\"a provider override adds claims without dropping the provider's\", () => {\n new McpServer({ name: \"t\", version: \"0\" }, {}, { oauth: customOAuth })\n .registerTool({ name: \"a\", inputSchema: {} }, (_args, extra) => {\n expectTypeOf(extra.http?.authInfo?.extra?.tenant).toEqualTypeOf<\n string | undefined\n >();\n expectTypeOf(extra.http?.authInfo?.extra?.org_id).toEqualTypeOf<\n string | undefined\n >();\n return { content: \"a\" };\n })\n .mcpMiddleware(\"tools/call\", (_request, extra, next) => {\n expectTypeOf(extra.http?.authInfo?.extra?.tenant).toEqualTypeOf<\n string | undefined\n >();\n return next();\n });\n});\n"]}
@@ -1,9 +1,46 @@
1
- import { type BearerAuthMiddlewareOptions } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
1
+ import { type BearerAuthMiddlewareOptions } from "@modelcontextprotocol/express";
2
+ import type { AuthInfo as SdkAuthInfo } from "@modelcontextprotocol/server";
2
3
  import type { RequestHandler } from "express";
3
- export { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
4
- export { type BearerAuthMiddlewareOptions, requireBearerAuth, } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
5
- export { type AuthMetadataOptions, mcpAuthMetadataRouter, } from "@modelcontextprotocol/sdk/server/auth/router.js";
6
- export type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
4
+ export { type AuthMetadataOptions, type BearerAuthMiddlewareOptions, mcpAuthMetadataRouter, requireBearerAuth, } from "@modelcontextprotocol/express";
5
+ export { OAuthError } from "@modelcontextprotocol/server";
6
+ /** Claims a verifier puts in `AuthInfo["extra"]`: any JSON-ish bag of them. */
7
+ export type ExtraClaims = Record<string, unknown>;
8
+ /**
9
+ * A validated access token, as resolved by a
10
+ * [verifier](https://docs.skybridge.tech/api-reference/verifier) and handed to
11
+ * handlers on `extra.authInfo`.
12
+ *
13
+ * Unparameterized, `extra` stays the SDK's `Record<string, unknown>` bag. The
14
+ * claims are owned by whoever verifies the token: parameterize the verifier and
15
+ * the shape flows through {@link OAuthConfig} to every handler.
16
+ *
17
+ * @typeParam TExtra - Claims populated in `extra`.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * type Claims = { subject?: string; email?: string };
22
+ *
23
+ * async function verifyAccessToken(token: string): Promise<AuthInfo<Claims>> {
24
+ * // ...
25
+ * }
26
+ * ```
27
+ */
28
+ export type AuthInfo<TExtra extends ExtraClaims = ExtraClaims> = Omit<SdkAuthInfo, "extra"> & {
29
+ extra?: TExtra;
30
+ };
31
+ /**
32
+ * Checks a bearer token and resolves the claims it carries. The type parameter
33
+ * is what makes `extra.authInfo.extra` typed downstream, so a verifier is the
34
+ * single source for the claim shape.
35
+ *
36
+ * Pass one to {@link requireBearerAuth} or {@link optionalBearerAuth} directly,
37
+ * or hand it to the `oauth` server option inside an {@link OAuthConfig}.
38
+ *
39
+ * @typeParam TExtra - Claims this verifier populates in `AuthInfo["extra"]`.
40
+ */
41
+ export type TokenVerifier<TExtra extends ExtraClaims = ExtraClaims> = {
42
+ verifyAccessToken(token: string): Promise<AuthInfo<TExtra>>;
43
+ };
7
44
  /**
8
45
  * Like `requireBearerAuth`, but lets requests through when no
9
46
  * `Authorization` header is present. Used for mixed-auth servers where some
@@ -1,7 +1,6 @@
1
- import { requireBearerAuth, } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
2
- export { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
3
- export { requireBearerAuth, } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
4
- export { mcpAuthMetadataRouter, } from "@modelcontextprotocol/sdk/server/auth/router.js";
1
+ import { requireBearerAuth, } from "@modelcontextprotocol/express";
2
+ export { mcpAuthMetadataRouter, requireBearerAuth, } from "@modelcontextprotocol/express";
3
+ export { OAuthError } from "@modelcontextprotocol/server";
5
4
  /**
6
5
  * Like `requireBearerAuth`, but lets requests through when no
7
6
  * `Authorization` header is present. Used for mixed-auth servers where some
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,GAClB,MAAM,gEAAgE,CAAC;AAIxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AACpF,OAAO,EAEL,iBAAiB,GAClB,MAAM,gEAAgE,CAAC;AACxE,OAAO,EAEL,qBAAqB,GACtB,MAAM,iDAAiD,CAAC;AAGzD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAoC;IAEpC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACxB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;YAC/B,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import {\n type BearerAuthMiddlewareOptions,\n requireBearerAuth,\n} from \"@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js\";\n\nimport type { RequestHandler } from \"express\";\n\nexport { InvalidTokenError } from \"@modelcontextprotocol/sdk/server/auth/errors.js\";\nexport {\n type BearerAuthMiddlewareOptions,\n requireBearerAuth,\n} from \"@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js\";\nexport {\n type AuthMetadataOptions,\n mcpAuthMetadataRouter,\n} from \"@modelcontextprotocol/sdk/server/auth/router.js\";\nexport type { AuthInfo } from \"@modelcontextprotocol/sdk/server/auth/types.js\";\n\n/**\n * Like `requireBearerAuth`, but lets requests through when no\n * `Authorization` header is present. Used for mixed-auth servers where some\n * tools are public and others require sign-in: each tool enforces its own\n * `securitySchemes` against `extra.authInfo`.\n *\n * Behavior:\n * - No `Authorization` header → `next()` without `req.auth`.\n * - Valid Bearer token → `req.auth` set, same as `requireBearerAuth`.\n * - Invalid / malformed / expired / insufficient-scope → same error response\n * as `requireBearerAuth` (401/403). Sending a bad token is still a client\n * error.\n */\nexport function optionalBearerAuth(\n options: BearerAuthMiddlewareOptions,\n): RequestHandler {\n const required = requireBearerAuth(options);\n return (req, res, next) => {\n if (!req.headers.authorization) {\n next();\n return;\n }\n return required(req, res, next);\n };\n}\n"]}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,GAClB,MAAM,+BAA+B,CAAC;AAKvC,OAAO,EAGL,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AA2C1D;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAoC;IAEpC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACxB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;YAC/B,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,OAAO,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import {\n type BearerAuthMiddlewareOptions,\n requireBearerAuth,\n} from \"@modelcontextprotocol/express\";\nimport type { AuthInfo as SdkAuthInfo } from \"@modelcontextprotocol/server\";\n\nimport type { RequestHandler } from \"express\";\n\nexport {\n type AuthMetadataOptions,\n type BearerAuthMiddlewareOptions,\n mcpAuthMetadataRouter,\n requireBearerAuth,\n} from \"@modelcontextprotocol/express\";\nexport { OAuthError } from \"@modelcontextprotocol/server\";\n/** Claims a verifier puts in `AuthInfo[\"extra\"]`: any JSON-ish bag of them. */\nexport type ExtraClaims = Record<string, unknown>;\n\n/**\n * A validated access token, as resolved by a\n * [verifier](https://docs.skybridge.tech/api-reference/verifier) and handed to\n * handlers on `extra.authInfo`.\n *\n * Unparameterized, `extra` stays the SDK's `Record<string, unknown>` bag. The\n * claims are owned by whoever verifies the token: parameterize the verifier and\n * the shape flows through {@link OAuthConfig} to every handler.\n *\n * @typeParam TExtra - Claims populated in `extra`.\n *\n * @example\n * ```ts\n * type Claims = { subject?: string; email?: string };\n *\n * async function verifyAccessToken(token: string): Promise<AuthInfo<Claims>> {\n * // ...\n * }\n * ```\n */\nexport type AuthInfo<TExtra extends ExtraClaims = ExtraClaims> = Omit<\n SdkAuthInfo,\n \"extra\"\n> & { extra?: TExtra };\n\n/**\n * Checks a bearer token and resolves the claims it carries. The type parameter\n * is what makes `extra.authInfo.extra` typed downstream, so a verifier is the\n * single source for the claim shape.\n *\n * Pass one to {@link requireBearerAuth} or {@link optionalBearerAuth} directly,\n * or hand it to the `oauth` server option inside an {@link OAuthConfig}.\n *\n * @typeParam TExtra - Claims this verifier populates in `AuthInfo[\"extra\"]`.\n */\nexport type TokenVerifier<TExtra extends ExtraClaims = ExtraClaims> = {\n verifyAccessToken(token: string): Promise<AuthInfo<TExtra>>;\n};\n\n/**\n * Like `requireBearerAuth`, but lets requests through when no\n * `Authorization` header is present. Used for mixed-auth servers where some\n * tools are public and others require sign-in: each tool enforces its own\n * `securitySchemes` against `extra.authInfo`.\n *\n * Behavior:\n * - No `Authorization` header → `next()` without `req.auth`.\n * - Valid Bearer token → `req.auth` set, same as `requireBearerAuth`.\n * - Invalid / malformed / expired / insufficient-scope → same error response\n * as `requireBearerAuth` (401/403). Sending a bad token is still a client\n * error.\n */\nexport function optionalBearerAuth(\n options: BearerAuthMiddlewareOptions,\n): RequestHandler {\n const required = requireBearerAuth(options);\n return (req, res, next) => {\n if (!req.headers.authorization) {\n next();\n return;\n }\n return required(req, res, next);\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AudioContent, EmbeddedResource, ImageContent, ResourceLink, TextContent } from "@modelcontextprotocol/sdk/types.js";
1
+ import type { AudioContent, EmbeddedResource, ImageContent, ResourceLink, TextContent } from "@modelcontextprotocol/server";
2
2
  /**
3
3
  * MCP content annotations applied to any returned block.
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"content-helpers.js","sourceRoot":"","sources":["../../src/server/content-helpers.ts"],"names":[],"mappings":"AAqBA;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,IAAyB;IACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAClB,KAAa,EACb,WAAgC;IAEhC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CACnB,IAAyB,EACzB,QAAgB,EAChB,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;QACpB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CACnB,IAAyB,EACzB,QAAgB,EAChB,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;QACpB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAEoD,EACpD,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,IAOC,EACD,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,GAAG,IAAI;QACP,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import type {\n AudioContent,\n EmbeddedResource,\n ImageContent,\n ResourceLink,\n TextContent,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * MCP content annotations applied to any returned block.\n *\n * - `audience` — who is meant to see the content (`\"user\"`, `\"assistant\"`, or both).\n * - `priority` — relative importance hint for the host.\n * - `lastModified` — ISO timestamp for when the content was produced.\n */\ntype ContentAnnotations = {\n audience?: (\"user\" | \"assistant\")[];\n priority?: number;\n lastModified?: string;\n};\n\n/**\n * Returns a base64-encoded string.\n * - `Uint8Array` input is encoded via `Buffer.toString(\"base64\")`.\n * - `string` input is assumed to be **already base64-encoded** and is returned\n * as-is. Passing raw/unencoded string bytes will produce invalid MCP content.\n */\nfunction toBase64(data: string | Uint8Array): string {\n if (typeof data === \"string\") {\n return data;\n }\n return Buffer.from(data).toString(\"base64\");\n}\n\n/**\n * Build an MCP text content block.\n *\n * @example\n * ```ts\n * return { content: [text(\"Found 3 results.\")] };\n * ```\n */\nexport function text(\n value: string,\n annotations?: ContentAnnotations,\n): TextContent {\n return { type: \"text\", text: value, ...(annotations && { annotations }) };\n}\n\n/**\n * Build an MCP image content block.\n *\n * `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is\n * **already base64-encoded**. Passing a raw byte-string will produce invalid\n * content.\n */\nexport function image(\n data: string | Uint8Array,\n mimeType: string,\n annotations?: ContentAnnotations,\n): ImageContent {\n return {\n type: \"image\",\n data: toBase64(data),\n mimeType,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP audio content block.\n *\n * `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is\n * **already base64-encoded**.\n */\nexport function audio(\n data: string | Uint8Array,\n mimeType: string,\n annotations?: ContentAnnotations,\n): AudioContent {\n return {\n type: \"audio\",\n data: toBase64(data),\n mimeType,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP embedded resource — the full content travels inline. Use this\n * when the client needs the bytes themselves rather than a link.\n *\n * Pass either `text` (UTF-8 string) or `blob` (base64-encoded bytes).\n */\nexport function embeddedResource(\n resource:\n | { uri: string; mimeType?: string; text: string }\n | { uri: string; mimeType?: string; blob: string },\n annotations?: ContentAnnotations,\n): EmbeddedResource {\n return {\n type: \"resource\",\n resource,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP resource link — a `type: \"resource_link\"` block carrying a URI\n * the client can fetch (or subscribe to) on demand. Use a link when the\n * client should retrieve the bytes itself; use {@link embeddedResource} when\n * the content must travel inline with the response.\n */\nexport function resourceLink(\n link: {\n uri: string;\n name: string;\n title?: string;\n description?: string;\n mimeType?: string;\n size?: number;\n },\n annotations?: ContentAnnotations,\n): ResourceLink {\n return {\n type: \"resource_link\",\n ...link,\n ...(annotations && { annotations }),\n };\n}\n"]}
1
+ {"version":3,"file":"content-helpers.js","sourceRoot":"","sources":["../../src/server/content-helpers.ts"],"names":[],"mappings":"AAqBA;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,IAAyB;IACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAClB,KAAa,EACb,WAAgC;IAEhC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CACnB,IAAyB,EACzB,QAAgB,EAChB,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;QACpB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,KAAK,CACnB,IAAyB,EACzB,QAAgB,EAChB,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;QACpB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAEoD,EACpD,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,QAAQ;QACR,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,IAOC,EACD,WAAgC;IAEhC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,GAAG,IAAI;QACP,GAAG,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import type {\n AudioContent,\n EmbeddedResource,\n ImageContent,\n ResourceLink,\n TextContent,\n} from \"@modelcontextprotocol/server\";\n\n/**\n * MCP content annotations applied to any returned block.\n *\n * - `audience` — who is meant to see the content (`\"user\"`, `\"assistant\"`, or both).\n * - `priority` — relative importance hint for the host.\n * - `lastModified` — ISO timestamp for when the content was produced.\n */\ntype ContentAnnotations = {\n audience?: (\"user\" | \"assistant\")[];\n priority?: number;\n lastModified?: string;\n};\n\n/**\n * Returns a base64-encoded string.\n * - `Uint8Array` input is encoded via `Buffer.toString(\"base64\")`.\n * - `string` input is assumed to be **already base64-encoded** and is returned\n * as-is. Passing raw/unencoded string bytes will produce invalid MCP content.\n */\nfunction toBase64(data: string | Uint8Array): string {\n if (typeof data === \"string\") {\n return data;\n }\n return Buffer.from(data).toString(\"base64\");\n}\n\n/**\n * Build an MCP text content block.\n *\n * @example\n * ```ts\n * return { content: [text(\"Found 3 results.\")] };\n * ```\n */\nexport function text(\n value: string,\n annotations?: ContentAnnotations,\n): TextContent {\n return { type: \"text\", text: value, ...(annotations && { annotations }) };\n}\n\n/**\n * Build an MCP image content block.\n *\n * `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is\n * **already base64-encoded**. Passing a raw byte-string will produce invalid\n * content.\n */\nexport function image(\n data: string | Uint8Array,\n mimeType: string,\n annotations?: ContentAnnotations,\n): ImageContent {\n return {\n type: \"image\",\n data: toBase64(data),\n mimeType,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP audio content block.\n *\n * `data` may be a `Uint8Array` (encoded to base64 for you) or a string that is\n * **already base64-encoded**.\n */\nexport function audio(\n data: string | Uint8Array,\n mimeType: string,\n annotations?: ContentAnnotations,\n): AudioContent {\n return {\n type: \"audio\",\n data: toBase64(data),\n mimeType,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP embedded resource — the full content travels inline. Use this\n * when the client needs the bytes themselves rather than a link.\n *\n * Pass either `text` (UTF-8 string) or `blob` (base64-encoded bytes).\n */\nexport function embeddedResource(\n resource:\n | { uri: string; mimeType?: string; text: string }\n | { uri: string; mimeType?: string; blob: string },\n annotations?: ContentAnnotations,\n): EmbeddedResource {\n return {\n type: \"resource\",\n resource,\n ...(annotations && { annotations }),\n };\n}\n\n/**\n * Build an MCP resource link — a `type: \"resource_link\"` block carrying a URI\n * the client can fetch (or subscribe to) on demand. Use a link when the\n * client should retrieve the bytes itself; use {@link embeddedResource} when\n * the content must travel inline with the response.\n */\nexport function resourceLink(\n link: {\n uri: string;\n name: string;\n title?: string;\n description?: string;\n mimeType?: string;\n size?: number;\n },\n annotations?: ContentAnnotations,\n): ResourceLink {\n return {\n type: \"resource_link\",\n ...link,\n ...(annotations && { annotations }),\n };\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2
+ import { toNodeHandler } from "@modelcontextprotocol/node";
3
+ import { createMcpHandler } from "@modelcontextprotocol/server";
3
4
  import cors from "cors";
4
5
  import express from "express";
5
6
  function parseControlPort(raw) {
@@ -61,37 +62,17 @@ export async function createApp({ mcpServer, httpServer, errorMiddleware = [], }
61
62
  return app;
62
63
  }
63
64
  const mcpMiddleware = (server) => {
65
+ const handler = toNodeHandler(createMcpHandler(() => server.createStatelessServerInstance(), {
66
+ onerror: (error) => {
67
+ console.error("Error handling MCP request:", error);
68
+ },
69
+ }));
64
70
  return async (req, res, next) => {
65
- if (req.method !== "POST") {
66
- res.writeHead(405).end(JSON.stringify({
67
- jsonrpc: "2.0",
68
- error: {
69
- code: -32000,
70
- message: "Method not allowed.",
71
- },
72
- id: null,
73
- }));
74
- return;
75
- }
76
71
  try {
77
- const transport = new StreamableHTTPServerTransport({
78
- sessionIdGenerator: undefined,
79
- // Respond with a single JSON body instead of SSE. Skybridge's stateless
80
- // transport never streams server-initiated messages, so SSE adds no
81
- // capability — and on workerd specifically, `cloudflare:node`'s http
82
- // bridge silently drops chunked writes that happen after the request
83
- // handler awaits, which manifests as a 200 with empty body for any
84
- // async tools/call.
85
- enableJsonResponse: true,
86
- });
87
- res.on("close", () => {
88
- transport.close();
89
- });
90
- await server.connectStatelessTransport(transport);
91
72
  // Express strips the mount path from req.url (e.g. "/mcp" becomes "/").
92
- // Restore it so the SDK builds the correct requestInfo.url.
73
+ // Restore it so the SDK builds the correct request URL.
93
74
  req.url = req.originalUrl;
94
- await transport.handleRequest(req, res, req.body);
75
+ await handler(req, res, req.body);
95
76
  }
96
77
  catch (error) {
97
78
  next(error);
@@ -1 +1 @@
1
- {"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/server/express.ts"],"names":[],"mappings":"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","sourcesContent":["import type http from \"node:http\";\nimport path from \"node:path\";\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport cors from \"cors\";\nimport express from \"express\";\nimport type { McpServer } from \"./server.js\";\n\nfunction parseControlPort(raw: string | undefined): number | null {\n if (raw === undefined) {\n return null;\n }\n const n = Number.parseInt(raw, 10);\n if (!Number.isFinite(n) || n <= 0 || n >= 65536) {\n return null;\n }\n return n;\n}\n\nfunction applyMiddlewares(\n app: express.Express,\n middlewares: Array<{\n path?: string;\n handlers: express.ErrorRequestHandler[];\n }>,\n): void {\n for (const middleware of middlewares) {\n if (middleware.path) {\n app.use(middleware.path, ...middleware.handlers);\n } else {\n app.use(...middleware.handlers);\n }\n }\n}\n\nfunction defaultErrorHandler(\n err: unknown,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction,\n) {\n console.error(\"Error handling MCP request:\", err);\n if (!res.headersSent) {\n res.status(500).json({\n jsonrpc: \"2.0\",\n error: { code: -32603, message: \"Internal server error\" },\n id: null,\n });\n }\n}\n\nexport async function createApp({\n mcpServer,\n httpServer,\n errorMiddleware = [],\n}: {\n mcpServer: McpServer;\n httpServer: http.Server;\n errorMiddleware?: {\n path?: string;\n handlers: express.ErrorRequestHandler[];\n }[];\n}): Promise<express.Express> {\n const app = mcpServer.express;\n\n // Read `process.env.NODE_ENV` inline: wrangler/esbuild only substitute the literal expression,\n // so a local const would defeat dead-code elimination of the dev-only imports below.\n if (process.env.NODE_ENV !== \"production\") {\n const { devtoolsStaticServer } = await import(\"@skybridge/devtools\");\n app.use(await devtoolsStaticServer());\n const { viewsDevServer } = await import(\"./viewsDevServer.js\");\n app.use(await viewsDevServer(httpServer));\n\n const controlPort = parseControlPort(process.env.__TUNNEL_CONTROL_PORT);\n if (controlPort !== null) {\n const { createTunnelProxyRouter } = await import(\n \"./tunnel-proxy-router.js\"\n );\n app.use(createTunnelProxyRouter(controlPort));\n } else if (process.env.__TUNNEL_CONTROL_PORT !== undefined) {\n console.warn(\n `Ignoring invalid __TUNNEL_CONTROL_PORT=${process.env.__TUNNEL_CONTROL_PORT}`,\n );\n }\n } else {\n const assetsPath = path.join(process.cwd(), \"dist\", \"assets\");\n\n app.use(\"/assets\", cors());\n app.use(\"/assets\", express.static(assetsPath));\n }\n\n app.use(\"/mcp\", mcpMiddleware(mcpServer));\n\n applyMiddlewares(app, errorMiddleware);\n\n app.use(\"/mcp\", defaultErrorHandler);\n\n return app;\n}\n\nconst mcpMiddleware = (server: McpServer): express.RequestHandler => {\n return async (\n req: express.Request,\n res: express.Response,\n next: express.NextFunction,\n ) => {\n if (req.method !== \"POST\") {\n res.writeHead(405).end(\n JSON.stringify({\n jsonrpc: \"2.0\",\n error: {\n code: -32000,\n message: \"Method not allowed.\",\n },\n id: null,\n }),\n );\n return;\n }\n\n try {\n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n // Respond with a single JSON body instead of SSE. Skybridge's stateless\n // transport never streams server-initiated messages, so SSE adds no\n // capability — and on workerd specifically, `cloudflare:node`'s http\n // bridge silently drops chunked writes that happen after the request\n // handler awaits, which manifests as a 200 with empty body for any\n // async tools/call.\n enableJsonResponse: true,\n });\n\n res.on(\"close\", () => {\n transport.close();\n });\n\n await server.connectStatelessTransport(transport);\n // Express strips the mount path from req.url (e.g. \"/mcp\" becomes \"/\").\n // Restore it so the SDK builds the correct requestInfo.url.\n req.url = req.originalUrl;\n await transport.handleRequest(req, res, req.body);\n } catch (error) {\n next(error);\n }\n };\n};\n"]}
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/server/express.ts"],"names":[],"mappings":"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","sourcesContent":["import type http from \"node:http\";\nimport path from \"node:path\";\nimport { toNodeHandler } from \"@modelcontextprotocol/node\";\nimport { createMcpHandler } from \"@modelcontextprotocol/server\";\nimport cors from \"cors\";\nimport express from \"express\";\nimport type { McpServer } from \"./server.js\";\n\nfunction parseControlPort(raw: string | undefined): number | null {\n if (raw === undefined) {\n return null;\n }\n const n = Number.parseInt(raw, 10);\n if (!Number.isFinite(n) || n <= 0 || n >= 65536) {\n return null;\n }\n return n;\n}\n\nfunction applyMiddlewares(\n app: express.Express,\n middlewares: Array<{\n path?: string;\n handlers: express.ErrorRequestHandler[];\n }>,\n): void {\n for (const middleware of middlewares) {\n if (middleware.path) {\n app.use(middleware.path, ...middleware.handlers);\n } else {\n app.use(...middleware.handlers);\n }\n }\n}\n\nfunction defaultErrorHandler(\n err: unknown,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction,\n) {\n console.error(\"Error handling MCP request:\", err);\n if (!res.headersSent) {\n res.status(500).json({\n jsonrpc: \"2.0\",\n error: { code: -32603, message: \"Internal server error\" },\n id: null,\n });\n }\n}\n\nexport async function createApp({\n mcpServer,\n httpServer,\n errorMiddleware = [],\n}: {\n mcpServer: McpServer;\n httpServer: http.Server;\n errorMiddleware?: {\n path?: string;\n handlers: express.ErrorRequestHandler[];\n }[];\n}): Promise<express.Express> {\n const app = mcpServer.express;\n\n // Read `process.env.NODE_ENV` inline: wrangler/esbuild only substitute the literal expression,\n // so a local const would defeat dead-code elimination of the dev-only imports below.\n if (process.env.NODE_ENV !== \"production\") {\n const { devtoolsStaticServer } = await import(\"@skybridge/devtools\");\n app.use(await devtoolsStaticServer());\n const { viewsDevServer } = await import(\"./viewsDevServer.js\");\n app.use(await viewsDevServer(httpServer));\n\n const controlPort = parseControlPort(process.env.__TUNNEL_CONTROL_PORT);\n if (controlPort !== null) {\n const { createTunnelProxyRouter } = await import(\n \"./tunnel-proxy-router.js\"\n );\n app.use(createTunnelProxyRouter(controlPort));\n } else if (process.env.__TUNNEL_CONTROL_PORT !== undefined) {\n console.warn(\n `Ignoring invalid __TUNNEL_CONTROL_PORT=${process.env.__TUNNEL_CONTROL_PORT}`,\n );\n }\n } else {\n const assetsPath = path.join(process.cwd(), \"dist\", \"assets\");\n\n app.use(\"/assets\", cors());\n app.use(\"/assets\", express.static(assetsPath));\n }\n\n app.use(\"/mcp\", mcpMiddleware(mcpServer));\n\n applyMiddlewares(app, errorMiddleware);\n\n app.use(\"/mcp\", defaultErrorHandler);\n\n return app;\n}\n\nconst mcpMiddleware = (server: McpServer): express.RequestHandler => {\n const handler = toNodeHandler(\n createMcpHandler(() => server.createStatelessServerInstance(), {\n onerror: (error) => {\n console.error(\"Error handling MCP request:\", error);\n },\n }),\n );\n\n return async (\n req: express.Request,\n res: express.Response,\n next: express.NextFunction,\n ) => {\n try {\n // Express strips the mount path from req.url (e.g. \"/mcp\" becomes \"/\").\n // Restore it so the SDK builds the correct request URL.\n req.url = req.originalUrl;\n await handler(req, res, req.body);\n } catch (error) {\n next(error);\n }\n };\n};\n"]}
@@ -1,4 +1,5 @@
1
1
  import http from "node:http";
2
+ import { Client, StreamableHTTPClientTransport, } from "@modelcontextprotocol/client";
2
3
  import { afterEach, describe, expect, it, vi } from "vitest";
3
4
  import { McpServer } from "./server.js";
4
5
  vi.mock("@skybridge/devtools", () => ({
@@ -95,7 +96,9 @@ describe("McpServer.express", () => {
95
96
  res.status(503).json({ from: "useOnError" });
96
97
  });
97
98
  // Force the /mcp handler to throw so the error pipeline runs.
98
- vi.spyOn(server, "connectStatelessTransport").mockRejectedValue(new Error("boom"));
99
+ server.use("/mcp", () => {
100
+ throw new Error("boom");
101
+ });
99
102
  const httpServer = http.createServer();
100
103
  await createApp({
101
104
  mcpServer: server,
@@ -288,10 +291,11 @@ describe("createApp", () => {
288
291
  const { createApp } = await import("./express.js");
289
292
  const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => { });
290
293
  const mcpServer = new McpServer({ name: "t", version: "0.0.0" });
291
- // Force the express-level error path: make connectStatelessTransport
292
- // reject so the request handler hits its try/catch and calls next(error),
293
- // which lands in the default /mcp error handler.
294
- vi.spyOn(mcpServer, "connectStatelessTransport").mockRejectedValue(new Error("boom"));
294
+ // Force the express-level error path: make a /mcp middleware throw so the
295
+ // error pipeline runs and lands in the default /mcp error handler.
296
+ mcpServer.use("/mcp", () => {
297
+ throw new Error("boom");
298
+ });
295
299
  const httpServer = http.createServer();
296
300
  const app = await createApp({ mcpServer, httpServer });
297
301
  const { port, server: httpListening } = await listen(app);
@@ -314,7 +318,9 @@ describe("createApp", () => {
314
318
  res.status(503).json({ custom: true });
315
319
  };
316
320
  const mcpServer = new McpServer({ name: "t", version: "0.0.0" });
317
- vi.spyOn(mcpServer, "connectStatelessTransport").mockRejectedValue(new Error("boom"));
321
+ mcpServer.use("/mcp", () => {
322
+ throw new Error("boom");
323
+ });
318
324
  const httpServer = http.createServer();
319
325
  const app = await createApp({
320
326
  mcpServer,
@@ -339,7 +345,9 @@ describe("createApp", () => {
339
345
  next(new Error("api error"));
340
346
  };
341
347
  const mcpServer = new McpServer({ name: "t", version: "0.0.0" });
342
- vi.spyOn(mcpServer, "connectStatelessTransport").mockRejectedValue(new Error("boom"));
348
+ mcpServer.use("/mcp", () => {
349
+ throw new Error("boom");
350
+ });
343
351
  mcpServer.use("/api/test", throwingApiRoute);
344
352
  const httpServer = http.createServer();
345
353
  const app = await createApp({
@@ -488,4 +496,31 @@ describe("createApp tunnel routes", () => {
488
496
  }
489
497
  });
490
498
  });
499
+ describe("createApp mount path", () => {
500
+ it("serves /mcp with the mount path intact on the request URL", async () => {
501
+ const { createApp } = await import("./express.js");
502
+ const mcpServer = new McpServer({
503
+ name: "t",
504
+ version: "0.0.0",
505
+ }).registerTool({
506
+ name: "echo-url",
507
+ description: "Echoes the request URL.",
508
+ inputSchema: {},
509
+ }, (_args, extra) => ({
510
+ content: [{ type: "text", text: extra.http?.req?.url ?? "" }],
511
+ }));
512
+ const httpServer = http.createServer();
513
+ const app = await createApp({ mcpServer, httpServer });
514
+ const { port, server } = await listen(app);
515
+ openServer = server;
516
+ const client = new Client({ name: "test-client", version: "0.0.0" });
517
+ await client.connect(new StreamableHTTPClientTransport(new URL(`http://localhost:${port}/mcp`)));
518
+ const result = (await client.callTool({
519
+ name: "echo-url",
520
+ arguments: {},
521
+ }));
522
+ await client.close();
523
+ expect(new URL(result.content[0]?.text ?? "").pathname).toBe("/mcp");
524
+ });
525
+ });
491
526
  //# sourceMappingURL=express.test.js.map