@ttoss/http-server-mcp 0.18.1 → 0.19.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 +72 -9
- package/dist/index.cjs +4 -0
- package/dist/index.d.cts +5 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.mjs +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -213,15 +213,19 @@ The router emits `401 Unauthorized` whenever `verifyToken` throws, regardless of
|
|
|
213
213
|
Inside any tool handler, call `getIdentity()` to retrieve the verified JWT payload:
|
|
214
214
|
|
|
215
215
|
```typescript
|
|
216
|
-
import {
|
|
216
|
+
import {
|
|
217
|
+
getIdentity,
|
|
218
|
+
createMcpRouter,
|
|
219
|
+
McpServer,
|
|
220
|
+
} from '@ttoss/http-server-mcp';
|
|
217
221
|
|
|
218
222
|
mcpServer.registerTool(
|
|
219
223
|
'get-profile',
|
|
220
|
-
{ description:
|
|
224
|
+
{ description: "Return the caller's profile", inputSchema: {} },
|
|
221
225
|
async () => {
|
|
222
|
-
const identity = getIdentity
|
|
226
|
+
const identity = getIdentity<{ sub: string; email: string }>();
|
|
223
227
|
return {
|
|
224
|
-
content: [{ type: 'text', text: `Hello, ${identity
|
|
228
|
+
content: [{ type: 'text', text: `Hello, ${identity?.email}` }],
|
|
225
229
|
};
|
|
226
230
|
}
|
|
227
231
|
);
|
|
@@ -253,7 +257,7 @@ mcpServer.registerTool(
|
|
|
253
257
|
async ({ userId }) => {
|
|
254
258
|
checkScopes(['admin', 'write:users']); // throws if either scope is missing
|
|
255
259
|
|
|
256
|
-
const identity = getIdentity
|
|
260
|
+
const identity = getIdentity<{ sub: string }>();
|
|
257
261
|
// proceed with deletion...
|
|
258
262
|
return { content: [{ type: 'text', text: `Deleted ${userId}` }] };
|
|
259
263
|
}
|
|
@@ -404,11 +408,14 @@ Generic HTTP helper for use inside MCP tool handlers.
|
|
|
404
408
|
|
|
405
409
|
**Returns:** `Promise<unknown>` — Parsed JSON response body
|
|
406
410
|
|
|
407
|
-
### `getIdentity()`
|
|
411
|
+
### `getIdentity<T>()`
|
|
408
412
|
|
|
409
413
|
Returns the verified JWT payload for the current MCP request. Only available inside a tool handler when `auth` is configured. Returns `undefined` when called outside an authenticated context.
|
|
410
414
|
|
|
411
|
-
|
|
415
|
+
Accepts an optional type parameter so tool handlers can avoid manual casts:
|
|
416
|
+
`getIdentity<{ sub: string; scope: string }>()` returns `T | undefined` instead of `unknown`.
|
|
417
|
+
|
|
418
|
+
**Returns:** `T | undefined` — Verified token payload typed as `T` (defaults to `unknown`)
|
|
412
419
|
|
|
413
420
|
### `checkScopes(required)`
|
|
414
421
|
|
|
@@ -659,8 +666,64 @@ This package implements the [Model Context Protocol](https://spec.modelcontextpr
|
|
|
659
666
|
|
|
660
667
|
### Stateless vs stateful mode
|
|
661
668
|
|
|
662
|
-
|
|
663
|
-
|
|
669
|
+
The router runs **stateless by default**: each request creates a fresh transport, and no `Mcp-Session-Id` is issued. This is the right mode for Bearer/API-token auth, serverless functions, and any multi-instance deployment, because every request carries its own identity through `auth.verifyToken` — there is no session to coordinate across instances.
|
|
670
|
+
|
|
671
|
+
Pass `sessionIdGenerator` only when you have a genuine session requirement: server-initiated events over SSE, or streaming that must preserve context across multiple requests. Stateful mode keeps a single shared transport per session, so it needs session affinity (or shared state) when running behind more than one instance.
|
|
672
|
+
|
|
673
|
+
If you authenticate with `auth.verifyToken`, you do not need `sessionIdGenerator`. The identity is resolved from the token on every request, so adding session tracking only adds coordination cost. Mixing the two — stateful transport plus per-request token auth — is the common source of "tools/call fails after initialize" bugs: the client binds to a session the auth layer never consults.
|
|
674
|
+
|
|
675
|
+
| Mode | When | Trade-off |
|
|
676
|
+
| ------------------------------- | --------------------------------------------- | ------------------------------------- |
|
|
677
|
+
| Stateless (default) | Bearer/API tokens, serverless, multi-instance | DB/verify hit per request |
|
|
678
|
+
| Stateful (`sessionIdGenerator`) | SSE events, context-preserving streams | Needs session affinity / shared state |
|
|
679
|
+
|
|
680
|
+
## Testing an MCP server
|
|
681
|
+
|
|
682
|
+
MCP requests are plain JSON-RPC POSTs to the router path. The `initialize` and `tools/list` methods are public by default, so they need no auth header; `tools/call` runs through `auth.verifyToken`. The client must send `Accept: application/json, text/event-stream` — the transport rejects requests that do not accept the event-stream media type.
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
const res = await request(app.callback())
|
|
686
|
+
.post('/mcp')
|
|
687
|
+
.set('Content-Type', 'application/json')
|
|
688
|
+
.set('Accept', 'application/json, text/event-stream')
|
|
689
|
+
.send({
|
|
690
|
+
jsonrpc: '2.0',
|
|
691
|
+
id: 1,
|
|
692
|
+
method: 'initialize',
|
|
693
|
+
params: {
|
|
694
|
+
protocolVersion: '2025-06-18',
|
|
695
|
+
capabilities: {},
|
|
696
|
+
clientInfo: { name: 'test', version: '1.0.0' },
|
|
697
|
+
},
|
|
698
|
+
});
|
|
699
|
+
expect(res.status).toBe(200);
|
|
700
|
+
// Stateless mode (default): no session header is issued.
|
|
701
|
+
// Stateful mode (sessionIdGenerator set): assert the header instead.
|
|
702
|
+
expect(res.headers['mcp-session-id']).toBeUndefined();
|
|
703
|
+
|
|
704
|
+
// A tool call carries identity through the Authorization header, not a session.
|
|
705
|
+
const call = await request(app.callback())
|
|
706
|
+
.post('/mcp')
|
|
707
|
+
.set('Content-Type', 'application/json')
|
|
708
|
+
.set('Accept', 'application/json, text/event-stream')
|
|
709
|
+
.set('Authorization', `Bearer ${token}`)
|
|
710
|
+
.send({
|
|
711
|
+
jsonrpc: '2.0',
|
|
712
|
+
id: 2,
|
|
713
|
+
method: 'tools/call',
|
|
714
|
+
params: { name: 'my-tool', arguments: {} },
|
|
715
|
+
});
|
|
716
|
+
expect(call.status).toBe(200);
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### `publicMethods` and OAuth clients
|
|
720
|
+
|
|
721
|
+
`auth.publicMethods` defaults to `['initialize', 'tools/list']`, which lets clients discover the server before they have a token. This default has an important effect on OAuth-aware clients (e.g. a Claude connector):
|
|
722
|
+
|
|
723
|
+
- With the default, `initialize` returns `200` unauthenticated, so an OAuth client concludes the server is public and never starts the OAuth flow — while `notifications/initialized` still returns `401`, breaking the handshake. The visible symptom is "connected, no tools available, no sign-in prompt".
|
|
724
|
+
- Setting `publicMethods: []` makes `initialize` return `401` + `WWW-Authenticate`, which triggers the client's OAuth discovery and PKCE flow.
|
|
725
|
+
|
|
726
|
+
Use the default when token auth is handled outside the OAuth flow (Cognito, API keys, Bearer tokens). Set `publicMethods: []` only when you want OAuth clients to self-discover and authenticate before anything else.
|
|
664
727
|
|
|
665
728
|
## AWS Lambda Deployment
|
|
666
729
|
|
package/dist/index.cjs
CHANGED
|
@@ -117,6 +117,10 @@ var apiCall = async (method, url, options) => {
|
|
|
117
117
|
/**
|
|
118
118
|
* Returns the verified JWT payload for the current MCP request.
|
|
119
119
|
* Only available inside a tool handler when `auth` is configured on the router.
|
|
120
|
+
*
|
|
121
|
+
* Accepts an optional type parameter to avoid casting at call sites:
|
|
122
|
+
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
123
|
+
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
120
124
|
*/
|
|
121
125
|
var getIdentity = () => {
|
|
122
126
|
return requestContextStore.getStore()?.identity;
|
package/dist/index.d.cts
CHANGED
|
@@ -790,8 +790,12 @@ declare const apiCall: (method: string, url: string, options?: ApiCallOptions) =
|
|
|
790
790
|
/**
|
|
791
791
|
* Returns the verified JWT payload for the current MCP request.
|
|
792
792
|
* Only available inside a tool handler when `auth` is configured on the router.
|
|
793
|
+
*
|
|
794
|
+
* Accepts an optional type parameter to avoid casting at call sites:
|
|
795
|
+
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
796
|
+
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
793
797
|
*/
|
|
794
|
-
declare const getIdentity: () =>
|
|
798
|
+
declare const getIdentity: <T = unknown>() => T | undefined;
|
|
795
799
|
/**
|
|
796
800
|
* Asserts that the current request's token contains all required scopes.
|
|
797
801
|
* Throws if any scope is missing — the MCP SDK catches this and returns a
|
package/dist/index.d.mts
CHANGED
|
@@ -790,8 +790,12 @@ declare const apiCall: (method: string, url: string, options?: ApiCallOptions) =
|
|
|
790
790
|
/**
|
|
791
791
|
* Returns the verified JWT payload for the current MCP request.
|
|
792
792
|
* Only available inside a tool handler when `auth` is configured on the router.
|
|
793
|
+
*
|
|
794
|
+
* Accepts an optional type parameter to avoid casting at call sites:
|
|
795
|
+
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
796
|
+
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
793
797
|
*/
|
|
794
|
-
declare const getIdentity: () =>
|
|
798
|
+
declare const getIdentity: <T = unknown>() => T | undefined;
|
|
795
799
|
/**
|
|
796
800
|
* Asserts that the current request's token contains all required scopes.
|
|
797
801
|
* Throws if any scope is missing — the MCP SDK catches this and returns a
|
package/dist/index.mjs
CHANGED
|
@@ -114,6 +114,10 @@ var apiCall = async (method, url, options) => {
|
|
|
114
114
|
/**
|
|
115
115
|
* Returns the verified JWT payload for the current MCP request.
|
|
116
116
|
* Only available inside a tool handler when `auth` is configured on the router.
|
|
117
|
+
*
|
|
118
|
+
* Accepts an optional type parameter to avoid casting at call sites:
|
|
119
|
+
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
120
|
+
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
117
121
|
*/
|
|
118
122
|
var getIdentity = () => {
|
|
119
123
|
return requestContextStore.getStore()?.identity;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
37
|
"zod": "^4.4.3",
|
|
38
|
-
"@ttoss/http-server-auth": "^0.3.5",
|
|
39
38
|
"@ttoss/auth-core": "^0.8.0",
|
|
39
|
+
"@ttoss/http-server-auth": "^0.3.5",
|
|
40
40
|
"@ttoss/http-server": "^0.7.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|