skybridge 2.0.0-beta.dd3cba3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/cli/telemetry.js +2 -0
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/server/app.d.ts +79 -60
- package/dist/server/app.js +61 -70
- package/dist/server/app.js.map +1 -1
- package/dist/server/app.test.js +97 -0
- package/dist/server/app.test.js.map +1 -0
- package/dist/server/auth/index.d.ts +19 -1
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/auth/providers/auth0.d.ts +2 -2
- package/dist/server/auth/providers/auth0.js +13 -8
- package/dist/server/auth/providers/auth0.js.map +1 -1
- package/dist/server/auth/providers/auth0.test.js +1 -1
- package/dist/server/auth/providers/auth0.test.js.map +1 -1
- package/dist/server/auth/providers/authplane.d.ts +2 -2
- package/dist/server/auth/providers/authplane.js.map +1 -1
- package/dist/server/auth/providers/authplane.test.js +10 -7
- package/dist/server/auth/providers/authplane.test.js.map +1 -1
- package/dist/server/auth/providers/clerk.d.ts +2 -2
- package/dist/server/auth/providers/clerk.js.map +1 -1
- package/dist/server/auth/providers/clerk.test.js +1 -1
- package/dist/server/auth/providers/clerk.test.js.map +1 -1
- package/dist/server/auth/providers/custom.d.ts +3 -3
- package/dist/server/auth/providers/custom.js +5 -2
- package/dist/server/auth/providers/custom.js.map +1 -1
- package/dist/server/auth/providers/custom.test.js +13 -10
- package/dist/server/auth/providers/custom.test.js.map +1 -1
- package/dist/server/auth/providers/descope.d.ts +2 -2
- package/dist/server/auth/providers/descope.js.map +1 -1
- package/dist/server/auth/providers/descope.test.js +3 -3
- package/dist/server/auth/providers/descope.test.js.map +1 -1
- package/dist/server/auth/providers/stytch.d.ts +2 -2
- package/dist/server/auth/providers/stytch.js.map +1 -1
- package/dist/server/auth/providers/workos.d.ts +2 -2
- package/dist/server/auth/providers/workos.js.map +1 -1
- package/dist/server/auth/setup.test.js +56 -54
- package/dist/server/auth/setup.test.js.map +1 -1
- package/dist/server/auth-extra.test-d.js +96 -37
- package/dist/server/auth-extra.test-d.js.map +1 -1
- package/dist/server/auth.d.ts +4 -4
- package/dist/server/auth.js +2 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/build-manifest.test.js +1 -1
- package/dist/server/build-manifest.test.js.map +1 -1
- package/dist/server/express.d.ts +6 -6
- package/dist/server/express.js +16 -22
- 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/index.d.ts +4 -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 +184 -135
- package/dist/server/middleware.test.js.map +1 -1
- package/dist/server/register-tool.test.js +8 -4
- package/dist/server/register-tool.test.js.map +1 -1
- package/dist/server/server.d.ts +20 -45
- package/dist/server/server.js +18 -63
- package/dist/server/server.js.map +1 -1
- package/dist/server/skills-integration.test.js +11 -4
- package/dist/server/skills-integration.test.js.map +1 -1
- package/dist/server/tool-extra.test.js +30 -14
- package/dist/server/tool-extra.test.js.map +1 -1
- package/dist/server/view-resource-resolution.test.js +7 -3
- package/dist/server/view-resource-resolution.test.js.map +1 -1
- package/dist/test/utils.d.ts +3 -3
- package/dist/test/utils.js +211 -197
- package/dist/test/utils.js.map +1 -1
- package/dist/test/view.test.js +19 -15
- package/dist/test/view.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/package.json +3 -3
- package/dist/server/factory-loader.test.js +0 -81
- package/dist/server/factory-loader.test.js.map +0 -1
- /package/dist/server/{factory-loader.test.d.ts → app.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
<p align="center">
|
|
17
17
|
<a href="https://docs.skybridge.tech">Documentation</a> ·
|
|
18
|
-
<a href="https://docs.skybridge.tech/quickstart
|
|
18
|
+
<a href="https://docs.skybridge.tech/get-started/quickstart">Quickstart</a> ·
|
|
19
19
|
<a href="https://github.com/alpic-ai/skybridge/tree/main/examples">Examples</a>
|
|
20
20
|
</p>
|
|
21
21
|
|
|
@@ -62,7 +62,7 @@ They chose to build their MCP apps with Skybridge:
|
|
|
62
62
|
|
|
63
63
|
**For agents**
|
|
64
64
|
|
|
65
|
-
Install our [skill](https://docs.skybridge.tech/
|
|
65
|
+
Install our [skill](https://docs.skybridge.tech/guides/migrate) for building MCP apps and ChatGPT apps:
|
|
66
66
|
```bash
|
|
67
67
|
npx skills add alpic-ai/skybridge -s skybridge
|
|
68
68
|
```
|
|
@@ -78,22 +78,22 @@ Bootstrap a new project with:
|
|
|
78
78
|
```bash
|
|
79
79
|
npm create skybridge@latest my-app
|
|
80
80
|
```
|
|
81
|
-
For full install instructions, read our [**Quickstart guide**](https://docs.skybridge.tech/quickstart
|
|
81
|
+
For full install instructions, read our [**Quickstart guide**](https://docs.skybridge.tech/get-started/quickstart).
|
|
82
82
|
|
|
83
83
|
## Documentation
|
|
84
84
|
|
|
85
85
|
The [Skybridge documentation](https://docs.skybridge.tech) covers the full lifecycle of building MCP Apps:
|
|
86
86
|
|
|
87
|
-
- [Fundamentals](https://docs.skybridge.tech/
|
|
88
|
-
- [Core concepts](https://docs.skybridge.tech/
|
|
89
|
-
- [Guides](https://docs.skybridge.tech/
|
|
87
|
+
- [Fundamentals](https://docs.skybridge.tech/get-started/architecture): understand MCP Apps, ChatGPT Apps, and how Skybridge bridges both runtimes.
|
|
88
|
+
- [Core concepts](https://docs.skybridge.tech/get-started/architecture): learn about server <> model <> UI data flows, LLM context sync, type safety, and instant local iteration with our devtools.
|
|
89
|
+
- [Guides](https://docs.skybridge.tech/build/tools): build real app behavior with tools, views, state, and model communication.
|
|
90
90
|
- [API Reference](https://docs.skybridge.tech/api-reference): browse our MCP server APIs, React hooks, CLI commands, and runtime compatibility.
|
|
91
91
|
|
|
92
92
|
## Deploy
|
|
93
93
|
|
|
94
94
|
Deploy Skybridge apps instantly on [Alpic](https://alpic.ai) for scalable hosting, MCP-specific analytics, permanent tunneling, app store compliance auditing and submission help. You can also self-host on any Node.js-compatible platform.
|
|
95
95
|
|
|
96
|
-
See our [deployment guide](https://docs.skybridge.tech/
|
|
96
|
+
See our [deployment guide](https://docs.skybridge.tech/ship/deploy) for the full production path.
|
|
97
97
|
|
|
98
98
|
## Community & Contributing
|
|
99
99
|
|
package/dist/cli/telemetry.js
CHANGED
|
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import ci from "ci-info";
|
|
5
5
|
import { PostHog } from "posthog-node";
|
|
6
|
+
import { agent } from "std-env";
|
|
6
7
|
const POSTHOG_API_KEY = "phc_rQdkCYr0DO4NcZBQXZnUwsHAbau9zuNwKIpil9FQP6v";
|
|
7
8
|
const POSTHOG_HOST = "https://us.i.posthog.com";
|
|
8
9
|
const ENV_TELEMETRY_DISABLED = "SKYBRIDGE_TELEMETRY_DISABLED";
|
|
@@ -98,6 +99,7 @@ const hook = async ({ id: command, config: { version }, error, }) => {
|
|
|
98
99
|
machineId: getMachineId(),
|
|
99
100
|
sessionId: crypto.randomUUID(),
|
|
100
101
|
isCI: ci.isCI,
|
|
102
|
+
agent,
|
|
101
103
|
nodeVersion: process.version,
|
|
102
104
|
platform: process.platform,
|
|
103
105
|
outcome: error ? "failure" : "success",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/cli/telemetry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/cli/telemetry.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Hook } from \"@oclif/core\";\nimport ci from \"ci-info\";\nimport { PostHog } from \"posthog-node\";\nimport { agent } from \"std-env\";\n\nconst POSTHOG_API_KEY = \"phc_rQdkCYr0DO4NcZBQXZnUwsHAbau9zuNwKIpil9FQP6v\";\nconst POSTHOG_HOST = \"https://us.i.posthog.com\";\n\nconst ENV_TELEMETRY_DISABLED = \"SKYBRIDGE_TELEMETRY_DISABLED\";\nconst ENV_TELEMETRY_DEBUG = \"SKYBRIDGE_TELEMETRY_DEBUG\";\nconst ENV_DO_NOT_TRACK = \"DO_NOT_TRACK\";\n\nconst GLOBAL_CONFIG_DIR = join(homedir(), \".skybridge\");\nconst GLOBAL_CONFIG_FILE = join(GLOBAL_CONFIG_DIR, \"config.json\");\n\ninterface GlobalConfig {\n machineId: string;\n telemetry: {\n enabled: boolean;\n };\n}\n\ninterface TelemetryEvent {\n version: string;\n machineId: string;\n sessionId: string;\n isCI: boolean;\n agent?: string;\n nodeVersion: string;\n platform: NodeJS.Platform;\n outcome: \"success\" | \"failure\";\n error?: string;\n}\n\nlet posthogClient: PostHog | null = null;\n\nfunction getPostHogClient(): PostHog {\n if (!posthogClient) {\n posthogClient = new PostHog(POSTHOG_API_KEY, {\n host: POSTHOG_HOST,\n flushAt: 1,\n flushInterval: 0,\n });\n }\n\n return posthogClient;\n}\n\nfunction readJsonFile<T>(filePath: string): T | null {\n try {\n if (existsSync(filePath)) {\n const content = readFileSync(filePath, \"utf-8\");\n return JSON.parse(content) as T;\n }\n } catch {\n // Ignore errors reading config\n }\n return null;\n}\n\nfunction writeJsonFile(filePath: string, data: unknown): void {\n try {\n const dir = join(filePath, \"..\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(filePath, JSON.stringify(data, null, 2), \"utf-8\");\n } catch {\n // Ignore errors writing config\n }\n}\n\nfunction getGlobalConfig(): GlobalConfig {\n const existing = readJsonFile<GlobalConfig>(GLOBAL_CONFIG_FILE);\n if (existing?.machineId && existing?.telemetry !== undefined) {\n return existing;\n }\n\n const config: GlobalConfig = {\n machineId: existing?.machineId || crypto.randomUUID(),\n telemetry: {\n enabled: existing?.telemetry?.enabled ?? true,\n },\n };\n\n writeJsonFile(GLOBAL_CONFIG_FILE, config);\n return config;\n}\n\nexport function isEnabled(): boolean {\n if (\n process.env[ENV_TELEMETRY_DISABLED] === \"1\" ||\n process.env[ENV_TELEMETRY_DISABLED]?.toLowerCase() === \"true\"\n ) {\n return false;\n }\n\n if (\n process.env[ENV_DO_NOT_TRACK] === \"1\" ||\n process.env[ENV_DO_NOT_TRACK]?.toLowerCase() === \"true\"\n ) {\n return false;\n }\n\n if (ci.isCI) {\n return true;\n }\n\n const config = getGlobalConfig();\n return config.telemetry.enabled;\n}\n\nexport function isDebugMode(): boolean {\n return (\n process.env[ENV_TELEMETRY_DEBUG] === \"1\" ||\n process.env[ENV_TELEMETRY_DEBUG]?.toLowerCase() === \"true\"\n );\n}\n\nexport function setEnabled(enabled: boolean): void {\n const config = getGlobalConfig();\n config.telemetry.enabled = enabled;\n writeJsonFile(GLOBAL_CONFIG_FILE, config);\n}\n\nexport function getMachineId(): string {\n if (ci.isCI) {\n return ci.name ?? \"unknown-ci\";\n }\n\n return getGlobalConfig().machineId;\n}\n\nconst hook: Hook<\"finally\"> = async ({\n id: command,\n config: { version },\n error,\n}) => {\n if (!isEnabled()) {\n return;\n }\n\n const event: TelemetryEvent = {\n version,\n machineId: getMachineId(),\n sessionId: crypto.randomUUID(),\n isCI: ci.isCI,\n agent,\n nodeVersion: process.version,\n platform: process.platform,\n outcome: error ? \"failure\" : \"success\",\n error: error?.message,\n };\n\n if (isDebugMode()) {\n console.error(\n \"[Telemetry Debug] Would send event:\",\n JSON.stringify(event, null, 2),\n );\n return;\n }\n\n try {\n const client = getPostHogClient();\n client.capture({\n distinctId: event.machineId,\n event: command,\n properties: event,\n });\n } catch {\n // Silently ignore telemetry errors - never block CLI operation\n }\n};\n\nexport default hook;\n"]}
|
package/dist/server/app.d.ts
CHANGED
|
@@ -1,65 +1,79 @@
|
|
|
1
1
|
import type { Implementation, Server as SdkServer, ServerOptions } from "@modelcontextprotocol/server";
|
|
2
2
|
import type { ErrorRequestHandler, Express, RequestHandler } from "express";
|
|
3
|
+
import type { OAuthConfig, OAuthProvider } 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: pools, clients and any other
|
|
10
|
+
* one-time work belong in `setup`, whose result is the second argument. It
|
|
11
|
+
* must **return** the chained server so `typeof app` carries the registered
|
|
12
|
+
* tool types.
|
|
13
|
+
*
|
|
14
|
+
* @typeParam TConfig - What `setup` resolved to, passed as the second argument.
|
|
10
15
|
*/
|
|
11
|
-
export type
|
|
16
|
+
export type SkybridgeHandler<TTools extends Record<string, ToolDef>, TConfig, TAuthExtra extends ExtraClaims> = (server: McpServer<Record<never, ToolDef>, TAuthExtra>, config: TConfig) => McpServer<TTools, TAuthExtra>;
|
|
12
17
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* inferred: the returned chain is what carries the tool registry into
|
|
19
|
-
* `typeof app`.
|
|
18
|
+
* What the `oauth` field accepts: an {@link OAuthConfig}, a provider
|
|
19
|
+
* (`oauth: auth0Provider(...)`), or a function of the `setup` result returning
|
|
20
|
+
* either. Providers and functions resolve once, at {@link Skybridge.run} or on
|
|
21
|
+
* the first request, never at module import. Anything asynchronous belongs in
|
|
22
|
+
* a provider's `resolve`.
|
|
20
23
|
*/
|
|
21
|
-
export type
|
|
24
|
+
export type SkybridgeOAuthInput<TConfig, TExtra extends ExtraClaims> = OAuthConfig<TExtra> | OAuthProvider<TExtra> | ((config: TConfig) => OAuthConfig<TExtra> | OAuthProvider<TExtra>);
|
|
22
25
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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`).
|
|
26
30
|
*
|
|
27
|
-
*
|
|
28
|
-
* tool
|
|
29
|
-
|
|
30
|
-
export type SkybridgeFactory<TTools extends Record<string, ToolDef>, TAuthExtra extends ExtraClaims> = (server: McpServer<Record<never, ToolDef>, TAuthExtra>) => McpServer<TTools, TAuthExtra>;
|
|
31
|
-
/**
|
|
32
|
-
* Async alternative to passing a {@link SkybridgeFactory} directly: a zero-arg
|
|
33
|
-
* function that loads whatever the factory needs (remote config, secrets, …)
|
|
34
|
-
* and resolves to it. It runs once — at {@link Skybridge.run} or on the first
|
|
35
|
-
* request, never at module import — so importing `server.ts` from tests and
|
|
36
|
-
* evals stays free of side effects. The factory it returns is still
|
|
37
|
-
* synchronous and still runs per request.
|
|
31
|
+
* All type parameters are inferred from the value: the config from `setup`,
|
|
32
|
+
* the auth claims from `oauth`, and the tool registry from the server
|
|
33
|
+
* `handler` returns.
|
|
38
34
|
*/
|
|
39
|
-
export type
|
|
35
|
+
export type SkybridgeConfig<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TConfig = 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?: () => TConfig;
|
|
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<TConfig>, TAuthExtra>;
|
|
56
|
+
/** Registers the MCP surface, per request. See {@link SkybridgeHandler}. */
|
|
57
|
+
handler: SkybridgeHandler<TTools, Awaited<TConfig>, TAuthExtra>;
|
|
58
|
+
};
|
|
40
59
|
/**
|
|
41
60
|
* A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
|
|
42
|
-
* route) plus a
|
|
61
|
+
* route) plus a handler that builds the MCP server for each request.
|
|
43
62
|
*
|
|
44
|
-
* The
|
|
63
|
+
* The handler runs for every request, so tools, resources, prompts and views
|
|
45
64
|
* are always registered on the instance that serves the request. Anything in
|
|
46
|
-
* the
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* OAuth layer the set of per-tool security schemes.
|
|
50
|
-
*
|
|
51
|
-
* Anything asynchronous the factory needs (remote config, secrets, …) goes in
|
|
52
|
-
* a {@link SkybridgeFactoryLoader} passed in its place:
|
|
53
|
-
* `new Skybridge(config, async () => { const cfg = await load(); return (server) => …; })`.
|
|
54
|
-
*
|
|
55
|
-
* @typeParam TTools - Accumulated tool registry, inferred from the server the
|
|
56
|
-
* factory returns. You almost never set this manually.
|
|
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.
|
|
57
68
|
*
|
|
58
69
|
* @example
|
|
59
70
|
* ```ts
|
|
60
|
-
* export const app = new Skybridge(
|
|
61
|
-
*
|
|
62
|
-
*
|
|
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) =>
|
|
63
77
|
* server.registerTool(
|
|
64
78
|
* {
|
|
65
79
|
* name: "search",
|
|
@@ -68,40 +82,45 @@ export type SkybridgeFactoryLoader<TTools extends Record<string, ToolDef>, TAuth
|
|
|
68
82
|
* },
|
|
69
83
|
* async ({ query }) => ({ content: `Results for ${query}` }),
|
|
70
84
|
* ),
|
|
71
|
-
* );
|
|
85
|
+
* });
|
|
72
86
|
*
|
|
73
87
|
* export type AppType = typeof app;
|
|
74
88
|
* ```
|
|
75
89
|
*
|
|
76
90
|
* @see https://docs.skybridge.tech/api-reference/mcp-server
|
|
77
91
|
*/
|
|
78
|
-
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>, TConfig = undefined, TAuthExtra extends ExtraClaims = ExtraClaims> {
|
|
79
93
|
readonly $types: McpServerTypes<TTools>;
|
|
80
94
|
private readonly serverInfo;
|
|
81
95
|
private readonly serverOptions;
|
|
82
|
-
private readonly
|
|
83
|
-
private
|
|
84
|
-
private readonly
|
|
96
|
+
private readonly skills?;
|
|
97
|
+
private oauthEnabled;
|
|
98
|
+
private readonly handler;
|
|
99
|
+
private readonly setup?;
|
|
100
|
+
private readonly oauthInput?;
|
|
101
|
+
private config?;
|
|
85
102
|
private readonly expressApp;
|
|
86
103
|
private readonly errorMiddleware;
|
|
87
104
|
private readonly monitoringEntry;
|
|
88
105
|
private resolveResourceMetadataUrl?;
|
|
89
|
-
private
|
|
90
|
-
private
|
|
91
|
-
constructor(
|
|
106
|
+
private readyPromise?;
|
|
107
|
+
private slowHandlerWarned;
|
|
108
|
+
constructor({ name, title, version, description, icons, websiteUrl, json, skills, setup, oauth, handler, ...serverOptions }: SkybridgeConfig<TTools, TConfig, TAuthExtra>);
|
|
92
109
|
/**
|
|
93
|
-
* Resolve
|
|
94
|
-
*
|
|
95
|
-
*
|
|
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
|
|
96
115
|
*/
|
|
97
|
-
|
|
116
|
+
ready(): Promise<void>;
|
|
98
117
|
/**
|
|
99
118
|
* The underlying Express app. Use this to extend the HTTP server with
|
|
100
119
|
* custom routes, middleware, or settings — e.g.
|
|
101
120
|
* `app.express.get("/health", ...)`.
|
|
102
121
|
*
|
|
103
122
|
* `express.json()` is pre-applied — tune it via the `json` config field,
|
|
104
|
-
* e.g. `new Skybridge({ name, version, json: { limit: "10mb" } }
|
|
123
|
+
* e.g. `new Skybridge({ name, version, json: { limit: "10mb" }, handler })`.
|
|
105
124
|
* Register your handlers before `run()`; after `run()`, dev-mode middleware,
|
|
106
125
|
* the `/mcp` route, and the default error handler are appended in that order.
|
|
107
126
|
*
|
|
@@ -111,13 +130,13 @@ export declare class Skybridge<TTools extends Record<string, ToolDef> = Record<n
|
|
|
111
130
|
get express(): Express;
|
|
112
131
|
/**
|
|
113
132
|
* Build a fresh server for one stateless HTTP request, as
|
|
114
|
-
* `createMcpHandler`'s factory contract requires: the
|
|
133
|
+
* `createMcpHandler`'s factory contract requires: the handler runs again so
|
|
115
134
|
* the SDK's handler closures belong to the instance whose protocol era it
|
|
116
135
|
* stamps. Sharing one instance's handler maps instead would bind them to an
|
|
117
136
|
* instance that is never marked, pinning every request to the 2025 codec and
|
|
118
137
|
* letting concurrent callers overwrite each other's negotiated version.
|
|
119
138
|
*
|
|
120
|
-
* Awaits
|
|
139
|
+
* Awaits `setup` and the `oauth` input on first use.
|
|
121
140
|
*/
|
|
122
141
|
createServerInstance(): Promise<SdkServer>;
|
|
123
142
|
/**
|
package/dist/server/app.js
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import { setupOAuth } from "./auth/setup.js";
|
|
3
|
-
import { createApp, createBaseApp
|
|
3
|
+
import { buildMcpHandler, createApp, createBaseApp } from "./express.js";
|
|
4
4
|
import { createMiddlewareEntry } from "./metric.js";
|
|
5
5
|
import { buildMiddlewareChain, getHandlerMaps } from "./middleware.js";
|
|
6
6
|
import { McpServer, } from "./server.js";
|
|
7
|
-
const
|
|
7
|
+
const SLOW_HANDLER_THRESHOLD_MS = 50;
|
|
8
|
+
async function resolveOAuthInput(input, config) {
|
|
9
|
+
const resolved = typeof input === "function" ? input(config) : input;
|
|
10
|
+
return resolved && "resolve" in resolved
|
|
11
|
+
? await resolved.resolve()
|
|
12
|
+
: resolved;
|
|
13
|
+
}
|
|
8
14
|
/**
|
|
9
15
|
* A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
|
|
10
|
-
* route) plus a
|
|
16
|
+
* route) plus a handler that builds the MCP server for each request.
|
|
11
17
|
*
|
|
12
|
-
* The
|
|
18
|
+
* The handler runs for every request, so tools, resources, prompts and views
|
|
13
19
|
* are always registered on the instance that serves the request. Anything in
|
|
14
|
-
* the
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* OAuth layer the set of per-tool security schemes.
|
|
18
|
-
*
|
|
19
|
-
* Anything asynchronous the factory needs (remote config, secrets, …) goes in
|
|
20
|
-
* a {@link SkybridgeFactoryLoader} passed in its place:
|
|
21
|
-
* `new Skybridge(config, async () => { const cfg = await load(); return (server) => …; })`.
|
|
22
|
-
*
|
|
23
|
-
* @typeParam TTools - Accumulated tool registry, inferred from the server the
|
|
24
|
-
* factory returns. You almost never set this manually.
|
|
20
|
+
* the handler body other than registration therefore runs per request too.
|
|
21
|
+
* Anything asynchronous the app needs (remote config, secrets, …) goes in
|
|
22
|
+
* `setup`, which runs once and feeds the handler's second argument.
|
|
25
23
|
*
|
|
26
24
|
* @example
|
|
27
25
|
* ```ts
|
|
28
|
-
* export const app = new Skybridge(
|
|
29
|
-
*
|
|
30
|
-
*
|
|
26
|
+
* export const app = new Skybridge({
|
|
27
|
+
* name: "my-app",
|
|
28
|
+
* version: "1.0.0",
|
|
29
|
+
* setup: async () => loadConfig(),
|
|
30
|
+
* oauth: (config) => descopeProvider({ url: config.mcpServerUrl }),
|
|
31
|
+
* handler: (server, config) =>
|
|
31
32
|
* server.registerTool(
|
|
32
33
|
* {
|
|
33
34
|
* name: "search",
|
|
@@ -36,7 +37,7 @@ const SLOW_FACTORY_THRESHOLD_MS = 50;
|
|
|
36
37
|
* },
|
|
37
38
|
* async ({ query }) => ({ content: `Results for ${query}` }),
|
|
38
39
|
* ),
|
|
39
|
-
* );
|
|
40
|
+
* });
|
|
40
41
|
*
|
|
41
42
|
* export type AppType = typeof app;
|
|
42
43
|
* ```
|
|
@@ -46,56 +47,50 @@ const SLOW_FACTORY_THRESHOLD_MS = 50;
|
|
|
46
47
|
export class Skybridge {
|
|
47
48
|
serverInfo;
|
|
48
49
|
serverOptions;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
skills;
|
|
51
|
+
oauthEnabled = false;
|
|
52
|
+
handler;
|
|
53
|
+
setup;
|
|
54
|
+
oauthInput;
|
|
55
|
+
config;
|
|
52
56
|
expressApp;
|
|
53
57
|
errorMiddleware = [];
|
|
54
58
|
monitoringEntry = createMiddlewareEntry();
|
|
55
59
|
resolveResourceMetadataUrl;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
constructor(
|
|
59
|
-
const { name, title, version, description, icons, websiteUrl, json, oauth, skills, ...serverOptions } = config;
|
|
60
|
+
readyPromise;
|
|
61
|
+
slowHandlerWarned = false;
|
|
62
|
+
constructor({ name, title, version, description, icons, websiteUrl, json, skills, setup, oauth, handler, ...serverOptions }) {
|
|
60
63
|
this.serverInfo = { name, title, version, description, icons, websiteUrl };
|
|
61
64
|
this.serverOptions = serverOptions;
|
|
62
|
-
this.
|
|
65
|
+
this.skills = skills;
|
|
66
|
+
this.setup = setup;
|
|
67
|
+
this.oauthInput = oauth;
|
|
68
|
+
this.handler = handler;
|
|
63
69
|
this.expressApp = createBaseApp(json);
|
|
64
|
-
if (factory.length === 0) {
|
|
65
|
-
this.factoryLoader = factory;
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
this.factory = factory;
|
|
69
|
-
if (typeof oauth === "function") {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const sample = this.buildServer();
|
|
73
|
-
if (oauth) {
|
|
74
|
-
this.resolveResourceMetadataUrl = setupOAuth(this.expressApp, oauth, sample.securitySchemesByTool);
|
|
75
|
-
}
|
|
76
|
-
this.ready = Promise.resolve();
|
|
77
70
|
}
|
|
78
71
|
/**
|
|
79
|
-
* Resolve
|
|
80
|
-
*
|
|
81
|
-
*
|
|
72
|
+
* Resolve `setup` and `oauth`, then wire OAuth onto the Express app. Runs
|
|
73
|
+
* once; every entry point that needs a built server awaits it, and a failed
|
|
74
|
+
* attempt is retried on the next call.
|
|
75
|
+
*
|
|
76
|
+
* @internal
|
|
82
77
|
*/
|
|
83
|
-
|
|
84
|
-
this.
|
|
85
|
-
if (this.
|
|
86
|
-
this.
|
|
78
|
+
ready() {
|
|
79
|
+
this.readyPromise ??= (async () => {
|
|
80
|
+
if (this.setup) {
|
|
81
|
+
this.config = await this.setup();
|
|
87
82
|
}
|
|
88
|
-
const
|
|
89
|
-
|
|
83
|
+
const oauth = await resolveOAuthInput(this.oauthInput, this.config);
|
|
84
|
+
this.oauthEnabled = Boolean(oauth);
|
|
90
85
|
const sample = this.buildServer();
|
|
91
|
-
if (
|
|
92
|
-
this.resolveResourceMetadataUrl = setupOAuth(this.expressApp,
|
|
86
|
+
if (oauth) {
|
|
87
|
+
this.resolveResourceMetadataUrl = setupOAuth(this.expressApp, oauth, sample.securitySchemesByTool);
|
|
93
88
|
}
|
|
94
89
|
})().catch((error) => {
|
|
95
|
-
this.
|
|
90
|
+
this.readyPromise = undefined;
|
|
96
91
|
throw error;
|
|
97
92
|
});
|
|
98
|
-
return this.
|
|
93
|
+
return this.readyPromise;
|
|
99
94
|
}
|
|
100
95
|
/**
|
|
101
96
|
* The underlying Express app. Use this to extend the HTTP server with
|
|
@@ -103,7 +98,7 @@ export class Skybridge {
|
|
|
103
98
|
* `app.express.get("/health", ...)`.
|
|
104
99
|
*
|
|
105
100
|
* `express.json()` is pre-applied — tune it via the `json` config field,
|
|
106
|
-
* e.g. `new Skybridge({ name, version, json: { limit: "10mb" } }
|
|
101
|
+
* e.g. `new Skybridge({ name, version, json: { limit: "10mb" }, handler })`.
|
|
107
102
|
* Register your handlers before `run()`; after `run()`, dev-mode middleware,
|
|
108
103
|
* the `/mcp` route, and the default error handler are appended in that order.
|
|
109
104
|
*
|
|
@@ -115,16 +110,16 @@ export class Skybridge {
|
|
|
115
110
|
}
|
|
116
111
|
/**
|
|
117
112
|
* Build a fresh server for one stateless HTTP request, as
|
|
118
|
-
* `createMcpHandler`'s factory contract requires: the
|
|
113
|
+
* `createMcpHandler`'s factory contract requires: the handler runs again so
|
|
119
114
|
* the SDK's handler closures belong to the instance whose protocol era it
|
|
120
115
|
* stamps. Sharing one instance's handler maps instead would bind them to an
|
|
121
116
|
* instance that is never marked, pinning every request to the 2025 codec and
|
|
122
117
|
* letting concurrent callers overwrite each other's negotiated version.
|
|
123
118
|
*
|
|
124
|
-
* Awaits
|
|
119
|
+
* Awaits `setup` and the `oauth` input on first use.
|
|
125
120
|
*/
|
|
126
121
|
async createServerInstance() {
|
|
127
|
-
await this.
|
|
122
|
+
await this.ready();
|
|
128
123
|
const server = this.buildServer();
|
|
129
124
|
this.instrumentHandlers(server);
|
|
130
125
|
return server.server;
|
|
@@ -174,7 +169,6 @@ export class Skybridge {
|
|
|
174
169
|
* wait on instead of polling.
|
|
175
170
|
*/
|
|
176
171
|
async run() {
|
|
177
|
-
await this.ensureReady();
|
|
178
172
|
if (process.env.VERCEL === "1") {
|
|
179
173
|
// createApp only reads httpServer inside its dev-only branch
|
|
180
174
|
// (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a
|
|
@@ -188,9 +182,11 @@ export class Skybridge {
|
|
|
188
182
|
return this.expressApp;
|
|
189
183
|
}
|
|
190
184
|
const httpServer = http.createServer();
|
|
185
|
+
const mcpHandler = buildMcpHandler(this);
|
|
191
186
|
await createApp({
|
|
192
187
|
app: this,
|
|
193
188
|
httpServer,
|
|
189
|
+
mcpHandler,
|
|
194
190
|
errorMiddleware: this.errorMiddleware,
|
|
195
191
|
});
|
|
196
192
|
httpServer.on("request", this.expressApp);
|
|
@@ -220,9 +216,7 @@ export class Skybridge {
|
|
|
220
216
|
// (force-quit on a second Ctrl+C while drain is hanging).
|
|
221
217
|
process.off("SIGTERM", shutdown);
|
|
222
218
|
process.off("SIGINT", shutdown);
|
|
223
|
-
|
|
224
|
-
.close()
|
|
225
|
-
.catch(() => { });
|
|
219
|
+
mcpHandler.close().catch(() => { });
|
|
226
220
|
httpServer.close(() => process.exit(0));
|
|
227
221
|
// Force exit if connections don't drain in time so the port is still
|
|
228
222
|
// released promptly (e.g. for nodemon restarts).
|
|
@@ -233,22 +227,19 @@ export class Skybridge {
|
|
|
233
227
|
return undefined;
|
|
234
228
|
}
|
|
235
229
|
buildServer() {
|
|
236
|
-
|
|
237
|
-
throw new Error("Skybridge factory not loaded yet: this app was constructed with an async factory loader. Await run(), connect(), or createServerInstance().");
|
|
238
|
-
}
|
|
239
|
-
const server = new McpServer(this.serverInfo, this.serverOptions, this.skybridgeOptions);
|
|
230
|
+
const server = new McpServer(this.serverInfo, this.serverOptions, { skills: this.skills, oauth: this.oauthEnabled });
|
|
240
231
|
if (this.resolveResourceMetadataUrl) {
|
|
241
232
|
server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);
|
|
242
233
|
}
|
|
243
234
|
const startedAt = performance.now();
|
|
244
|
-
const built = this.
|
|
235
|
+
const built = this.handler(server, this.config);
|
|
245
236
|
const elapsed = performance.now() - startedAt;
|
|
246
237
|
if (typeof built.then === "function") {
|
|
247
|
-
throw new Error("The Skybridge
|
|
238
|
+
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.");
|
|
248
239
|
}
|
|
249
|
-
if (elapsed >
|
|
250
|
-
this.
|
|
251
|
-
console.warn(`The Skybridge
|
|
240
|
+
if (elapsed > SLOW_HANDLER_THRESHOLD_MS && !this.slowHandlerWarned) {
|
|
241
|
+
this.slowHandlerWarned = true;
|
|
242
|
+
console.warn(`The Skybridge handler took ${Math.round(elapsed)}ms — it runs on every request, so this cost is paid per request. Move expensive work into \`setup\`, whose result is passed to the handler.`);
|
|
252
243
|
}
|
|
253
244
|
return built;
|
|
254
245
|
}
|