@karashiiro/mcp 0.1.0 → 0.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.
- package/README.md +226 -2
- package/dist/http.d.ts +73 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +230 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -151
- package/dist/index.js.map +1 -1
- package/dist/stdio.d.ts +13 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +33 -0
- package/dist/stdio.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -8
package/README.md
CHANGED
|
@@ -1,3 +1,227 @@
|
|
|
1
|
-
# mcp
|
|
1
|
+
# @karashiiro/mcp
|
|
2
2
|
|
|
3
|
-
(
|
|
3
|
+
Lightweight utilities for serving [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers in TypeScript.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @karashiiro/mcp @modelcontextprotocol/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### For HTTP transport
|
|
12
|
+
|
|
13
|
+
If you plan to use HTTP transport, you'll also need Hono:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install hono @hono/node-server
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Stdio Transport
|
|
22
|
+
|
|
23
|
+
The simplest way to serve an MCP server. Great for CLI tools and local integrations.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
27
|
+
import { serveStdio } from "@karashiiro/mcp/stdio";
|
|
28
|
+
|
|
29
|
+
function createServer() {
|
|
30
|
+
const server = new McpServer({
|
|
31
|
+
name: "my-server",
|
|
32
|
+
version: "1.0.0",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
server.registerTool(
|
|
36
|
+
"hello",
|
|
37
|
+
{ description: "Says hello" },
|
|
38
|
+
async () => ({
|
|
39
|
+
content: [{ type: "text", text: "Hello from MCP!" }],
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return server;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await serveStdio(createServer);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### HTTP Transport
|
|
50
|
+
|
|
51
|
+
Serve your MCP server over HTTP using the Streamable HTTP transport.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
55
|
+
import { serveHttp } from "@karashiiro/mcp/http";
|
|
56
|
+
|
|
57
|
+
function createServer() {
|
|
58
|
+
const server = new McpServer({
|
|
59
|
+
name: "my-server",
|
|
60
|
+
version: "1.0.0",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
server.registerTool(
|
|
64
|
+
"hello",
|
|
65
|
+
{ description: "Says hello" },
|
|
66
|
+
async () => ({
|
|
67
|
+
content: [{ type: "text", text: "Hello from MCP!" }],
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return server;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const handle = await serveHttp(createServer, {
|
|
75
|
+
port: 8080,
|
|
76
|
+
host: "127.0.0.1",
|
|
77
|
+
endpoint: "/mcp",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Later, to shut down:
|
|
81
|
+
await handle.close();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### Stateless vs Stateful Mode
|
|
85
|
+
|
|
86
|
+
By default, `serveHttp` runs in **stateless mode** where all clients share a single server instance.
|
|
87
|
+
|
|
88
|
+
For **stateful mode** with per-client sessions, provide the `sessions` option:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const handle = await serveHttp(createServer, {
|
|
92
|
+
port: 8080,
|
|
93
|
+
sessions: {}, // Enable stateful mode
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
In stateful mode, `createServer` is called once per client session, allowing each client to have isolated state.
|
|
98
|
+
|
|
99
|
+
#### Session-Aware Factories
|
|
100
|
+
|
|
101
|
+
In stateful mode, the factory function receives the session ID as a parameter, enabling session-specific initialization:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const handle = await serveHttp(
|
|
105
|
+
(sessionId) => {
|
|
106
|
+
console.log(`Creating server for session: ${sessionId}`);
|
|
107
|
+
return createServer();
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
port: 8080,
|
|
111
|
+
sessions: {},
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Async Factories
|
|
117
|
+
|
|
118
|
+
The factory function can be async, which is useful for initialization that requires async operations:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const handle = await serveHttp(
|
|
122
|
+
async (sessionId) => {
|
|
123
|
+
// Perform async initialization (e.g., connect to database, load config)
|
|
124
|
+
await initializeResources(sessionId);
|
|
125
|
+
return createServer();
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
port: 8080,
|
|
129
|
+
sessions: {},
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Custom Session IDs
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const handle = await serveHttp(createServer, {
|
|
138
|
+
port: 8080,
|
|
139
|
+
sessions: {
|
|
140
|
+
sessionIdGenerator: () => `session-${Date.now()}`,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### Legacy SSE Support
|
|
146
|
+
|
|
147
|
+
For backwards compatibility with older MCP clients that use SSE transport:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const handle = await serveHttp(createServer, {
|
|
151
|
+
port: 8080,
|
|
152
|
+
sessions: {
|
|
153
|
+
legacySse: {
|
|
154
|
+
sseEndpoint: "/sse", // default: "/sse"
|
|
155
|
+
messagesEndpoint: "/messages", // default: "/messages"
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Entry Points
|
|
162
|
+
|
|
163
|
+
This package provides multiple entry points for optimal bundle size:
|
|
164
|
+
|
|
165
|
+
| Entry Point | Description | Requires Hono |
|
|
166
|
+
|-------------|-------------|---------------|
|
|
167
|
+
| `@karashiiro/mcp` | Everything (re-exports all) | Yes |
|
|
168
|
+
| `@karashiiro/mcp/stdio` | Stdio transport only | No |
|
|
169
|
+
| `@karashiiro/mcp/http` | HTTP transport only | Yes |
|
|
170
|
+
|
|
171
|
+
If you only need stdio transport, import from `@karashiiro/mcp/stdio` to avoid bundling Hono.
|
|
172
|
+
|
|
173
|
+
## API Reference
|
|
174
|
+
|
|
175
|
+
### Factory Types
|
|
176
|
+
|
|
177
|
+
The library provides two factory types for type-safe server creation:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// For stateless mode (serveStdio and serveHttp without sessions)
|
|
181
|
+
type StatelessServerFactory = () => McpServer | Promise<McpServer>;
|
|
182
|
+
|
|
183
|
+
// For stateful mode (serveHttp with sessions)
|
|
184
|
+
type StatefulServerFactory = (sessionId: string) => McpServer | Promise<McpServer>;
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Both factory types support async initialization by returning a `Promise<McpServer>`.
|
|
188
|
+
|
|
189
|
+
### `serveStdio(serverFactory)`
|
|
190
|
+
|
|
191
|
+
Serves an MCP server over stdin/stdout.
|
|
192
|
+
|
|
193
|
+
- `serverFactory: StatelessServerFactory` - Factory function that creates a single server instance
|
|
194
|
+
- Returns: `Promise<ServerHandle>`
|
|
195
|
+
|
|
196
|
+
### `serveHttp(serverFactory, options?)` (stateless)
|
|
197
|
+
|
|
198
|
+
Serves an MCP server over HTTP in stateless mode.
|
|
199
|
+
|
|
200
|
+
- `serverFactory: StatelessServerFactory` - Factory function called once, creates a shared server
|
|
201
|
+
- `options.port` - Port to listen on (default: `8080`)
|
|
202
|
+
- `options.host` - Host to bind to (default: `"127.0.0.1"`)
|
|
203
|
+
- `options.endpoint` - MCP endpoint path (default: `"/mcp"`)
|
|
204
|
+
- Returns: `Promise<ServerHandle>`
|
|
205
|
+
|
|
206
|
+
### `serveHttp(serverFactory, options)` (stateful)
|
|
207
|
+
|
|
208
|
+
Serves an MCP server over HTTP in stateful mode with per-client sessions.
|
|
209
|
+
|
|
210
|
+
- `serverFactory: StatefulServerFactory` - Factory function called per session with the session ID
|
|
211
|
+
- `options.port` - Port to listen on (default: `8080`)
|
|
212
|
+
- `options.host` - Host to bind to (default: `"127.0.0.1"`)
|
|
213
|
+
- `options.endpoint` - MCP endpoint path (default: `"/mcp"`)
|
|
214
|
+
- `options.sessions` - **Required** to enable stateful mode
|
|
215
|
+
- `options.sessions.sessionIdGenerator` - Custom session ID generator function
|
|
216
|
+
- `options.sessions.legacySse` - Enable legacy SSE transport endpoints
|
|
217
|
+
- Returns: `Promise<ServerHandle>`
|
|
218
|
+
|
|
219
|
+
### `ServerHandle`
|
|
220
|
+
|
|
221
|
+
Handle for controlling the server lifecycle.
|
|
222
|
+
|
|
223
|
+
- `close(): Promise<void>` - Gracefully shut down the server
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
UNLICENSED
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ServerHandle, StatelessServerFactory, StatefulServerFactory } from "./types.js";
|
|
2
|
+
export type { ServerHandle, StatelessServerFactory, StatefulServerFactory, } from "./types.js";
|
|
3
|
+
export type { ServerFactory } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Options for legacy SSE transport compatibility.
|
|
6
|
+
* @deprecated SSE transport is deprecated in favor of Streamable HTTP.
|
|
7
|
+
*/
|
|
8
|
+
export interface LegacySseOptions {
|
|
9
|
+
/** Endpoint for SSE stream. Defaults to "/sse". */
|
|
10
|
+
sseEndpoint?: string;
|
|
11
|
+
/** Endpoint for messages. Defaults to "/messages". */
|
|
12
|
+
messagesEndpoint?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface HttpServerSessionOptions {
|
|
15
|
+
sessionIdGenerator?: () => string;
|
|
16
|
+
/**
|
|
17
|
+
* Enable legacy SSE transport compatibility.
|
|
18
|
+
* When provided, adds /sse and /messages endpoints for older clients.
|
|
19
|
+
* @deprecated SSE transport is deprecated in favor of Streamable HTTP.
|
|
20
|
+
*/
|
|
21
|
+
legacySse?: LegacySseOptions;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Base HTTP server options without session configuration.
|
|
25
|
+
*/
|
|
26
|
+
export interface HttpServerOptionsBase {
|
|
27
|
+
port: number;
|
|
28
|
+
host: string;
|
|
29
|
+
endpoint: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* HTTP server options for stateless mode (no sessions).
|
|
33
|
+
*/
|
|
34
|
+
export interface HttpServerStatelessOptions extends HttpServerOptionsBase {
|
|
35
|
+
sessions?: undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* HTTP server options for stateful mode (with sessions).
|
|
39
|
+
*/
|
|
40
|
+
export interface HttpServerStatefulOptions extends HttpServerOptionsBase {
|
|
41
|
+
sessions: HttpServerSessionOptions;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Combined HTTP server options type.
|
|
45
|
+
*/
|
|
46
|
+
export type HttpServerOptions = HttpServerStatelessOptions | HttpServerStatefulOptions;
|
|
47
|
+
/**
|
|
48
|
+
* Serve an MCP server over HTTP in stateless mode.
|
|
49
|
+
*
|
|
50
|
+
* In stateless mode, the factory is called once and all clients share the same server instance.
|
|
51
|
+
* The factory receives no parameters.
|
|
52
|
+
*
|
|
53
|
+
* @param serverFactory - Factory function that creates a single shared McpServer instance.
|
|
54
|
+
* @param options - Server configuration options (without sessions).
|
|
55
|
+
* @returns A handle to control the server lifecycle.
|
|
56
|
+
*/
|
|
57
|
+
export declare function serveHttp(serverFactory: StatelessServerFactory, options?: Partial<Omit<HttpServerOptionsBase, "sessions">> & {
|
|
58
|
+
sessions?: undefined;
|
|
59
|
+
}): Promise<ServerHandle>;
|
|
60
|
+
/**
|
|
61
|
+
* Serve an MCP server over HTTP in stateful mode with per-client sessions.
|
|
62
|
+
*
|
|
63
|
+
* In stateful mode, the factory is called once per client session, receiving the session ID.
|
|
64
|
+
* This allows each client to have isolated state.
|
|
65
|
+
*
|
|
66
|
+
* @param serverFactory - Factory function that creates McpServer instances per session.
|
|
67
|
+
* @param options - Server configuration options with sessions enabled.
|
|
68
|
+
* @returns A handle to control the server lifecycle.
|
|
69
|
+
*/
|
|
70
|
+
export declare function serveHttp(serverFactory: StatefulServerFactory, options: Partial<HttpServerOptionsBase> & {
|
|
71
|
+
sessions: HttpServerSessionOptions;
|
|
72
|
+
}): Promise<ServerHandle>;
|
|
73
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA2B,SAAQ,qBAAqB;IACvE,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,yBAA0B,SAAQ,qBAAqB;IACtE,QAAQ,EAAE,wBAAwB,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,0BAA0B,GAC1B,yBAAyB,CAAC;AAqD9B;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,aAAa,EAAE,sBAAsB,EACrC,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,qBAAqB,EAAE,UAAU,CAAC,CAAC,GAAG;IAC3D,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,GACA,OAAO,CAAC,YAAY,CAAC,CAAC;AAEzB;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,aAAa,EAAE,qBAAqB,EACpC,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG;IACxC,QAAQ,EAAE,wBAAwB,CAAC;CACpC,GACA,OAAO,CAAC,YAAY,CAAC,CAAC"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
|
|
3
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
5
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
import { cors } from "hono/cors";
|
|
8
|
+
import { v4 as uuidv4 } from "uuid";
|
|
9
|
+
import { InMemoryEventStore } from "./event-store.js";
|
|
10
|
+
const defaultOptions = Object.freeze({
|
|
11
|
+
port: 8080,
|
|
12
|
+
host: "127.0.0.1",
|
|
13
|
+
endpoint: "/mcp",
|
|
14
|
+
sessions: undefined,
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Helper to create a closeable handle from a node server.
|
|
18
|
+
*/
|
|
19
|
+
function createHandle(server) {
|
|
20
|
+
return {
|
|
21
|
+
close: () => new Promise((resolve, reject) => {
|
|
22
|
+
server.close((err) => {
|
|
23
|
+
if (err)
|
|
24
|
+
reject(err);
|
|
25
|
+
else
|
|
26
|
+
resolve();
|
|
27
|
+
});
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Implementation signature (accepts both)
|
|
32
|
+
export async function serveHttp(serverFactory, options = {}) {
|
|
33
|
+
const mergedOptions = {
|
|
34
|
+
...defaultOptions,
|
|
35
|
+
...options,
|
|
36
|
+
};
|
|
37
|
+
if (mergedOptions.sessions) {
|
|
38
|
+
return serveHttpStateful(serverFactory, mergedOptions);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return serveHttpStateless(serverFactory, mergedOptions);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Stateless mode: single server instance, single transport, no session tracking.
|
|
46
|
+
*/
|
|
47
|
+
async function serveHttpStateless(serverFactory, options) {
|
|
48
|
+
// Call factory ONCE to get the single server instance (no sessionId in stateless mode)
|
|
49
|
+
const server = await serverFactory();
|
|
50
|
+
// Create the transport (no session ID generator = stateless)
|
|
51
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
52
|
+
sessionIdGenerator: undefined,
|
|
53
|
+
});
|
|
54
|
+
// Create the Hono app
|
|
55
|
+
const app = new Hono();
|
|
56
|
+
addCors(app);
|
|
57
|
+
// MCP endpoint
|
|
58
|
+
app.all(options.endpoint, (c) => transport.handleRequest(c.req.raw));
|
|
59
|
+
await server.connect(transport);
|
|
60
|
+
const httpServer = serve({
|
|
61
|
+
fetch: app.fetch,
|
|
62
|
+
port: options.port,
|
|
63
|
+
hostname: options.host,
|
|
64
|
+
});
|
|
65
|
+
return createHandle(httpServer);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Stateful mode: per-session servers, transports, and event stores.
|
|
69
|
+
*/
|
|
70
|
+
function serveHttpStateful(serverFactory, options) {
|
|
71
|
+
const sessions = new Map();
|
|
72
|
+
const sessionIdGenerator = options.sessions.sessionIdGenerator ?? uuidv4;
|
|
73
|
+
// Legacy SSE options
|
|
74
|
+
const legacySse = options.sessions?.legacySse;
|
|
75
|
+
const sseEndpoint = legacySse?.sseEndpoint ?? "/sse";
|
|
76
|
+
const messagesEndpoint = legacySse?.messagesEndpoint ?? "/messages";
|
|
77
|
+
const app = new Hono();
|
|
78
|
+
addCors(app);
|
|
79
|
+
// Main MCP endpoint (Streamable HTTP)
|
|
80
|
+
app.all(options.endpoint, async (c) => {
|
|
81
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
82
|
+
// Clone the request so we can read the body without consuming it
|
|
83
|
+
const rawRequest = c.req.raw;
|
|
84
|
+
const bodyText = await rawRequest.text();
|
|
85
|
+
let body = null;
|
|
86
|
+
try {
|
|
87
|
+
body = bodyText ? JSON.parse(bodyText) : null;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Invalid JSON - body stays null
|
|
91
|
+
}
|
|
92
|
+
// Helper to recreate request with body (since we consumed it)
|
|
93
|
+
const recreateRequest = () => new Request(rawRequest.url, {
|
|
94
|
+
method: rawRequest.method,
|
|
95
|
+
headers: rawRequest.headers,
|
|
96
|
+
body: bodyText || undefined,
|
|
97
|
+
});
|
|
98
|
+
// New session (initialize request without session ID)
|
|
99
|
+
if (!sessionId && body && isInitializeRequest(body)) {
|
|
100
|
+
// Generate session ID upfront so we can pass it to the factory
|
|
101
|
+
const sid = sessionIdGenerator();
|
|
102
|
+
// Create server with session ID (supports async factories)
|
|
103
|
+
const server = await serverFactory(sid);
|
|
104
|
+
const eventStore = new InMemoryEventStore();
|
|
105
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
106
|
+
// Use our pre-generated session ID
|
|
107
|
+
sessionIdGenerator: () => sid,
|
|
108
|
+
eventStore,
|
|
109
|
+
onsessioninitialized: () => {
|
|
110
|
+
// Server already created above, just store the session state
|
|
111
|
+
const session = {
|
|
112
|
+
type: "streamable-http",
|
|
113
|
+
transport,
|
|
114
|
+
server,
|
|
115
|
+
eventStore,
|
|
116
|
+
};
|
|
117
|
+
sessions.set(sid, session);
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
// Connect server to transport before handling the request
|
|
121
|
+
await server.connect(transport);
|
|
122
|
+
transport.onclose = () => {
|
|
123
|
+
sessions.delete(sid);
|
|
124
|
+
};
|
|
125
|
+
return transport.handleRequest(recreateRequest());
|
|
126
|
+
}
|
|
127
|
+
// Existing session
|
|
128
|
+
if (sessionId) {
|
|
129
|
+
const session = sessions.get(sessionId);
|
|
130
|
+
if (!session) {
|
|
131
|
+
return c.text("Session not found", 404);
|
|
132
|
+
}
|
|
133
|
+
// Validate transport type matches endpoint
|
|
134
|
+
if (session.type !== "streamable-http") {
|
|
135
|
+
return c.json({
|
|
136
|
+
jsonrpc: "2.0",
|
|
137
|
+
error: {
|
|
138
|
+
code: -32000,
|
|
139
|
+
message: "Bad Request: Session exists but uses a different transport protocol",
|
|
140
|
+
},
|
|
141
|
+
id: null,
|
|
142
|
+
}, 400);
|
|
143
|
+
}
|
|
144
|
+
return session.transport.handleRequest(recreateRequest());
|
|
145
|
+
}
|
|
146
|
+
// Invalid request (no session ID, not an initialize request)
|
|
147
|
+
return c.text("Bad request - missing session ID", 400);
|
|
148
|
+
});
|
|
149
|
+
// Legacy SSE endpoints (only if enabled)
|
|
150
|
+
if (legacySse) {
|
|
151
|
+
// GET /sse - Establish SSE stream
|
|
152
|
+
app.get(sseEndpoint, async (c) => {
|
|
153
|
+
const { outgoing } = c.env;
|
|
154
|
+
// Create SSE transport with messages endpoint
|
|
155
|
+
const transport = new SSEServerTransport(messagesEndpoint, outgoing);
|
|
156
|
+
// Create server with session ID (supports async factories)
|
|
157
|
+
const server = await serverFactory(transport.sessionId);
|
|
158
|
+
// Store session
|
|
159
|
+
const session = {
|
|
160
|
+
type: "sse",
|
|
161
|
+
transport,
|
|
162
|
+
server,
|
|
163
|
+
};
|
|
164
|
+
sessions.set(transport.sessionId, session);
|
|
165
|
+
// Cleanup on close
|
|
166
|
+
transport.onclose = () => {
|
|
167
|
+
sessions.delete(transport.sessionId);
|
|
168
|
+
};
|
|
169
|
+
// Connect and start
|
|
170
|
+
await server.connect(transport);
|
|
171
|
+
// Signal to Hono that we've handled the response directly
|
|
172
|
+
return RESPONSE_ALREADY_SENT;
|
|
173
|
+
});
|
|
174
|
+
// POST /messages - Handle messages for SSE sessions
|
|
175
|
+
app.post(messagesEndpoint, async (c) => {
|
|
176
|
+
const sessionId = c.req.query("sessionId");
|
|
177
|
+
if (!sessionId) {
|
|
178
|
+
return c.text("Missing sessionId query parameter", 400);
|
|
179
|
+
}
|
|
180
|
+
const session = sessions.get(sessionId);
|
|
181
|
+
if (!session) {
|
|
182
|
+
return c.text("Session not found", 404);
|
|
183
|
+
}
|
|
184
|
+
// Validate transport type matches endpoint
|
|
185
|
+
if (session.type !== "sse") {
|
|
186
|
+
return c.json({
|
|
187
|
+
jsonrpc: "2.0",
|
|
188
|
+
error: {
|
|
189
|
+
code: -32000,
|
|
190
|
+
message: "Bad Request: Session exists but uses a different transport protocol",
|
|
191
|
+
},
|
|
192
|
+
id: null,
|
|
193
|
+
}, 400);
|
|
194
|
+
}
|
|
195
|
+
const { incoming, outgoing } = c.env;
|
|
196
|
+
// Parse body for SSEServerTransport
|
|
197
|
+
const bodyText = await c.req.text();
|
|
198
|
+
let parsedBody = null;
|
|
199
|
+
try {
|
|
200
|
+
parsedBody = bodyText ? JSON.parse(bodyText) : null;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Let handlePostMessage handle the error
|
|
204
|
+
}
|
|
205
|
+
await session.transport.handlePostMessage(incoming, outgoing, parsedBody);
|
|
206
|
+
return RESPONSE_ALREADY_SENT;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const httpServer = serve({
|
|
210
|
+
fetch: app.fetch,
|
|
211
|
+
port: options.port,
|
|
212
|
+
hostname: options.host,
|
|
213
|
+
});
|
|
214
|
+
return createHandle(httpServer);
|
|
215
|
+
}
|
|
216
|
+
function addCors(app) {
|
|
217
|
+
// Enable CORS for all origins
|
|
218
|
+
app.use("*", cors({
|
|
219
|
+
origin: "*",
|
|
220
|
+
allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
|
|
221
|
+
allowHeaders: [
|
|
222
|
+
"Content-Type",
|
|
223
|
+
"mcp-session-id",
|
|
224
|
+
"Last-Event-ID",
|
|
225
|
+
"mcp-protocol-version",
|
|
226
|
+
],
|
|
227
|
+
exposeHeaders: ["mcp-session-id", "mcp-protocol-version"],
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
sessionIdGenerator?: () => string;
|
|
4
|
-
}
|
|
5
|
-
export interface HttpServerOptions {
|
|
6
|
-
port: number;
|
|
7
|
-
host: string;
|
|
8
|
-
endpoint: string;
|
|
9
|
-
sessions?: HttpServerSessionOptions | undefined;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Handle returned by serveHttp for controlling the server lifecycle.
|
|
13
|
-
*/
|
|
14
|
-
export interface HttpServerHandle {
|
|
15
|
-
/** Close the HTTP server and stop accepting new connections. */
|
|
16
|
-
close: () => Promise<void>;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Serve an MCP server over HTTP.
|
|
20
|
-
*
|
|
21
|
-
* @param serverFactory - Factory function that creates McpServer instances.
|
|
22
|
-
* In stateless mode, called once. In stateful mode, called per session.
|
|
23
|
-
* @param options - Server configuration options.
|
|
24
|
-
* If `sessions` is provided, runs in stateful mode with per-session servers.
|
|
25
|
-
* If `sessions` is undefined, runs in stateless mode with a single server.
|
|
26
|
-
* @returns A handle to control the server lifecycle.
|
|
27
|
-
*/
|
|
28
|
-
export declare function serveHttp(serverFactory: () => McpServer, options?: Partial<HttpServerOptions>): Promise<HttpServerHandle>;
|
|
1
|
+
export * from "./http.js";
|
|
2
|
+
export * from "./stdio.js";
|
|
29
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,152 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { v4 as uuidv4 } from "uuid";
|
|
7
|
-
import { InMemoryEventStore } from "./event-store.js";
|
|
8
|
-
const defaultOptions = Object.freeze({
|
|
9
|
-
port: 8080,
|
|
10
|
-
host: "127.0.0.1",
|
|
11
|
-
endpoint: "/mcp",
|
|
12
|
-
sessions: undefined,
|
|
13
|
-
});
|
|
14
|
-
/**
|
|
15
|
-
* Helper to create a closeable handle from a node server.
|
|
16
|
-
*/
|
|
17
|
-
function createHandle(server) {
|
|
18
|
-
return {
|
|
19
|
-
close: () => new Promise((resolve, reject) => {
|
|
20
|
-
server.close((err) => {
|
|
21
|
-
if (err)
|
|
22
|
-
reject(err);
|
|
23
|
-
else
|
|
24
|
-
resolve();
|
|
25
|
-
});
|
|
26
|
-
}),
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Serve an MCP server over HTTP.
|
|
31
|
-
*
|
|
32
|
-
* @param serverFactory - Factory function that creates McpServer instances.
|
|
33
|
-
* In stateless mode, called once. In stateful mode, called per session.
|
|
34
|
-
* @param options - Server configuration options.
|
|
35
|
-
* If `sessions` is provided, runs in stateful mode with per-session servers.
|
|
36
|
-
* If `sessions` is undefined, runs in stateless mode with a single server.
|
|
37
|
-
* @returns A handle to control the server lifecycle.
|
|
38
|
-
*/
|
|
39
|
-
export async function serveHttp(serverFactory, options = {}) {
|
|
40
|
-
const mergedOptions = {
|
|
41
|
-
...defaultOptions,
|
|
42
|
-
...options,
|
|
43
|
-
};
|
|
44
|
-
if (mergedOptions.sessions) {
|
|
45
|
-
return serveHttpStateful(serverFactory, mergedOptions);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
return serveHttpStateless(serverFactory, mergedOptions);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Stateless mode: single server instance, single transport, no session tracking.
|
|
53
|
-
*/
|
|
54
|
-
async function serveHttpStateless(serverFactory, options) {
|
|
55
|
-
// Call factory ONCE to get the single server instance
|
|
56
|
-
const server = serverFactory();
|
|
57
|
-
// Create the transport (no session ID generator = stateless)
|
|
58
|
-
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
59
|
-
sessionIdGenerator: undefined,
|
|
60
|
-
});
|
|
61
|
-
// Create the Hono app
|
|
62
|
-
const app = new Hono();
|
|
63
|
-
addCors(app);
|
|
64
|
-
// MCP endpoint
|
|
65
|
-
app.all(options.endpoint, (c) => transport.handleRequest(c.req.raw));
|
|
66
|
-
await server.connect(transport);
|
|
67
|
-
const httpServer = serve({
|
|
68
|
-
fetch: app.fetch,
|
|
69
|
-
port: options.port,
|
|
70
|
-
hostname: options.host,
|
|
71
|
-
});
|
|
72
|
-
return createHandle(httpServer);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Stateful mode: per-session servers, transports, and event stores.
|
|
76
|
-
*/
|
|
77
|
-
function serveHttpStateful(serverFactory, options) {
|
|
78
|
-
const sessions = new Map();
|
|
79
|
-
const sessionIdGenerator = options.sessions?.sessionIdGenerator ?? uuidv4;
|
|
80
|
-
const app = new Hono();
|
|
81
|
-
addCors(app);
|
|
82
|
-
app.all(options.endpoint, async (c) => {
|
|
83
|
-
const sessionId = c.req.header("mcp-session-id");
|
|
84
|
-
// Clone the request so we can read the body without consuming it
|
|
85
|
-
const rawRequest = c.req.raw;
|
|
86
|
-
const bodyText = await rawRequest.text();
|
|
87
|
-
let body = null;
|
|
88
|
-
try {
|
|
89
|
-
body = bodyText ? JSON.parse(bodyText) : null;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
// Invalid JSON - body stays null
|
|
93
|
-
}
|
|
94
|
-
// Helper to recreate request with body (since we consumed it)
|
|
95
|
-
const recreateRequest = () => new Request(rawRequest.url, {
|
|
96
|
-
method: rawRequest.method,
|
|
97
|
-
headers: rawRequest.headers,
|
|
98
|
-
body: bodyText || undefined,
|
|
99
|
-
});
|
|
100
|
-
// New session (initialize request without session ID)
|
|
101
|
-
if (!sessionId && body && isInitializeRequest(body)) {
|
|
102
|
-
const eventStore = new InMemoryEventStore();
|
|
103
|
-
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
104
|
-
sessionIdGenerator,
|
|
105
|
-
eventStore,
|
|
106
|
-
onsessioninitialized: (sid) => {
|
|
107
|
-
// Factory called per session!
|
|
108
|
-
const server = serverFactory();
|
|
109
|
-
sessions.set(sid, { transport, server, eventStore });
|
|
110
|
-
server.connect(transport);
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
transport.onclose = () => {
|
|
114
|
-
if (transport.sessionId) {
|
|
115
|
-
sessions.delete(transport.sessionId);
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
return transport.handleRequest(recreateRequest());
|
|
119
|
-
}
|
|
120
|
-
// Existing session
|
|
121
|
-
if (sessionId) {
|
|
122
|
-
const session = sessions.get(sessionId);
|
|
123
|
-
if (!session) {
|
|
124
|
-
return c.text("Session not found", 404);
|
|
125
|
-
}
|
|
126
|
-
return session.transport.handleRequest(recreateRequest());
|
|
127
|
-
}
|
|
128
|
-
// Invalid request (no session ID, not an initialize request)
|
|
129
|
-
return c.text("Bad request - missing session ID", 400);
|
|
130
|
-
});
|
|
131
|
-
const httpServer = serve({
|
|
132
|
-
fetch: app.fetch,
|
|
133
|
-
port: options.port,
|
|
134
|
-
hostname: options.host,
|
|
135
|
-
});
|
|
136
|
-
return createHandle(httpServer);
|
|
137
|
-
}
|
|
138
|
-
function addCors(app) {
|
|
139
|
-
// Enable CORS for all origins
|
|
140
|
-
app.use("*", cors({
|
|
141
|
-
origin: "*",
|
|
142
|
-
allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
|
|
143
|
-
allowHeaders: [
|
|
144
|
-
"Content-Type",
|
|
145
|
-
"mcp-session-id",
|
|
146
|
-
"Last-Event-ID",
|
|
147
|
-
"mcp-protocol-version",
|
|
148
|
-
],
|
|
149
|
-
exposeHeaders: ["mcp-session-id", "mcp-protocol-version"],
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
1
|
+
// Re-export everything from both modules for convenience.
|
|
2
|
+
// Note: Importing from this entry point will load Hono dependencies.
|
|
3
|
+
// For stdio-only usage without Hono, import from "@karashiiro/mcp/stdio" instead.
|
|
4
|
+
export * from "./http.js";
|
|
5
|
+
export * from "./stdio.js";
|
|
152
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,qEAAqE;AACrE,kFAAkF;AAElF,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
package/dist/stdio.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ServerHandle, StatelessServerFactory } from "./types.js";
|
|
2
|
+
export type { ServerHandle, StatelessServerFactory } from "./types.js";
|
|
3
|
+
export type { ServerFactory } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Serve an MCP server over stdio (stdin/stdout).
|
|
6
|
+
*
|
|
7
|
+
* @param serverFactory - Factory function that creates an McpServer instance.
|
|
8
|
+
* Called once with no parameters (stdio is always single-session).
|
|
9
|
+
* Can return a Promise for async initialization.
|
|
10
|
+
* @returns A handle to control the server lifecycle.
|
|
11
|
+
*/
|
|
12
|
+
export declare function serveStdio(serverFactory: StatelessServerFactory): Promise<ServerHandle>;
|
|
13
|
+
//# sourceMappingURL=stdio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEvE,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGvE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,aAAa,EAAE,sBAAsB,GACpC,OAAO,CAAC,YAAY,CAAC,CA4BvB"}
|
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
/**
|
|
3
|
+
* Serve an MCP server over stdio (stdin/stdout).
|
|
4
|
+
*
|
|
5
|
+
* @param serverFactory - Factory function that creates an McpServer instance.
|
|
6
|
+
* Called once with no parameters (stdio is always single-session).
|
|
7
|
+
* Can return a Promise for async initialization.
|
|
8
|
+
* @returns A handle to control the server lifecycle.
|
|
9
|
+
*/
|
|
10
|
+
export async function serveStdio(serverFactory) {
|
|
11
|
+
const server = await serverFactory();
|
|
12
|
+
const transport = new StdioServerTransport();
|
|
13
|
+
// Set up the closed promise before connecting
|
|
14
|
+
let resolveClose;
|
|
15
|
+
const closedPromise = new Promise((resolve) => {
|
|
16
|
+
resolveClose = resolve;
|
|
17
|
+
});
|
|
18
|
+
transport.onclose = () => {
|
|
19
|
+
resolveClose();
|
|
20
|
+
};
|
|
21
|
+
// connect() automatically calls transport.start() for stdio
|
|
22
|
+
await server.connect(transport);
|
|
23
|
+
let closePromise;
|
|
24
|
+
return {
|
|
25
|
+
close: () => {
|
|
26
|
+
if (!closePromise) {
|
|
27
|
+
closePromise = transport.close().then(() => closedPromise);
|
|
28
|
+
}
|
|
29
|
+
return closePromise;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../src/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAQjF;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,aAAqC,EACd;IACvB,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC;IAErC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAE7C,8CAA8C;IAC9C,IAAI,YAAwB,CAAC;IAC7B,MAAM,aAAa,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACnD,YAAY,GAAG,OAAO,CAAC;IAAA,CACxB,CAAC,CAAC;IAEH,SAAS,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC;QACxB,YAAY,EAAE,CAAC;IAAA,CAChB,CAAC;IAEF,4DAA4D;IAC5D,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,IAAI,YAAuC,CAAC;IAE5C,OAAO;QACL,KAAK,EAAE,GAAG,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,YAAY,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,CAAC;YAC7D,CAAC;YACD,OAAO,YAAY,CAAC;QAAA,CACrB;KACF,CAAC;AAAA,CACH"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handle returned by serve functions for controlling the server lifecycle.
|
|
4
|
+
*/
|
|
5
|
+
export interface ServerHandle {
|
|
6
|
+
/** Close the server and stop accepting new connections. */
|
|
7
|
+
close: () => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Factory function for stateless mode (no session ID).
|
|
11
|
+
* Used by serveStdio and serveHttp without sessions.
|
|
12
|
+
*/
|
|
13
|
+
export type StatelessServerFactory = () => McpServer | Promise<McpServer>;
|
|
14
|
+
/**
|
|
15
|
+
* Factory function for stateful mode (receives session ID).
|
|
16
|
+
* Used by serveHttp with sessions enabled.
|
|
17
|
+
*/
|
|
18
|
+
export type StatefulServerFactory = (sessionId: string) => McpServer | Promise<McpServer>;
|
|
19
|
+
/**
|
|
20
|
+
* Union type for all server factory signatures.
|
|
21
|
+
* @deprecated Prefer using StatelessServerFactory or StatefulServerFactory directly.
|
|
22
|
+
*/
|
|
23
|
+
export type ServerFactory = (sessionId?: string) => McpServer | Promise<McpServer>;
|
|
24
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAE1E;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,SAAS,EAAE,MAAM,KACd,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAEpC;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,SAAS,CAAC,EAAE,MAAM,KACf,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karashiiro/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./stdio": "./dist/stdio.js",
|
|
10
|
+
"./http": "./dist/http.js"
|
|
11
|
+
},
|
|
7
12
|
"files": [
|
|
8
13
|
"dist"
|
|
9
14
|
],
|
|
@@ -12,26 +17,42 @@
|
|
|
12
17
|
"license": "UNLICENSED",
|
|
13
18
|
"devDependencies": {
|
|
14
19
|
"@eslint/js": "^9.39.2",
|
|
20
|
+
"@hono/node-server": "^1.19.8",
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
15
22
|
"@typescript/native-preview": "7.0.0-dev.20260113.1",
|
|
16
23
|
"eslint": "^9.39.2",
|
|
24
|
+
"eventsource": "^4.1.0",
|
|
17
25
|
"get-port": "^7.1.0",
|
|
18
26
|
"globals": "^17.0.0",
|
|
27
|
+
"hono": "^4.11.4",
|
|
19
28
|
"jiti": "^2.6.1",
|
|
20
29
|
"prettier": "^3.7.4",
|
|
21
30
|
"typescript-eslint": "^8.53.0",
|
|
22
|
-
"vitest": "^4.0.17"
|
|
31
|
+
"vitest": "^4.0.17",
|
|
32
|
+
"zod": "^4.3.5"
|
|
23
33
|
},
|
|
24
|
-
"
|
|
34
|
+
"peerDependencies": {
|
|
25
35
|
"@hono/node-server": "^1.19.8",
|
|
26
36
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
27
|
-
"hono": "^4.11.4"
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
"hono": "^4.11.4"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@hono/node-server": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"hono": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"uuid": "^13.0.0"
|
|
30
49
|
},
|
|
31
50
|
"scripts": {
|
|
32
51
|
"build": "tsgo -p tsconfig.dist.json",
|
|
33
|
-
"test": "vitest",
|
|
52
|
+
"test": "vitest --run",
|
|
34
53
|
"lint": "eslint . --ext .ts",
|
|
35
|
-
"format": "prettier --write ."
|
|
54
|
+
"format": "prettier --write .",
|
|
55
|
+
"typecheck": "tsgo --noEmit",
|
|
56
|
+
"check": "pnpm typecheck && pnpm lint && pnpm test"
|
|
36
57
|
}
|
|
37
58
|
}
|