@smithery/sdk 1.6.2 → 1.6.4

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/index.d.ts CHANGED
@@ -2,3 +2,5 @@ export * from "./shared/config.js";
2
2
  export * from "./shared/patch.js";
3
3
  export { createStatefulServer, type StatefulServerOptions, } from "./server/stateful.js";
4
4
  export * from "./server/session.js";
5
+ export * from "./server/auth/identity.js";
6
+ export * from "./server/auth/oauth.js";
package/dist/index.js CHANGED
@@ -7,3 +7,5 @@ export * from "./shared/patch.js";
7
7
  // Server-side helpers (selective to avoid duplicate type names)
8
8
  export { createStatefulServer, } from "./server/stateful.js";
9
9
  export * from "./server/session.js";
10
+ export * from "./server/auth/identity.js";
11
+ export * from "./server/auth/oauth.js";
@@ -0,0 +1,18 @@
1
+ import type { Application, Request, Router } from "express";
2
+ import { type JWTPayload } from "jose";
3
+ import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
4
+ export type IdentityJwtClaims = JWTPayload & Record<string, unknown>;
5
+ export interface IdentityHandler {
6
+ /** Base path to mount metadata and token endpoints. Default: "/" */
7
+ basePath?: string;
8
+ /** Expected JWT issuer. Default: "https://server.smithery.ai" */
9
+ issuer?: string;
10
+ /** JWKS URL for issuer. Default: "https://server.smithery.ai/.well-known/jwks.json" */
11
+ jwksUrl?: string;
12
+ /** Optional explicit token path. Overrides basePath+"token". */
13
+ tokenPath?: string;
14
+ /** Handle a JWT grant provided by an external identity provider (i.e., Smithery) and mint access tokens */
15
+ handleJwtGrant: (claims: IdentityJwtClaims, req: Request) => Promise<OAuthTokens | null>;
16
+ }
17
+ export declare function createIdentityTokenRouter(options: IdentityHandler): Router;
18
+ export declare function mountIdentity(app: Application, options: IdentityHandler): void;
@@ -0,0 +1,55 @@
1
+ import express from "express";
2
+ import { createRemoteJWKSet, jwtVerify } from "jose";
3
+ function normalizeBasePath(basePath) {
4
+ const value = basePath ?? "/";
5
+ return value.endsWith("/") ? value : `${value}/`;
6
+ }
7
+ export function createIdentityTokenRouter(options) {
8
+ const basePath = normalizeBasePath(options.basePath);
9
+ const issuer = options.issuer ?? "https://server.smithery.ai";
10
+ const jwksUrl = new URL(options.jwksUrl ?? "https://server.smithery.ai/.well-known/jwks.json");
11
+ const tokenPath = typeof options.tokenPath === "string" && options.tokenPath.length > 0
12
+ ? options.tokenPath
13
+ : `${basePath}token`;
14
+ // Create JWKS resolver once; jose caches keys internally
15
+ const JWKS = createRemoteJWKSet(jwksUrl);
16
+ const tokenRouter = express.Router();
17
+ // urlencoded parser required for OAuth token requests
18
+ tokenRouter.use(express.urlencoded({ extended: false }));
19
+ tokenRouter.post(tokenPath, async (req, res, next) => {
20
+ try {
21
+ const grantType = typeof req.body?.grant_type === "string"
22
+ ? req.body.grant_type
23
+ : undefined;
24
+ if (grantType !== "urn:ietf:params:oauth:grant-type:jwt-bearer")
25
+ return next();
26
+ const assertion = typeof req.body?.assertion === "string" ? req.body.assertion : undefined;
27
+ if (!assertion) {
28
+ res.status(400).json({
29
+ error: "invalid_request",
30
+ error_description: "Missing assertion",
31
+ });
32
+ return;
33
+ }
34
+ const host = req.get("host") ?? "localhost";
35
+ const audience = `${req.protocol}://${host}${tokenPath}`;
36
+ const { payload } = await jwtVerify(assertion, JWKS, {
37
+ issuer,
38
+ audience,
39
+ algorithms: ["RS256"],
40
+ });
41
+ const result = await options.handleJwtGrant(payload, req);
42
+ if (!result)
43
+ return next();
44
+ res.json(result);
45
+ }
46
+ catch (error) {
47
+ console.error(error);
48
+ res.status(400).json({ error: "invalid_grant" });
49
+ }
50
+ });
51
+ return tokenRouter;
52
+ }
53
+ export function mountIdentity(app, options) {
54
+ app.use(createIdentityTokenRouter(options));
55
+ }
@@ -0,0 +1,13 @@
1
+ import type { OAuthServerProvider, OAuthTokenVerifier } from "@modelcontextprotocol/sdk/server/auth/provider.js";
2
+ import type { Application, Response } from "express";
3
+ import { type IdentityHandler } from "./identity.js";
4
+ export interface CallbackOAuthServerProvider extends OAuthServerProvider {
5
+ basePath?: string;
6
+ callbackPath?: string;
7
+ handleOAuthCallback?: (code: string, state: string | undefined, res: Response) => Promise<URL>;
8
+ }
9
+ export interface OAuthMountOptions {
10
+ provider?: CallbackOAuthServerProvider | OAuthTokenVerifier;
11
+ identity?: IdentityHandler;
12
+ }
13
+ export declare function mountOAuth(app: Application, opts: OAuthMountOptions): void;
@@ -0,0 +1,153 @@
1
+ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
2
+ import { mcpAuthMetadataRouter, createOAuthMetadata, } from "@modelcontextprotocol/sdk/server/auth/router.js";
3
+ import { authorizationHandler } from "@modelcontextprotocol/sdk/server/auth/handlers/authorize.js";
4
+ import { tokenHandler } from "@modelcontextprotocol/sdk/server/auth/handlers/token.js";
5
+ import { clientRegistrationHandler } from "@modelcontextprotocol/sdk/server/auth/handlers/register.js";
6
+ import { revocationHandler } from "@modelcontextprotocol/sdk/server/auth/handlers/revoke.js";
7
+ import { metadataHandler } from "@modelcontextprotocol/sdk/server/auth/handlers/metadata.js";
8
+ import { mountIdentity } from "./identity.js";
9
+ function isOAuthProvider(provider) {
10
+ return !!provider && "authorize" in provider;
11
+ }
12
+ export function mountOAuth(app, opts) {
13
+ // Determine base path once based on OAuth provider or identity
14
+ const provider = opts.provider;
15
+ const hasOAuth = isOAuthProvider(provider);
16
+ const rawBasePath = hasOAuth
17
+ ? (provider.basePath ?? "/")
18
+ : (opts.identity?.basePath ?? "/");
19
+ const basePath = rawBasePath.endsWith("/") ? rawBasePath : `${rawBasePath}/`;
20
+ // Precompute endpoint pathnames from metadata
21
+ let authorizationPath;
22
+ let tokenPath;
23
+ let registrationPath;
24
+ let revocationPath;
25
+ if (isOAuthProvider(provider)) {
26
+ const placeholderIssuer = new URL("https://localhost");
27
+ const placeholderBaseUrl = new URL(basePath, placeholderIssuer);
28
+ const localMetadata = createOAuthMetadata({
29
+ provider,
30
+ issuerUrl: placeholderIssuer,
31
+ baseUrl: placeholderBaseUrl,
32
+ });
33
+ authorizationPath = new URL(localMetadata.authorization_endpoint).pathname;
34
+ tokenPath = new URL(localMetadata.token_endpoint).pathname;
35
+ if (localMetadata.registration_endpoint) {
36
+ registrationPath = new URL(localMetadata.registration_endpoint).pathname;
37
+ }
38
+ if (localMetadata.revocation_endpoint) {
39
+ revocationPath = new URL(localMetadata.revocation_endpoint).pathname;
40
+ }
41
+ }
42
+ // Metadata endpoints
43
+ if (isOAuthProvider(provider)) {
44
+ // Mount a per-request adapter so issuer/baseUrl reflect Host/Proto
45
+ app.use((req, res, next) => {
46
+ if (!req.path.startsWith("/.well-known/"))
47
+ return next();
48
+ const host = req.get("host") ?? "localhost";
49
+ if (req.protocol !== "https") {
50
+ console.warn("Detected http but using https for issuer URL in OAuth metadata since it will fail otherwise.");
51
+ }
52
+ const issuerUrl = new URL(`https://${host}`);
53
+ const baseUrl = new URL(basePath, issuerUrl);
54
+ const oauthMetadata = createOAuthMetadata({
55
+ provider,
56
+ issuerUrl,
57
+ baseUrl,
58
+ });
59
+ if (opts.identity) {
60
+ oauthMetadata.grant_types_supported = Array.from(new Set([
61
+ ...(oauthMetadata.grant_types_supported ?? []),
62
+ "urn:ietf:params:oauth:grant-type:jwt-bearer",
63
+ ]));
64
+ }
65
+ const resourceServerUrl = new URL("/mcp", issuerUrl);
66
+ const metadataRouter = mcpAuthMetadataRouter({
67
+ oauthMetadata,
68
+ resourceServerUrl,
69
+ });
70
+ return metadataRouter(req, res, next);
71
+ });
72
+ }
73
+ else if (opts.identity) {
74
+ // Identity-only: explicitly mount protected resource metadata endpoint
75
+ app.use("/.well-known/oauth-protected-resource", (req, res, next) => {
76
+ const host = req.get("host") ?? "localhost";
77
+ const issuerUrl = new URL(`${req.protocol}://${host}`);
78
+ const protectedResourceMetadata = {
79
+ resource: new URL("/mcp", issuerUrl).href,
80
+ authorization_servers: [issuerUrl.href],
81
+ };
82
+ return metadataHandler(protectedResourceMetadata)(req, res, next);
83
+ });
84
+ // Identity-only: also advertise minimal AS metadata for discovery per RFC 8414
85
+ app.use("/.well-known/oauth-authorization-server", (req, res, next) => {
86
+ const host = req.get("host") ?? "localhost";
87
+ const issuerUrl = new URL(`${req.protocol}://${host}`);
88
+ const oauthMetadata = {
89
+ issuer: issuerUrl.href,
90
+ token_endpoint: new URL(`${basePath}token`, issuerUrl).href,
91
+ grant_types_supported: ["urn:ietf:params:oauth:grant-type:jwt-bearer"],
92
+ };
93
+ return metadataHandler(oauthMetadata)(req, res, next);
94
+ });
95
+ }
96
+ // Mount identity (JWT bearer grant) first so OAuth token can fall through
97
+ if (opts.identity) {
98
+ const identityOptions = {
99
+ ...opts.identity,
100
+ basePath,
101
+ tokenPath: tokenPath ?? `${basePath}token`,
102
+ };
103
+ mountIdentity(app, identityOptions);
104
+ }
105
+ // Mount OAuth endpoints functionally if an OAuth provider is present
106
+ if (isOAuthProvider(provider)) {
107
+ // Authorization endpoint
108
+ const authPath = authorizationPath ?? `${basePath}authorize`;
109
+ app.use(authPath, authorizationHandler({ provider }));
110
+ // Token endpoint (OAuth); identity's token handler will handle JWT grant and call next() otherwise
111
+ const tokPath = tokenPath ?? `${basePath}token`;
112
+ app.use(tokPath, tokenHandler({ provider }));
113
+ // Dynamic client registration if supported
114
+ if (provider.clientsStore?.registerClient) {
115
+ const regPath = registrationPath ?? `${basePath}register`;
116
+ app.use(regPath, clientRegistrationHandler({ clientsStore: provider.clientsStore }));
117
+ }
118
+ // Token revocation if supported
119
+ if (provider.revokeToken) {
120
+ const revPath = revocationPath ?? `${basePath}revoke`;
121
+ app.use(revPath, revocationHandler({ provider }));
122
+ }
123
+ // Optional OAuth callback
124
+ const callbackHandler = provider.handleOAuthCallback?.bind(provider);
125
+ if (callbackHandler) {
126
+ const callbackPath = provider.callbackPath ?? "/callback";
127
+ app.get(callbackPath, async (req, res) => {
128
+ const code = typeof req.query.code === "string" ? req.query.code : undefined;
129
+ const state = typeof req.query.state === "string" ? req.query.state : undefined;
130
+ if (!code) {
131
+ res.status(400).send("Invalid request parameters");
132
+ return;
133
+ }
134
+ try {
135
+ const redirectUrl = await callbackHandler(code, state, res);
136
+ res.redirect(redirectUrl.toString());
137
+ }
138
+ catch (error) {
139
+ console.error(error);
140
+ res.status(500).send("Error during authentication callback");
141
+ }
142
+ });
143
+ }
144
+ }
145
+ // Protect MCP resource with bearer auth if a verifier/provider is present
146
+ if (provider) {
147
+ app.use("/mcp", (req, res, next) => {
148
+ return requireBearerAuth({
149
+ verifier: provider,
150
+ })(req, res, next);
151
+ });
152
+ }
153
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./stateful.js";
2
2
  export * from "./stateless.js";
3
3
  export * from "./session.js";
4
- export * from "./oauth.js";
4
+ export * from "./auth/oauth.js";
5
+ export * from "./auth/identity.js";
@@ -1,4 +1,5 @@
1
1
  export * from "./stateful.js";
2
2
  export * from "./stateless.js";
3
3
  export * from "./session.js";
4
- export * from "./oauth.js";
4
+ export * from "./auth/oauth.js";
5
+ export * from "./auth/identity.js";
@@ -4,7 +4,6 @@ import express from "express";
4
4
  import type { z } from "zod";
5
5
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
6
  import { type SessionStore } from "./session.js";
7
- import type { CallbackOAuthServerProvider } from "./oauth.js";
8
7
  /**
9
8
  * Arguments when we create a new instance of your server
10
9
  */
@@ -30,10 +29,6 @@ export interface StatefulServerOptions<T = Record<string, unknown>> {
30
29
  * Express app instance to use (optional)
31
30
  */
32
31
  app?: express.Application;
33
- /**
34
- * OAuth provider instance. If provided, OAuth routes and bearer protection are auto-wired.
35
- */
36
- oauthProvider?: CallbackOAuthServerProvider;
37
32
  }
38
33
  /**
39
34
  * Creates a stateful server for handling MCP requests.
@@ -5,7 +5,6 @@ import { randomUUID } from "node:crypto";
5
5
  import { parseAndValidateConfig } from "../shared/config.js";
6
6
  import { zodToJsonSchema } from "zod-to-json-schema";
7
7
  import { createLRUStore } from "./session.js";
8
- import { mountOAuth } from "./oauth.js";
9
8
  /**
10
9
  * Creates a stateful server for handling MCP requests.
11
10
  * For every new session, we invoke createMcpServer to create a new instance of the server.
@@ -15,11 +14,6 @@ import { mountOAuth } from "./oauth.js";
15
14
  */
16
15
  export function createStatefulServer(createMcpServer, options) {
17
16
  const app = options?.app ?? express();
18
- // Auto-wire OAuth routes and bearer protection if configured
19
- const oauthProvider = options?.oauthProvider;
20
- if (oauthProvider) {
21
- mountOAuth(app, oauthProvider);
22
- }
23
17
  app.use("/mcp", express.json());
24
18
  const sessionStore = options?.sessionStore ?? createLRUStore();
25
19
  // Handle POST requests for client-to-server communication
@@ -2,7 +2,7 @@ import express from "express";
2
2
  import type { z } from "zod";
3
3
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
4
  import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
5
- import type { CallbackOAuthServerProvider } from "./oauth.js";
5
+ import type { OAuthMountOptions } from "./auth/oauth.js";
6
6
  /**
7
7
  * Arguments when we create a stateless server instance
8
8
  */
@@ -23,10 +23,7 @@ export interface StatelessServerOptions<T = Record<string, unknown>> {
23
23
  * Express app instance to use (optional)
24
24
  */
25
25
  app?: express.Application;
26
- /**
27
- * OAuth provider instance. If provided, OAuth routes and bearer protection are auto-wired.
28
- */
29
- oauthProvider?: CallbackOAuthServerProvider;
26
+ oauth?: OAuthMountOptions;
30
27
  }
31
28
  /**
32
29
  * Creates a stateless server for handling MCP requests.
@@ -2,7 +2,6 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
2
2
  import express from "express";
3
3
  import { parseAndValidateConfig } from "../shared/config.js";
4
4
  import { zodToJsonSchema } from "zod-to-json-schema";
5
- import { mountOAuth } from "./oauth.js";
6
5
  /**
7
6
  * Creates a stateless server for handling MCP requests.
8
7
  * Each request creates a new server instance - no session state is maintained.
@@ -14,11 +13,6 @@ import { mountOAuth } from "./oauth.js";
14
13
  */
15
14
  export function createStatelessServer(createMcpServer, options) {
16
15
  const app = options?.app ?? express();
17
- // Auto-wire OAuth routes and bearer protection if configured
18
- const oauthProvider = options?.oauthProvider;
19
- if (oauthProvider) {
20
- mountOAuth(app, oauthProvider);
21
- }
22
16
  app.use("/mcp", express.json());
23
17
  // Handle POST requests for client-to-server communication
24
18
  app.post("/mcp", async (req, res) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smithery/sdk",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "SDK to develop with Smithery",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,10 +18,12 @@
18
18
  "watch": "tsc --watch",
19
19
  "check": "npx @biomejs/biome check --write --unsafe"
20
20
  },
21
+ "packageManager": "npm@11.4.1",
21
22
  "license": "MIT",
22
23
  "dependencies": {
23
24
  "@modelcontextprotocol/sdk": "^1.18.0",
24
25
  "express": "^5.1.0",
26
+ "jose": "^6.1.0",
25
27
  "json-schema": "^0.4.0",
26
28
  "lodash": "^4.17.21",
27
29
  "okay-error": "^1.0.3",
@@ -1,14 +0,0 @@
1
- import type express from "express";
2
- import type { OAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/provider.js";
3
- import type { Response } from "express";
4
- /**
5
- * OAuth server provider that supports a callback handler.
6
- * The callback handler is invoked to catch the OAuth callback from the OAuth provider.
7
- */
8
- export interface CallbackOAuthServerProvider extends OAuthServerProvider {
9
- /** Provider-specific callback handler used by the SDK */
10
- handleOAuthCallback?: (code: string, state: string | undefined, res: Response) => Promise<URL>;
11
- basePath?: string;
12
- callbackPath: string;
13
- }
14
- export declare function mountOAuth(app: express.Application, provider: CallbackOAuthServerProvider): void;
@@ -1,36 +0,0 @@
1
- import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
2
- import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
3
- export function mountOAuth(app, provider) {
4
- // Mount OAuth authorization and token routes with dynamic issuer URL and provider
5
- app.use(provider.basePath ?? "/", (req, res, next) => {
6
- const host = req.get("host") ?? "localhost";
7
- // Issuer URL must be https
8
- const issuerUrl = new URL(`https://${host}`);
9
- const router = mcpAuthRouter({ provider, issuerUrl });
10
- return router(req, res, next);
11
- });
12
- const callbackHandler = provider.handleOAuthCallback;
13
- if (callbackHandler) {
14
- // Callback handler
15
- app.get(provider.callbackPath, async (req, res) => {
16
- const code = typeof req.query.code === "string" ? req.query.code : undefined;
17
- const state = typeof req.query.state === "string" ? req.query.state : undefined;
18
- if (!code) {
19
- res.status(400).send("Invalid request parameters");
20
- return;
21
- }
22
- try {
23
- const redirectUrl = await callbackHandler.bind(provider)(code, state, res);
24
- res.redirect(redirectUrl.toString());
25
- }
26
- catch (error) {
27
- console.error(error);
28
- res.status(500).send("Error during authentication callback");
29
- }
30
- });
31
- }
32
- // Bearer protection for all /mcp routes (POST/GET/DELETE)
33
- app.use("/mcp", (req, res, next) => {
34
- return requireBearerAuth({ verifier: provider })(req, res, next);
35
- });
36
- }