skybridge 2.0.0-beta.68a71cb → 2.0.0-beta.8f56a42

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 (120) hide show
  1. package/dist/cli/detect-port.js +0 -1
  2. package/dist/cli/detect-port.js.map +1 -1
  3. package/dist/cli/use-nodemon.js +10 -2
  4. package/dist/cli/use-nodemon.js.map +1 -1
  5. package/dist/cli/use-typescript-check.js +1 -1
  6. package/dist/cli/use-typescript-check.js.map +1 -1
  7. package/dist/server/app.d.ts +88 -37
  8. package/dist/server/app.js +76 -42
  9. package/dist/server/app.js.map +1 -1
  10. package/dist/server/app.test.d.ts +1 -0
  11. package/dist/server/app.test.js +97 -0
  12. package/dist/server/app.test.js.map +1 -0
  13. package/dist/server/auth/discovery.d.ts +1 -1
  14. package/dist/server/auth/discovery.js +1 -1
  15. package/dist/server/auth/discovery.js.map +1 -1
  16. package/dist/server/auth/index.d.ts +1 -1
  17. package/dist/server/auth/index.js.map +1 -1
  18. package/dist/server/auth/setup.test.js +56 -51
  19. package/dist/server/auth/setup.test.js.map +1 -1
  20. package/dist/server/auth-extra.test-d.js +91 -35
  21. package/dist/server/auth-extra.test-d.js.map +1 -1
  22. package/dist/server/auth.d.ts +1 -1
  23. package/dist/server/auth.js +1 -1
  24. package/dist/server/auth.js.map +1 -1
  25. package/dist/server/express.d.ts +11 -2
  26. package/dist/server/express.js +22 -4
  27. package/dist/server/express.js.map +1 -1
  28. package/dist/server/express.test.js +120 -31
  29. package/dist/server/express.test.js.map +1 -1
  30. package/dist/server/file-ref.d.ts +1 -1
  31. package/dist/server/file-ref.js +1 -1
  32. package/dist/server/file-ref.js.map +1 -1
  33. package/dist/server/index.d.ts +3 -3
  34. package/dist/server/index.js +2 -1
  35. package/dist/server/index.js.map +1 -1
  36. package/dist/server/mcp-errors.test.js +5 -1
  37. package/dist/server/mcp-errors.test.js.map +1 -1
  38. package/dist/server/middleware.test.js +194 -145
  39. package/dist/server/middleware.test.js.map +1 -1
  40. package/dist/server/register-tool.test.js +9 -5
  41. package/dist/server/register-tool.test.js.map +1 -1
  42. package/dist/server/server.d.ts +29 -62
  43. package/dist/server/server.js +21 -69
  44. package/dist/server/server.js.map +1 -1
  45. package/dist/server/skills-integration.test.js +7 -2
  46. package/dist/server/skills-integration.test.js.map +1 -1
  47. package/dist/server/skills.js +1 -1
  48. package/dist/server/skills.js.map +1 -1
  49. package/dist/server/templateHelper.d.ts +0 -3
  50. package/dist/server/templateHelper.js.map +1 -1
  51. package/dist/server/templates.generated.js +4 -8
  52. package/dist/server/templates.generated.js.map +1 -1
  53. package/dist/server/tool-extra.test.js +32 -16
  54. package/dist/server/tool-extra.test.js.map +1 -1
  55. package/dist/server/view-resource-resolution.test.js +8 -4
  56. package/dist/server/view-resource-resolution.test.js.map +1 -1
  57. package/dist/test/utils.d.ts +3 -3
  58. package/dist/test/utils.js +210 -196
  59. package/dist/test/utils.js.map +1 -1
  60. package/dist/test/view.test.js +22 -17
  61. package/dist/test/view.test.js.map +1 -1
  62. package/dist/views/index.d.ts +7 -0
  63. package/dist/views/index.js.map +1 -1
  64. package/dist/web/bridges/adaptor.test.js +1 -1
  65. package/dist/web/bridges/adaptor.test.js.map +1 -1
  66. package/dist/web/bridges/get-adaptor.test.js +2 -2
  67. package/dist/web/bridges/get-adaptor.test.js.map +1 -1
  68. package/dist/web/bridges/mcp-app/bridge.js +1 -1
  69. package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
  70. package/dist/web/bridges/mcp-app/bridge.test.js +2 -2
  71. package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -1
  72. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -1
  73. package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
  74. package/dist/web/bridges/mcp-app/view-tools.test.js +1 -1
  75. package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -1
  76. package/dist/web/bridges/types.d.ts +6 -9
  77. package/dist/web/bridges/types.js.map +1 -1
  78. package/dist/web/create-store.test.js +2 -2
  79. package/dist/web/create-store.test.js.map +1 -1
  80. package/dist/web/data-llm.test.js +2 -2
  81. package/dist/web/data-llm.test.js.map +1 -1
  82. package/dist/web/generate-helpers.d.ts +15 -12
  83. package/dist/web/generate-helpers.js +15 -12
  84. package/dist/web/generate-helpers.js.map +1 -1
  85. package/dist/web/hooks/use-call-tool.test.js +2 -2
  86. package/dist/web/hooks/use-call-tool.test.js.map +1 -1
  87. package/dist/web/hooks/use-display-mode.test.js +1 -1
  88. package/dist/web/hooks/use-display-mode.test.js.map +1 -1
  89. package/dist/web/hooks/use-download.test.js +3 -3
  90. package/dist/web/hooks/use-download.test.js.map +1 -1
  91. package/dist/web/hooks/use-files.test.js +1 -1
  92. package/dist/web/hooks/use-files.test.js.map +1 -1
  93. package/dist/web/hooks/use-host.test.js +1 -1
  94. package/dist/web/hooks/use-host.test.js.map +1 -1
  95. package/dist/web/hooks/use-open-external.test.js +2 -2
  96. package/dist/web/hooks/use-open-external.test.js.map +1 -1
  97. package/dist/web/hooks/use-request-close.test.js +1 -1
  98. package/dist/web/hooks/use-request-close.test.js.map +1 -1
  99. package/dist/web/hooks/use-request-modal.test.js +1 -1
  100. package/dist/web/hooks/use-request-modal.test.js.map +1 -1
  101. package/dist/web/hooks/use-request-size.test.js +1 -1
  102. package/dist/web/hooks/use-request-size.test.js.map +1 -1
  103. package/dist/web/hooks/use-set-open-in-app-url.test.js +2 -8
  104. package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
  105. package/dist/web/hooks/use-tool-info.test.js +2 -2
  106. package/dist/web/hooks/use-tool-info.test.js.map +1 -1
  107. package/dist/web/hooks/use-user.test.js +2 -2
  108. package/dist/web/hooks/use-user.test.js.map +1 -1
  109. package/dist/web/hooks/use-view-state.test.js +2 -2
  110. package/dist/web/hooks/use-view-state.test.js.map +1 -1
  111. package/dist/web/hooks/use-viewport.test.js +2 -2
  112. package/dist/web/hooks/use-viewport.test.js.map +1 -1
  113. package/dist/web/index.d.ts +0 -1
  114. package/dist/web/index.js.map +1 -1
  115. package/dist/web/mount-view.js +2 -6
  116. package/dist/web/mount-view.js.map +1 -1
  117. package/package.json +6 -6
  118. package/dist/standard-schema.d.ts +0 -3
  119. package/dist/standard-schema.js +0 -2
  120. package/dist/standard-schema.js.map +0 -1
