@mcphero/vercel 1.1.6 → 1.3.0

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @mcphero/vercel@1.1.6 build /Users/atomic/projects/ai/mcphero/packages/vercel
2
+ > @mcphero/vercel@1.2.0 build /Users/atomic/projects/ai/mcphero/packages/vercel
3
3
  > tsup
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -9,9 +9,9 @@ CLI Using tsup config: /Users/atomic/projects/ai/mcphero/packages/vercel/tsup.co
9
9
  CLI Target: es2022
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
- ESM build/index.js 2.92 KB
13
- ESM build/index.js.map 5.42 KB
14
- ESM ⚡️ Build success in 5ms
12
+ ESM build/index.js 6.62 KB
13
+ ESM build/index.js.map 12.29 KB
14
+ ESM ⚡️ Build success in 9ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 569ms
17
- DTS build/index.d.ts 526.00 B
16
+ DTS ⚡️ Build success in 666ms
17
+ DTS build/index.d.ts 612.00 B
@@ -1,14 +1,12 @@
1
1
 
2
- > @mcphero/vercel@1.1.6 check /Users/atomic/projects/ai/mcphero/packages/vercel
2
+ > @mcphero/vercel@1.2.0 check /Users/atomic/projects/ai/mcphero/packages/vercel
3
3
  > pnpm lint && pnpm typecheck
4
4
 
5
5
 
6
- > @mcphero/vercel@1.1.6 lint /Users/atomic/projects/ai/mcphero/packages/vercel
6
+ > @mcphero/vercel@1.2.0 lint /Users/atomic/projects/ai/mcphero/packages/vercel
7
7
  > eslint
8
8
 
9
9
 
10
- > @mcphero/vercel@1.1.6 typecheck /Users/atomic/projects/ai/mcphero/packages/vercel
10
+ > @mcphero/vercel@1.2.0 typecheck /Users/atomic/projects/ai/mcphero/packages/vercel
11
11
  > tsc --noEmit
12
12
 
13
-  ELIFECYCLE  Command failed.
14
-  ELIFECYCLE  Command failed.
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > @mcphero/vercel@1.2.0 lint /Users/atomic/projects/ai/mcphero/packages/vercel
4
+ > eslint
5
+
@@ -1,14 +1,14 @@
1
1
 
2
2
  
3
- > @mcphero/vercel@1.1.6 prepack /Users/atomic/projects/ai/mcphero/packages/vercel
3
+ > @mcphero/vercel@1.2.0 prepack /Users/atomic/projects/ai/mcphero/packages/vercel
4
4
  > pnpm clean && pnpm build
5
5
 
6
6
 
7
- > @mcphero/vercel@1.1.6 clean /Users/atomic/projects/ai/mcphero/packages/vercel
7
+ > @mcphero/vercel@1.2.0 clean /Users/atomic/projects/ai/mcphero/packages/vercel
8
8
  > rimraf build
9
9
 
10
10
 
11
- > @mcphero/vercel@1.1.6 build /Users/atomic/projects/ai/mcphero/packages/vercel
11
+ > @mcphero/vercel@1.2.0 build /Users/atomic/projects/ai/mcphero/packages/vercel
12
12
  > tsup
13
13
 
14
14
  CLI Building entry: src/index.ts
@@ -18,9 +18,9 @@
18
18
  CLI Target: es2022
19
19
  CLI Cleaning output folder
20
20
  ESM Build start
21
- ESM build/index.js 2.92 KB
22
- ESM build/index.js.map 5.42 KB
23
- ESM ⚡️ Build success in 9ms
21
+ ESM build/index.js 6.46 KB
22
+ ESM build/index.js.map 11.48 KB
23
+ ESM ⚡️ Build success in 12ms
24
24
  DTS Build start
