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.
- package/dist/cli/detect-port.js +0 -1
- package/dist/cli/detect-port.js.map +1 -1
- package/dist/cli/use-nodemon.js +10 -2
- package/dist/cli/use-nodemon.js.map +1 -1
- package/dist/cli/use-typescript-check.js +1 -1
- package/dist/cli/use-typescript-check.js.map +1 -1
- package/dist/server/app.d.ts +88 -37
- package/dist/server/app.js +76 -42
- package/dist/server/app.js.map +1 -1
- package/dist/server/app.test.d.ts +1 -0
- package/dist/server/app.test.js +97 -0
- package/dist/server/app.test.js.map +1 -0
- package/dist/server/auth/discovery.d.ts +1 -1
- package/dist/server/auth/discovery.js +1 -1
- package/dist/server/auth/discovery.js.map +1 -1
- package/dist/server/auth/index.d.ts +1 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/auth/setup.test.js +56 -51
- package/dist/server/auth/setup.test.js.map +1 -1
- package/dist/server/auth-extra.test-d.js +91 -35
- package/dist/server/auth-extra.test-d.js.map +1 -1
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/auth.js +1 -1
- package/dist/server/auth.js.map +1 -1
- package/dist/server/express.d.ts +11 -2
- package/dist/server/express.js +22 -4
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.test.js +120 -31
- package/dist/server/express.test.js.map +1 -1
- package/dist/server/file-ref.d.ts +1 -1
- package/dist/server/file-ref.js +1 -1
- package/dist/server/file-ref.js.map +1 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp-errors.test.js +5 -1
- package/dist/server/mcp-errors.test.js.map +1 -1
- package/dist/server/middleware.test.js +194 -145
- package/dist/server/middleware.test.js.map +1 -1
- package/dist/server/register-tool.test.js +9 -5
- package/dist/server/register-tool.test.js.map +1 -1
- package/dist/server/server.d.ts +29 -62
- package/dist/server/server.js +21 -69
- package/dist/server/server.js.map +1 -1
- package/dist/server/skills-integration.test.js +7 -2
- package/dist/server/skills-integration.test.js.map +1 -1
- package/dist/server/skills.js +1 -1
- package/dist/server/skills.js.map +1 -1
- package/dist/server/templateHelper.d.ts +0 -3
- package/dist/server/templateHelper.js.map +1 -1
- package/dist/server/templates.generated.js +4 -8
- package/dist/server/templates.generated.js.map +1 -1
- package/dist/server/tool-extra.test.js +32 -16
- package/dist/server/tool-extra.test.js.map +1 -1
- package/dist/server/view-resource-resolution.test.js +8 -4
- package/dist/server/view-resource-resolution.test.js.map +1 -1
- package/dist/test/utils.d.ts +3 -3
- package/dist/test/utils.js +210 -196
- package/dist/test/utils.js.map +1 -1
- package/dist/test/view.test.js +22 -17
- package/dist/test/view.test.js.map +1 -1
- package/dist/views/index.d.ts +7 -0
- package/dist/views/index.js.map +1 -1
- package/dist/web/bridges/adaptor.test.js +1 -1
- package/dist/web/bridges/adaptor.test.js.map +1 -1
- package/dist/web/bridges/get-adaptor.test.js +2 -2
- package/dist/web/bridges/get-adaptor.test.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.js +1 -1
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.test.js +2 -2
- package/dist/web/bridges/mcp-app/bridge.test.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
- package/dist/web/bridges/mcp-app/view-tools.test.js +1 -1
- package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -1
- package/dist/web/bridges/types.d.ts +6 -9
- package/dist/web/bridges/types.js.map +1 -1
- package/dist/web/create-store.test.js +2 -2
- package/dist/web/create-store.test.js.map +1 -1
- package/dist/web/data-llm.test.js +2 -2
- package/dist/web/data-llm.test.js.map +1 -1
- package/dist/web/generate-helpers.d.ts +15 -12
- package/dist/web/generate-helpers.js +15 -12
- package/dist/web/generate-helpers.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test.js +2 -2
- package/dist/web/hooks/use-call-tool.test.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test.js +1 -1
- package/dist/web/hooks/use-display-mode.test.js.map +1 -1
- package/dist/web/hooks/use-download.test.js +3 -3
- package/dist/web/hooks/use-download.test.js.map +1 -1
- package/dist/web/hooks/use-files.test.js +1 -1
- package/dist/web/hooks/use-files.test.js.map +1 -1
- package/dist/web/hooks/use-host.test.js +1 -1
- package/dist/web/hooks/use-host.test.js.map +1 -1
- package/dist/web/hooks/use-open-external.test.js +2 -2
- package/dist/web/hooks/use-open-external.test.js.map +1 -1
- package/dist/web/hooks/use-request-close.test.js +1 -1
- package/dist/web/hooks/use-request-close.test.js.map +1 -1
- package/dist/web/hooks/use-request-modal.test.js +1 -1
- package/dist/web/hooks/use-request-modal.test.js.map +1 -1
- package/dist/web/hooks/use-request-size.test.js +1 -1
- package/dist/web/hooks/use-request-size.test.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.test.js +2 -8
- package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test.js +2 -2
- package/dist/web/hooks/use-tool-info.test.js.map +1 -1
- package/dist/web/hooks/use-user.test.js +2 -2
- package/dist/web/hooks/use-user.test.js.map +1 -1
- package/dist/web/hooks/use-view-state.test.js +2 -2
- package/dist/web/hooks/use-view-state.test.js.map +1 -1
- package/dist/web/hooks/use-viewport.test.js +2 -2
- package/dist/web/hooks/use-viewport.test.js.map +1 -1
- package/dist/web/index.d.ts +0 -1
- package/dist/web/index.js.map +1 -1
- package/dist/web/mount-view.js +2 -6
- package/dist/web/mount-view.js.map +1 -1
- package/package.json +6 -6
- package/dist/standard-schema.d.ts +0 -3
- package/dist/standard-schema.js +0 -2
- package/dist/standard-schema.js.map +0 -1
package/dist/cli/detect-port.js
CHANGED
|
@@ -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;
|
|
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"]}
|
package/dist/cli/use-nodemon.js
CHANGED
|
@@ -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
|
-
|
|
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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"use-nodemon.js","sourceRoot":"","sources":["../../src/cli/use-nodemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlC,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,CAAoB,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,8FAA8F,CAC/F,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,iBAAiB,GAAG,qDAAqD,CAAC;AAWhF;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAsB,EACtB,QAAyB;IAEzB,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC;QACnC,CAAC,CAAC;YACE,UAAU;SACX;QACH,CAAC,CAAC;YACE,KAAK,EAAE,CAAC,KAAK,CAAC;YACd,GAAG,EAAE,YAAY;YACjB,IAAI,EAAE,kBAAkB;SACzB,CAAC;IAEN,OAAO,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAE3C,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAE,EAAE;QACzC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC,CAAC;IAEF,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAE,EAAE;QACzC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QACD,6FAA6F;QAC7F,MAAM,QAAQ,GAAG,OAAO;aACrB,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAC/C,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,mBAAmB,GAAG,GAAG,EAAE;QAC/B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;YAC7C,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,mBAAmB,GAAG,GAAG,EAAE;QAC/B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;YAC7C,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE;QAC1B,mBAAmB,EAAE,CAAC;QACtB,mBAAmB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,KAAe,EAAE,EAAE;QACxC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC1B,mBAAmB,EAAE,CAAC;QACtB,mBAAmB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,EAAE;QACV,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAC/C,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,GAAsB,EACtB,WAAwB;IAExB,SAAS,CACP,GAAG,EAAE,CACH,YAAY,CAAC,GAAG,EAAE;QAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YAClB,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,OAAO,EAAE,CAAC;gBACZ,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QACD,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC;QACpD,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CACnB,WAAW,CACT,yCAAyC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAC3D,SAAS,CACV;KACJ,CAAC,EACJ,CAAC,GAAG,EAAE,WAAW,CAAC,CACnB,CAAC;AACJ,CAAC","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
|
|
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,
|
|
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"]}
|
package/dist/server/app.d.ts
CHANGED
|
@@ -1,37 +1,79 @@
|
|
|
1
|
-
import {
|
|
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
|
|
5
|
+
import { type JsonOptions, McpServer, type McpServerTypes, type ToolDef } from "./server.js";
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
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
|
|
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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
24
|
+
export type SkybridgeOAuthInput<TContext, TExtra extends ExtraClaims> = OAuthConfig<TExtra> | Promise<OAuthConfig<TExtra>> | ((context: TContext) => OAuthConfig<TExtra> | Promise<OAuthConfig<TExtra>>);
|
|
18
25
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
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
|
-
*
|
|
28
|
-
*
|
|
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
|
-
*
|
|
34
|
-
*
|
|
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
|
|
55
|
-
private
|
|
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
|
|
61
|
-
|
|
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" } }
|
|
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
|
|
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
|
package/dist/server/app.js
CHANGED
|
@@ -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
|
|
10
|
+
* route) plus a handler that builds the MCP server for each request.
|
|
11
11
|
*
|
|
12
|
-
* The
|
|
13
|
-
* are always registered on the instance that serves the request.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
-
*
|
|
24
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
60
|
+
this.skills = skills;
|
|
55
61
|
this.setup = setup;
|
|
56
|
-
|
|
62
|
+
this.oauthInput = oauth;
|
|
63
|
+
this.handler = handler;
|
|
57
64
|
this.expressApp = createBaseApp(json);
|
|
58
|
-
|
|
59
|
-
|
|
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" } }
|
|
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
|
|
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()
|
|
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.
|
|
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
|
-
|
|
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 [
|