@@ -33,7 +33,6 @@ export async function detectAvailablePort(startPort, host) {
33
33
  if (await isPortAvailable(port, host)) {
34
34
  return port;
35
35
  }
36
- console.log(`Port ${port} is in use, trying another one...`);
37
36
  }
38
37
  throw new Error(`No available port found between ${startPort} and ${startPort + MAX_PORT_INCREMENT - 1}`);
39
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"detect-port.js","sourceRoot":"","sources":["../../src/cli/detect-port.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAE3B,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAiB;IACjD,IAAI,QAAQ,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO;YACL,IAAI,EAAE,MAAM,mBAAmB,CAAC,YAAY,CAAC;YAC7C,QAAQ,EAAE,KAAK;YACf,UAAU,EAAE,iBAAiB,MAAM,+BAA+B;SACnE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACrD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB,EACjB,IAAa;IAEb,KAAK,IAAI,IAAI,GAAG,SAAS,EAAE,IAAI,GAAG,SAAS,GAAG,kBAAkB,EAAE,IAAI,EAAE,EAAE,CAAC;QACzE,IAAI,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,mCAAmC,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,IAAI,KAAK,CACb,mCAAmC,SAAS,QAAQ,SAAS,GAAG,kBAAkB,GAAG,CAAC,EAAE,CACzF,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,IAAa;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAElC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import net from \"node:net\";\n\nconst DEFAULT_PORT = 3000;\nconst MAX_PORT_INCREMENT = 100;\n\nexport async function resolvePort(flagPort?: number) {\n if (flagPort && flagPort > 1) {\n return { port: flagPort, fallback: false };\n }\n\n const rawEnv = process.env.PORT;\n if (rawEnv) {\n const parsed = Number(rawEnv);\n if (Number.isInteger(parsed) && parsed > 0) {\n return { port: parsed, fallback: false };\n }\n return {\n port: await detectAvailablePort(DEFAULT_PORT),\n fallback: false,\n envWarning: `Invalid PORT=\"${rawEnv}\", ignoring and using default`,\n };\n }\n\n const port = await detectAvailablePort(DEFAULT_PORT);\n return { port, fallback: port !== DEFAULT_PORT };\n}\n\n/**\n * Returns the first available port at or after `startPort`, incrementing\n * by one until a free port is found or `MAX_PORT_INCREMENT` is reached.\n *\n * @param host - Bind address for the check. Pass `\"localhost\"` for\n * services that bind to 127.0.0.1 (e.g. Vite HMR). Omit for\n * services that bind to all interfaces (e.g. the HTTP server).\n */\nexport async function detectAvailablePort(\n startPort: number,\n host?: string,\n): Promise<number> {\n for (let port = startPort; port < startPort + MAX_PORT_INCREMENT; port++) {\n if (await isPortAvailable(port, host)) {\n return port;\n }\n console.log(`Port ${port} is in use, trying another one...`);\n }\n throw new Error(\n `No available port found between ${startPort} and ${startPort + MAX_PORT_INCREMENT - 1}`,\n );\n}\n\nfunction isPortAvailable(port: number, host?: string): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n\n server.listen(port, host);\n });\n}\n"]}
1
+ {"version":3,"file":"detect-port.js","sourceRoot":"","sources":["../../src/cli/detect-port.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAE3B,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAiB;IACjD,IAAI,QAAQ,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO;YACL,IAAI,EAAE,MAAM,mBAAmB,CAAC,YAAY,CAAC;YAC7C,QAAQ,EAAE,KAAK;YACf,UAAU,EAAE,iBAAiB,MAAM,+BAA+B;SACnE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACrD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB,EACjB,IAAa;IAEb,KAAK,IAAI,IAAI,GAAG,SAAS,EAAE,IAAI,GAAG,SAAS,GAAG,kBAAkB,EAAE,IAAI,EAAE,EAAE,CAAC;QACzE,IAAI,MAAM,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,mCAAmC,SAAS,QAAQ,SAAS,GAAG,kBAAkB,GAAG,CAAC,EAAE,CACzF,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAAY,EAAE,IAAa;IAClD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAElC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import net from \"node:net\";\n\nconst DEFAULT_PORT = 3000;\nconst MAX_PORT_INCREMENT = 100;\n\nexport async function resolvePort(flagPort?: number) {\n if (flagPort && flagPort > 1) {\n return { port: flagPort, fallback: false };\n }\n\n const rawEnv = process.env.PORT;\n if (rawEnv) {\n const parsed = Number(rawEnv);\n if (Number.isInteger(parsed) && parsed > 0) {\n return { port: parsed, fallback: false };\n }\n return {\n port: await detectAvailablePort(DEFAULT_PORT),\n fallback: false,\n envWarning: `Invalid PORT=\"${rawEnv}\", ignoring and using default`,\n };\n }\n\n const port = await detectAvailablePort(DEFAULT_PORT);\n return { port, fallback: port !== DEFAULT_PORT };\n}\n\n/**\n * Returns the first available port at or after `startPort`, incrementing\n * by one until a free port is found or `MAX_PORT_INCREMENT` is reached.\n *\n * @param host - Bind address for the check. Pass `\"localhost\"` for\n * services that bind to 127.0.0.1 (e.g. Vite HMR). Omit for\n * services that bind to all interfaces (e.g. the HTTP server).\n */\nexport async function detectAvailablePort(\n startPort: number,\n host?: string,\n): Promise<number> {\n for (let port = startPort; port < startPort + MAX_PORT_INCREMENT; port++) {\n if (await isPortAvailable(port, host)) {\n return port;\n }\n }\n throw new Error(\n `No available port found between ${startPort} and ${startPort + MAX_PORT_INCREMENT - 1}`,\n );\n}\n\nfunction isPortAvailable(port: number, host?: string): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n\n server.listen(port, host);\n });\n}\n"]}
@@ -1,8 +1,15 @@
1
1
  import { existsSync } from "node:fs";