25
- DTS ⚡️ Build success in 773ms
26
- DTS build/index.d.ts 526.00 B
25
+ DTS ⚡️ Build success in 868ms
26
+ DTS build/index.d.ts 593.00 B
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # @mcphero/vercel
2
+
3
+ Vercel serverless adapter for [MCPHero](https://github.com/atomicbi/mcphero) — deploy your actions as a stateless MCP server on Vercel.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @mcphero/core @mcphero/vercel
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Vanilla Vercel Functions
14
+
15
+ ```typescript
16
+ // api/mcp.ts
17
+ import { mcphero } from '@mcphero/core'
18
+ import { vercel } from '@mcphero/vercel'
19
+ import { MyAction } from '../actions/my-action.js'
20
+
21
+ const { adapter, handler } = vercel()
22
+
23
+ await mcphero({ name: 'my-tools', description: 'My MCP Server', version: '1.0.0' })
24
+ .adapter(adapter)
25
+ .action(MyAction)
26
+ .start()
27
+
28
+ export default { fetch: handler }
29
+ ```
30
+
31
+ ### Next.js App Router
32
+
33
+ ```typescript
34
+ // app/api/mcp/route.ts
35
+ import { mcphero } from '@mcphero/core'
36
+ import { vercel } from '@mcphero/vercel'
37
+ import { MyAction } from '../../../actions/my-action.js'
38
+
39
+ const { adapter, GET, POST, DELETE } = vercel()
40
+
41
+ await mcphero({ name: 'my-tools', description: 'My MCP Server', version: '1.0.0' })
42
+ .adapter(adapter)
43
+ .action(MyAction)
44
+ .start()
45
+
46
+ export { GET, POST, DELETE }
47
+ ```
48
+
49
+ ## How It Works
50
+
51
+ - Uses `WebStandardStreamableHTTPServerTransport` from the MCP SDK in **stateless mode** (`sessionIdGenerator: undefined`)
52
+ - Each request creates a fresh `McpServer` + transport, registers tools, handles the request, and returns a Web Standard `Response`
53
+ - Actions are registered as MCP tools using `PascalCase` names (same as the stdio and http adapters)
54
+ - Logging goes to `process.stderr` (Vercel log drain) and MCP client notifications
55
+
56
+ ## Options
57
+
58
+ ```typescript
59
+ const { adapter, handler } = vercel({
60
+ enableJsonResponse: true // Return JSON instead of SSE streams
61
+ })
62
+ ```
63
+
64
+ | Option | Type | Default | Description |
65
+ |--------|------|---------|-------------|
66
+ | `enableJsonResponse` | `boolean` | `false` | Return JSON responses instead of SSE streams |
67
+
68
+ ## Compound Return Pattern
69
+
70
+ Unlike other MCPHero adapters that are simple `AdapterFactory` functions, `vercel()` returns a compound object:
71
+
72
+ ```typescript
73
+ interface VercelAdapter {
74
+ adapter: AdapterGenerator // Pass to mcphero().adapter()
75
+ handler: (request: Request) => Promise<Response> // The request handler
76
+ GET: (request: Request) => Promise<Response> // Alias for handler
77
+ POST: (request: Request) => Promise<Response> // Alias for handler
78
+ DELETE: (request: Request) => Promise<Response> // Alias for handler
79
+ }
80
+ ```
81
+
82
+ This is because Vercel functions export request handlers rather than starting long-lived servers. The `adapter` property integrates with the MCPHero builder, while `handler`/`GET`/`POST`/`DELETE` are exported from your route file.
83
+
84
+ ## Deployment
85
+
86
+ ### Vercel Configuration
87
+
88
+ ```json
89
+ {
90
+ "framework": null,
91
+ "functions": {
92
+ "api/mcp.ts": {
93
+ "memory": 1024,
94
+ "maxDuration": 60
95
+ }
96
+ },
97
+ "rewrites": [
98
+ { "source": "/mcp", "destination": "/api/mcp" }
99
+ ]
100
+ }
101
+ ```
102
+
103
+ ### CORS
104
+
105
+ CORS is not handled by the adapter. Use Next.js middleware or Vercel headers configuration:
106
+
107
+ ```json
108
+ {
109
+ "headers": [
110
+ {
111
+ "source": "/api/mcp",
112
+ "headers": [
113
+ { "key": "Access-Control-Allow-Origin", "value": "*" },
114
+ { "key": "Access-Control-Allow-Methods", "value": "GET, POST, DELETE, OPTIONS" },
115
+ { "key": "Access-Control-Allow-Headers", "value": "Content-Type, Mcp-Session-Id" }
116
+ ]
117
+ }
118
+ ]
119
+ }
120
+ ```
121
+
122
+ ## See Also
123
+
124
+ - [MCPHero README](https://github.com/atomicbi/mcphero) — Full documentation
125
+ - [`@mcphero/core`](https://www.npmjs.com/package/@mcphero/core) — Core library
126
+ - [`@mcphero/mcp`](https://www.npmjs.com/package/@mcphero/mcp) — MCP stdio and HTTP adapters (stateful)
package/build/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
+ import { AuthConfig } from '@mcphero/auth';
1
2
  import { AdapterGenerator } from '@mcphero/core';
2
3
 
3
4
  interface VercelAdapterOptions {
4
5
  enableJsonResponse?: boolean;
6
+ auth?: AuthConfig;
7
+ path?: string;
5
8
  }
6
9
  interface VercelAdapter {
7
10
  adapter: AdapterGenerator;
package/build/index.js CHANGED
@@ -1,10 +1,19 @@
1
1
  // src/adapter/vercel.ts
2
+ import { generateProtectedResourceMetadata, validateToken } from "@mcphero/auth";
2
3
  import { toolResponse } from "@mcphero/core";
3
4
  import { createLogger } from "@mcphero/logger";
4
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
6
7
  import { capitalCase, pascalCase } from "change-case";
8
+ var CORS_HEADERS = {
9
+ "Access-Control-Allow-Origin": "*",
10
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
11
+ "Access-Control-Allow-Headers": "*",
12
+ "Access-Control-Max-Age": "86400"
13
+ };
7
14
  function vercel(options = {}) {
15
+ const mcpPath = options.path ?? "/mcp";
16
+ const callbackPath = options.auth?.callbackPath ?? "/auth/callback";
8
17
  let _actions = [];
9
18
  let _options = null;
10
19
  let _context = null;
@@ -12,8 +21,61 @@ function vercel(options = {}) {
12
21
  const _ready = new Promise((resolve) => {
13
22
  _readyResolve = resolve;
14
23
  });
24
+ const validPaths = /* @__PURE__ */ new Set([mcpPath]);
25
+ if (options.auth?.authorizationServers?.length && options.auth.resourceUrl) {
26
+ validPaths.add("/.well-known/oauth-protected-resource");
27
+ }
28
+ if (options.auth?.provider) {
29
+ validPaths.add("/.well-known/oauth-authorization-server");
30
+ validPaths.add("/authorize");
31
+ validPaths.add(callbackPath);
32
+ validPaths.add("/token");
33
+ validPaths.add("/register");
34
+ }
35
+ const oauthPaths = options.auth?.provider ? {
36
+ "/.well-known/oauth-authorization-server": ["GET"],
37
+ "/authorize": ["GET"],
38
+ [callbackPath]: ["GET"],
39
+ "/token": ["POST"],
40
+ "/register": ["POST"]
41
+ } : null;
15
42
  const handleRequest = async (request) => {
43
+ const url = new URL(request.url);
44
+ if (!validPaths.has(url.pathname)) {
45
+ return new Response("Not Found", { status: 404 });
46
+ }
47
+ if (request.method === "OPTIONS") {
48
+ return new Response(null, { status: 200, headers: CORS_HEADERS });
49
+ }
50
+ if (url.pathname === "/.well-known/oauth-protected-resource") {
51
+ const metadata = generateProtectedResourceMetadata(options.auth.resourceUrl, options.auth.authorizationServers);
52
+ return new Response(JSON.stringify(metadata), {
53
+ headers: { ...CORS_HEADERS, "Content-Type": "application/json", "Cache-Control": "max-age=3600" }
54
+ });
55
+ }
56
+ if (oauthPaths) {
57
+ const methods = oauthPaths[url.pathname];
58
+ if (methods) {
59
+ if (!methods.includes(request.method)) {
60
+ return new Response("Method not allowed", { status: 405 });
61
+ }
62
+ return handleOAuth(url, request, options.auth.provider);
63
+ }
64
+ }
16
65
  await _ready;
66
+ let requestContext = _context;
67
+ if (options.auth) {
68
+ const result = await validateToken(request.headers.get("authorization"), options.auth);
69
+ if (result.error) {
70
+ return new Response(JSON.stringify(result.error.body), {
71
+ status: result.error.statusCode,
72
+ headers: result.error.headers
73
+ });
74
+ }
75
+ if (result.auth) {
76
+ requestContext = _context.fork({ auth: result.auth });
77
+ }
78
+ }
17
79
  const transport = new WebStandardStreamableHTTPServerTransport({
18
80
  sessionIdGenerator: void 0,
19
81
  enableJsonResponse: options.enableJsonResponse
@@ -51,7 +113,7 @@ function vercel(options = {}) {
51
113
  });
52
114
  }
53
115
  });
54
- return action.run(input, _context.fork({ logger, extra })).then((result) => {
116
+ return action.run(input, requestContext.fork({ logger, extra })).then((result) => {
55
117
  return toolResponse(result);
56
118
  }).catch((error) => {
57
119
  if (error instanceof Error) {
@@ -65,6 +127,41 @@ function vercel(options = {}) {
65
127
  await server.connect(transport);
66
128
  return transport.handleRequest(request);
67
129
  };
130
+ async function handleOAuth(url, request, provider) {
131
+ const toOAuthReq = async () => {
132
+ let body;
133
+ if (request.method === "POST") {
134
+ const contentType = request.headers.get("content-type") ?? "";
135
+ const text = await request.text();
136
+ if (contentType.includes("json")) {
137
+ body = JSON.parse(text);
138
+ } else {
139
+ body = Object.fromEntries(new URLSearchParams(text));
140
+ }
141
+ }
142
+ return {
143
+ method: request.method,
144
+ url,
145
+ headers: Object.fromEntries(request.headers.entries()),
146
+ body
147
+ };
148
+ };
149
+ const oauthReq = await toOAuthReq();
150
+ let oauthRes;
151
+ if (url.pathname === "/.well-known/oauth-authorization-server") {
152
+ oauthRes = provider.metadata();
153
+ } else if (url.pathname === "/authorize") {
154
+ oauthRes = await provider.authorize(oauthReq);
155
+ } else if (url.pathname === callbackPath) {
156
+ oauthRes = await provider.callback(oauthReq);
157
+ } else if (url.pathname === "/token") {
158
+ oauthRes = await provider.token(oauthReq);
159
+ } else {
160
+ oauthRes = await provider.register(oauthReq);
161
+ }
162
+ const responseBody = oauthRes.body ? typeof oauthRes.body === "string" ? oauthRes.body : JSON.stringify(oauthRes.body) : null;
163
+ return new Response(responseBody, { status: oauthRes.status, headers: oauthRes.headers });
164
+ }
68
165
  const adapter = (mcpHeroOptions, baseContext) => {
69
166
  _options = mcpHeroOptions;
70
167
  _context = baseContext.fork({ adapter: "vercel" });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapter/vercel.ts"],"sourcesContent":["import { Action, AdapterGenerator, MCPHeroContext, MCPHeroOptions, toolResponse } from '@mcphero/core'\nimport { createLogger } from '@mcphero/logger'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'\nimport { capitalCase, pascalCase } from 'change-case'\n\nexport interface VercelAdapterOptions {\n enableJsonResponse?: boolean\n}\n\nexport interface VercelAdapter {\n adapter: AdapterGenerator\n handler: (request: Request) => Promise<Response>\n GET: (request: Request) => Promise<Response>\n POST: (request: Request) => Promise<Response>\n DELETE: (request: Request) => Promise<Response>\n}\n\nexport function vercel(options: VercelAdapterOptions = {}): VercelAdapter {\n let _actions: Action[] = []\n let _options: MCPHeroOptions | null = null\n let _context: MCPHeroContext | null = null\n let _readyResolve: () => void\n const _ready = new Promise<void>((resolve) => {\n _readyResolve = resolve\n })\n\n const handleRequest = async (request: Request): Promise<Response> => {\n await _ready\n\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: options.enableJsonResponse\n })\n\n const server = new McpServer({\n name: _options!.name,\n description: _options!.description,\n version: _options!.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n\n for (const action of _actions) {\n server.registerTool(pascalCase(action.name), {\n title: capitalCase(action.name),\n description: action.description,\n inputSchema: action.input\n }, async (input, extra) => {\n const logger = createLogger({\n stream: process.stderr,\n onLog: (level, data) => {\n extra.sendNotification({ method: 'notifications/message', params: { level, data } })\n },\n onProgress: ({ progress, total, message }) => {\n if (!extra._meta?.progressToken) { return }\n extra.sendNotification({\n method: 'notifications/progress',\n params: {\n progress,\n total,\n message,\n progressToken: extra._meta.progressToken\n }\n })\n }\n })\n return action.run(input, _context!.fork({ logger, extra })).then((result) => {\n return toolResponse(result)\n }).catch((error) => {\n if (error instanceof Error) {\n return toolResponse({ success: false, name: error.name, message: error.message, stack: error.stack })\n } else {\n return toolResponse({ success: false, name: 'Unknown Error', message: 'An unknown error occured', error })\n }\n })\n })\n }\n\n await server.connect(transport)\n return transport.handleRequest(request)\n }\n\n const adapter: AdapterGenerator = (mcpHeroOptions: MCPHeroOptions, baseContext: MCPHeroContext) => {\n _options = mcpHeroOptions\n _context = baseContext.fork({ adapter: 'vercel' })\n return {\n context: _context,\n start: async (actions) => {\n _actions = actions\n _readyResolve()\n },\n stop: async () => {\n _actions = []\n }\n }\n }\n\n return {\n adapter,\n handler: handleRequest,\n GET: handleRequest,\n POST: handleRequest,\n DELETE: handleRequest\n }\n}\n"],"mappings":";AAAA,SAAmE,oBAAoB;AACvF,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,SAAS,gDAAgD;AACzD,SAAS,aAAa,kBAAkB;AAcjC,SAAS,OAAO,UAAgC,CAAC,GAAkB;AACxE,MAAI,WAAqB,CAAC;AAC1B,MAAI,WAAkC;AACtC,MAAI,WAAkC;AACtC,MAAI;AACJ,QAAM,SAAS,IAAI,QAAc,CAAC,YAAY;AAC5C,oBAAgB;AAAA,EAClB,CAAC;AAED,QAAM,gBAAgB,OAAO,YAAwC;AACnE,UAAM;AAEN,UAAM,YAAY,IAAI,yCAAyC;AAAA,MAC7D,oBAAoB;AAAA,MACpB,oBAAoB,QAAQ;AAAA,IAC9B,CAAC;AAED,UAAM,SAAS,IAAI,UAAU;AAAA,MAC3B,MAAM,SAAU;AAAA,MAChB,aAAa,SAAU;AAAA,MACvB,SAAS,SAAU;AAAA,IACrB,GAAG;AAAA,MACD,cAAc,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE;AAAA,IACzC,CAAC;AAED,eAAW,UAAU,UAAU;AAC7B,aAAO,aAAa,WAAW,OAAO,IAAI,GAAG;AAAA,QAC3C,OAAO,YAAY,OAAO,IAAI;AAAA,QAC9B,aAAa,OAAO;AAAA,QACpB,aAAa,OAAO;AAAA,MACtB,GAAG,OAAO,OAAO,UAAU;AACzB,cAAM,SAAS,aAAa;AAAA,UAC1B,QAAQ,QAAQ;AAAA,UAChB,OAAO,CAAC,OAAO,SAAS;AACtB,kBAAM,iBAAiB,EAAE,QAAQ,yBAAyB,QAAQ,EAAE,OAAO,KAAK,EAAE,CAAC;AAAA,UACrF;AAAA,UACA,YAAY,CAAC,EAAE,UAAU,OAAO,QAAQ,MAAM;AAC5C,gBAAI,CAAC,MAAM,OAAO,eAAe;AAAE;AAAA,YAAO;AAC1C,kBAAM,iBAAiB;AAAA,cACrB,QAAQ;AAAA,cACR,QAAQ;AAAA,gBACN;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,eAAe,MAAM,MAAM;AAAA,cAC7B;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF,CAAC;AACD,eAAO,OAAO,IAAI,OAAO,SAAU,KAAK,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW;AAC3E,iBAAO,aAAa,MAAM;AAAA,QAC5B,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,cAAI,iBAAiB,OAAO;AAC1B,mBAAO,aAAa,EAAE,SAAS,OAAO,MAAM,MAAM,MAAM,SAAS,MAAM,SAAS,OAAO,MAAM,MAAM,CAAC;AAAA,UACtG,OAAO;AACL,mBAAO,aAAa,EAAE,SAAS,OAAO,MAAM,iBAAiB,SAAS,4BAA4B,MAAM,CAAC;AAAA,UAC3G;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,QAAQ,SAAS;AAC9B,WAAO,UAAU,cAAc,OAAO;AAAA,EACxC;AAEA,QAAM,UAA4B,CAAC,gBAAgC,gBAAgC;AACjG,eAAW;AACX,eAAW,YAAY,KAAK,EAAE,SAAS,SAAS,CAAC;AACjD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,OAAO,YAAY;AACxB,mBAAW;AACX,sBAAc;AAAA,MAChB;AAAA,MACA,MAAM,YAAY;AAChB,mBAAW,CAAC;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,IACT,KAAK;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,EACV;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/adapter/vercel.ts"],"sourcesContent":["import { AuthConfig, generateProtectedResourceMetadata, OAuthRequest, validateToken } from '@mcphero/auth'\nimport { Action, AdapterGenerator, MCPHeroContext, MCPHeroOptions, toolResponse } from '@mcphero/core'\nimport { createLogger } from '@mcphero/logger'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js'\nimport { capitalCase, pascalCase } from 'change-case'\n\nexport interface VercelAdapterOptions {\n enableJsonResponse?: boolean\n auth?: AuthConfig\n path?: string\n}\n\nexport interface VercelAdapter {\n adapter: AdapterGenerator\n handler: (request: Request) => Promise<Response>\n GET: (request: Request) => Promise<Response>\n POST: (request: Request) => Promise<Response>\n DELETE: (request: Request) => Promise<Response>\n}\n\nconst CORS_HEADERS: Record<string, string> = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': '*',\n 'Access-Control-Max-Age': '86400'\n}\n\nexport function vercel(options: VercelAdapterOptions = {}): VercelAdapter {\n const mcpPath = options.path ?? '/mcp'\n const callbackPath = options.auth?.callbackPath ?? '/auth/callback'\n\n let _actions: Action[] = []\n let _options: MCPHeroOptions | null = null\n let _context: MCPHeroContext | null = null\n let _readyResolve: () => void\n const _ready = new Promise<void>((resolve) => {\n _readyResolve = resolve\n })\n\n // Pre-compute valid paths for fast rejection of bogus requests\n const validPaths = new Set<string>([mcpPath])\n if (options.auth?.authorizationServers?.length && options.auth.resourceUrl) {\n validPaths.add('/.well-known/oauth-protected-resource')\n }\n if (options.auth?.provider) {\n validPaths.add('/.well-known/oauth-authorization-server')\n validPaths.add('/authorize')\n validPaths.add(callbackPath)\n validPaths.add('/token')\n validPaths.add('/register')\n }\n\n // OAuth path → allowed methods (built once, not per-request)\n const oauthPaths: Record<string, string[]> | null = options.auth?.provider\n ? {\n '/.well-known/oauth-authorization-server': ['GET'],\n '/authorize': ['GET'],\n [callbackPath]: ['GET'],\n '/token': ['POST'],\n '/register': ['POST']\n }\n : null\n\n const handleRequest = async (request: Request): Promise<Response> => {\n const url = new URL(request.url)\n\n // Fast rejection — no async work, no allocations for unknown paths\n if (!validPaths.has(url.pathname)) {\n return new Response('Not Found', { status: 404 })\n }\n\n // CORS preflight\n if (request.method === 'OPTIONS') {\n return new Response(null, { status: 200, headers: CORS_HEADERS })\n }\n\n // Protected resource metadata (RFC 9728)\n if (url.pathname === '/.well-known/oauth-protected-resource') {\n const metadata = generateProtectedResourceMetadata(options.auth!.resourceUrl!, options.auth!.authorizationServers!)\n return new Response(JSON.stringify(metadata), {\n headers: { ...CORS_HEADERS, 'Content-Type': 'application/json', 'Cache-Control': 'max-age=3600' }\n })\n }\n\n // OAuth provider routes\n if (oauthPaths) {\n const methods = oauthPaths[url.pathname]\n if (methods) {\n if (!methods.includes(request.method)) {\n return new Response('Method not allowed', { status: 405 })\n }\n return handleOAuth(url, request, options.auth!.provider!)\n }\n }\n\n // MCP transport — wait for mcphero().start() to complete\n await _ready\n\n let requestContext = _context!\n if (options.auth) {\n const result = await validateToken(request.headers.get('authorization'), options.auth)\n if (result.error) {\n return new Response(JSON.stringify(result.error.body), {\n status: result.error.statusCode,\n headers: result.error.headers\n })\n }\n if (result.auth) {\n requestContext = _context!.fork({ auth: result.auth })\n }\n }\n\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: options.enableJsonResponse\n })\n\n const server = new McpServer({\n name: _options!.name,\n description: _options!.description,\n version: _options!.version\n }, {\n capabilities: { tools: {}, logging: {} }\n })\n\n for (const action of _actions) {\n server.registerTool(pascalCase(action.name), {\n title: capitalCase(action.name),\n description: action.description,\n inputSchema: action.input\n }, async (input, extra) => {\n const logger = createLogger({\n stream: process.stderr,\n onLog: (level, data) => {\n extra.sendNotification({ method: 'notifications/message', params: { level, data } })\n },\n onProgress: ({ progress, total, message }) => {\n if (!extra._meta?.progressToken) { return }\n extra.sendNotification({\n method: 'notifications/progress',\n params: {\n progress,\n total,\n message,\n progressToken: extra._meta.progressToken\n }\n })\n }\n })\n return action.run(input, requestContext.fork({ logger, extra })).then((result) => {\n return toolResponse(result)\n }).catch((error) => {\n if (error instanceof Error) {\n return toolResponse({ success: false, name: error.name, message: error.message, stack: error.stack })\n } else {\n return toolResponse({ success: false, name: 'Unknown Error', message: 'An unknown error occured', error })\n }\n })\n })\n }\n\n await server.connect(transport)\n return transport.handleRequest(request)\n }\n\n async function handleOAuth(url: URL, request: Request, provider: NonNullable<AuthConfig['provider']>): Promise<Response> {\n const toOAuthReq = async (): Promise<OAuthRequest> => {\n let body: Record<string, string> | undefined\n if (request.method === 'POST') {\n const contentType = request.headers.get('content-type') ?? ''\n const text = await request.text()\n if (contentType.includes('json')) {\n body = JSON.parse(text)\n } else {\n body = Object.fromEntries(new URLSearchParams(text))\n }\n }\n return {\n method: request.method,\n url,\n headers: Object.fromEntries(request.headers.entries()),\n body\n }\n }\n\n const oauthReq = await toOAuthReq()\n let oauthRes\n if (url.pathname === '/.well-known/oauth-authorization-server') {\n oauthRes = provider.metadata()\n } else if (url.pathname === '/authorize') {\n oauthRes = await provider.authorize(oauthReq)\n } else if (url.pathname === callbackPath) {\n oauthRes = await provider.callback(oauthReq)\n } else if (url.pathname === '/token') {\n oauthRes = await provider.token(oauthReq)\n } else {\n oauthRes = await provider.register(oauthReq)\n }\n\n const responseBody = oauthRes.body ? (typeof oauthRes.body === 'string' ? oauthRes.body : JSON.stringify(oauthRes.body)) : null\n return new Response(responseBody, { status: oauthRes.status, headers: oauthRes.headers })\n }\n\n const adapter: AdapterGenerator = (mcpHeroOptions: MCPHeroOptions, baseContext: MCPHeroContext) => {\n _options = mcpHeroOptions\n _context = baseContext.fork({ adapter: 'vercel' })\n return {\n context: _context,\n start: async (actions) => {\n _actions = actions\n _readyResolve()\n },\n stop: async () => {\n _actions = []\n }\n }\n }\n\n return {\n adapter,\n handler: handleRequest,\n GET: handleRequest,\n POST: handleRequest,\n DELETE: handleRequest\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcphero/vercel",
3
- "version": "1.1.6",
3
+ "version": "1.3.0",
4
4
  "description": "MCP Hero Vercel Serverless Adapter",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,8 +14,9 @@
14
14
  "@modelcontextprotocol/sdk": "^1.29.0",
15
15
  "change-case": "^5.4.4",
16
16
  "zod": "^4.3.6",
17
- "@mcphero/core": "1.1.6",
18
- "@mcphero/logger": "1.1.6"
17
+ "@mcphero/auth": "1.3.0",
18
+ "@mcphero/logger": "1.3.0",
19
+ "@mcphero/core": "1.3.0"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@eslint/js": "^10.0.1",
@@ -1,3 +1,4 @@
1
+ import { AuthConfig, generateProtectedResourceMetadata, OAuthRequest, validateToken } from '@mcphero/auth'
1
2
  import { Action, AdapterGenerator, MCPHeroContext, MCPHeroOptions, toolResponse } from '@mcphero/core'
2
3
  import { createLogger } from '@mcphero/logger'
3
4
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
@@ -6,6 +7,8 @@ import { capitalCase, pascalCase } from 'change-case'
6
7
 
7
8
  export interface VercelAdapterOptions {
8
9
  enableJsonResponse?: boolean
10
+ auth?: AuthConfig
11
+ path?: string
9
12
  }
10
13
 
11
14
  export interface VercelAdapter {
@@ -16,7 +19,17 @@ export interface VercelAdapter {
16
19
  DELETE: (request: Request) => Promise<Response>
17
20
  }
18
21
 
22
+ const CORS_HEADERS: Record<string, string> = {
23
+ 'Access-Control-Allow-Origin': '*',
24
+ 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
25
+ 'Access-Control-Allow-Headers': '*',
26
+ 'Access-Control-Max-Age': '86400'
27
+ }
28
+
19
29
  export function vercel(options: VercelAdapterOptions = {}): VercelAdapter {
30
+ const mcpPath = options.path ?? '/mcp'
31
+ const callbackPath = options.auth?.callbackPath ?? '/auth/callback'
32
+
20
33
  let _actions: Action[] = []
21
34
  let _options: MCPHeroOptions | null = null
22
35
  let _context: MCPHeroContext | null = null
@@ -25,9 +38,79 @@ export function vercel(options: VercelAdapterOptions = {}): VercelAdapter {
25
38
  _readyResolve = resolve
26
39
  })
27
40
 
41
+ // Pre-compute valid paths for fast rejection of bogus requests
42
+ const validPaths = new Set<string>([mcpPath])
43
+ if (options.auth?.authorizationServers?.length && options.auth.resourceUrl) {
44
+ validPaths.add('/.well-known/oauth-protected-resource')
45
+ }
46
+ if (options.auth?.provider) {
47
+ validPaths.add('/.well-known/oauth-authorization-server')
48
+ validPaths.add('/authorize')
49
+ validPaths.add(callbackPath)
50
+ validPaths.add('/token')
51
+ validPaths.add('/register')
52
+ }
53
+
54
+ // OAuth path → allowed methods (built once, not per-request)
55
+ const oauthPaths: Record<string, string[]> | null = options.auth?.provider
56
+ ? {
57
+ '/.well-known/oauth-authorization-server': ['GET'],
58
+ '/authorize': ['GET'],
59
+ [callbackPath]: ['GET'],
60
+ '/token': ['POST'],
61
+ '/register': ['POST']
62
+ }
63
+ : null
64
+
28
65
  const handleRequest = async (request: Request): Promise<Response> => {
66
+ const url = new URL(request.url)
67
+
68
+ // Fast rejection — no async work, no allocations for unknown paths
69
+ if (!validPaths.has(url.pathname)) {
70
+ return new Response('Not Found', { status: 404 })
71
+ }
72
+
73
+ // CORS preflight
74
+ if (request.method === 'OPTIONS') {
75
+ return new Response(null, { status: 200, headers: CORS_HEADERS })
76
+ }
77
+
78
+ // Protected resource metadata (RFC 9728)
79
+ if (url.pathname === '/.well-known/oauth-protected-resource') {
80
+ const metadata = generateProtectedResourceMetadata(options.auth!.resourceUrl!, options.auth!.authorizationServers!)
81
+ return new Response(JSON.stringify(metadata), {
82
+ headers: { ...CORS_HEADERS, 'Content-Type': 'application/json', 'Cache-Control': 'max-age=3600' }
83
+ })
84
+ }
85
+
86
+ // OAuth provider routes
87
+ if (oauthPaths) {
88
+ const methods = oauthPaths[url.pathname]
89
+ if (methods) {
90
+ if (!methods.includes(request.method)) {
91
+ return new Response('Method not allowed', { status: 405 })
92
+ }
93
+ return handleOAuth(url, request, options.auth!.provider!)
94
+ }
95
+ }
96
+
97
+ // MCP transport — wait for mcphero().start() to complete
29
98
  await _ready
30
99
 
100
+ let requestContext = _context!
101
+ if (options.auth) {
102
+ const result = await validateToken(request.headers.get('authorization'), options.auth)
103
+ if (result.error) {
104
+ return new Response(JSON.stringify(result.error.body), {
105
+ status: result.error.statusCode,
106
+ headers: result.error.headers
107
+ })
108
+ }
109
+ if (result.auth) {
110
+ requestContext = _context!.fork({ auth: result.auth })
111
+ }
112
+ }
113
+
31
114
  const transport = new WebStandardStreamableHTTPServerTransport({
32
115
  sessionIdGenerator: undefined,
33
116
  enableJsonResponse: options.enableJsonResponse
@@ -65,7 +148,7 @@ export function vercel(options: VercelAdapterOptions = {}): VercelAdapter {
65
148
  })
66
149
  }
67
150
  })
68
- return action.run(input, _context!.fork({ logger, extra })).then((result) => {
151
+ return action.run(input, requestContext.fork({ logger, extra })).then((result) => {
69
152
  return toolResponse(result)
70
153
  }).catch((error) => {
71
154
  if (error instanceof Error) {
@@ -81,6 +164,44 @@ export function vercel(options: VercelAdapterOptions = {}): VercelAdapter {
81
164
  return transport.handleRequest(request)
82
165
  }
83
166
 
167
+ async function handleOAuth(url: URL, request: Request, provider: NonNullable<AuthConfig['provider']>): Promise<Response> {
168
+ const toOAuthReq = async (): Promise<OAuthRequest> => {
169
+ let body: Record<string, string> | undefined
170
+ if (request.method === 'POST') {
171
+ const contentType = request.headers.get('content-type') ?? ''
172
+ const text = await request.text()
173
+ if (contentType.includes('json')) {
174
+ body = JSON.parse(text)
175
+ } else {
176
+ body = Object.fromEntries(new URLSearchParams(text))
177
+ }
178
+ }
179
+ return {
180
+ method: request.method,
181
+ url,
182
+ headers: Object.fromEntries(request.headers.entries()),
183
+ body
184
+ }
185
+ }
186
+
187
+ const oauthReq = await toOAuthReq()
188
+ let oauthRes
189
+ if (url.pathname === '/.well-known/oauth-authorization-server') {
190
+ oauthRes = provider.metadata()
191
+ } else if (url.pathname === '/authorize') {
192
+ oauthRes = await provider.authorize(oauthReq)
193
+ } else if (url.pathname === callbackPath) {
194
+ oauthRes = await provider.callback(oauthReq)
195
+ } else if (url.pathname === '/token') {
196
+ oauthRes = await provider.token(oauthReq)
197
+ } else {
198
+ oauthRes = await provider.register(oauthReq)
199
+ }
200
+
201
+ const responseBody = oauthRes.body ? (typeof oauthRes.body === 'string' ? oauthRes.body : JSON.stringify(oauthRes.body)) : null
202
+ return new Response(responseBody, { status: oauthRes.status, headers: oauthRes.headers })
203
+ }
204
+
84
205
  const adapter: AdapterGenerator = (mcpHeroOptions: MCPHeroOptions, baseContext: MCPHeroContext) => {
85
206
  _options = mcpHeroOptions
86
207
  _context = baseContext.fork({ adapter: 'vercel' })