@ttoss/http-server-mcp 0.18.0 → 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 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 { getIdentity, createMcpRouter, McpServer } from '@ttoss/http-server-mcp';
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: 'Return the caller's profile', inputSchema: {} },
224
+ { description: "Return the caller's profile", inputSchema: {} },
221
225
  async () => {
222
- const identity = getIdentity() as { sub: string; email: string };
226
+ const identity = getIdentity<{ sub: string; email: string }>();
223
227
  return {
224
- content: [{ type: 'text', text: `Hello, ${identity.email}` }],
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() as { sub: string };
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
- **Returns:** `unknown` — Verified token payload (cast to your expected shape)
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
- - **Stateless** (default, `sessionIdGenerator: undefined`) — a fresh transport is created per HTTP request. No session tracking. Suitable for serverless environments and simple integrations.
663
- - **Stateful** (`sessionIdGenerator` provided) — a single shared transport handles all requests and tracks sessions by ID.
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: () => unknown;
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: () => unknown;
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.18.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/auth-core": "^0.7.0",
39
- "@ttoss/http-server-auth": "^0.3.4",
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": {