skybridge 2.0.0-beta.78ceed1 → 2.0.0-beta.dba0bc8

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 (45) hide show
  1. package/dist/server/app.d.ts +84 -55
  2. package/dist/server/app.js +58 -70
  3. package/dist/server/app.js.map +1 -1
  4. package/dist/server/app.test.js +97 -0
  5. package/dist/server/app.test.js.map +1 -0
  6. package/dist/server/auth/index.d.ts +1 -1
  7. package/dist/server/auth/index.js.map +1 -1
  8. package/dist/server/auth/setup.test.js +56 -51
  9. package/dist/server/auth/setup.test.js.map +1 -1
  10. package/dist/server/auth-extra.test-d.js +91 -35
  11. package/dist/server/auth-extra.test-d.js.map +1 -1
  12. package/dist/server/express.d.ts +6 -6
  13. package/dist/server/express.js +16 -22
  14. package/dist/server/express.js.map +1 -1
  15. package/dist/server/express.test.js +120 -31
  16. package/dist/server/express.test.js.map +1 -1
  17. package/dist/server/index.d.ts +1 -1
  18. package/dist/server/index.js.map +1 -1
  19. package/dist/server/mcp-errors.test.js +5 -1
  20. package/dist/server/mcp-errors.test.js.map +1 -1
  21. package/dist/server/middleware.test.js +184 -135
  22. package/dist/server/middleware.test.js.map +1 -1
  23. package/dist/server/register-tool.test.js +8 -4
  24. package/dist/server/register-tool.test.js.map +1 -1
  25. package/dist/server/server.d.ts +17 -19
  26. package/dist/server/server.js +10 -7
  27. package/dist/server/server.js.map +1 -1
  28. package/dist/server/skills-integration.test.js +6 -1
  29. package/dist/server/skills-integration.test.js.map +1 -1
  30. package/dist/server/tool-extra.test.js +30 -14
  31. package/dist/server/tool-extra.test.js.map +1 -1
  32. package/dist/server/view-resource-resolution.test.js +7 -3
  33. package/dist/server/view-resource-resolution.test.js.map +1 -1
  34. package/dist/test/utils.d.ts +3 -3
  35. package/dist/test/utils.js +210 -196
  36. package/dist/test/utils.js.map +1 -1
  37. package/dist/test/view.test.js +20 -15
  38. package/dist/test/view.test.js.map +1 -1
  39. package/dist/web/generate-helpers.d.ts +15 -12
  40. package/dist/web/generate-helpers.js +15 -12
  41. package/dist/web/generate-helpers.js.map +1 -1
  42. package/package.json +1 -1
  43. package/dist/server/factory-loader.test.js +0 -81
  44. package/dist/server/factory-loader.test.js.map +0 -1
  45. /package/dist/server/{factory-loader.test.d.ts → app.test.d.ts} +0 -0
@@ -1,65 +1,89 @@
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 } 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`).
10
- */
11
- export type SkybridgeConfig<TAuthExtra extends ExtraClaims = ExtraClaims> = Implementation & ServerOptions & SkybridgeServerOptions<TAuthExtra>;
12
- /**
13
- * The bare {@link McpServer} a {@link SkybridgeFactory} receives: no tools
14
- * registered yet. Use it to annotate a factory extracted into its own
7
+ * The bare {@link McpServer} a {@link SkybridgeHandler} receives: no tools
8
+ * registered yet. Use it to annotate a handler extracted into its own
15
9
  * declaration — `(server: SkybridgeServer) => server.registerTool(…)` — and
16
10
  * pass the claims your OAuth verifier produces to type
17
- * `extra.http.authInfo.extra` in handlers. Leave the factory's return type
11
+ * `extra.http.authInfo.extra` in handlers. Leave the handler's return type
18
12
  * inferred: the returned chain is what carries the tool registry into
19
13
  * `typeof app`.
20
14
  */
21
15
  export type SkybridgeServer<TAuthExtra extends ExtraClaims = ExtraClaims> = McpServer<Record<never, ToolDef>, TAuthExtra>;
22
16
  /**
23
- * Builds an app's MCP surface. Runs again for **every incoming request**, on a
24
- * fresh {@link McpServer}, so keep it to registration: hoist pools, timers,
25
- * clients and any other side effect to module scope and close over them.
17
+ * The `handler` field of {@link SkybridgeConfig}: builds the app's MCP
18
+ * surface. Runs again for **every incoming request**, on a fresh
19
+ * {@link McpServer}, so keep it to registration: hoist pools, timers, clients
20
+ * and any other side effect to module scope (or into `setup`) and close over
21
+ * them. It must **return** the chained server so `typeof app` carries the
22
+ * registered tool types.
26
23
  *
27
- * It must **return** the chained server so `typeof app` carries the registered
28
- * tool types.
24
+ * @typeParam TContext - What `setup` resolved to, passed as the second argument.
29
25
  */
30
- export type SkybridgeFactory<TTools extends Record<string, ToolDef>, TAuthExtra extends ExtraClaims> = (server: McpServer<Record<never, ToolDef>, TAuthExtra>) => McpServer<TTools, TAuthExtra>;
26
+ export type SkybridgeHandler<TTools extends Record<string, ToolDef>, TContext, TAuthExtra extends ExtraClaims> = (server: McpServer<Record<never, ToolDef>, TAuthExtra>, context: TContext) => McpServer<TTools, TAuthExtra>;
31
27
  /**
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.
28
+ * What the `oauth` field accepts: a resolved {@link OAuthConfig}, a promise of
29
+ * one (the branded providers are async), or a function of the `setup` result.
30
+ * A function or promise is resolved once — at {@link Skybridge.run} or on the
31
+ * first request, never at module import — so prefer a function when building
32
+ * the config has side effects (network discovery, secrets).
33
+ */
34
+ export type SkybridgeOAuthInput<TContext, TExtra extends ExtraClaims> = OAuthConfig<TExtra> | Promise<OAuthConfig<TExtra>> | ((context: TContext) => OAuthConfig<TExtra> | Promise<OAuthConfig<TExtra>>);
35
+ /**
36
+ * Everything a Skybridge app needs in one bag: the MCP implementation info
37
+ * (`name`, `version`, …), the SDK's {@link ServerOptions} (`capabilities`,
38
+ * `instructions`, …), the Express and skills options, and the app's behavior
39
+ * (`setup`, `oauth`, `handler`).
40
+ *
41
+ * All type parameters are inferred from the value: the context from `setup`,
42
+ * the auth claims from `oauth`, and the tool registry from the server
43
+ * `handler` returns.
38
44
  */
39
- export type SkybridgeFactoryLoader<TTools extends Record<string, ToolDef>, TAuthExtra extends ExtraClaims> = () => Promise<SkybridgeFactory<TTools, TAuthExtra>>;
45
+ export type SkybridgeConfig<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TContext = undefined, TAuthExtra extends ExtraClaims = ExtraClaims> = Implementation & ServerOptions & {
46
+ /** Options for the built-in `express.json()` middleware, e.g. `{ limit: "10mb" }`. */
47
+ json?: JsonOptions;
48
+ /**
49
+ * @experimental Serve Agent Skills from `src/skills` over MCP (SEP-2640).
50
+ * API may change.
51
+ */
52
+ skills?: boolean;
53
+ /**
54
+ * Loads whatever the app needs up front (remote config, secrets, datasets,
55
+ * …). Runs **once** — at {@link Skybridge.run} or on the first request,
56
+ * never at module import — and its awaited return value is passed to an
57
+ * `oauth` function and to `handler` as the second argument.
58
+ */
59
+ setup?: () => TContext;
60
+ /**
61
+ * Resource-server OAuth. When set, mounts the well-known metadata routes
62
+ * and bearer auth on `/mcp`, and the verifier's claims type
63
+ * `extra.http.authInfo.extra` in tool handlers.
64
+ */
65
+ oauth?: SkybridgeOAuthInput<Awaited<TContext>, TAuthExtra>;
66
+ /** Registers the MCP surface, per request. See {@link SkybridgeHandler}. */
67
+ handler: SkybridgeHandler<TTools, Awaited<TContext>, TAuthExtra>;
68
+ };
40
69
  /**
41
70
  * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`
