@mcphero/vercel 1.1.6 → 1.2.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.
- package/.turbo/turbo-build.log +6 -6
- package/.turbo/turbo-check.log +3 -5
- package/.turbo/turbo-prepack.log +7 -7
- package/README.md +126 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +86 -1
- package/build/index.js.map +1 -1
- package/package.json +4 -3
- package/src/adapter/vercel.ts +92 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @mcphero/vercel@1.1.
|
|
2
|
+
> @mcphero/vercel@1.1.7 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
|
|
13
|
-
ESM build/index.js.map
|
|
14
|
-
ESM ⚡️ Build success in
|
|
12
|
+
ESM build/index.js 6.46 KB
|
|
13
|
+
ESM build/index.js.map 11.48 KB
|
|
14
|
+
ESM ⚡️ Build success in 7ms
|
|
15
15
|
DTS Build start
|
|
16
|
-
DTS ⚡️ Build success in
|
|
17
|
-
DTS build/index.d.ts
|
|
16
|
+
DTS ⚡️ Build success in 696ms
|
|
17
|
+
DTS build/index.d.ts 593.00 B
|
package/.turbo/turbo-check.log
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
|
|
2
|
-
> @mcphero/vercel@1.1.
|
|
2
|
+
> @mcphero/vercel@1.1.7 check /Users/atomic/projects/ai/mcphero/packages/vercel
|
|
3
3
|
> pnpm lint && pnpm typecheck
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> @mcphero/vercel@1.1.
|
|
6
|
+
> @mcphero/vercel@1.1.7 lint /Users/atomic/projects/ai/mcphero/packages/vercel
|
|
7
7
|
> eslint
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
> @mcphero/vercel@1.1.
|
|
10
|
+
> @mcphero/vercel@1.1.7 typecheck /Users/atomic/projects/ai/mcphero/packages/vercel
|
|
11
11
|
> tsc --noEmit
|
|
12
12
|
|
|
13
|
-
ELIFECYCLE Command failed.
|
|
14
|
-
ELIFECYCLE Command failed.
|
package/.turbo/turbo-prepack.log
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @mcphero/vercel@1.1.
|
|
3
|
+
> @mcphero/vercel@1.1.7 prepack /Users/atomic/projects/ai/mcphero/packages/vercel
|
|
4
4
|
> pnpm clean && pnpm build
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
> @mcphero/vercel@1.1.
|
|
7
|
+
> @mcphero/vercel@1.1.7 clean /Users/atomic/projects/ai/mcphero/packages/vercel
|
|
8
8
|
> rimraf build
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
> @mcphero/vercel@1.1.
|
|
11
|
+
> @mcphero/vercel@1.1.7 build /Users/atomic/projects/ai/mcphero/packages/vercel
|
|
12
12
|
> tsup
|
|
13
13
|
|
|
14
14
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
[34mCLI[39m Target: es2022
|
|
19
19
|
[34mCLI[39m Cleaning output folder
|
|
20
20
|
[34mESM[39m Build start
|
|
21
|
-
[32mESM[39m [1mbuild/index.js [22m[
|
|
22
|
-
[32mESM[39m [1mbuild/index.js.map [22m[
|
|
21
|
+
[32mESM[39m [1mbuild/index.js [22m[32m6.46 KB[39m
|
|
22
|
+
[32mESM[39m [1mbuild/index.js.map [22m[32m11.48 KB[39m
|
|
23
23
|
[32mESM[39m ⚡️ Build success in 9ms
|
|
24
24
|
DTS Build start
|
|
25
|
-
DTS ⚡️ Build success in
|
|
26
|
-
DTS build/index.d.ts
|
|
25
|
+
DTS ⚡️ Build success in 732ms
|
|
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
package/build/index.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
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, OPTIONS",
|
|
11
|
+
"Access-Control-Allow-Headers": "*",
|
|
12
|
+
"Access-Control-Max-Age": "86400"
|
|
13
|
+
};
|
|
7
14
|
function vercel(options = {}) {
|
|
8
15
|
let _actions = [];
|
|
9
16
|
let _options = null;
|
|
@@ -14,6 +21,84 @@ function vercel(options = {}) {
|
|
|
14
21
|
});
|
|
15
22
|
const handleRequest = async (request) => {
|
|
16
23
|
await _ready;
|
|
24
|
+
const url = new URL(request.url);
|
|
25
|
+
if (options.auth?.authorizationServers?.length && options.auth.resourceUrl) {
|
|
26
|
+
if (url.pathname === "/.well-known/oauth-protected-resource" || url.pathname.endsWith("/.well-known/oauth-protected-resource")) {
|
|
27
|
+
if (request.method === "OPTIONS") {
|
|
28
|
+
return new Response(null, { status: 200, headers: CORS_HEADERS });
|
|
29
|
+
}
|
|
30
|
+
const metadata = generateProtectedResourceMetadata(options.auth.resourceUrl, options.auth.authorizationServers);
|
|
31
|
+
return new Response(JSON.stringify(metadata), {
|
|
32
|
+
headers: { ...CORS_HEADERS, "Content-Type": "application/json", "Cache-Control": "max-age=3600" }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (options.auth?.provider) {
|
|
37
|
+
const provider = options.auth.provider;
|
|
38
|
+
const oauthPaths = {
|
|
39
|
+
"/.well-known/oauth-authorization-server": ["GET"],
|
|
40
|
+
"/authorize": ["GET"],
|
|
41
|
+
"/auth/callback": ["GET"],
|
|
42
|
+
"/token": ["POST"],
|
|
43
|
+
"/register": ["POST"]
|
|
44
|
+
};
|
|
45
|
+
const match = Object.entries(oauthPaths).find(([path]) => url.pathname === path || url.pathname.endsWith(path));
|
|
46
|
+
if (match) {
|
|
47
|
+
const [, methods] = match;
|
|
48
|
+
if (request.method === "OPTIONS") {
|
|
49
|
+
return new Response(null, { status: 200, headers: CORS_HEADERS });
|
|
50
|
+
}
|
|
51
|
+
if (!methods.includes(request.method)) {
|
|
52
|
+
return new Response("Method not allowed", { status: 405 });
|
|
53
|
+
}
|
|
54
|
+
const toOAuthReq = async () => {
|
|
55
|
+
let body;
|
|
56
|
+
if (request.method === "POST") {
|
|
57
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
58
|
+
const text = await request.text();
|
|
59
|
+
if (contentType.includes("json")) {
|
|
60
|
+
body = JSON.parse(text);
|
|
61
|
+
} else {
|
|
62
|
+
body = Object.fromEntries(new URLSearchParams(text));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
method: request.method,
|
|
67
|
+
url,
|
|
68
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
69
|
+
body
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const oauthReq = await toOAuthReq();
|
|
73
|
+
let oauthRes;
|
|
74
|
+
if (url.pathname.endsWith("/.well-known/oauth-authorization-server")) {
|
|
75
|
+
oauthRes = provider.metadata();
|
|
76
|
+
} else if (url.pathname.endsWith("/authorize")) {
|
|
77
|
+
oauthRes = await provider.authorize(oauthReq);
|
|
78
|
+
} else if (url.pathname.endsWith("/auth/callback")) {
|
|
79
|
+
oauthRes = await provider.callback(oauthReq);
|
|
80
|
+
} else if (url.pathname.endsWith("/token")) {
|
|
81
|
+
oauthRes = await provider.token(oauthReq);
|
|
82
|
+
} else {
|
|
83
|
+
oauthRes = await provider.register(oauthReq);
|
|
84
|
+
}
|
|
85
|
+
const responseBody = oauthRes.body ? typeof oauthRes.body === "string" ? oauthRes.body : JSON.stringify(oauthRes.body) : null;
|
|
86
|
+
return new Response(responseBody, { status: oauthRes.status, headers: oauthRes.headers });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
let requestContext = _context;
|
|
90
|
+
if (options.auth) {
|
|
91
|
+
const result = await validateToken(request.headers.get("authorization"), options.auth);
|
|
92
|
+
if (result.error) {
|
|
93
|
+
return new Response(JSON.stringify(result.error.body), {
|
|
94
|
+
status: result.error.statusCode,
|
|
95
|
+
headers: result.error.headers
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (result.auth) {
|
|
99
|
+
requestContext = _context.fork({ auth: result.auth });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
17
102
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
18
103
|
sessionIdGenerator: void 0,
|
|
19
104
|
enableJsonResponse: options.enableJsonResponse
|
|
@@ -51,7 +136,7 @@ function vercel(options = {}) {
|
|
|
51
136
|
});
|
|
52
137
|
}
|
|
53
138
|
});
|
|
54
|
-
return action.run(input,
|
|
139
|
+
return action.run(input, requestContext.fork({ logger, extra })).then((result) => {
|
|
55
140
|
return toolResponse(result);
|
|
56
141
|
}).catch((error) => {
|
|
57
142
|
if (error instanceof Error) {
|
package/build/index.js.map
CHANGED
|
@@ -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}\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, OPTIONS',\n 'Access-Control-Allow-Headers': '*',\n 'Access-Control-Max-Age': '86400'\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 url = new URL(request.url)\n\n if (options.auth?.authorizationServers?.length && options.auth.resourceUrl) {\n if (url.pathname === '/.well-known/oauth-protected-resource' || url.pathname.endsWith('/.well-known/oauth-protected-resource')) {\n if (request.method === 'OPTIONS') {\n return new Response(null, { status: 200, headers: CORS_HEADERS })\n }\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\n if (options.auth?.provider) {\n const provider = options.auth.provider\n const oauthPaths: Record<string, string[]> = {\n '/.well-known/oauth-authorization-server': ['GET'],\n '/authorize': ['GET'],\n '/auth/callback': ['GET'],\n '/token': ['POST'],\n '/register': ['POST']\n }\n const match = Object.entries(oauthPaths).find(([path]) => url.pathname === path || url.pathname.endsWith(path))\n if (match) {\n const [, methods] = match\n if (request.method === 'OPTIONS') {\n return new Response(null, { status: 200, headers: CORS_HEADERS })\n }\n if (!methods.includes(request.method)) {\n return new Response('Method not allowed', { status: 405 })\n }\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 const oauthReq = await toOAuthReq()\n let oauthRes\n if (url.pathname.endsWith('/.well-known/oauth-authorization-server')) {\n oauthRes = provider.metadata()\n } else if (url.pathname.endsWith('/authorize')) {\n oauthRes = await provider.authorize(oauthReq)\n } else if (url.pathname.endsWith('/auth/callback')) {\n oauthRes = await provider.callback(oauthReq)\n } else if (url.pathname.endsWith('/token')) {\n oauthRes = await provider.token(oauthReq)\n } else {\n oauthRes = await provider.register(oauthReq)\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\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 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.
|
|
3
|
+
"version": "1.2.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/
|
|
18
|
-
"@mcphero/logger": "1.
|
|
17
|
+
"@mcphero/auth": "1.2.0",
|
|
18
|
+
"@mcphero/logger": "1.2.0",
|
|
19
|
+
"@mcphero/core": "1.2.0"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"@eslint/js": "^10.0.1",
|
package/src/adapter/vercel.ts
CHANGED
|
@@ -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,7 @@ import { capitalCase, pascalCase } from 'change-case'
|
|
|
6
7
|
|
|
7
8
|
export interface VercelAdapterOptions {
|
|
8
9
|
enableJsonResponse?: boolean
|
|
10
|
+
auth?: AuthConfig
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface VercelAdapter {
|
|
@@ -16,6 +18,13 @@ export interface VercelAdapter {
|
|
|
16
18
|
DELETE: (request: Request) => Promise<Response>
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
const CORS_HEADERS: Record<string, string> = {
|
|
22
|
+
'Access-Control-Allow-Origin': '*',
|
|
23
|
+
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
|
24
|
+
'Access-Control-Allow-Headers': '*',
|
|
25
|
+
'Access-Control-Max-Age': '86400'
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
export function vercel(options: VercelAdapterOptions = {}): VercelAdapter {
|
|
20
29
|
let _actions: Action[] = []
|
|
21
30
|
let _options: MCPHeroOptions | null = null
|
|
@@ -28,6 +37,88 @@ export function vercel(options: VercelAdapterOptions = {}): VercelAdapter {
|
|
|
28
37
|
const handleRequest = async (request: Request): Promise<Response> => {
|
|
29
38
|
await _ready
|
|
30
39
|
|
|
40
|
+
const url = new URL(request.url)
|
|
41
|
+
|
|
42
|
+
if (options.auth?.authorizationServers?.length && options.auth.resourceUrl) {
|
|
43
|
+
if (url.pathname === '/.well-known/oauth-protected-resource' || url.pathname.endsWith('/.well-known/oauth-protected-resource')) {
|
|
44
|
+
if (request.method === 'OPTIONS') {
|
|
45
|
+
return new Response(null, { status: 200, headers: CORS_HEADERS })
|
|
46
|
+
}
|
|
47
|
+
const metadata = generateProtectedResourceMetadata(options.auth.resourceUrl, options.auth.authorizationServers)
|
|
48
|
+
return new Response(JSON.stringify(metadata), {
|
|
49
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json', 'Cache-Control': 'max-age=3600' }
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (options.auth?.provider) {
|
|
55
|
+
const provider = options.auth.provider
|
|
56
|
+
const oauthPaths: Record<string, string[]> = {
|
|
57
|
+
'/.well-known/oauth-authorization-server': ['GET'],
|
|
58
|
+
'/authorize': ['GET'],
|
|
59
|
+
'/auth/callback': ['GET'],
|
|
60
|
+
'/token': ['POST'],
|
|
61
|
+
'/register': ['POST']
|
|
62
|
+
}
|
|
63
|
+
const match = Object.entries(oauthPaths).find(([path]) => url.pathname === path || url.pathname.endsWith(path))
|
|
64
|
+
if (match) {
|
|
65
|
+
const [, methods] = match
|
|
66
|
+
if (request.method === 'OPTIONS') {
|
|
67
|
+
return new Response(null, { status: 200, headers: CORS_HEADERS })
|
|
68
|
+
}
|
|
69
|
+
if (!methods.includes(request.method)) {
|
|
70
|
+
return new Response('Method not allowed', { status: 405 })
|
|
71
|
+
}
|
|
72
|
+
const toOAuthReq = async (): Promise<OAuthRequest> => {
|
|
73
|
+
let body: Record<string, string> | undefined
|
|
74
|
+
if (request.method === 'POST') {
|
|
75
|
+
const contentType = request.headers.get('content-type') ?? ''
|
|
76
|
+
const text = await request.text()
|
|
77
|
+
if (contentType.includes('json')) {
|
|
78
|
+
body = JSON.parse(text)
|
|
79
|
+
} else {
|
|
80
|
+
body = Object.fromEntries(new URLSearchParams(text))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
method: request.method,
|
|
85
|
+
url,
|
|
86
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
87
|
+
body
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const oauthReq = await toOAuthReq()
|
|
91
|
+
let oauthRes
|
|
92
|
+
if (url.pathname.endsWith('/.well-known/oauth-authorization-server')) {
|
|
93
|
+
oauthRes = provider.metadata()
|
|
94
|
+
} else if (url.pathname.endsWith('/authorize')) {
|
|
95
|
+
oauthRes = await provider.authorize(oauthReq)
|
|
96
|
+
} else if (url.pathname.endsWith('/auth/callback')) {
|
|
97
|
+
oauthRes = await provider.callback(oauthReq)
|
|
98
|
+
} else if (url.pathname.endsWith('/token')) {
|
|
99
|
+
oauthRes = await provider.token(oauthReq)
|
|
100
|
+
} else {
|
|
101
|
+
oauthRes = await provider.register(oauthReq)
|
|
102
|
+
}
|
|
103
|
+
const responseBody = oauthRes.body ? (typeof oauthRes.body === 'string' ? oauthRes.body : JSON.stringify(oauthRes.body)) : null
|
|
104
|
+
return new Response(responseBody, { status: oauthRes.status, headers: oauthRes.headers })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let requestContext = _context!
|
|
109
|
+
if (options.auth) {
|
|
110
|
+
const result = await validateToken(request.headers.get('authorization'), options.auth)
|
|
111
|
+
if (result.error) {
|
|
112
|
+
return new Response(JSON.stringify(result.error.body), {
|
|
113
|
+
status: result.error.statusCode,
|
|
114
|
+
headers: result.error.headers
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
if (result.auth) {
|
|
118
|
+
requestContext = _context!.fork({ auth: result.auth })
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
31
122
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
32
123
|
sessionIdGenerator: undefined,
|
|
33
124
|
enableJsonResponse: options.enableJsonResponse
|
|
@@ -65,7 +156,7 @@ export function vercel(options: VercelAdapterOptions = {}): VercelAdapter {
|
|
|
65
156
|
})
|
|
66
157
|
}
|
|
67
158
|
})
|
|
68
|
-
return action.run(input,
|
|
159
|
+
return action.run(input, requestContext.fork({ logger, extra })).then((result) => {
|
|
69
160
|
return toolResponse(result)
|
|
70
161
|
}).catch((error) => {
|
|
71
162
|
if (error instanceof Error) {
|