mcp-ts-template 1.7.2 → 1.7.3
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 +38 -59
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +2 -2
- package/dist/mcp-server/server.js +5 -4
- package/dist/mcp-server/tools/echoTool/logic.js +1 -2
- package/dist/mcp-server/tools/imageTest/logic.js +3 -1
- package/dist/mcp-server/transports/auth/authFactory.d.ts +10 -0
- package/dist/mcp-server/transports/auth/authFactory.js +31 -0
- package/dist/mcp-server/transports/auth/authMiddleware.d.ts +25 -0
- package/dist/mcp-server/transports/auth/authMiddleware.js +48 -0
- package/dist/mcp-server/transports/auth/index.d.ts +8 -5
- package/dist/mcp-server/transports/auth/index.js +6 -4
- package/dist/mcp-server/transports/auth/{core → lib}/authTypes.d.ts +0 -5
- package/dist/mcp-server/transports/auth/lib/authTypes.js +8 -0
- package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts +17 -0
- package/dist/mcp-server/transports/auth/strategies/authStrategy.js +1 -0
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts +7 -0
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +78 -0
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts +7 -0
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +65 -0
- package/dist/mcp-server/transports/core/mcpTransportManager.d.ts +32 -0
- package/dist/mcp-server/transports/core/mcpTransportManager.js +151 -0
- package/dist/mcp-server/transports/core/transportTypes.d.ts +71 -0
- package/dist/mcp-server/transports/core/transportTypes.js +5 -0
- package/dist/mcp-server/transports/{httpErrorHandler.d.ts → http/httpErrorHandler.d.ts} +1 -1
- package/dist/mcp-server/transports/{httpErrorHandler.js → http/httpErrorHandler.js} +2 -2
- package/dist/mcp-server/transports/http/httpTransport.d.ts +44 -0
- package/dist/mcp-server/transports/{httpTransport.js → http/httpTransport.js} +96 -86
- package/dist/mcp-server/transports/http/httpTypes.d.ts +14 -0
- package/dist/mcp-server/transports/http/httpTypes.js +5 -0
- package/dist/mcp-server/transports/http/index.d.ts +7 -0
- package/dist/mcp-server/transports/http/index.js +6 -0
- package/dist/mcp-server/transports/stdio/index.d.ts +5 -0
- package/dist/mcp-server/transports/stdio/index.js +5 -0
- package/dist/mcp-server/transports/{stdioTransport.d.ts → stdio/stdioTransport.d.ts} +2 -2
- package/dist/mcp-server/transports/{stdioTransport.js → stdio/stdioTransport.js} +2 -2
- package/dist/services/llm-providers/openRouterProvider.d.ts +1 -2
- package/dist/services/llm-providers/openRouterProvider.js +3 -2
- package/dist/utils/internal/logger.d.ts +5 -0
- package/dist/utils/internal/logger.js +14 -0
- package/dist/utils/security/sanitization.js +32 -13
- package/package.json +2 -1
- package/dist/agent/agent-core/agent.d.ts +0 -20
- package/dist/agent/agent-core/agent.js +0 -380
- package/dist/agent/cli/boot.d.ts +0 -7
- package/dist/agent/cli/boot.js +0 -32
- package/dist/agent/cli/main.d.ts +0 -7
- package/dist/agent/cli/main.js +0 -44
- package/dist/mcp-client/client-config/configLoader.d.ts +0 -104
- package/dist/mcp-client/client-config/configLoader.js +0 -166
- package/dist/mcp-client/core/clientConnectionLogic.d.ts +0 -33
- package/dist/mcp-client/core/clientConnectionLogic.js +0 -115
- package/dist/mcp-client/core/clientManager.d.ts +0 -71
- package/dist/mcp-client/core/clientManager.js +0 -213
- package/dist/mcp-client/index.d.ts +0 -21
- package/dist/mcp-client/index.js +0 -27
- package/dist/mcp-client/transports/httpClientTransport.d.ts +0 -26
- package/dist/mcp-client/transports/httpClientTransport.js +0 -56
- package/dist/mcp-client/transports/index.d.ts +0 -8
- package/dist/mcp-client/transports/index.js +0 -8
- package/dist/mcp-client/transports/stdioClientTransport.d.ts +0 -32
- package/dist/mcp-client/transports/stdioClientTransport.js +0 -67
- package/dist/mcp-client/transports/transportFactory.d.ts +0 -23
- package/dist/mcp-client/transports/transportFactory.js +0 -63
- package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.d.ts +0 -27
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.d.ts +0 -20
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -124
- package/dist/mcp-server/transports/httpTransport.d.ts +0 -21
- /package/dist/mcp-server/transports/auth/{core → lib}/authContext.d.ts +0 -0
- /package/dist/mcp-server/transports/auth/{core → lib}/authContext.js +0 -0
- /package/dist/mcp-server/transports/auth/{core → lib}/authUtils.d.ts +0 -0
- /package/dist/mcp-server/transports/auth/{core → lib}/authUtils.js +0 -0
package/README.md
CHANGED
|
@@ -1,41 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
9
|
+
[](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-06-18/changelog.mdx)
|
|
10
|
+
[](./CHANGELOG.md)
|
|
11
|
+
[](./vitest.config.ts)
|
|
12
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
13
|
+
[](https://github.com/cyanheads/mcp-ts-template/issues)
|
|
9
14
|
[](https://github.com/cyanheads/mcp-ts-template)
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
</div>
|
|
12
17
|
|
|
13
|
-
This template provides a
|
|
18
|
+
This template provides a comprehensive foundation for building rich Model Context Protocol servers, adhering to the **MCP 2025-06-18 specification** and modern best practices. It includes a fully-featured server, production-ready utilities, and clear documentation to get you up and running quickly.
|
|
14
19
|
|
|
15
|
-
##
|
|
20
|
+
## 🤔 Why Use This Template?
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
Building a robust server for AI agents is more than just writing code. It requires a solid architecture, consistent error handling, and secure, type-safe practices from the ground up. This template solves these challenges by providing:
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
- **Accelerated Development**: Skip the boilerplate and focus on your tool's core logic.
|
|
25
|
+
- **Production-Ready Foundation**: Built-in logging, error handling, security, and testing.
|
|
26
|
+
- **Best Practices by Default**: Enforces a clean, modular architecture that's easy to maintain and extend.
|
|
27
|
+
- **AI-Ready**: Designed with LLM agents in mind, including detailed schemas and rich LLM developer-friendly resources (e.g. .clinerules).
|
|
28
|
+
|
|
29
|
+
> **Note on src/mcp-client & src/agent:** The MCP client & Agent components have been enhanced and moved to the [**atlas-mcp-agent**](https://github.com/cyanheads/atlas-mcp-agent) repository. This template now focuses exclusively on providing a best-in-class server implementation and framework.
|
|
22
30
|
|
|
23
31
|
## ✨ Key Features
|
|
24
32
|
|
|
25
|
-
| Feature Area | Description
|
|
26
|
-
| :-------------------------- |
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
| **Services** | Reusable modules for LLM (OpenRouter) and data storage (DuckDB) integration, with examples. | `src/services/`, `src/storage/duckdbExample.ts` |
|
|
38
|
-
| **🧪 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/` |
|
|
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/` |
|
|
39
45
|
|
|
40
46
|
## 🌟 Projects Using This Template
|
|
41
47
|
|
|
@@ -74,11 +80,7 @@ npm run build
|
|
|
74
80
|
# Or use 'npm run rebuild' for a clean install
|
|
75
81
|
```
|
|
76
82
|
|
|
77
|
-
### 3. Running the
|
|
78
|
-
|
|
79
|
-
#### Running the MCP Server
|
|
80
|
-
|
|
81
|
-
You can run the included MCP server to make its tools available.
|
|
83
|
+
### 3. Running the Server
|
|
82
84
|
|
|
83
85
|
- **Via Stdio (Default):**
|
|
84
86
|
```bash
|
|
@@ -89,17 +91,6 @@ You can run the included MCP server to make its tools available.
|
|
|
89
91
|
npm run start:server:http
|
|
90
92
|
```
|
|
91
93
|
|
|
92
|
-
#### Running the Agent
|
|
93
|
-
|
|
94
|
-
The agent can be run from the command line to perform tasks. It will automatically connect to the servers defined in `src/mcp-client/client-config/mcp-config.json`. If running the agent, you must have the MCP config set up correctly and your openrouter API key configured in .env.
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
npm run start:agent "Your prompt here"
|
|
98
|
-
|
|
99
|
-
# Example:
|
|
100
|
-
npm run start:agent "Use the echo tool to say hello world and then get a cat fact."
|
|
101
|
-
```
|
|
102
|
-
|
|
103
94
|
### 4. Running Tests
|
|
104
95
|
|
|
105
96
|
This template uses [Vitest](https://vitest.dev/) for unit testing. Tests are located in the `tests/` directory, mirroring the `src/` structure.
|
|
@@ -129,22 +120,14 @@ Configure the MCP server's behavior using these environment variables:
|
|
|
129
120
|
| `MCP_HTTP_PORT` | Port for the HTTP server (if `MCP_TRANSPORT_TYPE=http`). | `3010` |
|
|
130
121
|
| `MCP_HTTP_HOST` | Host address for the HTTP server (if `MCP_TRANSPORT_TYPE=http`). | `127.0.0.1` |
|
|
131
122
|
| `MCP_ALLOWED_ORIGINS` | Comma-separated allowed origins for CORS (if `MCP_TRANSPORT_TYPE=http`). | (none) |
|
|
132
|
-
| `MCP_AUTH_MODE` | Authentication mode for HTTP: `jwt`
|
|
123
|
+
| `MCP_AUTH_MODE` | Authentication mode for HTTP: `jwt`, `oauth`, or `none`. | `none` |
|
|
133
124
|
| `MCP_AUTH_SECRET_KEY` | **Required for `jwt` mode.** Secret key (min 32 chars) for signing/verifying auth tokens. | (none - **MUST be set in production**) |
|
|
134
125
|
| `OAUTH_ISSUER_URL` | **Required for `oauth` mode.** The issuer URL of your authorization server. | (none) |
|
|
135
126
|
| `OAUTH_AUDIENCE` | **Required for `oauth` mode.** The audience identifier for this MCP server. | (none) |
|
|
136
|
-
| `OPENROUTER_API_KEY` | API key for OpenRouter.ai service.
|
|
137
|
-
|
|
138
|
-
### Client & Agent Configuration
|
|
139
|
-
|
|
140
|
-
The agent uses the MCP client to connect to servers. This is configured in `src/mcp-client/client-config/mcp-config.json`. You must list all MCP servers the agent should connect to in this file.
|
|
141
|
-
|
|
142
|
-
For a detailed guide, see the [Client Configuration README](src/mcp-client/client-config/README.md).
|
|
127
|
+
| `OPENROUTER_API_KEY` | API key for OpenRouter.ai service. | (none) |
|
|
143
128
|
|
|
144
129
|
## 🏗️ Project Structure
|
|
145
130
|
|
|
146
|
-
- **`src/agent/`**: Contains the core agent framework, including the `Agent` class and a CLI for running the agent.
|
|
147
|
-
- **`src/mcp-client/`**: Implements the MCP client logic for connecting to and interacting with external MCP servers.
|
|
148
131
|
- **`src/mcp-server/`**: Contains the MCP server implementation, including example tools, resources, and transport handlers.
|
|
149
132
|
- **`src/config/`**: Handles loading and validation of environment variables and application configuration.
|
|
150
133
|
- **`src/services/`**: Provides reusable modules for integrating with external services (DuckDB, OpenRouter).
|
|
@@ -166,10 +149,6 @@ npm run tree
|
|
|
166
149
|
|
|
167
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).
|
|
168
151
|
|
|
169
|
-
### Modifying the Agent
|
|
170
|
-
|
|
171
|
-
The agent's core logic is in `src/agent/agent-core/agent.ts`. You can modify its system prompt, the models it uses (`google/gemini-2.5-flash` by default), and its decision-making loop to change its behavior.
|
|
172
|
-
|
|
173
152
|
## 🌍 Explore More MCP Resources
|
|
174
153
|
|
|
175
154
|
Looking for more examples, guides, and pre-built MCP servers? Check out the companion repository:
|
package/dist/config/index.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export declare const config: {
|
|
|
45
45
|
/** Auth secret key (JWTs, http transport). From `MCP_AUTH_SECRET_KEY`. CRITICAL. */
|
|
46
46
|
mcpAuthSecretKey: string | undefined;
|
|
47
47
|
/** The authentication mode ('jwt' or 'oauth'). From `MCP_AUTH_MODE`. */
|
|
48
|
-
mcpAuthMode: "jwt" | "oauth";
|
|
48
|
+
mcpAuthMode: "jwt" | "oauth" | "none";
|
|
49
49
|
/** OAuth 2.1 Issuer URL. From `OAUTH_ISSUER_URL`. */
|
|
50
50
|
oauthIssuerUrl: string | undefined;
|
|
51
51
|
/** OAuth 2.1 JWKS URI. From `OAUTH_JWKS_URI`. */
|
package/dist/config/index.js
CHANGED
|
@@ -96,8 +96,8 @@ const EnvSchema = z.object({
|
|
|
96
96
|
.string()
|
|
97
97
|
.min(32, "MCP_AUTH_SECRET_KEY must be at least 32 characters long for security reasons.")
|
|
98
98
|
.optional(),
|
|
99
|
-
/** The authentication mode to use. 'jwt' for internal simple JWTs, 'oauth' for OAuth 2.1. Default: '
|
|
100
|
-
MCP_AUTH_MODE: z.enum(["jwt", "oauth"]).default("
|
|
99
|
+
/** The authentication mode to use. 'jwt' for internal simple JWTs, 'oauth' for OAuth 2.1, or 'none'. Default: 'none'. */
|
|
100
|
+
MCP_AUTH_MODE: z.enum(["jwt", "oauth", "none"]).default("none"),
|
|
101
101
|
/** The expected issuer URL for OAuth 2.1 access tokens. CRITICAL for validation. */
|
|
102
102
|
OAUTH_ISSUER_URL: z.string().url().optional(),
|
|
103
103
|
/** The JWKS (JSON Web Key Set) URI for the OAuth 2.1 provider. If not provided, it's often discoverable from the issuer URL. */
|
|
@@ -21,8 +21,8 @@ import { registerEchoResource } from "./resources/echoResource/index.js";
|
|
|
21
21
|
import { registerCatFactFetcherTool } from "./tools/catFactFetcher/index.js";
|
|
22
22
|
import { registerEchoTool } from "./tools/echoTool/index.js";
|
|
23
23
|
import { registerFetchImageTestTool } from "./tools/imageTest/index.js";
|
|
24
|
-
import { startHttpTransport } from "./transports/
|
|
25
|
-
import {
|
|
24
|
+
import { startHttpTransport } from "./transports/http/index.js";
|
|
25
|
+
import { startStdioTransport } from "./transports/stdio/index.js";
|
|
26
26
|
/**
|
|
27
27
|
* Creates and configures a new instance of the `McpServer`.
|
|
28
28
|
*
|
|
@@ -77,11 +77,12 @@ async function startTransport() {
|
|
|
77
77
|
});
|
|
78
78
|
logger.info(`Starting transport: ${transportType}`, context);
|
|
79
79
|
if (transportType === "http") {
|
|
80
|
-
|
|
80
|
+
const { server } = await startHttpTransport(createMcpServerInstance, context);
|
|
81
|
+
return server;
|
|
81
82
|
}
|
|
82
83
|
if (transportType === "stdio") {
|
|
83
84
|
const server = await createMcpServerInstance();
|
|
84
|
-
await
|
|
85
|
+
await startStdioTransport(server, context);
|
|
85
86
|
return server;
|
|
86
87
|
}
|
|
87
88
|
throw new Error(`Unsupported transport type: ${transportType}. Must be 'stdio' or 'http'.`);
|
|
@@ -15,8 +15,7 @@ export const ECHO_MODES = ["standard", "uppercase", "lowercase"];
|
|
|
15
15
|
* Zod schema defining the input parameters for the `echo_message` tool.
|
|
16
16
|
* This schema is used by the MCP SDK to validate the arguments provided when the tool is called.
|
|
17
17
|
*/
|
|
18
|
-
export const EchoToolInputSchema = z
|
|
19
|
-
.object({
|
|
18
|
+
export const EchoToolInputSchema = z.object({
|
|
20
19
|
message: z
|
|
21
20
|
.string()
|
|
22
21
|
.min(1, "Message cannot be empty.")
|
|
@@ -14,7 +14,9 @@ export const FetchImageTestInputSchema = z.object({
|
|
|
14
14
|
});
|
|
15
15
|
export const FetchImageTestResponseSchema = z.object({
|
|
16
16
|
data: z.string().describe("Base64 encoded image data."),
|
|
17
|
-
mimeType: z
|
|
17
|
+
mimeType: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("The MIME type of the image (e.g., 'image/jpeg')."),
|
|
18
20
|
});
|
|
19
21
|
const CAT_API_URL = "https://cataas.com/cat";
|
|
20
22
|
export async function fetchImageTestLogic(input, parentRequestContext) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AuthStrategy } from "./strategies/authStrategy.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates and returns an authentication strategy instance based on the
|
|
4
|
+
* application's configuration (`config.mcpAuthMode`).
|
|
5
|
+
*
|
|
6
|
+
* @returns An instance of a class that implements the `AuthStrategy` interface,
|
|
7
|
+
* or `null` if authentication is disabled (`none`).
|
|
8
|
+
* @throws {Error} If the auth mode is unknown or misconfigured.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createAuthStrategy(): AuthStrategy | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Factory for creating an authentication strategy based on configuration.
|
|
3
|
+
* This module centralizes the logic for selecting and instantiating the correct
|
|
4
|
+
* authentication strategy, promoting loose coupling and easy extensibility.
|
|
5
|
+
* @module src/mcp-server/transports/auth/authFactory
|
|
6
|
+
*/
|
|
7
|
+
import { config } from "../../../config/index.js";
|
|
8
|
+
import { JwtStrategy } from "./strategies/jwtStrategy.js";
|
|
9
|
+
import { OauthStrategy } from "./strategies/oauthStrategy.js";
|
|
10
|
+
/**
|
|
11
|
+
* Creates and returns an authentication strategy instance based on the
|
|
12
|
+
* application's configuration (`config.mcpAuthMode`).
|
|
13
|
+
*
|
|
14
|
+
* @returns An instance of a class that implements the `AuthStrategy` interface,
|
|
15
|
+
* or `null` if authentication is disabled (`none`).
|
|
16
|
+
* @throws {Error} If the auth mode is unknown or misconfigured.
|
|
17
|
+
*/
|
|
18
|
+
export function createAuthStrategy() {
|
|
19
|
+
switch (config.mcpAuthMode) {
|
|
20
|
+
case "jwt":
|
|
21
|
+
return new JwtStrategy();
|
|
22
|
+
case "oauth":
|
|
23
|
+
return new OauthStrategy();
|
|
24
|
+
case "none":
|
|
25
|
+
return null; // No authentication
|
|
26
|
+
default:
|
|
27
|
+
// This ensures that if a new auth mode is added to the config type
|
|
28
|
+
// but not to this factory, we get a compile-time or runtime error.
|
|
29
|
+
throw new Error(`Unknown authentication mode: ${config.mcpAuthMode}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines a unified Hono middleware for authentication.
|
|
3
|
+
* This middleware is strategy-agnostic. It extracts a Bearer token,
|
|
4
|
+
* delegates verification to the provided authentication strategy, and
|
|
5
|
+
* populates the async-local storage context with the resulting auth info.
|
|
6
|
+
* @module src/mcp-server/transports/auth/authMiddleware
|
|
7
|
+
*/
|
|
8
|
+
import type { HttpBindings } from "@hono/node-server";
|
|
9
|
+
import type { Context, Next } from "hono";
|
|
10
|
+
import type { AuthInfo } from "./lib/authTypes.js";
|
|
11
|
+
import type { AuthStrategy } from "./strategies/authStrategy.js";
|
|
12
|
+
declare module "http" {
|
|
13
|
+
interface IncomingMessage {
|
|
14
|
+
auth?: AuthInfo;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a Hono middleware function that enforces authentication using a given strategy.
|
|
19
|
+
*
|
|
20
|
+
* @param strategy - An instance of a class that implements the `AuthStrategy` interface.
|
|
21
|
+
* @returns A Hono middleware function.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createAuthMiddleware(strategy: AuthStrategy): (c: Context<{
|
|
24
|
+
Bindings: HttpBindings;
|
|
25
|
+
}>, next: Next) => Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
2
|
+
import { logger, requestContextService } from "../../../utils/index.js";
|
|
3
|
+
import { authContext } from "./lib/authContext.js";
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Hono middleware function that enforces authentication using a given strategy.
|
|
6
|
+
*
|
|
7
|
+
* @param strategy - An instance of a class that implements the `AuthStrategy` interface.
|
|
8
|
+
* @returns A Hono middleware function.
|
|
9
|
+
*/
|
|
10
|
+
export function createAuthMiddleware(strategy) {
|
|
11
|
+
return async function authMiddleware(c, next) {
|
|
12
|
+
const context = requestContextService.createRequestContext({
|
|
13
|
+
operation: "authMiddleware",
|
|
14
|
+
method: c.req.method,
|
|
15
|
+
path: c.req.path,
|
|
16
|
+
});
|
|
17
|
+
const authHeader = c.req.header("Authorization");
|
|
18
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
19
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Missing or invalid Authorization header. Bearer scheme required.", context);
|
|
20
|
+
}
|
|
21
|
+
const token = authHeader.substring(7);
|
|
22
|
+
if (!token) {
|
|
23
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Authentication token is missing.", context);
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const authInfo = await strategy.verify(token);
|
|
27
|
+
// For logging and potential legacy use, attach to the raw request.
|
|
28
|
+
// The primary mechanism for access should be authContext.
|
|
29
|
+
c.env.incoming.auth = authInfo;
|
|
30
|
+
logger.debug("Authentication successful. Auth context populated.", {
|
|
31
|
+
...context,
|
|
32
|
+
clientId: authInfo.clientId,
|
|
33
|
+
scopes: authInfo.scopes,
|
|
34
|
+
});
|
|
35
|
+
// Run the next middleware in the chain within the populated auth context.
|
|
36
|
+
await authContext.run({ authInfo }, next);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// The strategy is expected to throw an McpError.
|
|
40
|
+
// We re-throw it here to be caught by the global httpErrorHandler.
|
|
41
|
+
logger.warning("Authentication verification failed.", {
|
|
42
|
+
...context,
|
|
43
|
+
error: error instanceof Error ? error.message : String(error),
|
|
44
|
+
});
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
* Exports core utilities and middleware strategies for easier imports.
|
|
4
4
|
* @module src/mcp-server/transports/auth/index
|
|
5
5
|
*/
|
|
6
|
-
export { authContext } from "./
|
|
7
|
-
export { withRequiredScopes } from "./
|
|
8
|
-
export type { AuthInfo } from "./
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
6
|
+
export { authContext } from "./lib/authContext.js";
|
|
7
|
+
export { withRequiredScopes } from "./lib/authUtils.js";
|
|
8
|
+
export type { AuthInfo } from "./lib/authTypes.js";
|
|
9
|
+
export { createAuthStrategy } from "./authFactory.js";
|
|
10
|
+
export { createAuthMiddleware } from "./authMiddleware.js";
|
|
11
|
+
export { AuthStrategy } from "./strategies/authStrategy.js";
|
|
12
|
+
export { JwtStrategy } from "./strategies/jwtStrategy.js";
|
|
13
|
+
export { OauthStrategy } from "./strategies/oauthStrategy.js";
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* Exports core utilities and middleware strategies for easier imports.
|
|
4
4
|
* @module src/mcp-server/transports/auth/index
|
|
5
5
|
*/
|
|
6
|
-
export { authContext } from "./
|
|
7
|
-
export { withRequiredScopes } from "./
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
6
|
+
export { authContext } from "./lib/authContext.js";
|
|
7
|
+
export { withRequiredScopes } from "./lib/authUtils.js";
|
|
8
|
+
export { createAuthStrategy } from "./authFactory.js";
|
|
9
|
+
export { createAuthMiddleware } from "./authMiddleware.js";
|
|
10
|
+
export { JwtStrategy } from "./strategies/jwtStrategy.js";
|
|
11
|
+
export { OauthStrategy } from "./strategies/oauthStrategy.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared types for authentication middleware.
|
|
3
|
+
* @module src/mcp-server/transports/auth/core/auth.types
|
|
4
|
+
*/
|
|
5
|
+
export {};
|
|
6
|
+
// The declaration for `http.IncomingMessage` is no longer needed here,
|
|
7
|
+
// as the new architecture avoids direct mutation where possible and handles
|
|
8
|
+
// the attachment within the Hono context.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the interface for all authentication strategies.
|
|
3
|
+
* This interface establishes a contract for verifying authentication tokens,
|
|
4
|
+
* ensuring that any authentication method (JWT, OAuth, etc.) can be used
|
|
5
|
+
* interchangeably by the core authentication middleware.
|
|
6
|
+
* @module src/mcp-server/transports/auth/strategies/AuthStrategy
|
|
7
|
+
*/
|
|
8
|
+
import type { AuthInfo } from "../lib/authTypes.js";
|
|
9
|
+
export interface AuthStrategy {
|
|
10
|
+
/**
|
|
11
|
+
* Verifies an authentication token.
|
|
12
|
+
* @param token The raw token string extracted from the request.
|
|
13
|
+
* @returns A promise that resolves with the AuthInfo on successful verification.
|
|
14
|
+
* @throws {McpError} if the token is invalid, expired, or fails verification for any reason.
|
|
15
|
+
*/
|
|
16
|
+
verify(token: string): Promise<AuthInfo>;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Implements the JWT authentication strategy.
|
|
3
|
+
* This module provides a concrete implementation of the AuthStrategy for validating
|
|
4
|
+
* JSON Web Tokens (JWTs). It encapsulates all logic related to JWT verification,
|
|
5
|
+
* including secret key management and payload validation.
|
|
6
|
+
* @module src/mcp-server/transports/auth/strategies/JwtStrategy
|
|
7
|
+
*/
|
|
8
|
+
import { jwtVerify } from "jose";
|
|
9
|
+
import { config, environment } from "../../../../config/index.js";
|
|
10
|
+
import { BaseErrorCode, McpError } from "../../../../types-global/errors.js";
|
|
11
|
+
import { logger } from "../../../../utils/index.js";
|
|
12
|
+
export class JwtStrategy {
|
|
13
|
+
constructor() {
|
|
14
|
+
if (config.mcpAuthMode === "jwt") {
|
|
15
|
+
if (environment === "production" && !config.mcpAuthSecretKey) {
|
|
16
|
+
logger.fatal("CRITICAL: MCP_AUTH_SECRET_KEY is not set in production for JWT auth.");
|
|
17
|
+
throw new Error("MCP_AUTH_SECRET_KEY must be set for JWT auth.");
|
|
18
|
+
}
|
|
19
|
+
else if (!config.mcpAuthSecretKey) {
|
|
20
|
+
logger.warning("MCP_AUTH_SECRET_KEY is not set. JWT auth will be bypassed (DEV ONLY).");
|
|
21
|
+
this.secretKey = null;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.secretKey = new TextEncoder().encode(config.mcpAuthSecretKey);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this.secretKey = null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async verify(token) {
|
|
32
|
+
// Handle development mode bypass
|
|
33
|
+
if (!this.secretKey) {
|
|
34
|
+
if (environment !== "production") {
|
|
35
|
+
logger.warning("Bypassing JWT verification: No secret key (DEV ONLY).");
|
|
36
|
+
return {
|
|
37
|
+
token: "dev-mode-placeholder-token",
|
|
38
|
+
clientId: "dev-client-id",
|
|
39
|
+
scopes: ["dev-scope"],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "Auth secret key is missing in production.");
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const { payload: decoded } = await jwtVerify(token, this.secretKey);
|
|
46
|
+
const clientId = typeof decoded.cid === "string"
|
|
47
|
+
? decoded.cid
|
|
48
|
+
: typeof decoded.client_id === "string"
|
|
49
|
+
? decoded.client_id
|
|
50
|
+
: undefined;
|
|
51
|
+
if (!clientId) {
|
|
52
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Invalid token: missing 'cid' or 'client_id' claim.");
|
|
53
|
+
}
|
|
54
|
+
let scopes = [];
|
|
55
|
+
if (Array.isArray(decoded.scp) &&
|
|
56
|
+
decoded.scp.every((s) => typeof s === "string")) {
|
|
57
|
+
scopes = decoded.scp;
|
|
58
|
+
}
|
|
59
|
+
else if (typeof decoded.scope === "string" && decoded.scope.trim()) {
|
|
60
|
+
scopes = decoded.scope.split(" ").filter(Boolean);
|
|
61
|
+
}
|
|
62
|
+
if (scopes.length === 0) {
|
|
63
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Token must contain valid, non-empty scopes.");
|
|
64
|
+
}
|
|
65
|
+
return { token, clientId, scopes };
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error instanceof McpError)
|
|
69
|
+
throw error;
|
|
70
|
+
const message = error instanceof Error && error.name === "JWTExpired"
|
|
71
|
+
? "Token has expired."
|
|
72
|
+
: "Token verification failed.";
|
|
73
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, message, {
|
|
74
|
+
originalError: error instanceof Error ? error.name : "Unknown",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Implements the OAuth 2.1 authentication strategy.
|
|
3
|
+
* This module provides a concrete implementation of the AuthStrategy for validating
|
|
4
|
+
* JWTs against a remote JSON Web Key Set (JWKS), as is common in OAuth 2.1 flows.
|
|
5
|
+
* @module src/mcp-server/transports/auth/strategies/OauthStrategy
|
|
6
|
+
*/
|
|
7
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
8
|
+
import { config } from "../../../../config/index.js";
|
|
9
|
+
import { BaseErrorCode, McpError } from "../../../../types-global/errors.js";
|
|
10
|
+
import { logger } from "../../../../utils/index.js";
|
|
11
|
+
export class OauthStrategy {
|
|
12
|
+
constructor() {
|
|
13
|
+
if (config.mcpAuthMode !== "oauth") {
|
|
14
|
+
throw new Error("OauthStrategy instantiated for non-oauth auth mode.");
|
|
15
|
+
}
|
|
16
|
+
if (!config.oauthIssuerUrl || !config.oauthAudience) {
|
|
17
|
+
throw new Error("OAUTH_ISSUER_URL and OAUTH_AUDIENCE must be set for OAuth mode.");
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const jwksUrl = new URL(config.oauthJwksUri ||
|
|
21
|
+
`${config.oauthIssuerUrl.replace(/\/$/, "")}/.well-known/jwks.json`);
|
|
22
|
+
this.jwks = createRemoteJWKSet(jwksUrl, {
|
|
23
|
+
cooldownDuration: 300000, // 5 minutes
|
|
24
|
+
timeoutDuration: 5000, // 5 seconds
|
|
25
|
+
});
|
|
26
|
+
logger.info(`JWKS client initialized for URL: ${jwksUrl.href}`);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
logger.fatal("Failed to initialize JWKS client.", error);
|
|
30
|
+
throw new Error("Could not initialize JWKS client for OAuth strategy.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async verify(token) {
|
|
34
|
+
try {
|
|
35
|
+
const { payload } = await jwtVerify(token, this.jwks, {
|
|
36
|
+
issuer: config.oauthIssuerUrl,
|
|
37
|
+
audience: config.oauthAudience,
|
|
38
|
+
});
|
|
39
|
+
const scopes = typeof payload.scope === "string" ? payload.scope.split(" ") : [];
|
|
40
|
+
if (scopes.length === 0) {
|
|
41
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Token must contain valid, non-empty scopes.");
|
|
42
|
+
}
|
|
43
|
+
const clientId = typeof payload.client_id === "string" ? payload.client_id : undefined;
|
|
44
|
+
if (!clientId) {
|
|
45
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Token must contain a 'client_id' claim.");
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
token,
|
|
49
|
+
clientId,
|
|
50
|
+
scopes,
|
|
51
|
+
subject: typeof payload.sub === "string" ? payload.sub : undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (error instanceof McpError)
|
|
56
|
+
throw error;
|
|
57
|
+
const message = error instanceof Error && error.name === "JWTExpired"
|
|
58
|
+
? "Token has expired."
|
|
59
|
+
: "OAuth token verification failed.";
|
|
60
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, message, {
|
|
61
|
+
originalError: error instanceof Error ? error.name : "Unknown",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MCP Transport Manager implementation using the MCP SDK.
|
|
3
|
+
* @module src/mcp-server/transports/core/mcpTransportManager
|
|
4
|
+
*/
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from "http";
|
|
7
|
+
import { RequestContext } from "../../../utils/index.js";
|
|
8
|
+
import { TransportManager, TransportResponse, TransportSession } from "./transportTypes.js";
|
|
9
|
+
/**
|
|
10
|
+
* MCP Transport Manager that handles MCP SDK integration and session management.
|
|
11
|
+
*/
|
|
12
|
+
export declare class McpTransportManager implements TransportManager {
|
|
13
|
+
private readonly transports;
|
|
14
|
+
private readonly sessions;
|
|
15
|
+
private readonly createServerInstanceFn;
|
|
16
|
+
constructor(createServerInstanceFn: () => Promise<McpServer>);
|
|
17
|
+
initializeSession(body: unknown, context: RequestContext): Promise<TransportResponse>;
|
|
18
|
+
handleRequest(sessionId: string, req: IncomingMessage, res: ServerResponse, context: RequestContext, body?: unknown): Promise<void>;
|
|
19
|
+
handleDeleteRequest(sessionId: string, context: RequestContext): Promise<TransportResponse>;
|
|
20
|
+
getSession(sessionId: string): TransportSession | undefined;
|
|
21
|
+
shutdown(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Helper method to close a specific session.
|
|
24
|
+
*/
|
|
25
|
+
private closeSession;
|
|
26
|
+
/**
|
|
27
|
+
* Find existing session that should be closed for re-initialization.
|
|
28
|
+
* This is a simplified implementation - in reality you might want more
|
|
29
|
+
* sophisticated session management.
|
|
30
|
+
*/
|
|
31
|
+
private findExistingSessionForReinit;
|
|
32
|
+
}
|