42
- * route) plus a factory that builds the MCP server for each request.
71
+ * route) plus a handler that builds the MCP server for each request.
43
72
  *
44
- * The factory runs for every request, so tools, resources, prompts and views
73
+ * The handler runs for every request, so tools, resources, prompts and views
45
74
  * are always registered on the instance that serves the request. Anything in
46
- * the factory body other than registration therefore runs per request too.
47
- * It also runs once up front at construction, or as soon as an async
48
- * loader resolves — which surfaces registration errors at boot and gives the
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.
75
+ * the handler body other than registration therefore runs per request too.
76
+ * Anything asynchronous the app needs (remote config, secrets, …) goes in
77
+ * `setup`, which runs once and feeds the handler's second argument.
57
78
  *
58
79
  * @example
59
80
  * ```ts
60
- * export const app = new Skybridge(
61
- * { name: "my-app", version: "1.0.0", capabilities: {} },
62
- * (server) =>
81
+ * export const app = new Skybridge({
82
+ * name: "my-app",
83
+ * version: "1.0.0",
84
+ * setup: async () => loadConfig(),
85
+ * oauth: (config) => descopeProvider({ url: config.mcpServerUrl }),
86
+ * handler: (server, config) =>
63
87
  * server.registerTool(
64
88
  * {
65
89
  * name: "search",
@@ -68,40 +92,45 @@ export type SkybridgeFactoryLoader<TTools extends Record<string, ToolDef>, TAuth
68
92
  * },
69
93
  * async ({ query }) => ({ content: `Results for ${query}` }),
70
94
  * ),
71
- * );
95
+ * });
72
96
  *
73
97
  * export type AppType = typeof app;
74
98
  * ```
75
99
  *
76
100
  * @see https://docs.skybridge.tech/api-reference/mcp-server
77
101
  */
