tiny-http-mcp-server 0.1.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/LICENSE +21 -0
- package/README.md +778 -0
- package/dist/auth.d.ts +69 -0
- package/dist/auth.js +261 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.js +465 -0
- package/dist/composition.json +25 -0
- package/dist/express-middleware.d.ts +18 -0
- package/dist/express-middleware.js +91 -0
- package/dist/http-server.d.ts +48 -0
- package/dist/http-server.js +263 -0
- package/dist/http-transport.d.ts +164 -0
- package/dist/http-transport.js +897 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +6 -0
- package/dist/load-oauth-verifier.d.ts +6 -0
- package/dist/load-oauth-verifier.js +43 -0
- package/dist/parse-body.d.ts +22 -0
- package/dist/parse-body.js +150 -0
- package/dist/session.d.ts +18 -0
- package/dist/session.js +37 -0
- package/dist/sse.d.ts +11 -0
- package/dist/sse.js +22 -0
- package/dist/test-support.d.ts +10 -0
- package/dist/test-support.js +398 -0
- package/dist/testing.d.ts +59 -0
- package/dist/testing.js +191 -0
- package/node_modules/auth-store/LICENSE +21 -0
- package/node_modules/auth-store/README.md +62 -0
- package/node_modules/auth-store/dist/create-secret-store.d.ts +2 -0
- package/node_modules/auth-store/dist/create-secret-store.js +44 -0
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +47 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +303 -0
- package/node_modules/auth-store/dist/error-codes.d.ts +1 -0
- package/node_modules/auth-store/dist/error-codes.js +5 -0
- package/node_modules/auth-store/dist/index.d.ts +7 -0
- package/node_modules/auth-store/dist/index.js +4 -0
- package/node_modules/auth-store/dist/keychain-store.d.ts +25 -0
- package/node_modules/auth-store/dist/keychain-store.js +154 -0
- package/node_modules/auth-store/dist/provider-store.d.ts +14 -0
- package/node_modules/auth-store/dist/provider-store.js +78 -0
- package/node_modules/auth-store/dist/types.d.ts +22 -0
- package/node_modules/auth-store/dist/types.js +1 -0
- package/node_modules/auth-store/package.json +27 -0
- package/node_modules/mcp-oauth/LICENSE +21 -0
- package/node_modules/mcp-oauth/README.md +70 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +14 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +169 -0
- package/node_modules/mcp-oauth/dist/client/authorization-state.d.ts +8 -0
- package/node_modules/mcp-oauth/dist/client/authorization-state.js +47 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.d.ts +3 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +627 -0
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +20 -0
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +207 -0
- package/node_modules/mcp-oauth/dist/client/pkce.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/pkce.js +7 -0
- package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +40 -0
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +164 -0
- package/node_modules/mcp-oauth/dist/client/types.d.ts +113 -0
- package/node_modules/mcp-oauth/dist/client/types.js +1 -0
- package/node_modules/mcp-oauth/dist/index.d.ts +10 -0
- package/node_modules/mcp-oauth/dist/index.js +7 -0
- package/node_modules/mcp-oauth/dist/resource-indicator.d.ts +1 -0
- package/node_modules/mcp-oauth/dist/resource-indicator.js +11 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +32 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +388 -0
- package/node_modules/mcp-oauth/dist/types.compile-check.d.ts +1 -0
- package/node_modules/mcp-oauth/dist/types.compile-check.js +22 -0
- package/node_modules/mcp-oauth/package.json +33 -0
- package/node_modules/tiny-mcp-client/LICENSE +21 -0
- package/node_modules/tiny-mcp-client/README.md +104 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +660 -0
- package/node_modules/tiny-mcp-client/dist/index.js +3870 -0
- package/node_modules/tiny-mcp-client/package.json +30 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
# tiny-http-mcp-server
|
|
2
|
+
|
|
3
|
+
Streamable HTTP transport for tiny MCP servers. It builds on top of `tiny-stdio-mcp-server` and gives you:
|
|
4
|
+
|
|
5
|
+
- A standalone HTTP server with `listenHttp()`
|
|
6
|
+
- An Express middleware adapter
|
|
7
|
+
- A `handleRequest()` API for raw Node.js servers
|
|
8
|
+
- Testing helpers for HTTP MCP integration tests
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Node.js 20+ is required.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install tiny-http-mcp-server
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If you want to mount it in Express:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install tiny-http-mcp-server express
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If you want to use the testing helpers with the official MCP SDK:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm install -D @modelcontextprotocol/sdk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start: Standalone Server
|
|
31
|
+
|
|
32
|
+
### Programmatic
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { createHttpServer, defineSchema } from "tiny-http-mcp-server";
|
|
36
|
+
|
|
37
|
+
const schema = defineSchema({
|
|
38
|
+
text: { type: "string", description: "Text to reverse" }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const server = createHttpServer({
|
|
42
|
+
name: "my-http-server",
|
|
43
|
+
version: "1.0.0"
|
|
44
|
+
}).tool("reverse", "Reverse a string", schema, ({ text }) => {
|
|
45
|
+
return text.split("").reverse().join("");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const handle = await server.listenHttp({
|
|
49
|
+
port: 3000,
|
|
50
|
+
hostname: "127.0.0.1",
|
|
51
|
+
path: "/mcp"
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log(handle.url);
|
|
55
|
+
|
|
56
|
+
process.on("SIGINT", () => {
|
|
57
|
+
void handle.close();
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`listenHttp()` starts a Node HTTP server and returns a handle with:
|
|
62
|
+
|
|
63
|
+
- `url`: full MCP endpoint URL
|
|
64
|
+
- `port`: resolved TCP port
|
|
65
|
+
- `close()`: graceful shutdown for the HTTP listener and transport
|
|
66
|
+
- `closeAllConnections()`: force-close remaining HTTP connections after a shutdown grace period
|
|
67
|
+
|
|
68
|
+
By default, programmatic `listenHttp()` uses:
|
|
69
|
+
|
|
70
|
+
- `port: 0`
|
|
71
|
+
- `hostname: "127.0.0.1"`
|
|
72
|
+
- `path: "/mcp"`
|
|
73
|
+
|
|
74
|
+
`path` is normalized, so `"mcp"` and `"/mcp"` serve the same endpoint.
|
|
75
|
+
|
|
76
|
+
### CLI
|
|
77
|
+
|
|
78
|
+
The package ships a `tiny-http-mcp-server` binary:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
npx tiny-http-mcp-server --port 3000
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
It prints the listening URL to stdout and stays alive until it receives `SIGINT` or `SIGTERM`.
|
|
85
|
+
|
|
86
|
+
The CLI starts a minimal HTTP MCP server with the package name/version and no custom tools. It is useful for smoke tests, transport debugging, and verifying client behavior.
|
|
87
|
+
|
|
88
|
+
## Quick Start: Express Middleware Mount
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import express from "express";
|
|
92
|
+
import { createExpressMiddleware, createHttpServer, defineSchema } from "tiny-http-mcp-server";
|
|
93
|
+
|
|
94
|
+
const app = express();
|
|
95
|
+
|
|
96
|
+
const server = createHttpServer({
|
|
97
|
+
name: "express-mcp-server",
|
|
98
|
+
version: "1.0.0"
|
|
99
|
+
}).tool("echo", "Echo text", defineSchema({ text: { type: "string" } }), ({ text }) => text);
|
|
100
|
+
|
|
101
|
+
app.use("/mcp", createExpressMiddleware(server));
|
|
102
|
+
|
|
103
|
+
app.listen(3000, "127.0.0.1");
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`createExpressMiddleware(server)` returns an Express `RequestHandler` that forwards MCP HTTP traffic to `server.handleRequest(req, res)`.
|
|
107
|
+
|
|
108
|
+
Do not mount `express.json()` or another body parser before the MCP middleware. The transport reads the raw request stream so it can enforce `maxRequestBytes` and return JSON-RPC parse error `-32700`. Any body parser mounted first takes over request-size limits and parse-error semantics; for example, Express defaults to a 100 KB JSON limit and returns its own HTML errors.
|
|
109
|
+
|
|
110
|
+
Use this when you want to:
|
|
111
|
+
|
|
112
|
+
- Reuse an existing Express app
|
|
113
|
+
- Put auth middleware in front of the MCP endpoint
|
|
114
|
+
- Mount MCP on a custom subpath like `/api/v1/mcp`
|
|
115
|
+
|
|
116
|
+
## OAuth Protected Resource
|
|
117
|
+
|
|
118
|
+
To publish RFC 9728 protected-resource metadata and require a Bearer header on MCP requests, pass `oauth` to `createHttpServer()`:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import express from "express";
|
|
122
|
+
import {
|
|
123
|
+
createHttpServer,
|
|
124
|
+
createExpressOAuthHandlers,
|
|
125
|
+
createJwksTokenVerifier
|
|
126
|
+
} from "tiny-http-mcp-server";
|
|
127
|
+
|
|
128
|
+
const app = express();
|
|
129
|
+
|
|
130
|
+
const oauth = {
|
|
131
|
+
resource: "https://example.com/mcp",
|
|
132
|
+
authorizationServers: ["https://auth.example.com"],
|
|
133
|
+
bearerMethodsSupported: ["header"],
|
|
134
|
+
scopesSupported: ["mcp.read", "mcp.write"],
|
|
135
|
+
requiredScopes: ["mcp.read"],
|
|
136
|
+
verifier: createJwksTokenVerifier({
|
|
137
|
+
jwksUrl: "https://auth.example.com/.well-known/jwks.json",
|
|
138
|
+
requireAccessTokenType: true
|
|
139
|
+
})
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const server = createHttpServer({
|
|
143
|
+
name: "oauth-server",
|
|
144
|
+
version: "1.0.0",
|
|
145
|
+
oauth
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const { metadataMiddleware, mcpMiddleware } = createExpressOAuthHandlers({
|
|
149
|
+
path: "/mcp",
|
|
150
|
+
server,
|
|
151
|
+
oauth
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
app.use(metadataMiddleware);
|
|
155
|
+
app.use("/mcp", mcpMiddleware);
|
|
156
|
+
|
|
157
|
+
app.listen(3000, "127.0.0.1");
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`oauth` currently supports:
|
|
161
|
+
|
|
162
|
+
- `resource`: canonical protected resource URI published in the metadata document
|
|
163
|
+
- `authorizationServers`: authorization server issuer URLs published as `authorization_servers`
|
|
164
|
+
- `requiredScopes`: optional scopes enforced on MCP requests
|
|
165
|
+
- `bearerMethodsSupported`: optional values published as `bearer_methods_supported`
|
|
166
|
+
- `scopesSupported`: optional values published as `scopes_supported`
|
|
167
|
+
- `verifier`: `TokenVerifier` implementation used to validate bearer tokens
|
|
168
|
+
|
|
169
|
+
For JWT bearer tokens signed by an authorization server JWKS endpoint, use the exported `createJwksTokenVerifier()` helper:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const verifier = createJwksTokenVerifier({
|
|
173
|
+
jwksUrl: "https://auth.example.com/.well-known/jwks.json",
|
|
174
|
+
jwksFetchTimeoutMs: 5000,
|
|
175
|
+
jwksRefreshCooldownMs: 30000,
|
|
176
|
+
requireAccessTokenType: true
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`createJwksTokenVerifier(options)` accepts:
|
|
181
|
+
|
|
182
|
+
| Option | Type | Default | Description |
|
|
183
|
+
| ------------------------ | ------------------- | -------------- | ---------------------------------------------------------------------------------------- |
|
|
184
|
+
| `jwksUrl` | `string \| URL` | none | Authorization server JWKS endpoint. |
|
|
185
|
+
| `clockSkewSeconds` | `number` | `30` | Allowed JWT time-claim clock skew. |
|
|
186
|
+
| `allowedAlgorithms` | `readonly string[]` | asymmetric set | Allowed JWT signature algorithms. |
|
|
187
|
+
| `jwksCacheTtlMs` | `number` | `300000` | Successful JWKS cache lifetime. |
|
|
188
|
+
| `jwksFetchTimeoutMs` | `number` | `5000` | Timeout for each JWKS HTTP fetch. |
|
|
189
|
+
| `jwksRefreshCooldownMs` | `number` | `30000` | Minimum interval between forced refreshes after an unknown key id. |
|
|
190
|
+
| `allowInsecureJwks` | `boolean` | `false` | Permit non-HTTPS JWKS URLs. Loopback HTTP URLs are allowed without enabling this option. |
|
|
191
|
+
| `requireAccessTokenType` | `boolean` | `false` | Require the JWT `typ` protected header to be `at+jwt`. |
|
|
192
|
+
| `fetch` | `typeof fetch` | global `fetch` | Custom fetch implementation. |
|
|
193
|
+
|
|
194
|
+
For opaque access tokens, implement `TokenVerifier` with RFC 7662 token introspection:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { TokenVerificationError, type TokenVerifier } from "tiny-http-mcp-server";
|
|
198
|
+
|
|
199
|
+
const verifier: TokenVerifier = {
|
|
200
|
+
async verify({ token, resource, authorizationServers, requiredScopes }) {
|
|
201
|
+
const response = await fetch("https://auth.example.com/oauth2/introspect", {
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers: {
|
|
204
|
+
authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`,
|
|
205
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
206
|
+
},
|
|
207
|
+
body: new URLSearchParams({ token, token_type_hint: "access_token" })
|
|
208
|
+
});
|
|
209
|
+
if (!response.ok) {
|
|
210
|
+
throw Object.assign(new Error("introspection unavailable"), {
|
|
211
|
+
error: "temporarily_unavailable"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const claims = (await response.json()) as Record<string, unknown>;
|
|
216
|
+
const scopes = typeof claims.scope === "string" ? claims.scope.split(" ").filter(Boolean) : [];
|
|
217
|
+
const audience =
|
|
218
|
+
typeof claims.aud === "string"
|
|
219
|
+
? [claims.aud]
|
|
220
|
+
: Array.isArray(claims.aud) && claims.aud.every((value) => typeof value === "string")
|
|
221
|
+
? claims.aud
|
|
222
|
+
: [];
|
|
223
|
+
const issuer = typeof claims.iss === "string" ? claims.iss : "";
|
|
224
|
+
const expiresAt = typeof claims.exp === "number" ? claims.exp : 0;
|
|
225
|
+
if (
|
|
226
|
+
claims.active !== true ||
|
|
227
|
+
!authorizationServers.includes(issuer) ||
|
|
228
|
+
!audience.includes(resource) ||
|
|
229
|
+
expiresAt <= Math.floor(Date.now() / 1000)
|
|
230
|
+
) {
|
|
231
|
+
throw new TokenVerificationError({ error: "invalid_token" });
|
|
232
|
+
}
|
|
233
|
+
if (!requiredScopes.every((scope) => scopes.includes(scope))) {
|
|
234
|
+
throw new TokenVerificationError({ error: "insufficient_scope", scope: requiredScopes });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
token,
|
|
239
|
+
issuer,
|
|
240
|
+
audience,
|
|
241
|
+
scopes,
|
|
242
|
+
expiresAt,
|
|
243
|
+
claims,
|
|
244
|
+
...(typeof claims.sub === "string" ? { subject: claims.sub } : {}),
|
|
245
|
+
...(typeof claims.client_id === "string" ? { clientId: claims.client_id } : {})
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Keep introspection credentials server-side, authenticate the introspection request using the method required by your authorization server, and validate any additional issuer, audience, token-type, or expiry rules your deployment requires.
|
|
252
|
+
|
|
253
|
+
When OAuth is enabled, the server exposes `GET /.well-known/oauth-protected-resource` with `application/json`:
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"resource": "https://example.com/mcp",
|
|
258
|
+
"authorization_servers": ["https://auth.example.com"],
|
|
259
|
+
"bearer_methods_supported": ["header"],
|
|
260
|
+
"scopes_supported": ["mcp.read", "mcp.write"]
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Unauthenticated requests to the MCP endpoint return `401` with:
|
|
265
|
+
|
|
266
|
+
```text
|
|
267
|
+
WWW-Authenticate: Bearer realm="mcp", resource_metadata="http://127.0.0.1:3000/.well-known/oauth-protected-resource"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Standalone `listenHttp()` serves both the MCP endpoint and the protected-resource metadata route. For Express, mount `metadataMiddleware` at the app root and mount `mcpMiddleware` on your MCP path, as shown above. Do not put a body parser before `mcpMiddleware`.
|
|
271
|
+
|
|
272
|
+
OAuth sessions are bound to the verified token `subject`, falling back to `clientId` when the subject is absent. This auth-subject binding prevents a different identity from reusing the session id: mismatched requests receive `404`, just like an unknown session. Tokens with neither a non-empty subject nor client id create unbound sessions.
|
|
273
|
+
|
|
274
|
+
For non-HTTP integrations, `createProtectedResourceMetadataDocument(oauth)` returns the metadata JSON document without creating middleware.
|
|
275
|
+
|
|
276
|
+
`createExpressOAuthHandlers()` also accepts:
|
|
277
|
+
|
|
278
|
+
- `trustedProxy`: trust `X-Forwarded-Proto` and `X-Forwarded-Host` when building metadata challenge URLs.
|
|
279
|
+
- `observability`: emit auth failure events through the same observability hook shape used by the HTTP transport.
|
|
280
|
+
|
|
281
|
+
The package does not define any OAuth-specific environment variables. Configure OAuth with the `oauth` object in code or the CLI flags below.
|
|
282
|
+
|
|
283
|
+
## Production Deployment
|
|
284
|
+
|
|
285
|
+
Prefer the standalone `listenHttp()` server behind a TLS-terminating reverse proxy such as nginx or an AWS Application Load Balancer. Reserve the Express adapter for embedding MCP into an existing Express application; it adds middleware-ordering concerns without replacing the transport's production controls.
|
|
286
|
+
|
|
287
|
+
Production checklist:
|
|
288
|
+
|
|
289
|
+
- Set `allowedHosts` to the public MCP hostname, such as `mcp.example.com`. The loopback-only default intentionally returns `403` for public hostnames.
|
|
290
|
+
- When TLS terminates at the proxy, set `trustedProxy: true` and have the proxy replace `X-Forwarded-Proto` and `X-Forwarded-Host`. Only trust these headers when requests can reach the server exclusively through that proxy.
|
|
291
|
+
- Set `allowedOrigins` only when browser-based clients need CORS. Non-browser MCP clients do not require it.
|
|
292
|
+
- Apply explicit limits for the workload: `maxRequestBytes` around 1-4 MiB, `maxBatchSize` around `16`, plus bounded `maxSessions`, `sessionTtlMs` around 15 minutes, `maxConcurrentToolCalls`, and `toolCallTimeoutMs`.
|
|
293
|
+
- Configure `requestTimeoutMs`, `headersTimeoutMs`, and `keepAliveTimeoutMs` deliberately. Keep Node timeouts that can end proxied work above the proxy idle timeout so the proxy owns idle connection cleanup.
|
|
294
|
+
- Keep `maxStreamsPerSession` at its default of `1` unless clients genuinely need parallel GET SSE streams. Bound slow consumers with `maxStreamBufferBytes` and the replay window with `maxSseEventHistory`.
|
|
295
|
+
- Configure graceful shutdown. The CLI defaults `--shutdown-grace-ms` to 10 seconds. Programmatic deployments should start `handle.close()`, then call `handle.closeAllConnections()` only if their own grace deadline expires first.
|
|
296
|
+
- Use OAuth with `createJwksTokenVerifier()` for JWT access tokens, or a custom `TokenVerifier` for opaque tokens. Leave `allowInsecureJwks` disabled outside local development and consider `requireAccessTokenType: true` when the issuer emits RFC 9068 access-token JWTs.
|
|
297
|
+
- Put request-rate and connection-rate limits at the reverse proxy, before requests consume Node streams, sessions, or tool-call capacity.
|
|
298
|
+
|
|
299
|
+
For nginx, disable response buffering for SSE, use HTTP/1.1 to the upstream, and keep `proxy_read_timeout` greater than `sseKeepAliveMs`:
|
|
300
|
+
|
|
301
|
+
```nginx
|
|
302
|
+
location /mcp {
|
|
303
|
+
proxy_pass http://127.0.0.1:3000;
|
|
304
|
+
proxy_http_version 1.1;
|
|
305
|
+
proxy_buffering off;
|
|
306
|
+
proxy_read_timeout 75s; # greater than the default 30s sseKeepAliveMs
|
|
307
|
+
proxy_set_header Host $host;
|
|
308
|
+
proxy_set_header X-Forwarded-Host $host;
|
|
309
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Wire `observability.onEvent` to structured logs or metrics. This console recipe can be replaced directly with `logger.info(event, "mcp.http")` for pino:
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
const server = createHttpServer({
|
|
317
|
+
name: "production-server",
|
|
318
|
+
version: "1.0.0",
|
|
319
|
+
observability: {
|
|
320
|
+
onEvent(event) {
|
|
321
|
+
console.info(JSON.stringify({ component: "mcp.http", ...event }));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Multiple Instances and Sticky Routing
|
|
328
|
+
|
|
329
|
+
A custom `sessionStore` can preserve the session record so an instance can reconstruct session lifecycle state, including the `authSubject` used to bind an OAuth session to its verified identity. It does not make the HTTP transport distributed: active SSE streams and `Last-Event-ID` replay history remain in memory on the instance that created them.
|
|
330
|
+
|
|
331
|
+
Horizontal scaling therefore requires sticky routing by `Mcp-Session-Id` so every request for a session reaches the same instance. Restarting or rerouting an instance loses its live streams and replay history even when the session record survives. `maxSseEventHistory` bounds how many instance-local events can be replayed; it is not a shared event log.
|
|
332
|
+
|
|
333
|
+
## BYO HTTP Server: Raw Node.js
|
|
334
|
+
|
|
335
|
+
If you already own the HTTP server, call `handleRequest()` yourself.
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import http from "node:http";
|
|
339
|
+
import { createHttpServer, defineSchema } from "tiny-http-mcp-server";
|
|
340
|
+
|
|
341
|
+
const server = createHttpServer({
|
|
342
|
+
name: "raw-http-server",
|
|
343
|
+
version: "1.0.0"
|
|
344
|
+
}).tool("uppercase", "Uppercase text", defineSchema({ text: { type: "string" } }), ({ text }) =>
|
|
345
|
+
text.toUpperCase()
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const nodeServer = http.createServer(async (req, res) => {
|
|
349
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
350
|
+
|
|
351
|
+
if (url.pathname !== "/mcp") {
|
|
352
|
+
res.writeHead(404);
|
|
353
|
+
res.end();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
await server.handleRequest(req, res);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
nodeServer.listen(3000, "127.0.0.1");
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Use this when you need full control over routing, TLS termination, or HTTP server lifecycle.
|
|
364
|
+
|
|
365
|
+
## Stateless Mode
|
|
366
|
+
|
|
367
|
+
By default, the transport creates MCP sessions and uses the `Mcp-Session-Id` header for follow-up `POST`, `GET`, and `DELETE` requests.
|
|
368
|
+
|
|
369
|
+
To disable sessions entirely, set `sessionIdGenerator` to `undefined`:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
const server = createHttpServer({
|
|
373
|
+
name: "stateless-server",
|
|
374
|
+
version: "1.0.0",
|
|
375
|
+
sessionIdGenerator: undefined
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
In stateless mode:
|
|
380
|
+
|
|
381
|
+
- `POST` requests work without `Mcp-Session-Id`
|
|
382
|
+
- Responses do not include `Mcp-Session-Id`
|
|
383
|
+
- `GET` returns `405`
|
|
384
|
+
- `DELETE` returns `405`
|
|
385
|
+
|
|
386
|
+
CLI equivalent:
|
|
387
|
+
|
|
388
|
+
```sh
|
|
389
|
+
npx tiny-http-mcp-server --stateless
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## API Reference
|
|
393
|
+
|
|
394
|
+
The package re-exports the base server helpers from `tiny-stdio-mcp-server`, so you can import `defineSchema`, `createServer`, `Image`, `Audio`, `File`, and related types from here as well.
|
|
395
|
+
|
|
396
|
+
HTTP tools have the same typed-output behavior as stdio tools. Pass an optional root-object `outputSchema` to `.tool(...)` to advertise MCP `Tool.outputSchema`, validate handler results, return `CallToolResult.structuredContent`, and keep a JSON text backstop in `content[]` for older clients. Omit `outputSchema` for prose, image, audio, file, and other content-block tools.
|
|
397
|
+
|
|
398
|
+
### `createHttpServer(options)`
|
|
399
|
+
|
|
400
|
+
Creates an MCP server with HTTP transport helpers attached.
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
import { createHttpServer } from "tiny-http-mcp-server";
|
|
404
|
+
|
|
405
|
+
const server = createHttpServer({
|
|
406
|
+
name: "my-server",
|
|
407
|
+
version: "1.0.0"
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Returned `HttpServer` instances support:
|
|
412
|
+
|
|
413
|
+
- `.tool(name, description, schema, handler, outputSchema?)` to register tools
|
|
414
|
+
- `.registerTool(definition, handler)` to register tools with full MCP metadata
|
|
415
|
+
- `.listenHttp(options?)` to start a standalone Node HTTP server
|
|
416
|
+
- `.handleRequest(req, res)` to plug into an existing HTTP stack
|
|
417
|
+
|
|
418
|
+
#### `createHttpServer(options)` config
|
|
419
|
+
|
|
420
|
+
`createHttpServer()` accepts the base `ServerOptions` from `tiny-stdio-mcp-server` plus HTTP transport options:
|
|
421
|
+
|
|
422
|
+
| Option | Type | Default | Description |
|
|
423
|
+
| ------------------------ | -------------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
424
|
+
| `name` | `string` | none | MCP server name exposed during initialization. |
|
|
425
|
+
| `version` | `string` | none | MCP server version exposed during initialization. |
|
|
426
|
+
| `toolCallTimeoutMs` | `number` | unlimited | Positive integer timeout in milliseconds. Returns `-32603` without cancelling the handler. |
|
|
427
|
+
| `sessionIdGenerator` | `(() => string) \| undefined` | built-in visible ASCII generator | Generates new session ids. Pass `undefined` to disable sessions entirely. |
|
|
428
|
+
| `enableJsonResponse` | `boolean` | `false` | Return `application/json` bodies for `POST` responses instead of `text/event-stream`. |
|
|
429
|
+
| `allowedHosts` | `readonly string[]` | loopback hosts | Allowed `Host` header values for DNS rebinding protection. |
|
|
430
|
+
| `allowedOrigins` | `readonly string[]` | `[]` | Allowed CORS `Origin` values. Empty means no cross-origin browser clients are allowed. |
|
|
431
|
+
| `maxRequestBytes` | `number` | unlimited | Maximum JSON request body size. |
|
|
432
|
+
| `maxBatchSize` | `number` | unlimited | Maximum JSON-RPC batch member count. |
|
|
433
|
+
| `maxSessions` | `number` | unlimited | Maximum active sessions. |
|
|
434
|
+
| `sessionTtlMs` | `number` | no idle expiry | Expire idle sessions after this duration. |
|
|
435
|
+
| `maxStreamsPerSession` | `number` | `1` | Maximum concurrent GET SSE streams per session. |
|
|
436
|
+
| `maxStreamBufferBytes` | `number` | `1048576` | End a GET SSE stream before a live write when its buffered bytes exceed this limit. |
|
|
437
|
+
| `maxSseEventHistory` | `number` | `100` | Number of server-sent events retained for `Last-Event-ID` replay. |
|
|
438
|
+
| `sseKeepAliveMs` | `number` | `30000` | GET SSE keepalive interval in milliseconds. Set to `0` to disable keepalive comments. |
|
|
439
|
+
| `maxConcurrentToolCalls` | `number` | unlimited | Maximum concurrent tool calls across sessions. |
|
|
440
|
+
| `sessionStore` | `SessionStore` | in-memory store | Pluggable session-record storage; SSE streams and replay history remain instance-local. |
|
|
441
|
+
| `requestIdGenerator` | `() => string` | incrementing ids | Generates request ids when `X-Request-Id` is absent. |
|
|
442
|
+
| `observability` | `HttpObservabilityOptions` | none | Emits request, auth, session, stream, and tool lifecycle events. |
|
|
443
|
+
| `trustedProxy` | `boolean` | `false` | Trust `X-Forwarded-Proto` and `X-Forwarded-Host` for metadata challenge URLs. |
|
|
444
|
+
| `oauth` | `TinyHttpMcpServerOAuthOptions \| undefined` | `undefined` | Enables OAuth protected-resource metadata and bearer-token verification for the MCP endpoint. |
|
|
445
|
+
|
|
446
|
+
### `registerTool(definition, handler)`
|
|
447
|
+
|
|
448
|
+
Registers a tool using the complete MCP tool definition. Use it instead of `.tool(...)` when you need fields such as `title`, `annotations`, `execution`, `icons`, or `_meta`.
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
server.registerTool(
|
|
452
|
+
{
|
|
453
|
+
name: "lookup",
|
|
454
|
+
title: "Lookup",
|
|
455
|
+
description: "Look up a record",
|
|
456
|
+
inputSchema: defineSchema({ id: { type: "string" } }),
|
|
457
|
+
annotations: { readOnlyHint: true }
|
|
458
|
+
},
|
|
459
|
+
async ({ id }, context) => {
|
|
460
|
+
return `Lookup ${id} for session ${context.sessionId ?? "stateless"}`;
|
|
461
|
+
}
|
|
462
|
+
);
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
The handler receives the same `HttpToolContext` as `.tool(...)`. The optional `outputSchema` in the definition enables typed structured-output validation.
|
|
466
|
+
|
|
467
|
+
### `createExpressMiddleware(server)`
|
|
468
|
+
|
|
469
|
+
Adapts an `HttpServer` into Express:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
import { createExpressMiddleware } from "tiny-http-mcp-server";
|
|
473
|
+
|
|
474
|
+
app.use("/mcp", createExpressMiddleware(server));
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Behavior:
|
|
478
|
+
|
|
479
|
+
- Returns an Express `RequestHandler`
|
|
480
|
+
- Passes request failures to `next(error)`
|
|
481
|
+
- Works with normal Express middleware ordering, including authentication middleware
|
|
482
|
+
- Must be mounted before any body parser that would consume the MCP request stream
|
|
483
|
+
|
|
484
|
+
### Types
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
import type {
|
|
488
|
+
HttpListenOptions,
|
|
489
|
+
HttpServer,
|
|
490
|
+
HttpServerHandle,
|
|
491
|
+
TinyHttpMcpServerOAuthOptions,
|
|
492
|
+
HttpObservabilityOptions,
|
|
493
|
+
HttpTransportOptions,
|
|
494
|
+
Session,
|
|
495
|
+
SessionStore,
|
|
496
|
+
StreamableHttpTransportOptions
|
|
497
|
+
} from "tiny-http-mcp-server";
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### `HttpListenOptions`
|
|
501
|
+
|
|
502
|
+
Options for `server.listenHttp()`:
|
|
503
|
+
|
|
504
|
+
| Option | Type | Default | Description |
|
|
505
|
+
| -------------------- | ------------- | ------------- | --------------------------------------------------------------------------------------------- |
|
|
506
|
+
| `port` | `number` | `0` | TCP port to bind to. Use `0` for an ephemeral port. |
|
|
507
|
+
| `hostname` | `string` | `"127.0.0.1"` | Interface/host to bind to. IPv4, hostnames, and IPv6 literals are supported. |
|
|
508
|
+
| `path` | `string` | `"/mcp"` | URL pathname to serve the MCP endpoint on. `mcp` and `/mcp` are normalized to the same value. |
|
|
509
|
+
| `signal` | `AbortSignal` | none | Aborts the listener and closes the server when triggered. |
|
|
510
|
+
| `requestTimeoutMs` | `number` | Node default | Sets `http.Server.requestTimeout`. |
|
|
511
|
+
| `headersTimeoutMs` | `number` | Node default | Sets `http.Server.headersTimeout`. |
|
|
512
|
+
| `keepAliveTimeoutMs` | `number` | Node default | Sets `http.Server.keepAliveTimeout`. |
|
|
513
|
+
|
|
514
|
+
#### `HttpServerHandle`
|
|
515
|
+
|
|
516
|
+
Returned by `listenHttp()`:
|
|
517
|
+
|
|
518
|
+
| Property | Type | Description |
|
|
519
|
+
| --------------------- | --------------------- | ----------------------------------------------------------------------------------------- |
|
|
520
|
+
| `url` | `string` | Full MCP endpoint URL. |
|
|
521
|
+
| `port` | `number` | Resolved TCP port. |
|
|
522
|
+
| `close` | `() => Promise<void>` | Gracefully shuts down the listener and transport. |
|
|
523
|
+
| `closeAllConnections` | `() => void` | Force-closes all remaining HTTP connections. Use only after a graceful shutdown deadline. |
|
|
524
|
+
|
|
525
|
+
#### `HttpTransportOptions` / `StreamableHttpTransportOptions`
|
|
526
|
+
|
|
527
|
+
`HttpTransportOptions` combines the base `ServerOptions` with `StreamableHttpTransportOptions` and the optional OAuth config. The table notes options that are not part of the lower-level transport type.
|
|
528
|
+
|
|
529
|
+
| Option | Type | Default | Description |
|
|
530
|
+
| ------------------------ | -------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------- |
|
|
531
|
+
| `toolCallTimeoutMs` | `number` | unlimited | `HttpTransportOptions` only. Returns `-32603` on timeout without cancelling the handler. |
|
|
532
|
+
| `sessionIdGenerator` | `(() => string) \| undefined` | built-in generator | Controls session support and session id creation. |
|
|
533
|
+
| `enableJsonResponse` | `boolean` | `false` | Switches `POST` responses from SSE framing to plain JSON responses. |
|
|
534
|
+
| `allowedHosts` | `readonly string[]` | loopback hosts | Allowed `Host` header values. |
|
|
535
|
+
| `allowedOrigins` | `readonly string[]` | `[]` | Allowed CORS origins. |
|
|
536
|
+
| `maxRequestBytes` | `number` | unlimited | Maximum JSON request body size. |
|
|
537
|
+
| `maxBatchSize` | `number` | unlimited | Maximum JSON-RPC batch member count. |
|
|
538
|
+
| `maxSessions` | `number` | unlimited | Maximum active sessions. |
|
|
539
|
+
| `sessionTtlMs` | `number` | no idle expiry | Idle session expiration window. |
|
|
540
|
+
| `maxStreamsPerSession` | `number` | `1` | Maximum concurrent GET SSE streams per session. |
|
|
541
|
+
| `maxStreamBufferBytes` | `number` | `1048576` | End a GET SSE stream before a live write when its buffered bytes exceed this limit. |
|
|
542
|
+
| `maxSseEventHistory` | `number` | `100` | Number of SSE events retained for replay. |
|
|
543
|
+
| `sseKeepAliveMs` | `number` | `30000` | GET SSE keepalive interval in milliseconds. Set to `0` to disable keepalive comments. |
|
|
544
|
+
| `maxConcurrentToolCalls` | `number` | unlimited | Maximum concurrent tool calls across sessions. |
|
|
545
|
+
| `sessionStore` | `SessionStore` | in-memory store | Pluggable session-record storage; SSE and replay state remain local to each instance. |
|
|
546
|
+
| `requestIdGenerator` | `() => string` | incrementing ids | Request id generator used when the request lacks `X-Request-Id`. |
|
|
547
|
+
| `observability` | `HttpObservabilityOptions` | none | Event hook for request, auth, session, stream, and tool lifecycle telemetry. |
|
|
548
|
+
| `trustedProxy` | `boolean` | `false` | Trust forwarded host/proto headers for metadata challenge URLs. |
|
|
549
|
+
| `oauth` | `TinyHttpMcpServerOAuthOptions \| undefined` | `undefined` | Publishes RFC 9728 metadata and protects the MCP endpoint with bearer-token verification. |
|
|
550
|
+
|
|
551
|
+
#### `HttpServer`
|
|
552
|
+
|
|
553
|
+
`HttpServer` extends the base tiny stdio server with HTTP methods:
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
interface HttpServer {
|
|
557
|
+
tool<TIn, TOut>(
|
|
558
|
+
name: string,
|
|
559
|
+
description: string,
|
|
560
|
+
inputSchema: TypedSchema<TIn>,
|
|
561
|
+
handler: HttpToolHandler<TIn, TOut>,
|
|
562
|
+
outputSchema?: TypedSchema<TOut>
|
|
563
|
+
): HttpServer;
|
|
564
|
+
registerTool<TIn, TOut>(
|
|
565
|
+
definition: Omit<ToolDefinition<TIn, TOut>, "handler">,
|
|
566
|
+
handler: HttpToolHandler<TIn, TOut>
|
|
567
|
+
): HttpServer;
|
|
568
|
+
listenHttp(options?: HttpListenOptions): Promise<HttpServerHandle>;
|
|
569
|
+
handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
interface HttpServerHandle {
|
|
573
|
+
url: string;
|
|
574
|
+
port: number;
|
|
575
|
+
close(): Promise<void>;
|
|
576
|
+
closeAllConnections(): void;
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Invalid typed handler results are treated as server bugs and fail the JSON-RPC call with an internal `ToolError`, matching `tiny-stdio-mcp-server`.
|
|
581
|
+
|
|
582
|
+
#### `HttpToolContext`
|
|
583
|
+
|
|
584
|
+
HTTP tool handlers receive request-specific context as their second argument:
|
|
585
|
+
|
|
586
|
+
```ts
|
|
587
|
+
interface HttpToolContext {
|
|
588
|
+
request: AuthenticatedIncomingMessage;
|
|
589
|
+
sessionId?: string;
|
|
590
|
+
auth?: RequestAuthInfo;
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
- `request`: the Node.js incoming request for HTTP calls.
|
|
595
|
+
- `sessionId`: the `Mcp-Session-Id` request header, or `undefined` for initialization, stateless, and non-HTTP calls.
|
|
596
|
+
- `auth`: the verified `RequestAuthInfo` attached to `request.auth`, or `undefined` when the request is unauthenticated.
|
|
597
|
+
|
|
598
|
+
Calls made directly through `server.handleMessage()` do not have an HTTP request. Their fallback `request` has empty `headers` and `socket` objects, so reads such as `context.request.headers["x-custom-header"]` safely return `undefined`.
|
|
599
|
+
|
|
600
|
+
## CLI Usage
|
|
601
|
+
|
|
602
|
+
```sh
|
|
603
|
+
tiny-http-mcp-server [options]
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Flags
|
|
607
|
+
|
|
608
|
+
| Flag | Default | Description |
|
|
609
|
+
| -------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
610
|
+
| `--port <port>` | `3000` | Port to listen on. Use `0` for an ephemeral port. |
|
|
611
|
+
| `--hostname <hostname>` | `127.0.0.1` | Hostname/interface to bind to. IPv4, hostnames, and IPv6 literals such as `::1` are supported. |
|
|
612
|
+
| `--path <path>` | `/mcp` | MCP endpoint path. `api/mcp` and `/api/mcp` are equivalent. |
|
|
613
|
+
| `--stateless` | off | Disable session support. |
|
|
614
|
+
| `--json-response` | off | Return `application/json` for `POST` responses. |
|
|
615
|
+
| `--allowed-host <host>` | loopback | Allowed `Host` header value. Repeat the flag for multiple hosts. |
|
|
616
|
+
| `--allowed-origin <url>` | none | Allowed CORS origin. Repeat the flag for multiple origins. Path/query are normalized to the URL origin. |
|
|
617
|
+
| `--max-request-bytes <bytes>` | unlimited | Maximum JSON request body size. |
|
|
618
|
+
| `--max-batch-size <count>` | unlimited | Maximum JSON-RPC batch member count. |
|
|
619
|
+
| `--max-sessions <count>` | unlimited | Maximum active sessions. |
|
|
620
|
+
| `--session-ttl-ms <ms>` | none | Expire idle sessions after this duration. |
|
|
621
|
+
| `--max-streams-per-session <count>` | `1` | Maximum concurrent GET SSE streams per session. |
|
|
622
|
+
| `--max-stream-buffer-bytes <bytes>` | `1048576` | End a GET SSE stream before a live notification or keepalive write when buffered bytes exceed this limit. |
|
|
623
|
+
| `--max-sse-event-history <count>` | `100` | Number of SSE events retained for `Last-Event-ID` replay. |
|
|
624
|
+
| `--sse-keep-alive-ms <ms>` | `30000` | GET SSE keepalive interval in milliseconds. Set to `0` to disable keepalive comments. |
|
|
625
|
+
| `--max-concurrent-tool-calls <count>` | unlimited | Maximum concurrent tool calls across sessions. |
|
|
626
|
+
| `--trusted-proxy` | off | Trust `X-Forwarded-Proto` and `X-Forwarded-Host` for metadata challenge URLs. |
|
|
627
|
+
| `--request-timeout-ms <ms>` | Node default | Node HTTP request timeout. |
|
|
628
|
+
| `--headers-timeout-ms <ms>` | Node default | Node HTTP headers timeout. |
|
|
629
|
+
| `--keep-alive-timeout-ms <ms>` | Node default | Node HTTP keep-alive timeout. |
|
|
630
|
+
| `--shutdown-grace-ms <ms>` | `10000` | Grace period after the first `SIGINT` or `SIGTERM` before remaining connections are force-closed and the CLI exits non-zero. |
|
|
631
|
+
| `--oauth-resource <uri>` | none | Enable OAuth mode with this canonical protected resource URI. Requires `--oauth-authorization-server` and `--oauth-verifier-module`. |
|
|
632
|
+
| `--oauth-authorization-server <issuer>` | none | Authorization server issuer URL to publish in metadata. Repeat the flag for multiple issuers. |
|
|
633
|
+
| `--oauth-supported-scope <scope>` | none | Scope to publish in `scopes_supported`. Repeat the flag for multiple scopes. |
|
|
634
|
+
| `--oauth-required-scope <scope>` | none | Scope required on incoming MCP requests. Repeat the flag for multiple scopes. |
|
|
635
|
+
| `--oauth-bearer-method <method>` | none | Bearer transport to publish in `bearer_methods_supported`. Repeat the flag for multiple methods. |
|
|
636
|
+
| `--oauth-verifier-module <path-or-file-url>` | none | Module path, `file:` URL, or package specifier that exports the `TokenVerifier` used in CLI mode. |
|
|
637
|
+
| `--oauth-verifier-export <name>` | `default` | Named export to load from `--oauth-verifier-module`. |
|
|
638
|
+
| `--version` | off | Print the package version and exit. |
|
|
639
|
+
| `-h`, `--help` | off | Print help and exit. |
|
|
640
|
+
|
|
641
|
+
Examples:
|
|
642
|
+
|
|
643
|
+
```sh
|
|
644
|
+
tiny-http-mcp-server --port 8080 --path /api/mcp
|
|
645
|
+
tiny-http-mcp-server --port 0 --stateless --json-response
|
|
646
|
+
tiny-http-mcp-server \
|
|
647
|
+
--port 8080 \
|
|
648
|
+
--allowed-host mcp.example.com \
|
|
649
|
+
--allowed-origin https://app.example.com \
|
|
650
|
+
--max-request-bytes 1048576 \
|
|
651
|
+
--max-batch-size 16 \
|
|
652
|
+
--max-sessions 1000 \
|
|
653
|
+
--session-ttl-ms 900000 \
|
|
654
|
+
--max-streams-per-session 2 \
|
|
655
|
+
--max-concurrent-tool-calls 32 \
|
|
656
|
+
--request-timeout-ms 30000
|
|
657
|
+
tiny-http-mcp-server \
|
|
658
|
+
--oauth-resource https://example.com/mcp \
|
|
659
|
+
--oauth-authorization-server https://auth.example.com \
|
|
660
|
+
--oauth-supported-scope mcp.read \
|
|
661
|
+
--oauth-required-scope mcp.read \
|
|
662
|
+
--oauth-verifier-module ./verify-token.mjs
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
## Testing Helpers
|
|
666
|
+
|
|
667
|
+
Testing helpers are exported from the package subpath:
|
|
668
|
+
|
|
669
|
+
```ts
|
|
670
|
+
import {
|
|
671
|
+
createHttpTestPair,
|
|
672
|
+
createHttpTestPairWithTinyClient,
|
|
673
|
+
createTestMcpServer
|
|
674
|
+
} from "tiny-http-mcp-server/testing";
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### `createHttpTestPair(server)`
|
|
678
|
+
|
|
679
|
+
Starts an `HttpServer`, connects an official MCP SDK client to it, and returns:
|
|
680
|
+
|
|
681
|
+
- `client`: `@modelcontextprotocol/sdk` client
|
|
682
|
+
- `transport`: SDK streamable HTTP client transport
|
|
683
|
+
- `handle`: `HttpServerHandle`
|
|
684
|
+
- `url`: endpoint URL
|
|
685
|
+
- `cleanup()`: closes client and server
|
|
686
|
+
|
|
687
|
+
Example:
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
import { expect, test } from "vitest";
|
|
691
|
+
import { createHttpTestPair, createTestMcpServer } from "tiny-http-mcp-server/testing";
|
|
692
|
+
|
|
693
|
+
test("calls a tool over HTTP", async () => {
|
|
694
|
+
const pair = await createHttpTestPair(createTestMcpServer());
|
|
695
|
+
|
|
696
|
+
try {
|
|
697
|
+
const result = await pair.client.callTool({
|
|
698
|
+
name: "echo",
|
|
699
|
+
arguments: { text: "hello" }
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
expect(result.content).toEqual([{ type: "text", text: "hello" }]);
|
|
703
|
+
} finally {
|
|
704
|
+
await pair.cleanup();
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### `createTestMcpServer(options?)`
|
|
710
|
+
|
|
711
|
+
Creates a ready-made `HttpServer` for integration and conformance tests. It includes tools such as:
|
|
712
|
+
|
|
713
|
+
- `echo`, `reverse`, `uppercase` — text transformations
|
|
714
|
+
- `get_user`, `get_list` — structured data
|
|
715
|
+
- `get_image`, `get_audio`, `get_file`, `get_mixed` — binary/resource content blocks
|
|
716
|
+
- `throw_sync`, `throw_async` — error handling scenarios
|
|
717
|
+
- `empty_result`, `slow`, `large_output` — edge-case coverage
|
|
718
|
+
|
|
719
|
+
Supported options:
|
|
720
|
+
|
|
721
|
+
| Option | Type | Default |
|
|
722
|
+
| -------------------- | ------------------------------- | -------------------------------------- |
|
|
723
|
+
| `name` | `string` | `"conformance-test-server"` |
|
|
724
|
+
| `version` | `string` | `"1.0.0"` |
|
|
725
|
+
| `enableJsonResponse` | `boolean` | inherited default (`false`) |
|
|
726
|
+
| `sessionIdGenerator` | `(() => string) \| undefined` | inherited default (built-in generator) |
|
|
727
|
+
| `oauth` | `TinyHttpMcpServerOAuthOptions` | none |
|
|
728
|
+
|
|
729
|
+
Example:
|
|
730
|
+
|
|
731
|
+
```ts
|
|
732
|
+
const server = createTestMcpServer({
|
|
733
|
+
enableJsonResponse: true,
|
|
734
|
+
sessionIdGenerator: undefined
|
|
735
|
+
});
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### `createHttpTestPairWithTinyClient(server)`
|
|
739
|
+
|
|
740
|
+
Like `createHttpTestPair`, but connects a [`tiny-mcp-client`](https://www.npmjs.com/package/tiny-mcp-client) transport instead of the official SDK. Returns `null` when `tiny-mcp-client` is not installed.
|
|
741
|
+
|
|
742
|
+
The returned `TinyHttpTestPair` includes a `requests` array that logs every HTTP request the client makes — useful for asserting transport-level behavior (session headers, `DELETE` teardown, SSE vs JSON responses).
|
|
743
|
+
|
|
744
|
+
```ts
|
|
745
|
+
import { expect, test } from "vitest";
|
|
746
|
+
import {
|
|
747
|
+
createHttpTestPairWithTinyClient,
|
|
748
|
+
createTestMcpServer
|
|
749
|
+
} from "tiny-http-mcp-server/testing";
|
|
750
|
+
|
|
751
|
+
test("tiny-mcp-client sends DELETE on close", async () => {
|
|
752
|
+
const pair = await createHttpTestPairWithTinyClient(createTestMcpServer());
|
|
753
|
+
if (pair === null) return; // tiny-mcp-client not installed
|
|
754
|
+
|
|
755
|
+
try {
|
|
756
|
+
await pair.client.callTool({ name: "echo", arguments: { text: "hi" } });
|
|
757
|
+
await pair.client.close();
|
|
758
|
+
|
|
759
|
+
expect(pair.requests.some((r) => r.method === "DELETE")).toBe(true);
|
|
760
|
+
} finally {
|
|
761
|
+
await pair.cleanup();
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
## Environment Variables
|
|
767
|
+
|
|
768
|
+
This package does not use any environment variables.
|
|
769
|
+
|
|
770
|
+
All runtime configuration is passed through function options or CLI flags.
|
|
771
|
+
|
|
772
|
+
## Configuration Options
|
|
773
|
+
|
|
774
|
+
Programmatic configuration is passed through `createHttpServer(options)`, `listenHttp(options)`, OAuth options, and testing helper options documented above. CLI configuration is passed through the flags in [CLI Usage](#cli-usage); there is no config file.
|
|
775
|
+
|
|
776
|
+
## License
|
|
777
|
+
|
|
778
|
+
MIT
|