2
+ import { createRequire } from "node:module";
2
3
  import { resolve } from "node:path";
3
- import nodemonOriginal from "nodemon";
4
4
  import { useEffect } from "react";
5
- const nodemon = nodemonOriginal;
5
+ function loadNodemon() {
6
+ try {
7
+ return createRequire(import.meta.url)("nodemon");
8
+ }
9
+ catch {
10
+ throw new Error('skybridge dev needs the "nodemon" peer dependency. Install it with `npm install -D nodemon`.');
11
+ }
12
+ }
6
13
  const SOURCEMAP_WARNING = /^Sourcemap for ".*" points to missing source files$/;
7
14
  /**
8
15
  * Boot nodemon and wire its stdout/stderr to the provided handlers. Returns a
@@ -10,6 +17,7 @@ const SOURCEMAP_WARNING = /^Sourcemap for ".*" points to missing source files$/;
10
17
  * Ink-based dev UI (via {@link useNodemon}) and the `--plain` runner.
11
18
  */
12
19
  export function startNodemon(env, handlers) {
20
+ const nodemon = loadNodemon();
13
21
  const configFile = resolve(process.cwd(), "nodemon.json");
14
22
  const config = existsSync(configFile)
15
23
  ? {
@@ -1 +1 @@
1
- {"version":3,"file":"use-nodemon.js","sourceRoot":"","sources":["../../src/cli/use-nodemon.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport nodemonOriginal from \"nodemon\";\nimport { useEffect } from \"react\";\nimport type { ExtendedNodemon } from \"./nodemon.d.ts\";\nimport type { PushMessage } from \"./use-messages.js\";\n\nconst nodemon = nodemonOriginal as ExtendedNodemon;\n\nconst SOURCEMAP_WARNING = /^Sourcemap for \".*\" points to missing source files$/;\n\nexport interface NodemonHandlers {\n /** A raw chunk of server stdout, forwarded untouched. */\n onStdout: (chunk: Buffer) => void;\n /** A (filtered) chunk of server stderr. */\n onStderr: (message: string) => void;\n /** The server restarted because the listed files changed. */\n onRestart: (files: string[]) => void;\n}\n\n/**\n * Boot nodemon and wire its stdout/stderr to the provided handlers. Returns a\n * cleanup function that detaches the listeners and quits nodemon. Shared by the\n * Ink-based dev UI (via {@link useNodemon}) and the `--plain` runner.\n */\nexport function startNodemon(\n env: NodeJS.ProcessEnv,\n handlers: NodemonHandlers,\n): () => void {\n const configFile = resolve(process.cwd(), \"nodemon.json\");\n\n const config = existsSync(configFile)\n ? {\n configFile,\n }\n : {\n watch: [\"src\"],\n ext: \"ts,json,md\",\n exec: \"tsx src/index.ts\",\n };\n\n nodemon({ ...config, env, stdout: false });\n\n const handleStdoutData = (chunk: Buffer) => {\n handlers.onStdout(chunk);\n };\n\n const handleStderrData = (chunk: Buffer) => {\n const message = chunk.toString().trim();\n if (!message) {\n return;\n }\n // Node's source-map warnings for third-party deps (superjson, @mcp/sdk, …) — not actionable.\n const filtered = message\n .split(\"\\n\")\n .filter((line) => !SOURCEMAP_WARNING.test(line))\n .join(\"\\n\");\n if (filtered) {\n handlers.onStderr(filtered);\n }\n };\n\n const setupStdoutListener = () => {\n if (nodemon.stdout) {\n nodemon.stdout.off(\"data\", handleStdoutData);\n nodemon.stdout.on(\"data\", handleStdoutData);\n }\n };\n\n const setupStderrListener = () => {\n if (nodemon.stderr) {\n nodemon.stderr.off(\"data\", handleStderrData);\n nodemon.stderr.on(\"data\", handleStderrData);\n }\n };\n\n nodemon.on(\"readable\", () => {\n setupStdoutListener();\n setupStderrListener();\n });\n\n nodemon.on(\"restart\", (files: string[]) => {\n handlers.onRestart(files);\n setupStdoutListener();\n setupStderrListener();\n });\n\n return () => {\n if (nodemon.stdout) {\n nodemon.stdout.off(\"data\", handleStdoutData);\n }\n if (nodemon.stderr) {\n nodemon.stderr.off(\"data\", handleStderrData);\n }\n nodemon.emit(\"quit\");\n };\n}\n\nexport function useNodemon(\n env: NodeJS.ProcessEnv,\n pushMessage: PushMessage,\n): void {\n useEffect(\n () =>\n startNodemon(env, {\n onStdout: (chunk) => {\n const message = chunk.toString().trim();\n if (message) {\n pushMessage(message, \"log\");\n }\n },\n onStderr: (message) => pushMessage(message, \"error\"),\n onRestart: (files) =>\n pushMessage(\n `Server restarted due to file changes: ${files.join(\", \")}`,\n \"restart\",\n ),\n }),\n [env, pushMessage],\n );\n}\n"]}
1
+ {"version":3,"file":"use-nodemon.js","sourceRoot":"","sources":["../../src/cli/use-nodemon.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { resolve } from \"node:path\";\nimport { useEffect } from \"react\";\nimport type { ExtendedNodemon } from \"./nodemon.d.ts\";\nimport type { PushMessage } from \"./use-messages.js\";\n\nfunction loadNodemon(): ExtendedNodemon {\n try {\n return createRequire(import.meta.url)(\"nodemon\") as ExtendedNodemon;\n } catch {\n throw new Error(\n 'skybridge dev needs the \"nodemon\" peer dependency. Install it with `npm install -D nodemon`.',\n );\n }\n}\n\nconst SOURCEMAP_WARNING = /^Sourcemap for \".*\" points to missing source files$/;\n\nexport interface NodemonHandlers {\n /** A raw chunk of server stdout, forwarded untouched. */\n onStdout: (chunk: Buffer) => void;\n /** A (filtered) chunk of server stderr. */\n onStderr: (message: string) => void;\n /** The server restarted because the listed files changed. */\n onRestart: (files: string[]) => void;\n}\n\n/**\n * Boot nodemon and wire its stdout/stderr to the provided handlers. Returns a\n * cleanup function that detaches the listeners and quits nodemon. Shared by the\n * Ink-based dev UI (via {@link useNodemon}) and the `--plain` runner.\n */\nexport function startNodemon(\n env: NodeJS.ProcessEnv,\n handlers: NodemonHandlers,\n): () => void {\n const nodemon = loadNodemon();\n const configFile = resolve(process.cwd(), \"nodemon.json\");\n\n const config = existsSync(configFile)\n ? {\n configFile,\n }\n : {\n watch: [\"src\"],\n ext: \"ts,json,md\",\n exec: \"tsx src/index.ts\",\n };\n\n nodemon({ ...config, env, stdout: false });\n\n const handleStdoutData = (chunk: Buffer) => {\n handlers.onStdout(chunk);\n };\n\n const handleStderrData = (chunk: Buffer) => {\n const message = chunk.toString().trim();\n if (!message) {\n return;\n }\n // Node's source-map warnings for third-party deps (superjson, @mcp/sdk, …) — not actionable.\n const filtered = message\n .split(\"\\n\")\n .filter((line) => !SOURCEMAP_WARNING.test(line))\n .join(\"\\n\");\n if (filtered) {\n handlers.onStderr(filtered);\n }\n };\n\n const setupStdoutListener = () => {\n if (nodemon.stdout) {\n nodemon.stdout.off(\"data\", handleStdoutData);\n nodemon.stdout.on(\"data\", handleStdoutData);\n }\n };\n\n const setupStderrListener = () => {\n if (nodemon.stderr) {\n nodemon.stderr.off(\"data\", handleStderrData);\n nodemon.stderr.on(\"data\", handleStderrData);\n }\n };\n\n nodemon.on(\"readable\", () => {\n setupStdoutListener();\n setupStderrListener();\n });\n\n nodemon.on(\"restart\", (files: string[]) => {\n handlers.onRestart(files);\n setupStdoutListener();\n setupStderrListener();\n });\n\n return () => {\n if (nodemon.stdout) {\n nodemon.stdout.off(\"data\", handleStdoutData);\n }\n if (nodemon.stderr) {\n nodemon.stderr.off(\"data\", handleStderrData);\n }\n nodemon.emit(\"quit\");\n };\n}\n\nexport function useNodemon(\n env: NodeJS.ProcessEnv,\n pushMessage: PushMessage,\n): void {\n useEffect(\n () =>\n startNodemon(env, {\n onStdout: (chunk) => {\n const message = chunk.toString().trim();\n if (message) {\n pushMessage(message, \"log\");\n }\n },\n onStderr: (message) => pushMessage(message, \"error\"),\n onRestart: (files) =>\n pushMessage(\n `Server restarted due to file changes: ${files.join(\", \")}`,\n \"restart\",\n ),\n }),\n [env, pushMessage],\n );\n}\n"]}
@@ -26,7 +26,7 @@ function extractBestMessage(message, continuationLines) {
26
26
  * and the `--plain` runner.
27
27
  */
28
28
  export function startTypeScriptCheck(onErrors) {
29
- const tsProcess = spawn("npx", ["tsc", "--noEmit", "--watch", "--pretty", "false"], {
29
+ const tsProcess = spawn("npx tsc --noEmit --watch --pretty false", {
30
30
  stdio: ["ignore", "pipe", "pipe"],
31
31
  shell: true,
32
32
  });
@@ -1 +1 @@
1
- {"version":3,"file":"use-typescript-check.js","sourceRoot":"","sources":["../../src/cli/use-typescript-check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,MAAM,aAAa,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAU5C,kFAAkF;AAClF,SAAS,kBAAkB,CACzB,OAAe,EACf,iBAAgC;IAEhC,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,CAAC;QAC9B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC;QACrD,IAAI,MAAM,GAAG,SAAS,EAAE,CAAC;YACvB,SAAS,GAAG,MAAM,CAAC;QACrB,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,iBAAiB;SAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC;SAC5D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,OAAO,OAAO,IAAI,OAAO,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA0C;IAE1C,MAAM,SAAS,GAAG,KAAK,CACrB,KAAK,EACL,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,EACnD;QACE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;QACjC,KAAK,EAAE,IAAI;KACZ,CACF,CAAC;IAEF,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,IAAI,aAAa,GAAmB,EAAE,CAAC;IACvC,IAAI,YAAY,GAAmB,IAAI,CAAC;IACxC,IAAI,iBAAiB,GAAkB,EAAE,CAAC;IAE1C,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,YAAY,CAAC,OAAO,GAAG,kBAAkB,CACvC,YAAY,CAAC,OAAO,EACpB,iBAAiB,CAClB,CAAC;QACF,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACjC,YAAY,GAAG,IAAI,CAAC;QACpB,iBAAiB,GAAG,EAAE,CAAC;IACzB,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,EAAE;QACrC,YAAY,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;QAEjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAE5B,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAC9B,wDAAwD,CACzD,CAAC;YACF,IAAI,UAAU,EAAE,CAAC;gBACf,YAAY,EAAE,CAAC;gBACf,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,UAAU,CAAC;gBAC5D,IAAI,IAAI,IAAI,OAAO,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;oBACzC,IAAI,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;oBAC5B,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;wBAC1B,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;oBACjD,CAAC;oBACD,YAAY,GAAG;wBACb,IAAI,EAAE,SAAS;wBACf,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;wBAClC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;wBAChC,IAAI,EAAE,IAAI,IAAI,EAAE;wBAChB,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE;qBACxB,CAAC;gBACJ,CAAC;gBACD,SAAS;YACX,CAAC;YAED,IAAI,YAAY,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC7B,SAAS;YACX,CAAC;YAED,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3D,YAAY,EAAE,CAAC;gBACf,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC;gBACnE,aAAa,GAAG,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,GAAG,EAAE;QACV,SAAS,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAiB,EAAE,CAAC,CAAC;IAE7D,SAAS,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import { isAbsolute, relative } from \"node:path\";\nimport spawn from \"cross-spawn\";\nimport { useEffect, useState } from \"react\";\n\nexport type TsError = {\n file: string;\n line: number;\n col: number;\n code: string;\n message: string;\n};\n\n// TypeScript nests from general to specific — the deepest line is the root cause.\nfunction extractBestMessage(\n message: string,\n continuationLines: Array<string>,\n): string {\n if (!continuationLines.length) {\n return message;\n }\n let maxIndent = 0;\n for (const line of continuationLines) {\n const indent = line.length - line.trimStart().length;\n if (indent > maxIndent) {\n maxIndent = indent;\n }\n }\n const deepest = continuationLines\n .filter((l) => l.length - l.trimStart().length === maxIndent)\n .map((l) => l.trim())\n .filter(Boolean)[0];\n return deepest ?? message;\n}\n\n/**\n * Spawn `tsc --noEmit --watch` and report each completed check via `onErrors`\n * (empty array when the check is clean). Returns a cleanup function that kills\n * the watcher. Shared by the Ink-based dev UI (via {@link useTypeScriptCheck})\n * and the `--plain` runner.\n */\nexport function startTypeScriptCheck(\n onErrors: (errors: Array<TsError>) => void,\n): () => void {\n const tsProcess = spawn(\n \"npx\",\n [\"tsc\", \"--noEmit\", \"--watch\", \"--pretty\", \"false\"],\n {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n shell: true,\n },\n );\n\n let outputBuffer = \"\";\n let currentErrors: Array<TsError> = [];\n let pendingError: TsError | null = null;\n let continuationLines: Array<string> = [];\n\n const flushPending = () => {\n if (!pendingError) {\n return;\n }\n pendingError.message = extractBestMessage(\n pendingError.message,\n continuationLines,\n );\n currentErrors.push(pendingError);\n pendingError = null;\n continuationLines = [];\n };\n\n const processOutput = (data: Buffer) => {\n outputBuffer += data.toString();\n const lines = outputBuffer.split(\"\\n\");\n outputBuffer = lines.pop() || \"\";\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n const errorMatch = trimmed.match(\n /^(.+?)\\((\\d+),(\\d+)\\):\\s+error\\s+(TS\\d+)?\\s*:?\\s*(.+)$/,\n );\n if (errorMatch) {\n flushPending();\n const [, file, lineStr, colStr, code, message] = errorMatch;\n if (file && lineStr && colStr && message) {\n let cleanFile = file.trim();\n if (isAbsolute(cleanFile)) {\n cleanFile = relative(process.cwd(), cleanFile);\n }\n pendingError = {\n file: cleanFile,\n line: Number.parseInt(lineStr, 10),\n col: Number.parseInt(colStr, 10),\n code: code ?? \"\",\n message: message.trim(),\n };\n }\n continue;\n }\n\n if (pendingError && line.startsWith(\" \")) {\n continuationLines.push(line);\n continue;\n }\n\n if (trimmed.includes(\"Found\") && trimmed.includes(\"error\")) {\n flushPending();\n onErrors(trimmed.match(/Found 0 error/) ? [] : [...currentErrors]);\n currentErrors = [];\n }\n }\n };\n\n if (tsProcess.stdout) {\n tsProcess.stdout.on(\"data\", processOutput);\n }\n if (tsProcess.stderr) {\n tsProcess.stderr.on(\"data\", processOutput);\n }\n\n return () => {\n tsProcess.kill();\n };\n}\n\nexport function useTypeScriptCheck(): Array<TsError> {\n const [tsErrors, setTsErrors] = useState<Array<TsError>>([]);\n\n useEffect(() => startTypeScriptCheck(setTsErrors), []);\n\n return tsErrors;\n}\n"]}
1
+ {"version":3,"file":"use-typescript-check.js","sourceRoot":"","sources":["../../src/cli/use-typescript-check.ts"],"names":[],"mappings":"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","sourcesContent":["import { isAbsolute, relative } from \"node:path\";\nimport spawn from \"cross-spawn\";\nimport { useEffect, useState } from \"react\";\n\nexport type TsError = {\n file: string;\n line: number;\n col: number;\n code: string;\n message: string;\n};\n\n// TypeScript nests from general to specific — the deepest line is the root cause.\nfunction extractBestMessage(\n message: string,\n continuationLines: Array<string>,\n): string {\n if (!continuationLines.length) {\n return message;\n }\n let maxIndent = 0;\n for (const line of continuationLines) {\n const indent = line.length - line.trimStart().length;\n if (indent > maxIndent) {\n maxIndent = indent;\n }\n }\n const deepest = continuationLines\n .filter((l) => l.length - l.trimStart().length === maxIndent)\n .map((l) => l.trim())\n .filter(Boolean)[0];\n return deepest ?? message;\n}\n\n/**\n * Spawn `tsc --noEmit --watch` and report each completed check via `onErrors`\n * (empty array when the check is clean). Returns a cleanup function that kills\n * the watcher. Shared by the Ink-based dev UI (via {@link useTypeScriptCheck})\n * and the `--plain` runner.\n */\nexport function startTypeScriptCheck(\n onErrors: (errors: Array<TsError>) => void,\n): () => void {\n const tsProcess = spawn(\"npx tsc --noEmit --watch --pretty false\", {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n shell: true,\n });\n\n let outputBuffer = \"\";\n let currentErrors: Array<TsError> = [];\n let pendingError: TsError | null = null;\n let continuationLines: Array<string> = [];\n\n const flushPending = () => {\n if (!pendingError) {\n return;\n }\n pendingError.message = extractBestMessage(\n pendingError.message,\n continuationLines,\n );\n currentErrors.push(pendingError);\n pendingError = null;\n continuationLines = [];\n };\n\n const processOutput = (data: Buffer) => {\n outputBuffer += data.toString();\n const lines = outputBuffer.split(\"\\n\");\n outputBuffer = lines.pop() || \"\";\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n const errorMatch = trimmed.match(\n /^(.+?)\\((\\d+),(\\d+)\\):\\s+error\\s+(TS\\d+)?\\s*:?\\s*(.+)$/,\n );\n if (errorMatch) {\n flushPending();\n const [, file, lineStr, colStr, code, message] = errorMatch;\n if (file && lineStr && colStr && message) {\n let cleanFile = file.trim();\n if (isAbsolute(cleanFile)) {\n cleanFile = relative(process.cwd(), cleanFile);\n }\n pendingError = {\n file: cleanFile,\n line: Number.parseInt(lineStr, 10),\n col: Number.parseInt(colStr, 10),\n code: code ?? \"\",\n message: message.trim(),\n };\n }\n continue;\n }\n\n if (pendingError && line.startsWith(\" \")) {\n continuationLines.push(line);\n continue;\n }\n\n if (trimmed.includes(\"Found\") && trimmed.includes(\"error\")) {\n flushPending();\n onErrors(trimmed.match(/Found 0 error/) ? [] : [...currentErrors]);\n currentErrors = [];\n }\n }\n };\n\n if (tsProcess.stdout) {\n tsProcess.stdout.on(\"data\", processOutput);\n }\n if (tsProcess.stderr) {\n tsProcess.stderr.on(\"data\", processOutput);\n }\n\n return () => {\n tsProcess.kill();\n };\n}\n\nexport function useTypeScriptCheck(): Array<TsError> {\n const [tsErrors, setTsErrors] = useState<Array<TsError>>([]);\n\n useEffect(() => startTypeScriptCheck(setTsErrors), []);\n\n return tsErrors;\n}\n"]}
@@ -1,37 +1,79 @@
1
- import { type Implementation, type McpHttpHandler, type Server as SdkServer, type ServerOptions } from "@modelcontextprotocol/server";
1
+ import type { Implementation, Server as SdkServer, ServerOptions } from "@modelcontextprotocol/server";
2
2
  import type { ErrorRequestHandler, Express, RequestHandler } from "express";
3
+ import type { OAuthConfig } from "./auth/index.js";
3
4
  import type { ExtraClaims } from "./auth.js";
4
- import { McpServer, type McpServerTypes, type SkybridgeServerOptions, type ToolDef } from "./server.js";
5
+ import { type JsonOptions, McpServer, type McpServerTypes, type ToolDef } from "./server.js";
5
6
  /**
6
- * Everything a Skybridge app needs in one bag: the MCP implementation info
7
- * (`name`, `version`, …), the SDK's {@link ServerOptions} (`capabilities`,
8
- * `instructions`, …), and the Skybridge-specific options (`oauth`, `json`,
9
- * `skills`).
7
+ * The `handler` field of {@link SkybridgeConfig}: builds the app's MCP
8
+ * surface. Runs again for **every incoming request**, on a fresh
9
+ * {@link McpServer}, so keep it to registration: hoist pools, timers, clients
10
+ * and any other side effect to module scope (or into `setup`) and close over
11
+ * them. It must **return** the chained server so `typeof app` carries the
12
+ * registered tool types.
13
+ *
14
+ * @typeParam TContext - What `setup` resolved to, passed as the second argument.
10
15
  */
11
- export type SkybridgeConfig<TAuthExtra extends ExtraClaims = ExtraClaims> = Implementation & ServerOptions & SkybridgeServerOptions<TAuthExtra>;
16
+ export type SkybridgeHandler<TTools extends Record<string, ToolDef>, TContext, TAuthExtra extends ExtraClaims> = (server: McpServer<Record<never, ToolDef>, TAuthExtra>, context: TContext) => McpServer<TTools, TAuthExtra>;
12
17
  /**
13
- * The factory that declares an app's MCP surface. Called with a fresh
14
- * {@link McpServer} for every request, it must **return** the chained server so
15
- * `typeof app` carries the registered tool types.
18
+ * What the `oauth` field accepts: a resolved {@link OAuthConfig}, a promise of
19
+ * one (the branded providers are async), or a function of the `setup` result.
20
+ * A function or promise is resolved once — at {@link Skybridge.run} or on the
21
+ * first request, never at module import — so prefer a function when building
22
+ * the config has side effects (network discovery, secrets).
16
23
  */
17
- export type SkybridgeSetup<TTools extends Record<string, ToolDef>, TAuthExtra extends ExtraClaims> = (server: McpServer<Record<never, ToolDef>, TAuthExtra>) => McpServer<TTools, TAuthExtra>;
24
+ export type SkybridgeOAuthInput<TContext, TExtra extends ExtraClaims> = OAuthConfig<TExtra> | Promise<OAuthConfig<TExtra>> | ((context: TContext) => OAuthConfig<TExtra> | Promise<OAuthConfig<TExtra>>);
18
25
  /**
19
- * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
20
- * route) plus a factory that builds the MCP server for each request.
26
+ * Everything a Skybridge app needs in one bag: the MCP implementation info
27
+ * (`name`, `version`, …), the SDK's {@link ServerOptions} (`capabilities`,
28
+ * `instructions`, …), the Express and skills options, and the app's behavior
29
+ * (`setup`, `oauth`, `handler`).
21
30
  *
22
- * The factory runs once per request, so tools, resources, prompts and views
23
- * are always registered on the instance that serves the request. It also runs
24
- * once at construction time, which surfaces registration errors at boot and
25
- * gives the OAuth layer the set of per-tool security schemes.
31
+ * All type parameters are inferred from the value: the context from `setup`,
32
+ * the auth claims from `oauth`, and the tool registry from the server
33
+ * `handler` returns.
34
+ */
35
+ export type SkybridgeConfig<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TContext = undefined, TAuthExtra extends ExtraClaims = ExtraClaims> = Implementation & ServerOptions & {
36
+ /** Options for the built-in `express.json()` middleware, e.g. `{ limit: "10mb" }`. */
37
+ json?: JsonOptions;
38
+ /**
39
+ * @experimental Serve Agent Skills from `src/skills` over MCP (SEP-2640).
40
+ * API may change.
41
+ */
42
+ skills?: boolean;
43
+ /**
44
+ * Loads whatever the app needs up front (remote config, secrets, datasets,
45
+ * …). Runs **once** — at {@link Skybridge.run} or on the first request,
46
+ * never at module import — and its awaited return value is passed to an
47
+ * `oauth` function and to `handler` as the second argument.
48
+ */
49
+ setup?: () => TContext;
50
+ /**
51
+ * Resource-server OAuth. When set, mounts the well-known metadata routes
52
+ * and bearer auth on `/mcp`, and the verifier's claims type
53
+ * `extra.http.authInfo.extra` in tool handlers.
54
+ */
55
+ oauth?: SkybridgeOAuthInput<Awaited<TContext>, TAuthExtra>;
56
+ /** Registers the MCP surface, per request. See {@link SkybridgeHandler}. */
57
+ handler: SkybridgeHandler<TTools, Awaited<TContext>, TAuthExtra>;
58
+ };
59
+ /**
60
+ * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
61
+ * route) plus a handler that builds the MCP server for each request.
26
62
  *
27
- * @typeParam TTools - Accumulated tool registry, inferred from the server the
28
- * factory returns. You almost never set this manually.
63
+ * The handler runs for every request, so tools, resources, prompts and views
64
+ * are always registered on the instance that serves the request. Anything in
65
+ * the handler body other than registration therefore runs per request too.
66
+ * Anything asynchronous the app needs (remote config, secrets, …) goes in
67
+ * `setup`, which runs once and feeds the handler's second argument.
29
68
  *
30
69
  * @example
31
70
  * ```ts
32
- * export const app = new Skybridge(
33
- * { name: "my-app", version: "1.0.0", capabilities: {} },
34
- * (server) =>
71
+ * export const app = new Skybridge({
72
+ * name: "my-app",
73
+ * version: "1.0.0",
74
+ * setup: async () => loadConfig(),
75
+ * oauth: (config) => descopeProvider({ url: config.mcpServerUrl }),
76
+ * handler: (server, config) =>
35
77
  * server.registerTool(
36
78
  * {
37
79
  * name: "search",
@@ -40,32 +82,45 @@ export type SkybridgeSetup<TTools extends Record<string, ToolDef>, TAuthExtra ex
40
82
  * },
41
83
  * async ({ query }) => ({ content: `Results for ${query}` }),
42
84
  * ),
43
- * );
85
+ * });
44
86
  *
45
87
  * export type AppType = typeof app;
46
88
  * ```
47
89
  *
48
90
  * @see https://docs.skybridge.tech/api-reference/mcp-server
49
91
  */
50
- export declare class Skybridge<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TAuthExtra extends ExtraClaims = ExtraClaims> {
92
+ export declare class Skybridge<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TContext = undefined, TAuthExtra extends ExtraClaims = ExtraClaims> {
51
93
  readonly $types: McpServerTypes<TTools>;
52
94
  private readonly serverInfo;
53
95
  private readonly serverOptions;
54
- private readonly skybridgeOptions;
55
- private readonly setup;
96
+ private readonly skills?;
97
+ private oauthEnabled;
98
+ private readonly handler;
99
+ private readonly setup?;
100
+ private readonly oauthInput?;
101
+ private context?;
56
102
  private readonly expressApp;
57
103
  private readonly errorMiddleware;
58
104
  private readonly monitoringEntry;
59
105
  private resolveResourceMetadataUrl?;
60
- private cachedFetchHandler?;
61
- constructor(config: SkybridgeConfig<TAuthExtra>, setup: SkybridgeSetup<TTools, TAuthExtra>);
106
+ private readyPromise?;
107
+ private slowHandlerWarned;
108
+ constructor(config: SkybridgeConfig<TTools, TContext, TAuthExtra>);
109
+ /**
110
+ * Resolve `setup` and `oauth`, then wire OAuth onto the Express app. Runs
111
+ * once; every entry point that needs a built server awaits it, and a failed
112
+ * attempt is retried on the next call.
113
+ *
114
+ * @internal
115
+ */
116
+ ready(): Promise<void>;
62
117
  /**
63
118
  * The underlying Express app. Use this to extend the HTTP server with
64
119
  * custom routes, middleware, or settings — e.g.
65
120
  * `app.express.get("/health", ...)`.
66
121
  *
67
122
  * `express.json()` is pre-applied — tune it via the `json` config field,
68
- * e.g. `new Skybridge({ name, version, json: { limit: "10mb" } }, setup)`.
123
+ * e.g. `new Skybridge({ name, version, json: { limit: "10mb" }, handler })`.
69
124
  * Register your handlers before `run()`; after `run()`, dev-mode middleware,
70
125
  * the `/mcp` route, and the default error handler are appended in that order.
71
126
  *
@@ -73,21 +128,17 @@ export declare class Skybridge<TTools extends Record<string, ToolDef> = Record<n
73
128
  * locally and on self-hosted deployments.
74
129
  */
75
130
  get express(): Express;
76
- /**
77
- * The app's fetch handler for `/mcp`: a `Request` → `Response` function that
78
- * builds a fresh MCP server per request. Memoized, so the same handler is
79
- * reused across requests.
80
- */
81
- get fetchHandler(): McpHttpHandler;
82
131
  /**
83
132
  * Build a fresh server for one stateless HTTP request, as
84
- * `createMcpHandler`'s factory contract requires: the factory runs again so
133
+ * `createMcpHandler`'s factory contract requires: the handler runs again so
85
134
  * the SDK's handler closures belong to the instance whose protocol era it
86
135
  * stamps. Sharing one instance's handler maps instead would bind them to an
87
136
  * instance that is never marked, pinning every request to the 2025 codec and
88
137
  * letting concurrent callers overwrite each other's negotiated version.
138
+ *
139
+ * Awaits `setup` and the `oauth` input on first use.
89
140
  */
90
- createServerInstance(): SdkServer;
141
+ createServerInstance(): Promise<SdkServer>;
91
142
  /**
92
143
  * Connect a Skybridge app to an MCP transport. Use this when you're
93
144
  * embedding Skybridge in a host that already manages its own transport
@@ -1,27 +1,28 @@
1
1
  import http from "node:http";
2
- import { createMcpHandler, } from "@modelcontextprotocol/server";
3
2
  import { setupOAuth } from "./auth/setup.js";
4
- import { createApp, createBaseApp } from "./express.js";
3
+ import { buildMcpHandler, createApp, createBaseApp } from "./express.js";
5
4
  import { createMiddlewareEntry } from "./metric.js";
6
5
  import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
7
6
  import { McpServer, } from "./server.js";
7
+ const SLOW_HANDLER_THRESHOLD_MS = 50;
8
8
  /**
9
9
  * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
10
- * route) plus a factory that builds the MCP server for each request.
10
+ * route) plus a handler that builds the MCP server for each request.
11
11
  *
12
- * The factory runs once per request, so tools, resources, prompts and views
13
- * are always registered on the instance that serves the request. It also runs
14
- * once at construction time, which surfaces registration errors at boot and
15
- * gives the OAuth layer the set of per-tool security schemes.
16
- *
17
- * @typeParam TTools - Accumulated tool registry, inferred from the server the
18
- * factory returns. You almost never set this manually.
12
+ * The handler runs for every request, so tools, resources, prompts and views
13
+ * are always registered on the instance that serves the request. Anything in
14
+ * the handler body other than registration therefore runs per request too.
15
+ * Anything asynchronous the app needs (remote config, secrets, …) goes in
16
+ * `setup`, which runs once and feeds the handler's second argument.
19
17
  *
20
18
  * @example
21
19
  * ```ts
22
- * export const app = new Skybridge(
23
- * { name: "my-app", version: "1.0.0", capabilities: {} },
24
- * (server) =>
20
+ * export const app = new Skybridge({
21
+ * name: "my-app",
22
+ * version: "1.0.0",
23
+ * setup: async () => loadConfig(),
24
+ * oauth: (config) => descopeProvider({ url: config.mcpServerUrl }),
25
+ * handler: (server, config) =>
25
26
  * server.registerTool(
26
27
  * {
27
28
  * name: "search",
@@ -30,7 +31,7 @@ import { McpServer, } from "./server.js";
30
31
  * },
31
32
  * async ({ query }) => ({ content: `Results for ${query}` }),
32
33
  * ),
33
- * );
34
+ * });
34
35
  *
35
36
  * export type AppType = typeof app;
36
37
  * ```
@@ -40,24 +41,53 @@ import { McpServer, } from "./server.js";
40
41
  export class Skybridge {
41
42
  serverInfo;
42
43
  serverOptions;
43
- skybridgeOptions;
44
+ skills;
45
+ oauthEnabled = false;
46
+ handler;
44
47
  setup;
48
+ oauthInput;
49
+ context;
45
50
  expressApp;
46
51
  errorMiddleware = [];
47
52
  monitoringEntry = createMiddlewareEntry();
48
53
  resolveResourceMetadataUrl;
49
- cachedFetchHandler;
50
- constructor(config, setup) {
51
- const { name, title, version, description, icons, websiteUrl, json, oauth, skills, ...serverOptions } = config;
54
+ readyPromise;
55
+ slowHandlerWarned = false;
56
+ constructor(config) {
57
+ const { name, title, version, description, icons, websiteUrl, json, skills, setup, oauth, handler, ...serverOptions } = config;
52
58
  this.serverInfo = { name, title, version, description, icons, websiteUrl };
53
59
  this.serverOptions = serverOptions;
54
- this.skybridgeOptions = { json, oauth, skills };
60
+ this.skills = skills;
55
61
  this.setup = setup;
56
- const sample = this.buildServer();
62
+ this.oauthInput = oauth;
63
+ this.handler = handler;
57
64
  this.expressApp = createBaseApp(json);
58
- if (oauth) {
59
- this.resolveResourceMetadataUrl = setupOAuth(this.expressApp, oauth, sample.securitySchemesByTool);
60
- }
65
+ }
66
+ /**
67
+ * Resolve `setup` and `oauth`, then wire OAuth onto the Express app. Runs
68
+ * once; every entry point that needs a built server awaits it, and a failed
69
+ * attempt is retried on the next call.
70
+ *
71
+ * @internal
72
+ */
73
+ ready() {
74
+ this.readyPromise ??= (async () => {
75
+ if (this.setup) {
76
+ this.context = await this.setup();
77
+ }
78
+ const oauth = typeof this.oauthInput === "function"
79
+ ? await this.oauthInput(this.context)
80
+ : await this.oauthInput;
81
+ this.oauthEnabled = Boolean(oauth);
82
+ const sample = this.buildServer();
83
+ if (oauth) {
84
+ this.resolveResourceMetadataUrl = setupOAuth(this.expressApp, oauth, sample.securitySchemesByTool);
85
+ }
86
+ })().catch((error) => {
87
+ this.readyPromise = undefined;
88
+ throw error;
89
+ });
90
+ return this.readyPromise;
61
91
  }
62
92
  /**
63
93
  * The underlying Express app. Use this to extend the HTTP server with
@@ -65,7 +95,7 @@ export class Skybridge {
65
95
  * `app.express.get("/health", ...)`.
66
96
  *
67
97
  * `express.json()` is pre-applied — tune it via the `json` config field,
68
- * e.g. `new Skybridge({ name, version, json: { limit: "10mb" } }, setup)`.
98
+ * e.g. `new Skybridge({ name, version, json: { limit: "10mb" }, handler })`.
69
99
  * Register your handlers before `run()`; after `run()`, dev-mode middleware,
70
100
  * the `/mcp` route, and the default error handler are appended in that order.
71
101
  *
@@ -75,28 +105,18 @@ export class Skybridge {
75
105
  get express() {
76
106
  return this.expressApp;
77
107
  }
78
- /**
79
- * The app's fetch handler for `/mcp`: a `Request` → `Response` function that
80
- * builds a fresh MCP server per request. Memoized, so the same handler is
81
- * reused across requests.
82
- */
83
- get fetchHandler() {
84
- this.cachedFetchHandler ??= createMcpHandler(() => this.createServerInstance(), {
85
- onerror: (error) => {
86
- console.error("Error handling MCP request:", error);
87
- },
88
- });
89
- return this.cachedFetchHandler;
90
- }
91
108
  /**
92
109
  * Build a fresh server for one stateless HTTP request, as
93
- * `createMcpHandler`'s factory contract requires: the factory runs again so
110
+ * `createMcpHandler`'s factory contract requires: the handler runs again so
94
111
  * the SDK's handler closures belong to the instance whose protocol era it
95
112
  * stamps. Sharing one instance's handler maps instead would bind them to an
96
113
  * instance that is never marked, pinning every request to the 2025 codec and
97
114
  * letting concurrent callers overwrite each other's negotiated version.
115
+ *
116
+ * Awaits `setup` and the `oauth` input on first use.
98
117
  */
99
- createServerInstance() {
118
+ async createServerInstance() {
119
+ await this.ready();
100
120
  const server = this.buildServer();
101
121
  this.instrumentHandlers(server);
102
122
  return server.server;
@@ -108,7 +128,8 @@ export class Skybridge {
108
128
  * which sets the transport up for you.
109
129
  */
110
130
  async connect(transport) {
111
- await this.createServerInstance().connect(transport);
131
+ const instance = await this.createServerInstance();
132
+ await instance.connect(transport);
112
133
  }
113
134
  use(pathOrHandler, ...handlers) {
114
135
  // Branching is load-bearing: Express's `app.use` overloads can't be
@@ -158,9 +179,11 @@ export class Skybridge {
158
179
  return this.expressApp;
159
180
  }
160
181
  const httpServer = http.createServer();
182
+ const mcpHandler = buildMcpHandler(this);
161
183
  await createApp({
162
184
  app: this,
163
185
  httpServer,
186
+ mcpHandler,
164
187
  errorMiddleware: this.errorMiddleware,
165
188
  });
166
189
  httpServer.on("request", this.expressApp);
@@ -190,6 +213,7 @@ export class Skybridge {
190
213
  // (force-quit on a second Ctrl+C while drain is hanging).
191
214
  process.off("SIGTERM", shutdown);
192
215
  process.off("SIGINT", shutdown);
216
+ mcpHandler.close().catch(() => { });
193
217
  httpServer.close(() => process.exit(0));
194
218
  // Force exit if connections don't drain in time so the port is still
195
219
  // released promptly (e.g. for nodemon restarts).
@@ -200,11 +224,21 @@ export class Skybridge {
200
224
  return undefined;
201
225
  }
202
226
  buildServer() {
203
- const server = new McpServer(this.serverInfo, this.serverOptions, this.skybridgeOptions);
227
+ const server = new McpServer(this.serverInfo, this.serverOptions, { skills: this.skills, oauth: this.oauthEnabled });
204
228
  if (this.resolveResourceMetadataUrl) {
205
229
  server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);
206
230
  }
207
- return this.setup(server);
231
+ const startedAt = performance.now();
232
+ const built = this.handler(server, this.context);
233
+ const elapsed = performance.now() - startedAt;
234
+ if (typeof built.then === "function") {
235
+ throw new Error("The Skybridge handler must be synchronous — it runs on every request. Load config or secrets in `setup` instead and read them from the handler's second argument.");
236
+ }
237
+ if (elapsed > SLOW_HANDLER_THRESHOLD_MS && !this.slowHandlerWarned) {
238
+ this.slowHandlerWarned = true;
239
+ console.warn(`The Skybridge handler took ${Math.round(elapsed)}ms — it runs on every request, so this cost is paid per request. Hoist expensive work to module scope or into \`setup\`, whose result is passed to the handler.`);
240
+ }
241
+ return built;
208
242
  }
209
243
  middlewareEntries(server) {
210
244
  return [