mcp-ts-template 1.7.4 โ†’ 1.7.7

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.
Files changed (29) hide show
  1. package/README.md +42 -46
  2. package/dist/config/index.d.ts +14 -0
  3. package/dist/config/index.js +37 -3
  4. package/dist/mcp-server/server.js +10 -7
  5. package/dist/mcp-server/transports/auth/authFactory.js +10 -0
  6. package/dist/mcp-server/transports/auth/authMiddleware.js +16 -4
  7. package/dist/mcp-server/transports/auth/lib/authUtils.js +21 -14
  8. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +48 -15
  9. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +48 -12
  10. package/dist/mcp-server/transports/core/baseTransportManager.d.ts +17 -0
  11. package/dist/mcp-server/transports/core/baseTransportManager.js +18 -0
  12. package/dist/mcp-server/transports/core/honoNodeBridge.d.ts +23 -0
  13. package/dist/mcp-server/transports/core/honoNodeBridge.js +51 -0
  14. package/dist/mcp-server/transports/core/statefulTransportManager.d.ts +31 -0
  15. package/dist/mcp-server/transports/core/statefulTransportManager.js +233 -0
  16. package/dist/mcp-server/transports/core/statelessTransportManager.d.ts +20 -0
  17. package/dist/mcp-server/transports/core/statelessTransportManager.js +92 -0
  18. package/dist/mcp-server/transports/core/transportTypes.d.ts +23 -29
  19. package/dist/mcp-server/transports/http/httpErrorHandler.d.ts +4 -9
  20. package/dist/mcp-server/transports/http/httpErrorHandler.js +25 -3
  21. package/dist/mcp-server/transports/http/httpTransport.d.ts +6 -30
  22. package/dist/mcp-server/transports/http/httpTransport.js +141 -104
  23. package/dist/mcp-server/transports/http/httpTypes.d.ts +3 -1
  24. package/dist/mcp-server/transports/http/mcpTransportMiddleware.d.ts +25 -0
  25. package/dist/mcp-server/transports/http/mcpTransportMiddleware.js +63 -0
  26. package/dist/mcp-server/transports/stdio/stdioTransport.js +8 -3
  27. package/package.json +1 -1
  28. package/dist/mcp-server/transports/core/mcpTransportManager.d.ts +0 -32
  29. package/dist/mcp-server/transports/core/mcpTransportManager.js +0 -148