78
- export declare class Skybridge<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TAuthExtra extends ExtraClaims = ExtraClaims> {
102
+ export declare class Skybridge<TTools extends Record<string, ToolDef> = Record<never, ToolDef>, TContext = undefined, TAuthExtra extends ExtraClaims = ExtraClaims> {
79
103
  readonly $types: McpServerTypes<TTools>;
80
104
  private readonly serverInfo;
81
105
  private readonly serverOptions;
82
- private readonly skybridgeOptions;
83
- private factory?;
84
- private readonly factoryLoader?;
106
+ private readonly skills?;
107
+ private oauthEnabled;
108
+ private readonly handler;
109
+ private readonly setup?;
110
+ private readonly oauthInput?;
111
+ private context?;
85
112
  private readonly expressApp;
86
113
  private readonly errorMiddleware;
87
114
  private readonly monitoringEntry;
88
115
  private resolveResourceMetadataUrl?;
89
- private ready?;
90
- private slowFactoryWarned;
91
- constructor(config: SkybridgeConfig<TAuthExtra>, factory: SkybridgeFactory<TTools, TAuthExtra> | SkybridgeFactoryLoader<TTools, TAuthExtra>);
116
+ private readyPromise?;
117
+ private slowHandlerWarned;
118
+ constructor(config: SkybridgeConfig<TTools, TContext, TAuthExtra>);
92
119
  /**
93
- * Resolve the factory loader and the OAuth config thunk, then wire OAuth
94
- * onto the Express app. Runs once; every entry point that needs a built
95
- * server awaits it. Immediate no-op when both were passed synchronously.
120
+ * Resolve `setup` and `oauth`, then wire OAuth onto the Express app. Runs
121
+ * once; every entry point that needs a built server awaits it, and a failed
122
+ * attempt is retried on the next call.
123
+ *
124
+ * @internal
96
125
  */
97
- private ensureReady;
126
+ ready(): Promise<void>;
98
127
  /**
99
128
  * The underlying Express app. Use this to extend the HTTP server with
100
129
  * custom routes, middleware, or settings — e.g.
101
130
  * `app.express.get("/health", ...)`.
102
131
  *
103
132
  * `express.json()` is pre-applied — tune it via the `json` config field,
104
- * e.g. `new Skybridge({ name, version, json: { limit: "10mb" } }, setup)`.
133
+ * e.g. `new Skybridge({ name, version, json: { limit: "10mb" }, handler })`.
105
134
  * Register your handlers before `run()`; after `run()`, dev-mode middleware,
106
135
  * the `/mcp` route, and the default error handler are appended in that order.
107
136
  *
@@ -111,13 +140,13 @@ export declare class Skybridge<TTools extends Record<string, ToolDef> = Record<n
111
140
  get express(): Express;
112
141
  /**
113
142
  * Build a fresh server for one stateless HTTP request, as
114
- * `createMcpHandler`'s factory contract requires: the factory runs again so
143
+ * `createMcpHandler`'s factory contract requires: the handler runs again so
115
144
  * the SDK's handler closures belong to the instance whose protocol era it
116
145
  * stamps. Sharing one instance's handler maps instead would bind them to an
117
146
  * instance that is never marked, pinning every request to the 2025 codec and
118
147
  * letting concurrent callers overwrite each other's negotiated version.
119
148
  *
120
- * Awaits the async factory loader and the lazy OAuth config on first use.
149
+ * Awaits `setup` and the `oauth` input on first use.
121
150
  */
122
151
  createServerInstance(): Promise<SdkServer>;
123
152
  /**
@@ -1,33 +1,28 @@
1
1
  import http from "node:http";
2
2
  import { setupOAuth } from "./auth/setup.js";
3
- import { createApp, createBaseApp, getMcpHandler } from "./express.js";
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 SLOW_FACTORY_THRESHOLD_MS = 50;
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 for every request, so tools, resources, prompts and views
12
+ * The handler runs for every request, so tools, resources, prompts and views
13
13
  * are always registered on the instance that serves the request. Anything in
14
- * the factory body other than registration therefore runs per request too.
15
- * It also runs once up front at construction, or as soon as an async
16
- * loader resolves — which surfaces registration errors at boot and gives the
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.
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.
25
17
  *
26
18
  * @example
27
19
  * ```ts
28
- * export const app = new Skybridge(
29
- * { name: "my-app", version: "1.0.0", capabilities: {} },
30
- * (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) =>
31
26
  * server.registerTool(
32
27
  * {
33
28
  * name: "search",
@@ -36,7 +31,7 @@ const SLOW_FACTORY_THRESHOLD_MS = 50;
36
31
  * },
37
32
  * async ({ query }) => ({ content: `Results for ${query}` }),
38
33
  * ),
39
- * );
34
+ * });
40
35
  *
41
36
  * export type AppType = typeof app;
42
37
  * ```
@@ -46,56 +41,53 @@ const SLOW_FACTORY_THRESHOLD_MS = 50;
46
41
  export class Skybridge {
47
42
  serverInfo;
48
43
  serverOptions;
49
- skybridgeOptions;
50
- factory;
51
- factoryLoader;
44
+ skills;
45
+ oauthEnabled = false;
46
+ handler;
47
+ setup;
48
+ oauthInput;
49
+ context;
52
50
  expressApp;
53
51
  errorMiddleware = [];
54
52
  monitoringEntry = createMiddlewareEntry();
55
53
  resolveResourceMetadataUrl;
56
- ready;
57
- slowFactoryWarned = false;
58
- constructor(config, factory) {
59
- 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;
60
58
  this.serverInfo = { name, title, version, description, icons, websiteUrl };
61
59
  this.serverOptions = serverOptions;
62
- this.skybridgeOptions = { json, oauth, skills };
60
+ this.skills = skills;
61
+ this.setup = setup;
62
+ this.oauthInput = oauth;
63
+ this.handler = handler;
63
64
  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
65
  }
78
66
  /**
79
- * Resolve the factory loader and the OAuth config thunk, then wire OAuth
80
- * onto the Express app. Runs once; every entry point that needs a built
81
- * server awaits it. Immediate no-op when both were passed synchronously.
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
82
72
  */
83
- ensureReady() {
84
- this.ready ??= (async () => {
85
- if (this.factoryLoader) {
86
- this.factory = await this.factoryLoader();
73
+ ready() {
74
+ this.readyPromise ??= (async () => {
75
+ if (this.setup) {
76
+ this.context = await this.setup();
87
77
  }
88
- const { oauth } = this.skybridgeOptions;
89
- const resolvedOauth = typeof oauth === "function" ? await oauth() : oauth;
78
+ const oauth = typeof this.oauthInput === "function"
79
+ ? await this.oauthInput(this.context)
80
+ : await this.oauthInput;
81
+ this.oauthEnabled = Boolean(oauth);
90
82
  const sample = this.buildServer();
91
- if (resolvedOauth) {
92
- this.resolveResourceMetadataUrl = setupOAuth(this.expressApp, resolvedOauth, sample.securitySchemesByTool);
83
+ if (oauth) {
84
+ this.resolveResourceMetadataUrl = setupOAuth(this.expressApp, oauth, sample.securitySchemesByTool);
93
85
  }
94
86
  })().catch((error) => {
95
- this.ready = undefined;
87
+ this.readyPromise = undefined;
96
88
  throw error;
97
89
  });
98
- return this.ready;
90
+ return this.readyPromise;
99
91
  }
100
92
  /**
101
93
  * The underlying Express app. Use this to extend the HTTP server with
@@ -103,7 +95,7 @@ export class Skybridge {
103
95
  * `app.express.get("/health", ...)`.
104
96
  *
105
97
  * `express.json()` is pre-applied — tune it via the `json` config field,
106
- * e.g. `new Skybridge({ name, version, json: { limit: "10mb" } }, setup)`.
98
+ * e.g. `new Skybridge({ name, version, json: { limit: "10mb" }, handler })`.
107
99
  * Register your handlers before `run()`; after `run()`, dev-mode middleware,
108
100
  * the `/mcp` route, and the default error handler are appended in that order.
109
101
  *
@@ -115,16 +107,16 @@ export class Skybridge {
115
107
  }
116
108
  /**
117
109
  * Build a fresh server for one stateless HTTP request, as
118
- * `createMcpHandler`'s factory contract requires: the factory runs again so
110
+ * `createMcpHandler`'s factory contract requires: the handler runs again so
119
111
  * the SDK's handler closures belong to the instance whose protocol era it
120
112
  * stamps. Sharing one instance's handler maps instead would bind them to an
121
113
  * instance that is never marked, pinning every request to the 2025 codec and
122
114
  * letting concurrent callers overwrite each other's negotiated version.
123
115
  *
124
- * Awaits the async factory loader and the lazy OAuth config on first use.
116
+ * Awaits `setup` and the `oauth` input on first use.
125
117
  */
126
118
  async createServerInstance() {
127
- await this.ensureReady();
119
+ await this.ready();
128
120
  const server = this.buildServer();
129
121
  this.instrumentHandlers(server);
130
122
  return server.server;
@@ -174,7 +166,6 @@ export class Skybridge {
174
166
  * wait on instead of polling.
175
167
  */
176
168
  async run() {
177
- await this.ensureReady();
178
169
  if (process.env.VERCEL === "1") {
179
170
  // createApp only reads httpServer inside its dev-only branch
180
171
  // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a
@@ -188,9 +179,11 @@ export class Skybridge {
188
179
  return this.expressApp;
189
180
  }
190
181
  const httpServer = http.createServer();
182
+ const mcpHandler = buildMcpHandler(this);
191
183
  await createApp({
192
184
  app: this,
193
185
  httpServer,
186
+ mcpHandler,
194
187
  errorMiddleware: this.errorMiddleware,
195
188
  });
196
189
  httpServer.on("request", this.expressApp);
@@ -220,9 +213,7 @@ export class Skybridge {
220
213
  // (force-quit on a second Ctrl+C while drain is hanging).
221
214
  process.off("SIGTERM", shutdown);
222
215
  process.off("SIGINT", shutdown);
223
- getMcpHandler(this)
224
- .close()
225
- .catch(() => { });
216
+ mcpHandler.close().catch(() => { });
226
217
  httpServer.close(() => process.exit(0));
227
218
  // Force exit if connections don't drain in time so the port is still
228
219
  // released promptly (e.g. for nodemon restarts).
@@ -233,22 +224,19 @@ export class Skybridge {
233
224
  return undefined;
234
225
  }
235
226
  buildServer() {
236
- if (!this.factory) {
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);
227
+ const server = new McpServer(this.serverInfo, this.serverOptions, { skills: this.skills, oauth: this.oauthEnabled });
240
228
  if (this.resolveResourceMetadataUrl) {
241
229
  server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);
242
230
  }
243
231
  const startedAt = performance.now();
244
- const built = this.factory(server);
232
+ const built = this.handler(server, this.context);
245
233
  const elapsed = performance.now() - startedAt;
246
234
  if (typeof built.then === "function") {
247
- throw new Error("The Skybridge factory must be synchronous — it runs on every request. To load config or secrets asynchronously, pass a loader instead: new Skybridge(config, async () => factory).");
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.");
248
236
  }
249
- if (elapsed > SLOW_FACTORY_THRESHOLD_MS && !this.slowFactoryWarned) {
250
- this.slowFactoryWarned = true;
251
- console.warn(`The Skybridge factory took ${Math.round(elapsed)}ms — it runs on every request, so this cost is paid per request. Hoist expensive work to module scope, or move awaited setup into an async loader: new Skybridge(config, async () => factory).`);
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.`);
252
240
  }
253
241
  return built;
254
242
  }
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"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","sourcesContent":["import http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport type {\n Implementation,\n Server as SdkServer,\n ServerOptions,\n} from \"@modelcontextprotocol/server\";\nimport type { ErrorRequestHandler, Express, RequestHandler } from \"express\";\nimport { type ResourceMetadataUrlResolver, setupOAuth } from \"./auth/setup.js\";\nimport type { ExtraClaims } from \"./auth.js\";\nimport { createApp, createBaseApp, getMcpHandler } from \"./express.js\";\nimport { createMiddlewareEntry } from \"./metric.js\";\nimport type { McpMiddlewareEntry } from \"./middleware.js\";\nimport { buildMiddlewareChain, getHandlerMaps } from \"./middleware.js\";\nimport {\n McpServer,\n type McpServerTypes,\n type SkybridgeServerOptions,\n type ToolDef,\n} from \"./server.js\";\n\nconst SLOW_FACTORY_THRESHOLD_MS = 50;\n\ntype ErrorMiddlewareConfig = {\n path?: string;\n handlers: ErrorRequestHandler[];\n};\n\n/**\n * Everything a Skybridge app needs in one bag: the MCP implementation info\n * (`name`, `version`, …), the SDK's {@link ServerOptions} (`capabilities`,\n * `instructions`, …), and the Skybridge-specific options (`oauth`, `json`,\n * `skills`).\n */\nexport type SkybridgeConfig<TAuthExtra extends ExtraClaims = ExtraClaims> =\n Implementation & ServerOptions & SkybridgeServerOptions<TAuthExtra>;\n\n/**\n * The bare {@link McpServer} a {@link SkybridgeFactory} receives: no tools\n * registered yet. Use it to annotate a factory extracted into its own\n * declaration — `(server: SkybridgeServer) => server.registerTool(…)` — and\n * pass the claims your OAuth verifier produces to type\n * `extra.http.authInfo.extra` in handlers. Leave the factory's return type\n * inferred: the returned chain is what carries the tool registry into\n * `typeof app`.\n */\nexport type SkybridgeServer<TAuthExtra extends ExtraClaims = ExtraClaims> =\n McpServer<Record<never, ToolDef>, TAuthExtra>;\n\n/**\n * Builds an app's MCP surface. Runs again for **every incoming request**, on a\n * fresh {@link McpServer}, so keep it to registration: hoist pools, timers,\n * clients and any other side effect to module scope and close over them.\n *\n * It must **return** the chained server so `typeof app` carries the registered\n * tool types.\n */\nexport type SkybridgeFactory<\n TTools extends Record<string, ToolDef>,\n TAuthExtra extends ExtraClaims,\n> = (\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n) => McpServer<TTools, TAuthExtra>;\n\n/**\n * Async alternative to passing a {@link SkybridgeFactory} directly: a zero-arg\n * function that loads whatever the factory needs (remote config, secrets, …)\n * and resolves to it. It runs once — at {@link Skybridge.run} or on the first\n * request, never at module import — so importing `server.ts` from tests and\n * evals stays free of side effects. The factory it returns is still\n * synchronous and still runs per request.\n */\nexport type SkybridgeFactoryLoader<\n TTools extends Record<string, ToolDef>,\n TAuthExtra extends ExtraClaims,\n> = () => Promise<SkybridgeFactory<TTools, TAuthExtra>>;\n\n/**\n * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`\n * route) plus a factory that builds the MCP server for each request.\n *\n * The factory runs for every request, so tools, resources, prompts and views\n * are always registered on the instance that serves the request. Anything in\n * the factory body other than registration therefore runs per request too.\n * It also runs once up front — at construction, or as soon as an async\n * loader resolves — which surfaces registration errors at boot and gives the\n * OAuth layer the set of per-tool security schemes.\n *\n * Anything asynchronous the factory needs (remote config, secrets, …) goes in\n * a {@link SkybridgeFactoryLoader} passed in its place:\n * `new Skybridge(config, async () => { const cfg = await load(); return (server) => …; })`.\n *\n * @typeParam TTools - Accumulated tool registry, inferred from the server the\n * factory returns. You almost never set this manually.\n *\n * @example\n * ```ts\n * export const app = new Skybridge(\n * { name: \"my-app\", version: \"1.0.0\", capabilities: {} },\n * (server) =>\n * server.registerTool(\n * {\n * name: \"search\",\n * inputSchema: { query: z.string() },\n * view: { component: \"search\" },\n * },\n * async ({ query }) => ({ content: `Results for ${query}` }),\n * ),\n * );\n *\n * export type AppType = typeof app;\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/mcp-server\n */\nexport class Skybridge<\n TTools extends Record<string, ToolDef> = Record<never, ToolDef>,\n TAuthExtra extends ExtraClaims = ExtraClaims,\n> {\n declare readonly $types: McpServerTypes<TTools>;\n private readonly serverInfo: Implementation;\n private readonly serverOptions: ServerOptions;\n private readonly skybridgeOptions: SkybridgeServerOptions<TAuthExtra>;\n private factory?: SkybridgeFactory<TTools, TAuthExtra>;\n private readonly factoryLoader?: SkybridgeFactoryLoader<TTools, TAuthExtra>;\n private readonly expressApp: Express;\n private readonly errorMiddleware: ErrorMiddlewareConfig[] = [];\n private readonly monitoringEntry: McpMiddlewareEntry | null =\n createMiddlewareEntry();\n private resolveResourceMetadataUrl?: ResourceMetadataUrlResolver;\n private ready?: Promise<void>;\n private slowFactoryWarned = false;\n\n constructor(\n config: SkybridgeConfig<TAuthExtra>,\n factory:\n | SkybridgeFactory<TTools, TAuthExtra>\n | SkybridgeFactoryLoader<TTools, TAuthExtra>,\n ) {\n const {\n name,\n title,\n version,\n description,\n icons,\n websiteUrl,\n json,\n oauth,\n skills,\n ...serverOptions\n } = config;\n\n this.serverInfo = { name, title, version, description, icons, websiteUrl };\n this.serverOptions = serverOptions;\n this.skybridgeOptions = { json, oauth, skills };\n this.expressApp = createBaseApp(json);\n\n if (factory.length === 0) {\n this.factoryLoader = factory as SkybridgeFactoryLoader<\n TTools,\n TAuthExtra\n >;\n return;\n }\n this.factory = factory as SkybridgeFactory<TTools, TAuthExtra>;\n\n if (typeof oauth === \"function\") {\n return;\n }\n\n const sample = this.buildServer();\n if (oauth) {\n this.resolveResourceMetadataUrl = setupOAuth(\n this.expressApp,\n oauth,\n sample.securitySchemesByTool,\n );\n }\n this.ready = Promise.resolve();\n }\n\n /**\n * Resolve the factory loader and the OAuth config thunk, then wire OAuth\n * onto the Express app. Runs once; every entry point that needs a built\n * server awaits it. Immediate no-op when both were passed synchronously.\n */\n private ensureReady(): Promise<void> {\n this.ready ??= (async () => {\n if (this.factoryLoader) {\n this.factory = await this.factoryLoader();\n }\n const { oauth } = this.skybridgeOptions;\n const resolvedOauth = typeof oauth === \"function\" ? await oauth() : oauth;\n const sample = this.buildServer();\n if (resolvedOauth) {\n this.resolveResourceMetadataUrl = setupOAuth(\n this.expressApp,\n resolvedOauth,\n sample.securitySchemesByTool,\n );\n }\n })().catch((error) => {\n this.ready = undefined;\n throw error;\n });\n return this.ready;\n }\n\n /**\n * The underlying Express app. Use this to extend the HTTP server with\n * custom routes, middleware, or settings — e.g.\n * `app.express.get(\"/health\", ...)`.\n *\n * `express.json()` is pre-applied — tune it via the `json` config field,\n * e.g. `new Skybridge({ name, version, json: { limit: \"10mb\" } }, setup)`.\n * Register your handlers before `run()`; after `run()`, dev-mode middleware,\n * the `/mcp` route, and the default error handler are appended in that order.\n *\n * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work\n * locally and on self-hosted deployments.\n */\n get express(): Express {\n return this.expressApp;\n }\n\n /**\n * Build a fresh server for one stateless HTTP request, as\n * `createMcpHandler`'s factory contract requires: the factory runs again so\n * the SDK's handler closures belong to the instance whose protocol era it\n * stamps. Sharing one instance's handler maps instead would bind them to an\n * instance that is never marked, pinning every request to the 2025 codec and\n * letting concurrent callers overwrite each other's negotiated version.\n *\n * Awaits the async factory loader and the lazy OAuth config on first use.\n */\n async createServerInstance(): Promise<SdkServer> {\n await this.ensureReady();\n const server = this.buildServer();\n this.instrumentHandlers(server);\n return server.server;\n }\n\n /**\n * Connect a Skybridge app to an MCP transport. Use this when you're\n * embedding Skybridge in a host that already manages its own transport\n * (e.g. stdio for desktop apps); for HTTP, prefer {@link Skybridge.run}\n * which sets the transport up for you.\n */\n async connect(transport: Parameters<SdkServer[\"connect\"]>[0]): Promise<void> {\n const instance = await this.createServerInstance();\n await instance.connect(transport);\n }\n\n /**\n * Register Express middleware on the underlying app. Mirrors `app.use` —\n * pass handlers directly or a path-prefixed handler list. Register before\n * {@link Skybridge.run}; ordering matches Express.\n *\n * Note: Alpic Cloud only routes traffic to `/mcp`. Custom paths work\n * locally and on self-hosted deployments.\n */\n use(...handlers: RequestHandler[]): this;\n use(path: string, ...handlers: RequestHandler[]): this;\n use(\n pathOrHandler: string | RequestHandler,\n ...handlers: RequestHandler[]\n ): this {\n // Branching is load-bearing: Express's `app.use` overloads can't be\n // resolved against a `string | RequestHandler` union, so we narrow.\n if (typeof pathOrHandler === \"string\") {\n this.expressApp.use(pathOrHandler, ...handlers);\n } else {\n this.expressApp.use(pathOrHandler, ...handlers);\n }\n return this;\n }\n\n /**\n * Register Express error-handling middleware to run after the built-in\n * `/mcp` route (or your custom route). Use this to log or transform errors\n * thrown by tool handlers before the default error handler responds.\n *\n * @example\n * ```ts\n * app.useOnError((err, _req, _res, next) => {\n * logger.error(err);\n * next(err);\n * });\n * ```\n */\n useOnError(...handlers: ErrorRequestHandler[]): this;\n useOnError(path: string, ...handlers: ErrorRequestHandler[]): this;\n useOnError(\n pathOrHandler: string | ErrorRequestHandler,\n ...handlers: ErrorRequestHandler[]\n ): this {\n if (typeof pathOrHandler === \"string\") {\n this.errorMiddleware.push({ path: pathOrHandler, handlers });\n } else {\n this.errorMiddleware.push({ handlers: [pathOrHandler, ...handlers] });\n }\n return this;\n }\n\n /**\n * Start the HTTP server. Listens on `process.env.__PORT` (default `3000`),\n * mounts the `/mcp` route, and applies any custom Express middleware\n * registered via {@link Skybridge.use} / {@link Skybridge.useOnError}.\n *\n * On Cloudflare Workers / workerd, returns an object exposing `fetch` so\n * the runtime can bridge incoming requests to the Node HTTP server. On\n * Vercel (`VERCEL === \"1\"`), returns the Express app directly so the\n * serverless function entry can call it as a `(req, res)` handler. On\n * Node, returns `undefined` once listening. When the process was spawned\n * with an IPC channel, the bound port is reported to the parent as\n * `{ type: \"skybridge:listening\", port }`, the readiness signal test runners\n * wait on instead of polling.\n */\n async run(): Promise<\n { fetch: (...args: unknown[]) => unknown } | Express | undefined\n > {\n await this.ensureReady();\n\n if (process.env.VERCEL === \"1\") {\n // createApp only reads httpServer inside its dev-only branch\n // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a\n // bare object passed to satisfy the required parameter.\n const httpServer = http.createServer();\n await createApp({\n app: this,\n httpServer,\n errorMiddleware: this.errorMiddleware,\n });\n return this.expressApp;\n }\n\n const httpServer = http.createServer();\n\n await createApp({\n app: this,\n httpServer,\n errorMiddleware: this.errorMiddleware,\n });\n\n httpServer.on(\"request\", this.expressApp);\n const intendedPort = parseInt(process.env.__PORT ?? \"3000\", 10);\n await new Promise<void>((resolve, reject) => {\n httpServer.on(\"error\", (error: Error) => {\n console.error(\"Failed to start server:\", error);\n reject(error);\n });\n httpServer.listen(intendedPort, () => {\n resolve();\n });\n });\n\n const { port } = httpServer.address() as AddressInfo;\n process.send?.({ type: \"skybridge:listening\", port });\n\n // On workerd, bridge the Node http server to a Workers fetch handler.\n // The specifier is held in a variable to sidestep tsc's module resolution\n // (`cloudflare:node` only exists under wrangler/workerd).\n if (\n typeof navigator !== \"undefined\" &&\n navigator.userAgent === \"Cloudflare-Workers\"\n ) {\n const cloudflareNode = \"cloudflare:node\";\n const { httpServerHandler } = await import(cloudflareNode);\n return httpServerHandler({ port });\n }\n\n const shutdown = () => {\n // Drop both handlers so a second signal falls through to Node's default\n // (force-quit on a second Ctrl+C while drain is hanging).\n process.off(\"SIGTERM\", shutdown);\n process.off(\"SIGINT\", shutdown);\n getMcpHandler(this)\n .close()\n .catch(() => {});\n httpServer.close(() => process.exit(0));\n // Force exit if connections don't drain in time so the port is still\n // released promptly (e.g. for nodemon restarts).\n setTimeout(() => process.exit(0), 3000).unref();\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n return undefined;\n }\n\n private buildServer(): McpServer<TTools, TAuthExtra> {\n if (!this.factory) {\n throw new Error(\n \"Skybridge factory not loaded yet: this app was constructed with an async factory loader. Await run(), connect(), or createServerInstance().\",\n );\n }\n const server = new McpServer<Record<never, ToolDef>, TAuthExtra>(\n this.serverInfo,\n this.serverOptions,\n this.skybridgeOptions,\n );\n if (this.resolveResourceMetadataUrl) {\n server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);\n }\n const startedAt = performance.now();\n const built = this.factory(server);\n const elapsed = performance.now() - startedAt;\n if (typeof (built as { then?: unknown }).then === \"function\") {\n throw new Error(\n \"The Skybridge factory must be synchronous — it runs on every request. To load config or secrets asynchronously, pass a loader instead: new Skybridge(config, async () => factory).\",\n );\n }\n if (elapsed > SLOW_FACTORY_THRESHOLD_MS && !this.slowFactoryWarned) {\n this.slowFactoryWarned = true;\n console.warn(\n `The Skybridge factory took ${Math.round(elapsed)}ms — it runs on every request, so this cost is paid per request. Hoist expensive work to module scope, or move awaited setup into an async loader: new Skybridge(config, async () => factory).`,\n );\n }\n return built;\n }\n\n private middlewareEntries(\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n ): McpMiddlewareEntry[] {\n return [\n ...(this.monitoringEntry ? [this.monitoringEntry] : []),\n ...server.protocolMiddlewareEntries(),\n ];\n }\n\n private instrumentHandlers(\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n ): void {\n const entries = this.middlewareEntries(server);\n const { requestHandlers, notificationHandlers } = getHandlerMaps(\n server.server,\n );\n\n const instrumentMap = (\n map: Map<string, (...args: unknown[]) => Promise<unknown>>,\n isNotification: boolean,\n ) => {\n for (const [method, handler] of map) {\n map.set(\n method,\n buildMiddlewareChain(method, isNotification, handler, entries),\n );\n }\n const originalSet = map.set.bind(map);\n map.set = (\n method: string,\n handler: (...args: unknown[]) => Promise<unknown>,\n ) =>\n originalSet(\n method,\n buildMiddlewareChain(method, isNotification, handler, entries),\n );\n };\n\n instrumentMap(requestHandlers, false);\n instrumentMap(notificationHandlers, true);\n }\n}\n"]}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"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","sourcesContent":["import http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport type {\n Implementation,\n Server as SdkServer,\n ServerOptions,\n} from \"@modelcontextprotocol/server\";\nimport type { ErrorRequestHandler, Express, RequestHandler } from \"express\";\nimport type { OAuthConfig } from \"./auth/index.js\";\nimport { type ResourceMetadataUrlResolver, setupOAuth } from \"./auth/setup.js\";\nimport type { ExtraClaims } from \"./auth.js\";\nimport { buildMcpHandler, createApp, createBaseApp } from \"./express.js\";\nimport { createMiddlewareEntry } from \"./metric.js\";\nimport type { McpMiddlewareEntry } from \"./middleware.js\";\nimport { buildMiddlewareChain, getHandlerMaps } from \"./middleware.js\";\nimport {\n type JsonOptions,\n McpServer,\n type McpServerTypes,\n type ToolDef,\n} from \"./server.js\";\n\nconst SLOW_HANDLER_THRESHOLD_MS = 50;\n\ntype ErrorMiddlewareConfig = {\n path?: string;\n handlers: ErrorRequestHandler[];\n};\n\n/**\n * The bare {@link McpServer} a {@link SkybridgeHandler} receives: no tools\n * registered yet. Use it to annotate a handler extracted into its own\n * declaration — `(server: SkybridgeServer) => server.registerTool(…)` — and\n * pass the claims your OAuth verifier produces to type\n * `extra.http.authInfo.extra` in handlers. Leave the handler's return type\n * inferred: the returned chain is what carries the tool registry into\n * `typeof app`.\n */\nexport type SkybridgeServer<TAuthExtra extends ExtraClaims = ExtraClaims> =\n McpServer<Record<never, ToolDef>, TAuthExtra>;\n\n/**\n * The `handler` field of {@link SkybridgeConfig}: builds the app's MCP\n * surface. Runs again for **every incoming request**, on a fresh\n * {@link McpServer}, so keep it to registration: hoist pools, timers, clients\n * and any other side effect to module scope (or into `setup`) and close over\n * them. It must **return** the chained server so `typeof app` carries the\n * registered tool types.\n *\n * @typeParam TContext - What `setup` resolved to, passed as the second argument.\n */\nexport type SkybridgeHandler<\n TTools extends Record<string, ToolDef>,\n TContext,\n TAuthExtra extends ExtraClaims,\n> = (\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n context: TContext,\n) => McpServer<TTools, TAuthExtra>;\n\n/**\n * What the `oauth` field accepts: a resolved {@link OAuthConfig}, a promise of\n * one (the branded providers are async), or a function of the `setup` result.\n * A function or promise is resolved once — at {@link Skybridge.run} or on the\n * first request, never at module import — so prefer a function when building\n * the config has side effects (network discovery, secrets).\n */\nexport type SkybridgeOAuthInput<TContext, TExtra extends ExtraClaims> =\n | OAuthConfig<TExtra>\n | Promise<OAuthConfig<TExtra>>\n | ((context: TContext) => OAuthConfig<TExtra> | Promise<OAuthConfig<TExtra>>);\n\n/**\n * Everything a Skybridge app needs in one bag: the MCP implementation info\n * (`name`, `version`, …), the SDK's {@link ServerOptions} (`capabilities`,\n * `instructions`, …), the Express and skills options, and the app's behavior\n * (`setup`, `oauth`, `handler`).\n *\n * All type parameters are inferred from the value: the context from `setup`,\n * the auth claims from `oauth`, and the tool registry from the server\n * `handler` returns.\n */\nexport type SkybridgeConfig<\n TTools extends Record<string, ToolDef> = Record<never, ToolDef>,\n TContext = undefined,\n TAuthExtra extends ExtraClaims = ExtraClaims,\n> = Implementation &\n ServerOptions & {\n /** Options for the built-in `express.json()` middleware, e.g. `{ limit: \"10mb\" }`. */\n json?: JsonOptions;\n /**\n * @experimental Serve Agent Skills from `src/skills` over MCP (SEP-2640).\n * API may change.\n */\n skills?: boolean;\n /**\n * Loads whatever the app needs up front (remote config, secrets, datasets,\n * …). Runs **once** — at {@link Skybridge.run} or on the first request,\n * never at module import — and its awaited return value is passed to an\n * `oauth` function and to `handler` as the second argument.\n */\n setup?: () => TContext;\n /**\n * Resource-server OAuth. When set, mounts the well-known metadata routes\n * and bearer auth on `/mcp`, and the verifier's claims type\n * `extra.http.authInfo.extra` in tool handlers.\n */\n oauth?: SkybridgeOAuthInput<Awaited<TContext>, TAuthExtra>;\n /** Registers the MCP surface, per request. See {@link SkybridgeHandler}. */\n handler: SkybridgeHandler<TTools, Awaited<TContext>, TAuthExtra>;\n };\n\n/**\n * A Skybridge app: the HTTP surface (Express, OAuth metadata, the `/mcp`\n * route) plus a handler that builds the MCP server for each request.\n *\n * The handler runs for every request, so tools, resources, prompts and views\n * are always registered on the instance that serves the request. Anything in\n * the handler body other than registration therefore runs per request too.\n * Anything asynchronous the app needs (remote config, secrets, …) goes in\n * `setup`, which runs once and feeds the handler's second argument.\n *\n * @example\n * ```ts\n * export const app = new Skybridge({\n * name: \"my-app\",\n * version: \"1.0.0\",\n * setup: async () => loadConfig(),\n * oauth: (config) => descopeProvider({ url: config.mcpServerUrl }),\n * handler: (server, config) =>\n * server.registerTool(\n * {\n * name: \"search\",\n * inputSchema: { query: z.string() },\n * view: { component: \"search\" },\n * },\n * async ({ query }) => ({ content: `Results for ${query}` }),\n * ),\n * });\n *\n * export type AppType = typeof app;\n * ```\n *\n * @see https://docs.skybridge.tech/api-reference/mcp-server\n */\nexport class Skybridge<\n TTools extends Record<string, ToolDef> = Record<never, ToolDef>,\n TContext = undefined,\n TAuthExtra extends ExtraClaims = ExtraClaims,\n> {\n declare readonly $types: McpServerTypes<TTools>;\n private readonly serverInfo: Implementation;\n private readonly serverOptions: ServerOptions;\n private readonly skills?: boolean;\n private oauthEnabled = false;\n private readonly handler: SkybridgeHandler<\n TTools,\n Awaited<TContext>,\n TAuthExtra\n >;\n private readonly setup?: () => TContext;\n private readonly oauthInput?: SkybridgeOAuthInput<\n Awaited<TContext>,\n TAuthExtra\n >;\n private context?: Awaited<TContext>;\n private readonly expressApp: Express;\n private readonly errorMiddleware: ErrorMiddlewareConfig[] = [];\n private readonly monitoringEntry: McpMiddlewareEntry | null =\n createMiddlewareEntry();\n private resolveResourceMetadataUrl?: ResourceMetadataUrlResolver;\n private readyPromise?: Promise<void>;\n private slowHandlerWarned = false;\n\n constructor(config: SkybridgeConfig<TTools, TContext, TAuthExtra>) {\n const {\n name,\n title,\n version,\n description,\n icons,\n websiteUrl,\n json,\n skills,\n setup,\n oauth,\n handler,\n ...serverOptions\n } = config;\n\n this.serverInfo = { name, title, version, description, icons, websiteUrl };\n this.serverOptions = serverOptions;\n this.skills = skills;\n this.setup = setup;\n this.oauthInput = oauth;\n this.handler = handler;\n this.expressApp = createBaseApp(json);\n }\n\n /**\n * Resolve `setup` and `oauth`, then wire OAuth onto the Express app. Runs\n * once; every entry point that needs a built server awaits it, and a failed\n * attempt is retried on the next call.\n *\n * @internal\n */\n ready(): Promise<void> {\n this.readyPromise ??= (async () => {\n if (this.setup) {\n this.context = await this.setup();\n }\n const oauth =\n typeof this.oauthInput === \"function\"\n ? await this.oauthInput(this.context as Awaited<TContext>)\n : await this.oauthInput;\n this.oauthEnabled = Boolean(oauth);\n const sample = this.buildServer();\n if (oauth) {\n this.resolveResourceMetadataUrl = setupOAuth(\n this.expressApp,\n oauth,\n sample.securitySchemesByTool,\n );\n }\n })().catch((error) => {\n this.readyPromise = undefined;\n throw error;\n });\n return this.readyPromise;\n }\n\n /**\n * The underlying Express app. Use this to extend the HTTP server with\n * custom routes, middleware, or settings — e.g.\n * `app.express.get(\"/health\", ...)`.\n *\n * `express.json()` is pre-applied — tune it via the `json` config field,\n * e.g. `new Skybridge({ name, version, json: { limit: \"10mb\" }, handler })`.\n * Register your handlers before `run()`; after `run()`, dev-mode middleware,\n * the `/mcp` route, and the default error handler are appended in that order.\n *\n * Note: Alpic Cloud only routes traffic to `/mcp` — custom routes work\n * locally and on self-hosted deployments.\n */\n get express(): Express {\n return this.expressApp;\n }\n\n /**\n * Build a fresh server for one stateless HTTP request, as\n * `createMcpHandler`'s factory contract requires: the handler runs again so\n * the SDK's handler closures belong to the instance whose protocol era it\n * stamps. Sharing one instance's handler maps instead would bind them to an\n * instance that is never marked, pinning every request to the 2025 codec and\n * letting concurrent callers overwrite each other's negotiated version.\n *\n * Awaits `setup` and the `oauth` input on first use.\n */\n async createServerInstance(): Promise<SdkServer> {\n await this.ready();\n const server = this.buildServer();\n this.instrumentHandlers(server);\n return server.server;\n }\n\n /**\n * Connect a Skybridge app to an MCP transport. Use this when you're\n * embedding Skybridge in a host that already manages its own transport\n * (e.g. stdio for desktop apps); for HTTP, prefer {@link Skybridge.run}\n * which sets the transport up for you.\n */\n async connect(transport: Parameters<SdkServer[\"connect\"]>[0]): Promise<void> {\n const instance = await this.createServerInstance();\n await instance.connect(transport);\n }\n\n /**\n * Register Express middleware on the underlying app. Mirrors `app.use` —\n * pass handlers directly or a path-prefixed handler list. Register before\n * {@link Skybridge.run}; ordering matches Express.\n *\n * Note: Alpic Cloud only routes traffic to `/mcp`. Custom paths work\n * locally and on self-hosted deployments.\n */\n use(...handlers: RequestHandler[]): this;\n use(path: string, ...handlers: RequestHandler[]): this;\n use(\n pathOrHandler: string | RequestHandler,\n ...handlers: RequestHandler[]\n ): this {\n // Branching is load-bearing: Express's `app.use` overloads can't be\n // resolved against a `string | RequestHandler` union, so we narrow.\n if (typeof pathOrHandler === \"string\") {\n this.expressApp.use(pathOrHandler, ...handlers);\n } else {\n this.expressApp.use(pathOrHandler, ...handlers);\n }\n return this;\n }\n\n /**\n * Register Express error-handling middleware to run after the built-in\n * `/mcp` route (or your custom route). Use this to log or transform errors\n * thrown by tool handlers before the default error handler responds.\n *\n * @example\n * ```ts\n * app.useOnError((err, _req, _res, next) => {\n * logger.error(err);\n * next(err);\n * });\n * ```\n */\n useOnError(...handlers: ErrorRequestHandler[]): this;\n useOnError(path: string, ...handlers: ErrorRequestHandler[]): this;\n useOnError(\n pathOrHandler: string | ErrorRequestHandler,\n ...handlers: ErrorRequestHandler[]\n ): this {\n if (typeof pathOrHandler === \"string\") {\n this.errorMiddleware.push({ path: pathOrHandler, handlers });\n } else {\n this.errorMiddleware.push({ handlers: [pathOrHandler, ...handlers] });\n }\n return this;\n }\n\n /**\n * Start the HTTP server. Listens on `process.env.__PORT` (default `3000`),\n * mounts the `/mcp` route, and applies any custom Express middleware\n * registered via {@link Skybridge.use} / {@link Skybridge.useOnError}.\n *\n * On Cloudflare Workers / workerd, returns an object exposing `fetch` so\n * the runtime can bridge incoming requests to the Node HTTP server. On\n * Vercel (`VERCEL === \"1\"`), returns the Express app directly so the\n * serverless function entry can call it as a `(req, res)` handler. On\n * Node, returns `undefined` once listening. When the process was spawned\n * with an IPC channel, the bound port is reported to the parent as\n * `{ type: \"skybridge:listening\", port }`, the readiness signal test runners\n * wait on instead of polling.\n */\n async run(): Promise<\n { fetch: (...args: unknown[]) => unknown } | Express | undefined\n > {\n if (process.env.VERCEL === \"1\") {\n // createApp only reads httpServer inside its dev-only branch\n // (viewsDevServer); under VERCEL=1 + NODE_ENV=production it's a\n // bare object passed to satisfy the required parameter.\n const httpServer = http.createServer();\n await createApp({\n app: this,\n httpServer,\n errorMiddleware: this.errorMiddleware,\n });\n return this.expressApp;\n }\n\n const httpServer = http.createServer();\n const mcpHandler = buildMcpHandler(this);\n\n await createApp({\n app: this,\n httpServer,\n mcpHandler,\n errorMiddleware: this.errorMiddleware,\n });\n\n httpServer.on(\"request\", this.expressApp);\n const intendedPort = parseInt(process.env.__PORT ?? \"3000\", 10);\n await new Promise<void>((resolve, reject) => {\n httpServer.on(\"error\", (error: Error) => {\n console.error(\"Failed to start server:\", error);\n reject(error);\n });\n httpServer.listen(intendedPort, () => {\n resolve();\n });\n });\n\n const { port } = httpServer.address() as AddressInfo;\n process.send?.({ type: \"skybridge:listening\", port });\n\n // On workerd, bridge the Node http server to a Workers fetch handler.\n // The specifier is held in a variable to sidestep tsc's module resolution\n // (`cloudflare:node` only exists under wrangler/workerd).\n if (\n typeof navigator !== \"undefined\" &&\n navigator.userAgent === \"Cloudflare-Workers\"\n ) {\n const cloudflareNode = \"cloudflare:node\";\n const { httpServerHandler } = await import(cloudflareNode);\n return httpServerHandler({ port });\n }\n\n const shutdown = () => {\n // Drop both handlers so a second signal falls through to Node's default\n // (force-quit on a second Ctrl+C while drain is hanging).\n process.off(\"SIGTERM\", shutdown);\n process.off(\"SIGINT\", shutdown);\n mcpHandler.close().catch(() => {});\n httpServer.close(() => process.exit(0));\n // Force exit if connections don't drain in time so the port is still\n // released promptly (e.g. for nodemon restarts).\n setTimeout(() => process.exit(0), 3000).unref();\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n return undefined;\n }\n\n private buildServer(): McpServer<TTools, TAuthExtra> {\n const server = new McpServer<Record<never, ToolDef>, TAuthExtra>(\n this.serverInfo,\n this.serverOptions,\n { skills: this.skills, oauth: this.oauthEnabled },\n );\n if (this.resolveResourceMetadataUrl) {\n server.setResourceMetadataUrlResolver(this.resolveResourceMetadataUrl);\n }\n const startedAt = performance.now();\n const built = this.handler(server, this.context as Awaited<TContext>);\n const elapsed = performance.now() - startedAt;\n if (typeof (built as { then?: unknown }).then === \"function\") {\n throw new Error(\n \"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.\",\n );\n }\n if (elapsed > SLOW_HANDLER_THRESHOLD_MS && !this.slowHandlerWarned) {\n this.slowHandlerWarned = true;\n console.warn(\n `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.`,\n );\n }\n return built;\n }\n\n private middlewareEntries(\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n ): McpMiddlewareEntry[] {\n return [\n ...(this.monitoringEntry ? [this.monitoringEntry] : []),\n ...server.protocolMiddlewareEntries(),\n ];\n }\n\n private instrumentHandlers(\n server: McpServer<Record<never, ToolDef>, TAuthExtra>,\n ): void {\n const entries = this.middlewareEntries(server);\n const { requestHandlers, notificationHandlers } = getHandlerMaps(\n server.server,\n );\n\n const instrumentMap = (\n map: Map<string, (...args: unknown[]) => Promise<unknown>>,\n isNotification: boolean,\n ) => {\n for (const [method, handler] of map) {\n map.set(\n method,\n buildMiddlewareChain(method, isNotification, handler, entries),\n );\n }\n const originalSet = map.set.bind(map);\n map.set = (\n method: string,\n handler: (...args: unknown[]) => Promise<unknown>,\n ) =>\n originalSet(\n method,\n buildMiddlewareChain(method, isNotification, handler, entries),\n );\n };\n\n instrumentMap(requestHandlers, false);\n instrumentMap(notificationHandlers, true);\n }\n}\n"]}