package/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-^5.8.3-blue?style=flat-square)](https://www.typescriptlang.org/)
8
8
  [![Model Context Protocol SDK](https://img.shields.io/badge/MCP%20SDK-^1.17.0-green?style=flat-square)](https://github.com/modelcontextprotocol/typescript-sdk)
9
9
  [![MCP Spec Version](https://img.shields.io/badge/MCP%20Spec-2025--06--18-lightgrey?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-06-18/changelog.mdx)
10
- [![Version](https://img.shields.io/badge/Version-1.7.4-blue?style=flat-square)](./CHANGELOG.md)
11
- [![Coverage](https://img.shields.io/badge/Coverage-83.2%25-green?style=flat-square)](./vitest.config.ts)
10
+ [![Version](https://img.shields.io/badge/Version-1.7.7-blue?style=flat-square)](./CHANGELOG.md)
11
+ [![Coverage](https://img.shields.io/badge/Coverage-67.1%25-brightgreen?style=flat-square)](./vitest.config.ts)
12
12
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue?style=flat-square)](https://opensource.org/licenses/Apache-2.0)
13
13
  [![Status](https://img.shields.io/badge/Status-Stable-green?style=flat-square)](https://github.com/cyanheads/mcp-ts-template/issues)
14
14
  [![GitHub](https://img.shields.io/github/stars/cyanheads/mcp-ts-template?style=social)](https://github.com/cyanheads/mcp-ts-template)
@@ -30,36 +30,30 @@ Building a robust server for AI agents is more than just writing code. It requir
30
30
 
31
31
  ## โœจ Key Features
32
32
 
33
- | Feature Area | Description | Key Components / Location |
34
- | :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------- |
35
- | **๐Ÿ”Œ MCP Server** | Functional server with example tools (`EchoTool`, `CatFactFetcher`) and an `EchoResource`. Supports `stdio` and **Streamable HTTP** transports. | `src/mcp-server/` |
36
- | **๐Ÿš€ Production Utilities** | Logging, Error Handling, ID Generation, Rate Limiting, Request Context tracking, Input Sanitization. | `src/utils/` |
37
- | **๐Ÿ”’ Type Safety/Security** | Strong type checking via TypeScript & Zod validation. Built-in security utilities (sanitization, auth middleware for HTTP). | Throughout, `src/utils/security/`, `src/mcp-server/transports/auth/` |
38
- | **โš™๏ธ Error Handling** | Consistent error categorization (`BaseErrorCode`), detailed logging, centralized handling (`ErrorHandler`). | `src/utils/internal/errorHandler.ts`, `src/types-global/` |
39
- | **๐Ÿ“š Documentation** | Comprehensive `README.md`, structured JSDoc comments, API references. | `README.md`, Codebase, `tsdoc.json`, `docs/api-references/` |
40
- | **๐Ÿ•ต๏ธ Interaction Logging** | Captures raw requests and responses for all external LLM provider interactions to a dedicated `interactions.log` file for full traceability. | `src/utils/internal/logger.ts` |
41
- | **๐Ÿค– Agent Ready** | Includes a [.clinerules](.clinerules) developer cheatsheet tailored for LLM coding agents. | `.clinerules` |
42
- | **๐Ÿ› ๏ธ Utility Scripts** | Scripts for cleaning builds, setting executable permissions, generating directory trees, and fetching OpenAPI specs. | `scripts/` |
43
- | **๐Ÿงฉ Services** | Reusable modules for LLM (OpenRouter) and data storage (DuckDB) integration, with examples. | `src/services/`, `src/storage/duckdbExample.ts` |
44
- | **๐Ÿงช Unit Testing** | Integrated with Vitest for fast and reliable unit testing. Includes example tests for core tool logic and a coverage reporter. | `vitest.config.ts`, `tests/` |
45
-
46
- ## ๐ŸŒŸ Projects Using This Template
47
-
48
- This template is already powering several MCP servers, demonstrating its flexibility and robustness:
49
-
50
- | Project | Description |
51
- | :-------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
52
- | [**clinicaltrialsgov-mcp-server**](https://github.com/cyanheads/clinicaltrialsgov-mcp-server) | Provides an LLM-friendly interface to the official ClinicalTrials.gov v2 API, enabling agents to analyze clinical study data. |
53
- | [**pubmed-mcp-server**](https://github.com/cyanheads/pubmed-mcp-server) | Enables AI agents to search, retrieve, and visualize biomedical literature from PubMed via NCBI E-utilities. |
54
- | [**git-mcp-server**](https://github.com/cyanheads/git-mcp-server) | Provides an enterprise-ready MCP interface for Git operations, allowing agents to manage repositories programmatically. |
55
- | [**obsidian-mcp-server**](https://github.com/cyanheads/obsidian-mcp-server) | Allows AI agents to read, write, search, and manage notes in Obsidian via the Local REST API plugin. |
56
- | [**atlas-mcp-server**](https://github.com/cyanheads/atlas-mcp-server) | An advanced task and knowledge management system with a Neo4j backend for structured data organization. |
57
- | [**filesystem-mcp-server**](https://github.com/cyanheads/filesystem-mcp-server) | Offers platform-agnostic file system capabilities for AI agents, including advanced search and directory traversal. |
58
- | [**workflows-mcp-server**](https://github.com/cyanheads/workflows-mcp-server) | A declarative workflow engine that allows agents to execute complex, multi-step automations from simple YAML files. |
59
-
60
- _Note: [**toolkit-mcp-server**](https://github.com/cyanheads/toolkit-mcp-server) was built on an older version of this template and is pending updates._
61
-
62
- You can also **see my [GitHub profile](https://github.com/cyanheads/)** for additional MCP servers I've created.
33
+ | Feature Area | Description | Key Components / Location |
34
+ | :-------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------- |
35
+ | **๐Ÿ”Œ MCP Server** | A functional server with example tools and resources. Supports `stdio` and a **Streamable HTTP** transport built with [**Hono**](https://hono.dev/). | `src/mcp-server/`, `src/mcp-server/transports/` |
36
+ | **๐Ÿš€ Production Utilities** | Logging, Error Handling, ID Generation, Rate Limiting, Request Context tracking, Input Sanitization. | `src/utils/` |
37
+ | **๐Ÿ”’ Type Safety/Security** | Strong type checking via TypeScript & Zod validation. Built-in security utilities (sanitization, auth middleware for HTTP). | Throughout, `src/utils/security/`, `src/mcp-server/transports/auth/` |
38
+ | **โš™๏ธ Error Handling** | Consistent error categorization (`BaseErrorCode`), detailed logging, centralized handling (`ErrorHandler`). | `src/utils/internal/errorHandler.ts`, `src/types-global/` |
39
+ | **๐Ÿ“š Documentation** | Comprehensive `README.md`, structured JSDoc comments, API references. | `README.md`, Codebase, `tsdoc.json`, `docs/api-references/` |
40
+ | **๐Ÿ•ต๏ธ Interaction Logging** | Captures raw requests and responses for all external LLM provider interactions to a dedicated `interactions.log` file for full traceability. | `src/utils/internal/logger.ts` |
41
+ | **๐Ÿค– Agent Ready** | Includes a [.clinerules](.clinerules) developer cheatsheet tailored for LLM coding agents. | `.clinerules` |
42
+ | **๐Ÿ› ๏ธ Utility Scripts** | Scripts for cleaning builds, setting executable permissions, generating directory trees, and fetching OpenAPI specs. | `scripts/` |
43
+ | **๐Ÿงฉ Services** | Reusable modules for LLM (OpenRouter) and data storage (DuckDB) integration, with examples. | `src/services/`, `src/storage/duckdbExample.ts` |
44
+ | **๐Ÿงช Integration Testing** | Integrated with Vitest for fast and reliable integration testing. Includes example tests for core logic and a coverage reporter. | `vitest.config.ts`, `tests/` |
45
+
46
+ ## Architecture Overview
47
+
48
+ This template employs a modular, transport-agnostic architecture to ensure a clean separation of concerns.
49
+
50
+ - **Core Server (`src/mcp-server/server.ts`)**: The central point where tools and resources are registered. It remains independent of how the server is accessed.
51
+ - **Transports (`src/mcp-server/transports/`)**: The transport layer connects the core server to the outside world.
52
+ - **StdioTransport**: For direct process-to-process communication.
53
+ - **HttpTransport**: A modern, streamable HTTP server built with **Hono**. It uses a middleware-based approach for handling CORS, rate limiting, authentication, and MCP request processing.
54
+ - **Transport Managers (`src/mcp-server/transports/core/`)**: These managers bridge the gap between the transport layer (like Hono) and the MCP SDK, handling session management (stateful vs. stateless) and request lifecycle.
55
+
56
+ This design allows you to add new tools and logic to the core server without worrying about the underlying transport details.
63
57
 
64
58
  ## Quick Start
65
59
 
@@ -93,7 +87,7 @@ npm run build
93
87
 
94
88
  ### 4. Running Tests
95
89
 
96
- This template uses [Vitest](https://vitest.dev/) for unit testing. Tests are located in the `tests/` directory, mirroring the `src/` structure.
90
+ This template uses [Vitest](https://vitest.dev/) for testing, with a strong emphasis on **integration testing** to ensure all components work together correctly.
97
91
 
98
92
  - **Run all tests once:**
99
93
  ```bash
@@ -110,16 +104,15 @@ This template uses [Vitest](https://vitest.dev/) for unit testing. Tests are loc
110
104
 
111
105
  ## โš™๏ธ Configuration
112
106
 
113
- ### Server Configuration (Environment Variables)
114
-
115
- Configure the MCP server's behavior using these environment variables:
107
+ Configure the server using these environment variables (or a `.env` file):
116
108
 
117
109
  | Variable | Description | Default |
118
110
  | :-------------------- | :---------------------------------------------------------------------------------------- | :------------------------------------- |
119
111
  | `MCP_TRANSPORT_TYPE` | Server transport: `stdio` or `http`. | `stdio` |
120
- | `MCP_HTTP_PORT` | Port for the HTTP server (if `MCP_TRANSPORT_TYPE=http`). | `3010` |
121
- | `MCP_HTTP_HOST` | Host address for the HTTP server (if `MCP_TRANSPORT_TYPE=http`). | `127.0.0.1` |
122
- | `MCP_ALLOWED_ORIGINS` | Comma-separated allowed origins for CORS (if `MCP_TRANSPORT_TYPE=http`). | (none) |
112
+ | `MCP_SESSION_MODE` | Session mode for HTTP: `stateless`, `stateful`, or `auto`. | `auto` |
113
+ | `MCP_HTTP_PORT` | Port for the HTTP server. | `3010` |
114
+ | `MCP_HTTP_HOST` | Host address for the HTTP server. | `127.0.0.1` |
115
+ | `MCP_ALLOWED_ORIGINS` | Comma-separated allowed origins for CORS. | (none) |
123
116
  | `MCP_AUTH_MODE` | Authentication mode for HTTP: `jwt`, `oauth`, or `none`. | `none` |
124
117
  | `MCP_AUTH_SECRET_KEY` | **Required for `jwt` mode.** Secret key (min 32 chars) for signing/verifying auth tokens. | (none - **MUST be set in production**) |
125
118
  | `OAUTH_ISSUER_URL` | **Required for `oauth` mode.** The issuer URL of your authorization server. | (none) |
@@ -128,12 +121,12 @@ Configure the MCP server's behavior using these environment variables:
128
121
 
129
122
  ## ๐Ÿ—๏ธ Project Structure
130
123
 
131
- - **`src/mcp-server/`**: Contains the MCP server implementation, including example tools, resources, and transport handlers.
132
- - **`src/config/`**: Handles loading and validation of environment variables and application configuration.
133
- - **`src/services/`**: Provides reusable modules for integrating with external services (DuckDB, OpenRouter).
124
+ - **`src/mcp-server/`**: Contains the core MCP server, tools, resources, and transport handlers.
125
+ - **`src/config/`**: Handles loading and validation of environment variables.
126
+ - **`src/services/`**: Reusable modules for integrating with external services (DuckDB, OpenRouter).
134
127
  - **`src/types-global/`**: Defines shared TypeScript interfaces and type definitions.
135
- - **`src/utils/`**: A collection of core utilities (logging, error handling, security, etc.).
136
- - **`src/index.ts`**: The main entry point for the application, responsible for initializing and starting the MCP server.
128
+ - **`src/utils/`**: Core utilities (logging, error handling, security, etc.).
129
+ - **`src/index.ts`**: The main entry point that initializes and starts the server.
137
130
 
138
131
  **Explore the full structure yourself:**
139
132
 
@@ -145,9 +138,12 @@ npm run tree
145
138
 
146
139
  ## ๐Ÿงฉ Extending the System
147
140
 
148
- ### Adding Tools to the Server
141
+ The canonical pattern for adding new tools and resources is defined in the [.clinerules](.clinerules) file. It mandates a strict separation of concerns:
142
+
143
+ 1. **`logic.ts`**: Contains the pure business logic, Zod schemas, and type definitions. This file throws structured errors on failure.
144
+ 2. **`registration.ts`**: Acts as the "handler." It registers the tool with the server, wraps the logic call in a `try...catch` block, and formats the final success or error response.
149
145
 
150
- For detailed guidance on how to add your own custom Tools and Resources to the MCP server, please see the [Server Extension Guide](src/mcp-server/README.md).
146
+ This "Logic Throws, Handler Catches" pattern ensures that core logic remains pure and testable, while the registration layer handles all side effects and response formatting.
151
147
 
152
148
  ## ๐ŸŒ Explore More MCP Resources
153
149
 
@@ -36,10 +36,20 @@ export declare const config: {
36
36
  environment: string;
37
37
  /** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
38
38
  mcpTransportType: "stdio" | "http";
39
+ /** MCP session mode ('stateless', 'stateful', 'auto'). From `MCP_SESSION_MODE` env var. Default: "auto". */
40
+ mcpSessionMode: "stateless" | "stateful" | "auto";
39
41
  /** HTTP server port (if http transport). From `MCP_HTTP_PORT` env var. Default: 3010. */
40
42
  mcpHttpPort: number;
41
43
  /** HTTP server host (if http transport). From `MCP_HTTP_HOST` env var. Default: "127.0.0.1". */
42
44
  mcpHttpHost: string;
45
+ /** MCP endpoint path for HTTP transport. From `MCP_HTTP_ENDPOINT_PATH`. Default: "/mcp". */
46
+ mcpHttpEndpointPath: string;
47
+ /** Max retries for port binding. From `MCP_HTTP_MAX_PORT_RETRIES`. Default: 15. */
48
+ mcpHttpMaxPortRetries: number;
49
+ /** Delay between port binding retries. From `MCP_HTTP_PORT_RETRY_DELAY_MS`. Default: 50. */
50
+ mcpHttpPortRetryDelayMs: number;
51
+ /** Timeout for stale stateful sessions. From `MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS`. Default: 1800000. */
52
+ mcpStatefulSessionStaleTimeoutMs: number;
43
53
  /** Array of allowed CORS origins (http transport). From `MCP_ALLOWED_ORIGINS` (comma-separated). */
44
54
  mcpAllowedOrigins: string[] | undefined;
45
55
  /** Auth secret key (JWTs, http transport). From `MCP_AUTH_SECRET_KEY`. CRITICAL. */
@@ -52,6 +62,10 @@ export declare const config: {
52
62
  oauthJwksUri: string | undefined;
53
63
  /** OAuth 2.1 Audience. From `OAUTH_AUDIENCE`. */
54
64
  oauthAudience: string | undefined;
65
+ /** Development mode client ID. From `DEV_MCP_CLIENT_ID`. */
66
+ devMcpClientId: string | undefined;
67
+ /** Development mode scopes. From `DEV_MCP_SCOPES`. */
68
+ devMcpScopes: string[] | undefined;
55
69
  /** OpenRouter App URL. From `OPENROUTER_APP_URL`. Default: "http://localhost:3000". */
56
70
  openrouterAppUrl: string;
57
71
  /** OpenRouter App Name. From `OPENROUTER_APP_NAME`. Defaults to `mcpServerName`. */
@@ -85,10 +85,28 @@ const EnvSchema = z.object({
85
85
  NODE_ENV: z.string().default("development"),
86
86
  /** MCP communication transport ("stdio" or "http"). Default: "stdio". */
87
87
  MCP_TRANSPORT_TYPE: z.enum(["stdio", "http"]).default("stdio"),
88
+ /** MCP session mode ('stateless', 'stateful', 'auto'). Default: 'auto'. */
89
+ MCP_SESSION_MODE: z.enum(["stateless", "stateful", "auto"]).default("auto"),
88
90
  /** HTTP server port (if MCP_TRANSPORT_TYPE is "http"). Default: 3010. */
89
91
  MCP_HTTP_PORT: z.coerce.number().int().positive().default(3010),
90
92
  /** HTTP server host (if MCP_TRANSPORT_TYPE is "http"). Default: "127.0.0.1". */
91
93
  MCP_HTTP_HOST: z.string().default("127.0.0.1"),
94
+ /** The endpoint path for the MCP server. Default: "/mcp". */
95
+ MCP_HTTP_ENDPOINT_PATH: z.string().default("/mcp"),
96
+ /** Max retries for binding to a port if the initial one is in use. Default: 15. */
97
+ MCP_HTTP_MAX_PORT_RETRIES: z.coerce.number().int().nonnegative().default(15),
98
+ /** Delay in ms between port binding retries. Default: 50. */
99
+ MCP_HTTP_PORT_RETRY_DELAY_MS: z.coerce
100
+ .number()
101
+ .int()
102
+ .nonnegative()
103
+ .default(50),
104
+ /** Timeout in ms for considering a stateful session stale and eligible for cleanup. Default: 1800000 (30 minutes). */
105
+ MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS: z.coerce
106
+ .number()
107
+ .int()
108
+ .positive()
109
+ .default(1800000),
92
110
  /** Optional. Comma-separated allowed origins for CORS (HTTP transport). */
93
111
  MCP_ALLOWED_ORIGINS: z.string().optional(),
94
112
  /** Optional. Secret key (min 32 chars) for auth tokens (HTTP transport). CRITICAL for production. */
@@ -104,6 +122,10 @@ const EnvSchema = z.object({
104
122
  OAUTH_JWKS_URI: z.string().url().optional(),
105
123
  /** The audience claim for the OAuth 2.1 access tokens. This server will reject tokens not intended for it. */
106
124
  OAUTH_AUDIENCE: z.string().optional(),
125
+ /** Optional. Client ID to use in development mode for JWT strategy. Default: "dev-client-id". */
126
+ DEV_MCP_CLIENT_ID: z.string().optional(),
127
+ /** Optional. Comma-separated scopes for development mode JWT strategy. Default: "dev-scope". */
128
+ DEV_MCP_SCOPES: z.string().optional(),
107
129
  /** Optional. Application URL for OpenRouter integration. */
108
130
  OPENROUTER_APP_URL: z
109
131
  .string()
@@ -114,9 +136,7 @@ const EnvSchema = z.object({
114
136
  /** Optional. API key for OpenRouter services. */
115
137
  OPENROUTER_API_KEY: z.string().optional(),
116
138
  /** Default LLM model. Default: "google/gemini-2.5-flash". */
117
- LLM_DEFAULT_MODEL: z
118
- .string()
119
- .default("google/gemini-2.5-flash"),
139
+ LLM_DEFAULT_MODEL: z.string().default("google/gemini-2.5-flash"),
120
140
  /** Optional. Default LLM temperature (0.0-2.0). */
121
141
  LLM_DEFAULT_TEMPERATURE: z.coerce.number().min(0).max(2).optional(),
122
142
  /** Optional. Default LLM top_p (0.0-1.0). */
@@ -262,10 +282,20 @@ export const config = {
262
282
  environment: env.NODE_ENV,
263
283
  /** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
264
284
  mcpTransportType: env.MCP_TRANSPORT_TYPE,
285
+ /** MCP session mode ('stateless', 'stateful', 'auto'). From `MCP_SESSION_MODE` env var. Default: "auto". */
286
+ mcpSessionMode: env.MCP_SESSION_MODE,
265
287
  /** HTTP server port (if http transport). From `MCP_HTTP_PORT` env var. Default: 3010. */
266
288
  mcpHttpPort: env.MCP_HTTP_PORT,
267
289
  /** HTTP server host (if http transport). From `MCP_HTTP_HOST` env var. Default: "127.0.0.1". */
268
290
  mcpHttpHost: env.MCP_HTTP_HOST,
291
+ /** MCP endpoint path for HTTP transport. From `MCP_HTTP_ENDPOINT_PATH`. Default: "/mcp". */
292
+ mcpHttpEndpointPath: env.MCP_HTTP_ENDPOINT_PATH,
293
+ /** Max retries for port binding. From `MCP_HTTP_MAX_PORT_RETRIES`. Default: 15. */
294
+ mcpHttpMaxPortRetries: env.MCP_HTTP_MAX_PORT_RETRIES,
295
+ /** Delay between port binding retries. From `MCP_HTTP_PORT_RETRY_DELAY_MS`. Default: 50. */
296
+ mcpHttpPortRetryDelayMs: env.MCP_HTTP_PORT_RETRY_DELAY_MS,
297
+ /** Timeout for stale stateful sessions. From `MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS`. Default: 1800000. */
298
+ mcpStatefulSessionStaleTimeoutMs: env.MCP_STATEFUL_SESSION_STALE_TIMEOUT_MS,
269
299
  /** Array of allowed CORS origins (http transport). From `MCP_ALLOWED_ORIGINS` (comma-separated). */
270
300
  mcpAllowedOrigins: env.MCP_ALLOWED_ORIGINS?.split(",")
271
301
  .map((origin) => origin.trim())
@@ -280,6 +310,10 @@ export const config = {
280
310
  oauthJwksUri: env.OAUTH_JWKS_URI,
281
311
  /** OAuth 2.1 Audience. From `OAUTH_AUDIENCE`. */
282
312
  oauthAudience: env.OAUTH_AUDIENCE,
313
+ /** Development mode client ID. From `DEV_MCP_CLIENT_ID`. */
314
+ devMcpClientId: env.DEV_MCP_CLIENT_ID,
315
+ /** Development mode scopes. From `DEV_MCP_SCOPES`. */
316
+ devMcpScopes: env.DEV_MCP_SCOPES?.split(",").map((s) => s.trim()),
283
317
  /** OpenRouter App URL. From `OPENROUTER_APP_URL`. Default: "http://localhost:3000". */
284
318
  openrouterAppUrl: env.OPENROUTER_APP_URL || "http://localhost:3000",
285
319
  /** OpenRouter App Name. From `OPENROUTER_APP_NAME`. Defaults to `mcpServerName`. */
@@ -35,11 +35,6 @@ async function createMcpServerInstance() {
35
35
  operation: "createMcpServerInstance",
36
36
  });
37
37
  logger.info("Initializing MCP server instance", context);
38
- requestContextService.configure({
39
- appName: config.mcpServerName,
40
- appVersion: config.mcpServerVersion,
41
- environment,
42
- });
43
38
  const server = new McpServer({ name: config.mcpServerName, version: config.mcpServerVersion }, {
44
39
  capabilities: {
45
40
  logging: {},
@@ -76,12 +71,14 @@ async function startTransport() {
76
71
  transport: transportType,
77
72
  });
78
73
  logger.info(`Starting transport: ${transportType}`, context);
74
+ // Always use the factory pattern
75
+ const serverFactory = createMcpServerInstance;
79
76
  if (transportType === "http") {
80
- const { server } = await startHttpTransport(createMcpServerInstance, context);
77
+ const { server } = await startHttpTransport(serverFactory, context);
81
78
  return server;
82
79
  }
83
80
  if (transportType === "stdio") {
84
- const server = await createMcpServerInstance();
81
+ const server = await serverFactory(); // Call the factory once here
85
82
  await startStdioTransport(server, context);
86
83
  return server;
87
84
  }
@@ -95,6 +92,12 @@ export async function initializeAndStartServer() {
95
92
  operation: "initializeAndStartServer",
96
93
  });
97
94
  logger.info("MCP Server initialization sequence started.", context);
95
+ // Configure the global request context service once at startup.
96
+ requestContextService.configure({
97
+ appName: config.mcpServerName,
98
+ appVersion: config.mcpServerVersion,
99
+ environment,
100
+ });
98
101
  try {
99
102
  const result = await startTransport();
100
103
  logger.info("MCP Server initialization sequence completed successfully.", context);
@@ -5,6 +5,7 @@
5
5
  * @module src/mcp-server/transports/auth/authFactory
6
6
  */
7
7
  import { config } from "../../../config/index.js";
8
+ import { logger, requestContextService } from "../../../utils/index.js";
8
9
  import { JwtStrategy } from "./strategies/jwtStrategy.js";
9
10
  import { OauthStrategy } from "./strategies/oauthStrategy.js";
10
11
  /**
@@ -16,16 +17,25 @@ import { OauthStrategy } from "./strategies/oauthStrategy.js";
16
17
  * @throws {Error} If the auth mode is unknown or misconfigured.
17
18
  */
18
19
  export function createAuthStrategy() {
20
+ const context = requestContextService.createRequestContext({
21
+ operation: "createAuthStrategy",
22
+ authMode: config.mcpAuthMode,
23
+ });
24
+ logger.info("Creating authentication strategy...", context);
19
25
  switch (config.mcpAuthMode) {
20
26
  case "jwt":
27
+ logger.debug("Instantiating JWT authentication strategy.", context);
21
28
  return new JwtStrategy();
22
29
  case "oauth":
30
+ logger.debug("Instantiating OAuth authentication strategy.", context);
23
31
  return new OauthStrategy();
24
32
  case "none":
33
+ logger.info("Authentication is disabled ('none' mode).", context);
25
34
  return null; // No authentication
26
35
  default:
27
36
  // This ensures that if a new auth mode is added to the config type
28
37
  // but not to this factory, we get a compile-time or runtime error.
38
+ logger.error(`Unknown authentication mode: ${config.mcpAuthMode}`, context);
29
39
  throw new Error(`Unknown authentication mode: ${config.mcpAuthMode}`);
30
40
  }
31
41
  }
@@ -1,5 +1,5 @@
1
1
  import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
2
- import { logger, requestContextService } from "../../../utils/index.js";
2
+ import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
3
3
  import { authContext } from "./lib/authContext.js";
4
4
  /**
5
5
  * Creates a Hono middleware function that enforces authentication using a given strategy.
@@ -14,21 +14,27 @@ export function createAuthMiddleware(strategy) {
14
14
  method: c.req.method,
15
15
  path: c.req.path,
16
16
  });
17
+ logger.debug("Initiating authentication check.", context);
17
18
  const authHeader = c.req.header("Authorization");
18
19
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
20
+ logger.warning("Authorization header missing or invalid.", context);
19
21
  throw new McpError(BaseErrorCode.UNAUTHORIZED, "Missing or invalid Authorization header. Bearer scheme required.", context);
20
22
  }
21
23
  const token = authHeader.substring(7);
22
24
  if (!token) {
25
+ logger.warning("Bearer token is missing from Authorization header.", context);
23
26
  throw new McpError(BaseErrorCode.UNAUTHORIZED, "Authentication token is missing.", context);
24
27
  }
28
+ logger.debug("Extracted Bearer token, proceeding to verification.", context);
25
29
  try {
26
30
  const authInfo = await strategy.verify(token);
27
- logger.debug("Authentication successful. Auth context populated.", {
31
+ const authLogContext = {
28
32
  ...context,
29
33
  clientId: authInfo.clientId,
34
+ subject: authInfo.subject,
30
35
  scopes: authInfo.scopes,
31
- });
36
+ };
37
+ logger.info("Authentication successful. Auth context populated.", authLogContext);
32
38
  // Run the next middleware in the chain within the populated auth context.
33
39
  await authContext.run({ authInfo }, next);
34
40
  }
@@ -39,7 +45,13 @@ export function createAuthMiddleware(strategy) {
39
45
  ...context,
40
46
  error: error instanceof Error ? error.message : String(error),
41
47
  });
42
- throw error;
48
+ // Ensure consistent error handling
49
+ throw ErrorHandler.handleError(error, {
50
+ operation: "authMiddlewareVerification",
51
+ context,
52
+ rethrow: true, // Rethrow to be caught by Hono's global error handler
53
+ errorCode: BaseErrorCode.UNAUTHORIZED, // Default to unauthorized if not more specific
54
+ });
43
55
  }
44
56
  };
45
57
  }
@@ -19,27 +19,34 @@ import { authContext } from "./authContext.js";
19
19
  * more required scopes are not present in the validated token.
20
20
  */
21
21
  export function withRequiredScopes(requiredScopes) {
22
+ const operationName = "withRequiredScopesCheck";
23
+ const initialContext = requestContextService.createRequestContext({
24
+ operation: operationName,
25
+ requiredScopes,
26
+ });
27
+ logger.debug("Performing scope authorization check.", initialContext);
22
28
  const store = authContext.getStore();
23
29
  if (!store || !store.authInfo) {
30
+ logger.crit("Authentication context is missing in withRequiredScopes. This is a server configuration error.", initialContext);
24
31
  // This is a server-side logic error; the auth middleware should always populate this.
25
- throw new McpError(BaseErrorCode.INTERNAL_ERROR, "Authentication context is missing. This indicates a server configuration error.", requestContextService.createRequestContext({
26
- operation: "withRequiredScopesCheck",
32
+ throw new McpError(BaseErrorCode.INTERNAL_ERROR, "Authentication context is missing. This indicates a server configuration error.", {
33
+ ...initialContext,
27
34
  error: "AuthStore not found in AsyncLocalStorage.",
28
- }));
35
+ });
29
36
  }
30
- const { scopes: grantedScopes, clientId } = store.authInfo;
37
+ const { scopes: grantedScopes, clientId, subject } = store.authInfo;
31
38
  const grantedScopeSet = new Set(grantedScopes);
32
39
  const missingScopes = requiredScopes.filter((scope) => !grantedScopeSet.has(scope));
40
+ const finalContext = {
41
+ ...initialContext,
42
+ grantedScopes,
43
+ clientId,
44
+ subject,
45
+ };
33
46
  if (missingScopes.length > 0) {
34
- const context = requestContextService.createRequestContext({
35
- operation: "withRequiredScopesCheck",
36
- required: requiredScopes,
37
- granted: grantedScopes,
38
- missing: missingScopes,
39
- clientId: clientId,
40
- subject: store.authInfo.subject,
41
- });
42
- logger.warning("Authorization failed: Missing required scopes.", context);
43
- throw new McpError(BaseErrorCode.FORBIDDEN, `Insufficient permissions. Missing required scopes: ${missingScopes.join(", ")}`, { requiredScopes, missingScopes });
47
+ const errorContext = { ...finalContext, missingScopes };
48
+ logger.warning("Authorization failed: Missing required scopes.", errorContext);
49
+ throw new McpError(BaseErrorCode.FORBIDDEN, `Insufficient permissions. Missing required scopes: ${missingScopes.join(", ")}`, errorContext);
44
50
  }
51
+ logger.debug("Scope authorization successful.", finalContext);
45
52
  }
@@ -8,19 +8,24 @@
8
8
  import { jwtVerify } from "jose";
9
9
  import { config, environment } from "../../../../config/index.js";
10
10
  import { BaseErrorCode, McpError } from "../../../../types-global/errors.js";
11
- import { logger } from "../../../../utils/index.js";
11
+ import { ErrorHandler, logger, requestContextService, } from "../../../../utils/index.js";
12
12
  export class JwtStrategy {
13
13
  constructor() {
14
+ const context = requestContextService.createRequestContext({
15
+ operation: "JwtStrategy.constructor",
16
+ });
17
+ logger.debug("Initializing JwtStrategy...", context);
14
18
  if (config.mcpAuthMode === "jwt") {
15
19
  if (environment === "production" && !config.mcpAuthSecretKey) {
16
- logger.fatal("CRITICAL: MCP_AUTH_SECRET_KEY is not set in production for JWT auth.");
17
- throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "MCP_AUTH_SECRET_KEY must be set for JWT auth in production.");
20
+ logger.fatal("CRITICAL: MCP_AUTH_SECRET_KEY is not set in production for JWT auth.", context);
21
+ throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "MCP_AUTH_SECRET_KEY must be set for JWT auth in production.", context);
18
22
  }
19
23
  else if (!config.mcpAuthSecretKey) {
20
- logger.warning("MCP_AUTH_SECRET_KEY is not set. JWT auth will be bypassed (DEV ONLY).");
24
+ logger.warning("MCP_AUTH_SECRET_KEY is not set. JWT auth will be bypassed (DEV ONLY).", context);
21
25
  this.secretKey = null;
22
26
  }
23
27
  else {
28
+ logger.info("JWT secret key loaded successfully.", context);
24
29
  this.secretKey = new TextEncoder().encode(config.mcpAuthSecretKey);
25
30
  }
26
31
  }
@@ -29,28 +34,38 @@ export class JwtStrategy {
29
34
  }
30
35
  }
31
36
  async verify(token) {
37
+ const context = requestContextService.createRequestContext({
38
+ operation: "JwtStrategy.verify",
39
+ });
40
+ logger.debug("Attempting to verify JWT.", context);
32
41
  // Handle development mode bypass
33
42
  if (!this.secretKey) {
34
43
  if (environment !== "production") {
35
- logger.warning("Bypassing JWT verification: No secret key (DEV ONLY).");
44
+ logger.warning("Bypassing JWT verification: No secret key (DEV ONLY).", context);
36
45
  return {
37
46
  token: "dev-mode-placeholder-token",
38
- clientId: "dev-client-id",
39
- scopes: ["dev-scope"],
47
+ clientId: config.devMcpClientId || "dev-client-id",
48
+ scopes: config.devMcpScopes || ["dev-scope"],
40
49
  };
41
50
  }
42
51
  // This path is defensive. The constructor should prevent this state in production.
43
- throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "Auth secret key is missing in production. This indicates a server configuration error.");
52
+ logger.crit("Auth secret key is missing in production.", context);
53
+ throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "Auth secret key is missing in production. This indicates a server configuration error.", context);
44
54
  }
45
55
  try {
46
56
  const { payload: decoded } = await jwtVerify(token, this.secretKey);
57
+ logger.debug("JWT signature verified successfully.", {
58
+ ...context,
59
+ claims: decoded,
60
+ });
47
61
  const clientId = typeof decoded.cid === "string"
48
62
  ? decoded.cid
49
63
  : typeof decoded.client_id === "string"
50
64
  ? decoded.client_id
51
65
  : undefined;
52
66
  if (!clientId) {
53
- throw new McpError(BaseErrorCode.UNAUTHORIZED, "Invalid token: missing 'cid' or 'client_id' claim.");
67
+ logger.warning("Invalid token: missing 'cid' or 'client_id' claim.", context);
68
+ throw new McpError(BaseErrorCode.UNAUTHORIZED, "Invalid token: missing 'cid' or 'client_id' claim.", context);
54
69
  }
55
70
  let scopes = [];
56
71
  if (Array.isArray(decoded.scp) &&
@@ -61,18 +76,36 @@ export class JwtStrategy {
61
76
  scopes = decoded.scope.split(" ").filter(Boolean);
62
77
  }
63
78
  if (scopes.length === 0) {
64
- throw new McpError(BaseErrorCode.UNAUTHORIZED, "Token must contain valid, non-empty scopes.");
79
+ logger.warning("Invalid token: missing or empty 'scp' or 'scope' claim.", context);
80
+ throw new McpError(BaseErrorCode.UNAUTHORIZED, "Token must contain valid, non-empty scopes.", context);
65
81
  }
66
- return { token, clientId, scopes };
82
+ const authInfo = {
83
+ token,
84
+ clientId,
85
+ scopes,
86
+ subject: decoded.sub,
87
+ };
88
+ logger.info("JWT verification successful.", {
89
+ ...context,
90
+ clientId,
91
+ scopes,
92
+ });
93
+ return authInfo;
67
94
  }
68
95
  catch (error) {
69
- if (error instanceof McpError)
70
- throw error;
71
96
  const message = error instanceof Error && error.name === "JWTExpired"
72
97
  ? "Token has expired."
73
98
  : "Token verification failed.";
74
- throw new McpError(BaseErrorCode.UNAUTHORIZED, message, {
75
- originalError: error instanceof Error ? error.name : "Unknown",
99
+ logger.warning(`JWT verification failed: ${message}`, {
100
+ ...context,
101
+ errorName: error instanceof Error ? error.name : "Unknown",
102
+ });
103
+ throw ErrorHandler.handleError(error, {
104
+ operation: "JwtStrategy.verify",
105
+ context,
106
+ rethrow: true,
107
+ errorCode: BaseErrorCode.UNAUTHORIZED,
108
+ errorMapper: () => new McpError(BaseErrorCode.UNAUTHORIZED, message, context),
76
109
  });
77
110
  }
78
111
  }