@cyanheads/git-mcp-server 2.1.2 → 2.1.4
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 +92 -72
- package/dist/config/index.js +10 -2
- package/dist/mcp-server/server.js +33 -32
- package/dist/mcp-server/transports/auth/core/authContext.js +24 -0
- package/dist/mcp-server/transports/auth/core/authTypes.js +5 -0
- package/dist/mcp-server/transports/auth/core/authUtils.js +45 -0
- package/dist/mcp-server/transports/auth/index.js +9 -0
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +149 -0
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +127 -0
- package/dist/mcp-server/transports/httpErrorHandler.js +73 -0
- package/dist/mcp-server/transports/httpTransport.js +149 -495
- package/dist/mcp-server/transports/stdioTransport.js +18 -48
- package/package.json +10 -8
- package/dist/mcp-server/transports/authentication/authMiddleware.js +0 -167
package/README.md
CHANGED
|
@@ -1,55 +1,69 @@
|
|
|
1
1
|
# Git MCP Server
|
|
2
2
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
|
-
[](https://modelcontextprotocol.io/)
|
|
5
|
+
[](./CHANGELOG.md)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://github.com/cyanheads/git-mcp-server/issues)
|
|
8
8
|
[](https://github.com/cyanheads/git-mcp-server)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**Empower your AI agents with comprehensive, secure, and programmatic control over Git repositories!**
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
An MCP (Model Context Protocol) server providing a robust, LLM-friendly interface to the standard `git` command-line tool. Enables LLMs and AI agents to perform a wide range of Git operations like clone, commit, push, pull, branch, diff, log, status, and more via the MCP standard.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Built on the [`cyanheads/mcp-ts-template`](https://github.com/cyanheads/mcp-ts-template), this server follows a modular architecture with robust error handling, logging, and security features.
|
|
15
|
+
|
|
16
|
+
## 🚀 Core Capabilities: Git Tools 🛠️
|
|
17
|
+
|
|
18
|
+
This server equips your AI with a comprehensive suite of tools to interact with Git repositories:
|
|
19
|
+
|
|
20
|
+
| Tool Category | Description | Key Features - |
|
|
21
|
+
| :----------------------- | :---------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| **Repository & Staging** | Manage repository state, from initialization to staging changes. | - `git_init`: Initialize a new repository.<br/>- `git_clone`: Clone remote repositories.<br/>- `git_add`: Stage changes for commit.<br/>- `git_status`: Check the status of the working directory.<br/>- `git_clean`: Remove untracked files (requires force flag). - |
|
|
23
|
+
| **Committing & History** | Create commits, inspect history, and view changes over time. | - `git_commit`: Create new commits with conventional messages.<br/>- `git_log`: View commit history with filtering options.<br/>- `git_diff`: Show changes between commits, branches, or the working tree.<br/>- `git_show`: Inspect Git objects like commits and tags. - |
|
|
24
|
+
| **Branching & Merging** | Manage branches, merge changes, and rebase commits. | - `git_branch`: List, create, delete, and rename branches.<br/>- `git_checkout`: Switch between branches or commits.<br/>- `git_merge`: Merge branches together.<br/>- `git_rebase`: Re-apply commits on top of another base.<br/>- `git_cherry_pick`: Apply specific commits from other branches. - |
|
|
25
|
+
| **Remote Operations** | Interact with remote repositories. | - `git_remote`: Manage remote repository connections.<br/>- `git_fetch`: Download objects and refs from a remote.<br/>- `git_pull`: Fetch and integrate with another repository.<br/>- `git_push`: Update remote refs with local changes. - |
|
|
26
|
+
| **Advanced Workflows** | Support for more complex Git workflows and repository management. | - `git_tag`: Create, list, or delete tags.<br/>- `git_stash`: Temporarily store modified files.<br/>- `git_worktree`: Manage multiple working trees attached to a single repository.<br/>- `git_set_working_dir`: Set a persistent working directory for a session.<br/>- `git_wrapup_instructions`: Get a standard workflow for finalizing changes. - |
|
|
27
|
+
|
|
28
|
+
---
|
|
15
29
|
|
|
16
30
|
## Table of Contents
|
|
17
31
|
|
|
18
32
|
| [Overview](#overview) | [Features](#features) | [Installation](#installation) |
|
|
19
|
-
|
|
20
33
|
| [Configuration](#configuration) | [Project Structure](#project-structure) |
|
|
21
|
-
|
|
22
34
|
| [Tools](#tools) | [Resources](#resources) | [Development](#development) | [License](#license) |
|
|
23
35
|
|
|
24
36
|
## Overview
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
The Git MCP Server acts as a bridge, allowing applications (MCP Clients) that understand the Model Context Protocol (MCP) – like advanced AI assistants (LLMs), IDE extensions, or custom scripts – to interact directly and safely with local Git repositories.
|
|
38
|
+
The Git MCP Server acts as a bridge, allowing applications (MCP Clients) that understand the Model Context Protocol (MCP) – like advanced AI coding assistants (LLMs), IDE extensions, or custom research tools – to interact directly and safely with local Git repositories.
|
|
29
39
|
|
|
30
|
-
Instead of complex scripting or manual
|
|
40
|
+
Instead of complex scripting or manual command-line interaction, your tools can leverage this server to:
|
|
31
41
|
|
|
32
42
|
- **Automate Git workflows**: Clone repositories, create branches, stage changes, commit work, push updates, and manage tags programmatically.
|
|
33
43
|
- **Gain repository insights**: Check status, view logs, diff changes, and inspect Git objects without leaving the host application.
|
|
34
|
-
- **Integrate Git into AI-driven development**: Enable LLMs to manage version control as part of their coding or refactoring tasks.
|
|
44
|
+
- **Integrate Git into AI-driven development**: Enable LLMs to manage version control as part of their coding or refactoring tasks, ensuring code integrity and history.
|
|
45
|
+
- **Support CI/CD and DevOps automation**: Build custom scripts and tools that orchestrate complex Git operations for automated builds, testing, and deployments.
|
|
35
46
|
|
|
36
47
|
Built on the robust `mcp-ts-template`, this server provides a standardized, secure, and efficient way to expose Git functionality via the MCP standard. It achieves this by securely executing the standard `git` command-line tool installed on the system using Node.js's `child_process` module, ensuring compatibility and leveraging the full power of Git.
|
|
37
48
|
|
|
49
|
+
> **Developer Note**: This repository includes a [.clinerules](.clinerules) file that serves as a developer cheat sheet for your LLM coding agent with quick reference for the codebase patterns, file locations, and code snippets.
|
|
50
|
+
|
|
38
51
|
## Features
|
|
39
52
|
|
|
40
|
-
### Core Utilities
|
|
53
|
+
### Core Utilities
|
|
41
54
|
|
|
42
55
|
Leverages the robust utilities provided by the `mcp-ts-template`:
|
|
43
56
|
|
|
44
|
-
- **Logging**: Structured, configurable logging (file rotation,
|
|
57
|
+
- **Logging**: Structured, configurable logging (file rotation, stdout JSON, MCP notifications) with sensitive data redaction.
|
|
45
58
|
- **Error Handling**: Centralized error processing, standardized error types (`McpError`), and automatic logging.
|
|
46
|
-
- **Configuration**: Environment variable loading (`dotenv`).
|
|
59
|
+
- **Configuration**: Environment variable loading (`dotenv`) with comprehensive validation.
|
|
47
60
|
- **Input Validation/Sanitization**: Uses `zod` for schema validation and custom sanitization logic (crucial for paths).
|
|
48
|
-
- **Request Context**: Tracking and correlation of operations via unique request IDs
|
|
61
|
+
- **Request Context**: Tracking and correlation of operations via unique request IDs using `AsyncLocalStorage`.
|
|
49
62
|
- **Type Safety**: Strong typing enforced by TypeScript and Zod schemas.
|
|
50
|
-
- **HTTP Transport
|
|
63
|
+
- **HTTP Transport**: High-performance HTTP server using **Hono**, featuring session management, CORS, and authentication support.
|
|
64
|
+
- **Deployment**: Multi-stage `Dockerfile` for creating small, secure production images with native dependency support.
|
|
51
65
|
|
|
52
|
-
### Git
|
|
66
|
+
### Git Integration
|
|
53
67
|
|
|
54
68
|
- **Direct Git CLI Execution**: Interacts with Git by securely executing the standard `git` command-line tool via Node.js `child_process`, ensuring full compatibility and access to Git's features.
|
|
55
69
|
- **Comprehensive Command Coverage**: Exposes a wide range of Git commands as MCP tools (see [Tools](#tools) section).
|
|
@@ -62,39 +76,63 @@ Leverages the robust utilities provided by the `mcp-ts-template`:
|
|
|
62
76
|
|
|
63
77
|
### Prerequisites
|
|
64
78
|
|
|
65
|
-
- [Node.js (>=
|
|
79
|
+
- [Node.js (>=20.0.0)](https://nodejs.org/)
|
|
66
80
|
- [npm](https://www.npmjs.com/) (comes with Node.js)
|
|
67
81
|
- [Git](https://git-scm.com/) installed and accessible in the system PATH.
|
|
68
82
|
|
|
69
|
-
###
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
83
|
+
### MCP Client Settings
|
|
84
|
+
|
|
85
|
+
Add the following to your MCP client's configuration file (e.g., `cline_mcp_settings.json`). This configuration uses `npx` to run the server, which will automatically install the package if not already present:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcpServers": {
|
|
90
|
+
"git-mcp-server": {
|
|
91
|
+
"command": "npx",
|
|
92
|
+
"args": ["@cyanheads/git-mcp-server"],
|
|
93
|
+
"env": {
|
|
94
|
+
"MCP_LOG_LEVEL": "info",
|
|
95
|
+
"GIT_SIGN_COMMITS": "false"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### If running manually (not via MCP client) for development or testing
|
|
103
|
+
|
|
104
|
+
#### Install via npm
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm install @cyanheads/git-mcp-server
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Alternatively Install from Source (recommended for development)
|
|
111
|
+
|
|
112
|
+
1. Clone the repository:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
git clone https://github.com/cyanheads/git-mcp-server.git
|
|
116
|
+
cd git-mcp-server
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
2. Install dependencies:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm install
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
3. Build the project:
|
|
126
|
+
```bash
|
|
127
|
+
npm run build
|
|
128
|
+
# or npm run rebuild
|
|
129
|
+
```
|
|
92
130
|
|
|
93
131
|
## Configuration
|
|
94
132
|
|
|
95
133
|
### Environment Variables
|
|
96
134
|
|
|
97
|
-
Configure the server using environment variables.
|
|
135
|
+
Configure the server using environment variables. These environmental variables are set within your MCP client config/settings (e.g. `claude_desktop_config.json` for Claude Desktop)
|
|
98
136
|
|
|
99
137
|
| Variable | Description | Default |
|
|
100
138
|
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
|
@@ -104,29 +142,10 @@ Configure the server using environment variables. Create a `.env` file in the pr
|
|
|
104
142
|
| `MCP_ALLOWED_ORIGINS` | Comma-separated list of allowed origins for CORS (if `MCP_TRANSPORT_TYPE=http`). | (none) |
|
|
105
143
|
| `MCP_LOG_LEVEL` | Logging level (`debug`, `info`, `notice`, `warning`, `error`, `crit`, `alert`, `emerg`). Inherited from template. | `info` |
|
|
106
144
|
| `GIT_SIGN_COMMITS` | Set to `"true"` to enable signing attempts for commits made by the `git_commit` tool. Requires server-side Git/key setup (see below). | `false` |
|
|
107
|
-
| `
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
Add to your MCP client settings (e.g., `cline_mcp_settings.json`):
|
|
112
|
-
|
|
113
|
-
```json
|
|
114
|
-
{
|
|
115
|
-
"mcpServers": {
|
|
116
|
-
"git-mcp-server": {
|
|
117
|
-
"command": "node",
|
|
118
|
-
"args": ["/path/to/your/git-mcp-server/dist/index.js"],
|
|
119
|
-
"env": {
|
|
120
|
-
"GIT_SIGN_COMMITS": "true"
|
|
121
|
-
},
|
|
122
|
-
"disabled": false,
|
|
123
|
-
"autoApprove": []
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
**Note**: You can see [mcp.json](mcp.json) for an example MCP client configuration file that includes the Git MCP Server.\*
|
|
145
|
+
| `MCP_AUTH_MODE` | Authentication mode: `jwt`, `oauth`, or `none`. | `none` |
|
|
146
|
+
| `MCP_AUTH_SECRET_KEY` | Secret key for JWT validation (if `MCP_AUTH_MODE=jwt`). | `''` |
|
|
147
|
+
| `OAUTH_ISSUER_URL` | OIDC issuer URL for OAuth validation (if `MCP_AUTH_MODE=oauth`). | `''` |
|
|
148
|
+
| `OAUTH_AUDIENCE` | Audience claim for OAuth validation (if `MCP_AUTH_MODE=oauth`). | `''` |
|
|
130
149
|
|
|
131
150
|
## Project Structure
|
|
132
151
|
|
|
@@ -184,16 +203,14 @@ _Note: The `path` parameter for most tools defaults to the session's working dir
|
|
|
184
203
|
|
|
185
204
|
## Resources
|
|
186
205
|
|
|
187
|
-
**MCP Resources are not implemented in this version (v2.1.
|
|
206
|
+
**MCP Resources are not implemented in this version (v2.1.4).**
|
|
188
207
|
|
|
189
|
-
This version focuses on the refactored Git tools implementation based on the latest `mcp-ts-template` and MCP SDK v1.
|
|
208
|
+
This version focuses on the refactored Git tools implementation based on the latest `mcp-ts-template` and MCP SDK v1.13.0. Resource capabilities, previously available, have been temporarily removed during this major update.
|
|
190
209
|
|
|
191
210
|
If you require MCP Resource access (e.g., for reading file content directly via the server), please use the stable **[v1.2.4 release](https://github.com/cyanheads/git-mcp-server/releases/tag/v1.2.4)**.
|
|
192
211
|
|
|
193
212
|
Future development may reintroduce resource capabilities in a subsequent release.
|
|
194
213
|
|
|
195
|
-
> **Note:** This version (v2.0.0) focuses on refactoring and updating the core Git tools based on the latest MCP SDK. MCP Resource capabilities are not implemented in this version. For resource access, please use [v1.2.4](https://github.com/cyanheads/git-mcp-server/releases/tag/v1.2.4).
|
|
196
|
-
|
|
197
214
|
## Development
|
|
198
215
|
|
|
199
216
|
### Build and Test
|
|
@@ -208,15 +225,18 @@ npm run inspector
|
|
|
208
225
|
# Test the server locally using the MCP inspector tool (http transport)
|
|
209
226
|
npm run inspector:http
|
|
210
227
|
|
|
211
|
-
# Clean build artifacts
|
|
228
|
+
# Clean build artifacts
|
|
212
229
|
npm run clean
|
|
213
230
|
|
|
214
|
-
# Generate a file tree representation for documentation
|
|
231
|
+
# Generate a file tree representation for documentation
|
|
215
232
|
npm run tree
|
|
216
233
|
|
|
217
234
|
# Clean build artifacts and then rebuild the project
|
|
218
235
|
npm run rebuild
|
|
219
236
|
|
|
237
|
+
# Format code with Prettier
|
|
238
|
+
npm run format
|
|
239
|
+
|
|
220
240
|
# Start the server using stdio (default)
|
|
221
241
|
npm start
|
|
222
242
|
# Or explicitly:
|
package/dist/config/index.js
CHANGED
|
@@ -40,14 +40,22 @@ export const config = {
|
|
|
40
40
|
mcpAllowedOrigins: process.env.MCP_ALLOWED_ORIGINS?.split(",") || [],
|
|
41
41
|
/** Flag to enable GPG signing for commits made by the git_commit tool. Requires server-side GPG setup. */
|
|
42
42
|
gitSignCommits: process.env.GIT_SIGN_COMMITS === "true",
|
|
43
|
+
/** The authentication mode ('jwt', 'oauth', or 'none'). Defaults to 'none'. */
|
|
44
|
+
mcpAuthMode: process.env.MCP_AUTH_MODE || "none",
|
|
45
|
+
/** Secret key for signing/verifying JWTs. Required if mcpAuthMode is 'jwt'. */
|
|
46
|
+
mcpAuthSecretKey: process.env.MCP_AUTH_SECRET_KEY,
|
|
47
|
+
/** The OIDC issuer URL for OAuth token validation. Required if mcpAuthMode is 'oauth'. */
|
|
48
|
+
oauthIssuerUrl: process.env.OAUTH_ISSUER_URL,
|
|
49
|
+
/** The audience claim for OAuth token validation. Required if mcpAuthMode is 'oauth'. */
|
|
50
|
+
oauthAudience: process.env.OAUTH_AUDIENCE,
|
|
51
|
+
/** The JWKS URI for fetching public keys for OAuth. Optional, can be derived from issuer URL. */
|
|
52
|
+
oauthJwksUri: process.env.OAUTH_JWKS_URI,
|
|
43
53
|
/** Security-related configurations. */
|
|
44
54
|
security: {
|
|
45
55
|
// Placeholder for security settings
|
|
46
56
|
// Example: authRequired: process.env.AUTH_REQUIRED === 'true'
|
|
47
57
|
/** Indicates if authentication is required for server operations. */
|
|
48
58
|
authRequired: false,
|
|
49
|
-
/** Secret key for signing/verifying authentication tokens (required if authRequired is true). */
|
|
50
|
-
mcpAuthSecretKey: process.env.MCP_AUTH_SECRET_KEY || "", // Default to empty string, validation should happen elsewhere
|
|
51
59
|
},
|
|
52
60
|
// Note: mcpClient configuration is now loaded separately from mcp-config.json
|
|
53
61
|
};
|
|
@@ -43,9 +43,9 @@ import { initializeGitStatusStateAccessors, registerGitStatusTool, } from "./too
|
|
|
43
43
|
import { initializeGitTagStateAccessors, registerGitTagTool, } from "./tools/gitTag/index.js";
|
|
44
44
|
import { initializeGitWorktreeStateAccessors, registerGitWorktreeTool, } from "./tools/gitWorktree/index.js";
|
|
45
45
|
import { initializeGitWrapupInstructionsStateAccessors, registerGitWrapupInstructionsTool, } from "./tools/gitWrapupInstructions/index.js";
|
|
46
|
-
// Import transport setup functions
|
|
47
|
-
import {
|
|
48
|
-
import { connectStdioTransport
|
|
46
|
+
// Import transport setup functions
|
|
47
|
+
import { startHttpTransport } from "./transports/httpTransport.js";
|
|
48
|
+
import { connectStdioTransport } from "./transports/stdioTransport.js";
|
|
49
49
|
/**
|
|
50
50
|
* Creates and configures a new instance of the McpServer.
|
|
51
51
|
*
|
|
@@ -79,7 +79,9 @@ import { connectStdioTransport, getStdioWorkingDirectory, setStdioWorkingDirecto
|
|
|
79
79
|
*/
|
|
80
80
|
// Removed sessionId parameter, it will be retrieved from context within tool handlers
|
|
81
81
|
async function createMcpServerInstance() {
|
|
82
|
-
const context = {
|
|
82
|
+
const context = requestContextService.createRequestContext({
|
|
83
|
+
operation: "createMcpServerInstance",
|
|
84
|
+
});
|
|
83
85
|
logger.info("Initializing MCP server instance", context);
|
|
84
86
|
// Configure the request context service (used for correlating logs/errors).
|
|
85
87
|
requestContextService.configure({
|
|
@@ -110,6 +112,9 @@ async function createMcpServerInstance() {
|
|
|
110
112
|
tools: { listChanged: true },
|
|
111
113
|
},
|
|
112
114
|
});
|
|
115
|
+
// Each server instance is isolated per session. This variable will hold the
|
|
116
|
+
// working directory for the duration of this session.
|
|
117
|
+
let sessionWorkingDirectory = undefined;
|
|
113
118
|
// --- Define Unified State Accessor Functions ---
|
|
114
119
|
// These functions abstract away the transport type to get/set session state.
|
|
115
120
|
/** Gets the session ID from the tool's execution context. */
|
|
@@ -117,34 +122,21 @@ async function createMcpServerInstance() {
|
|
|
117
122
|
// The RequestContext created by the tool registration wrapper should contain the sessionId.
|
|
118
123
|
return toolContext?.sessionId;
|
|
119
124
|
};
|
|
120
|
-
/** Gets the working directory
|
|
125
|
+
/** Gets the working directory for the current session. */
|
|
121
126
|
const getWorkingDirectory = (sessionId) => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
return getHttpSessionWorkingDirectory(sessionId);
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
// For stdio, there's only one implicit session, ID is not needed.
|
|
131
|
-
return getStdioWorkingDirectory();
|
|
132
|
-
}
|
|
127
|
+
// The working directory is now stored in a variable scoped to this server instance.
|
|
128
|
+
// The sessionId is kept for potential logging or more complex future state management.
|
|
129
|
+
return sessionWorkingDirectory;
|
|
133
130
|
};
|
|
134
|
-
/** Sets the working directory
|
|
131
|
+
/** Sets the working directory for the current session. */
|
|
135
132
|
const setWorkingDirectory = (sessionId, dir) => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
// For stdio, set the single session's directory.
|
|
146
|
-
setStdioWorkingDirectory(dir);
|
|
147
|
-
}
|
|
133
|
+
// The working directory is now stored in a variable scoped to this server instance.
|
|
134
|
+
logger.debug("Setting session working directory", {
|
|
135
|
+
...context,
|
|
136
|
+
sessionId,
|
|
137
|
+
newDirectory: dir,
|
|
138
|
+
});
|
|
139
|
+
sessionWorkingDirectory = dir;
|
|
148
140
|
};
|
|
149
141
|
// --- Initialize Tool State Accessors BEFORE Registration ---
|
|
150
142
|
// Pass the defined unified accessor functions to the initializers.
|
|
@@ -254,7 +246,10 @@ async function createMcpServerInstance() {
|
|
|
254
246
|
async function startTransport() {
|
|
255
247
|
// Determine the transport type from the validated configuration.
|
|
256
248
|
const transportType = config.mcpTransportType;
|
|
257
|
-
const context = {
|
|
249
|
+
const context = requestContextService.createRequestContext({
|
|
250
|
+
operation: "startTransport",
|
|
251
|
+
transport: transportType,
|
|
252
|
+
});
|
|
258
253
|
logger.info(`Starting transport: ${transportType}`, context);
|
|
259
254
|
// --- HTTP Transport Setup ---
|
|
260
255
|
if (transportType === "http") {
|
|
@@ -294,7 +289,9 @@ async function startTransport() {
|
|
|
294
289
|
* @returns {Promise<void | McpServer>} Resolves upon successful startup (void for http, McpServer for stdio). Rejects on critical failure.
|
|
295
290
|
*/
|
|
296
291
|
export async function initializeAndStartServer() {
|
|
297
|
-
const context = {
|
|
292
|
+
const context = requestContextService.createRequestContext({
|
|
293
|
+
operation: "initializeAndStartServer",
|
|
294
|
+
});
|
|
298
295
|
logger.info("MCP Server initialization sequence started.", context);
|
|
299
296
|
try {
|
|
300
297
|
// Initiate the transport setup based on configuration.
|
|
@@ -310,7 +307,11 @@ export async function initializeAndStartServer() {
|
|
|
310
307
|
stack: err instanceof Error ? err.stack : undefined,
|
|
311
308
|
});
|
|
312
309
|
// Use the centralized error handler for consistent critical error reporting.
|
|
313
|
-
ErrorHandler.handleError(err, {
|
|
310
|
+
ErrorHandler.handleError(err, {
|
|
311
|
+
...context,
|
|
312
|
+
operation: "initializeAndStartServer_Catch",
|
|
313
|
+
critical: true,
|
|
314
|
+
});
|
|
314
315
|
// Exit the process with a non-zero code to indicate failure.
|
|
315
316
|
logger.info("Exiting process due to critical initialization error.", context);
|
|
316
317
|
process.exit(1);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the AsyncLocalStorage context for authentication information.
|
|
3
|
+
* This module provides a mechanism to store and retrieve authentication details
|
|
4
|
+
* (like scopes and client ID) across asynchronous operations, making it available
|
|
5
|
+
* from the middleware layer down to the tool and resource handlers without
|
|
6
|
+
* drilling props.
|
|
7
|
+
*
|
|
8
|
+
* @module src/mcp-server/transports/auth/core/authContext
|
|
9
|
+
*/
|
|
10
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
11
|
+
/**
|
|
12
|
+
* An instance of AsyncLocalStorage to hold the authentication context (`AuthStore`).
|
|
13
|
+
* This allows `authInfo` to be accessible throughout the async call chain of a request
|
|
14
|
+
* after being set in the authentication middleware.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // In middleware:
|
|
18
|
+
* await authContext.run({ authInfo }, next);
|
|
19
|
+
*
|
|
20
|
+
* // In a deeper handler:
|
|
21
|
+
* const store = authContext.getStore();
|
|
22
|
+
* const scopes = store?.authInfo.scopes;
|
|
23
|
+
*/
|
|
24
|
+
export const authContext = new AsyncLocalStorage();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Provides utility functions for authorization, specifically for
|
|
3
|
+
* checking token scopes against required permissions for a given operation.
|
|
4
|
+
* @module src/mcp-server/transports/auth/core/authUtils
|
|
5
|
+
*/
|
|
6
|
+
import { BaseErrorCode, McpError } from "../../../../types-global/errors.js";
|
|
7
|
+
import { logger, requestContextService } from "../../../../utils/index.js";
|
|
8
|
+
import { authContext } from "./authContext.js";
|
|
9
|
+
/**
|
|
10
|
+
* Checks if the current authentication context contains all the specified scopes.
|
|
11
|
+
* This function is designed to be called within tool or resource handlers to
|
|
12
|
+
* enforce scope-based access control. It retrieves the authentication information
|
|
13
|
+
* from `authContext` (AsyncLocalStorage).
|
|
14
|
+
*
|
|
15
|
+
* @param requiredScopes - An array of scope strings that are mandatory for the operation.
|
|
16
|
+
* @throws {McpError} Throws an error with `BaseErrorCode.INTERNAL_ERROR` if the
|
|
17
|
+
* authentication context is missing, which indicates a server configuration issue.
|
|
18
|
+
* @throws {McpError} Throws an error with `BaseErrorCode.FORBIDDEN` if one or
|
|
19
|
+
* more required scopes are not present in the validated token.
|
|
20
|
+
*/
|
|
21
|
+
export function withRequiredScopes(requiredScopes) {
|
|
22
|
+
const store = authContext.getStore();
|
|
23
|
+
if (!store || !store.authInfo) {
|
|
24
|
+
// 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",
|
|
27
|
+
error: "AuthStore not found in AsyncLocalStorage.",
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
const { scopes: grantedScopes, clientId } = store.authInfo;
|
|
31
|
+
const grantedScopeSet = new Set(grantedScopes);
|
|
32
|
+
const missingScopes = requiredScopes.filter((scope) => !grantedScopeSet.has(scope));
|
|
33
|
+
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 });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel file for the auth module.
|
|
3
|
+
* Exports core utilities and middleware strategies for easier imports.
|
|
4
|
+
* @module src/mcp-server/transports/auth/index
|
|
5
|
+
*/
|
|
6
|
+
export { authContext } from "./core/authContext.js";
|
|
7
|
+
export { withRequiredScopes } from "./core/authUtils.js";
|
|
8
|
+
export { mcpAuthMiddleware as jwtAuthMiddleware } from "./strategies/jwt/jwtMiddleware.js";
|
|
9
|
+
export { oauthMiddleware } from "./strategies/oauth/oauthMiddleware.js";
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MCP Authentication Middleware for Bearer Token Validation (JWT) for Hono.
|
|
3
|
+
*
|
|
4
|
+
* This middleware validates JSON Web Tokens (JWT) passed via the 'Authorization' header
|
|
5
|
+
* using the 'Bearer' scheme (e.g., "Authorization: Bearer <your_token>").
|
|
6
|
+
* It verifies the token's signature and expiration using the secret key defined
|
|
7
|
+
* in the configuration (`config.mcpAuthSecretKey`).
|
|
8
|
+
*
|
|
9
|
+
* If the token is valid, an object conforming to the MCP SDK's `AuthInfo` type
|
|
10
|
+
* is attached to `c.env.incoming.auth`. This direct attachment to the raw Node.js
|
|
11
|
+
* request object is for compatibility with the underlying SDK transport, which is
|
|
12
|
+
* not Hono-context-aware.
|
|
13
|
+
* If the token is missing, invalid, or expired, it throws an `McpError`, which is
|
|
14
|
+
* then handled by the centralized `httpErrorHandler`.
|
|
15
|
+
*
|
|
16
|
+
* @see {@link https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/authorization.mdx | MCP Authorization Specification}
|
|
17
|
+
* @module src/mcp-server/transports/auth/strategies/jwt/jwtMiddleware
|
|
18
|
+
*/
|
|
19
|
+
import { jwtVerify } from "jose";
|
|
20
|
+
import { config, environment } from "../../../../../config/index.js";
|
|
21
|
+
import { logger, requestContextService } from "../../../../../utils/index.js";
|
|
22
|
+
import { BaseErrorCode, McpError } from "../../../../../types-global/errors.js";
|
|
23
|
+
import { authContext } from "../../core/authContext.js";
|
|
24
|
+
// Startup Validation: Validate secret key presence on module load.
|
|
25
|
+
if (config.mcpAuthMode === "jwt") {
|
|
26
|
+
if (environment === "production" && !config.mcpAuthSecretKey) {
|
|
27
|
+
logger.fatal("CRITICAL: MCP_AUTH_SECRET_KEY is not set in production environment for JWT auth. Authentication cannot proceed securely.");
|
|
28
|
+
throw new Error("MCP_AUTH_SECRET_KEY must be set in production environment for JWT authentication.");
|
|
29
|
+
}
|
|
30
|
+
else if (!config.mcpAuthSecretKey) {
|
|
31
|
+
logger.warning("MCP_AUTH_SECRET_KEY is not set. JWT auth middleware will bypass checks (DEVELOPMENT ONLY). This is insecure for production.");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Hono middleware for verifying JWT Bearer token authentication.
|
|
36
|
+
* It attaches authentication info to `c.env.incoming.auth` for SDK compatibility with the node server.
|
|
37
|
+
*/
|
|
38
|
+
export async function mcpAuthMiddleware(c, next) {
|
|
39
|
+
const context = requestContextService.createRequestContext({
|
|
40
|
+
operation: "mcpAuthMiddleware",
|
|
41
|
+
method: c.req.method,
|
|
42
|
+
path: c.req.path,
|
|
43
|
+
});
|
|
44
|
+
logger.debug("Running MCP Authentication Middleware (Bearer Token Validation)...", context);
|
|
45
|
+
const reqWithAuth = c.env.incoming;
|
|
46
|
+
// If JWT auth is not enabled, skip the middleware.
|
|
47
|
+
if (config.mcpAuthMode !== "jwt") {
|
|
48
|
+
return await next();
|
|
49
|
+
}
|
|
50
|
+
// Development Mode Bypass
|
|
51
|
+
if (!config.mcpAuthSecretKey) {
|
|
52
|
+
if (environment !== "production") {
|
|
53
|
+
logger.warning("Bypassing JWT authentication: MCP_AUTH_SECRET_KEY is not set (DEVELOPMENT ONLY).", context);
|
|
54
|
+
reqWithAuth.auth = {
|
|
55
|
+
token: "dev-mode-placeholder-token",
|
|
56
|
+
clientId: "dev-client-id",
|
|
57
|
+
scopes: ["dev-scope"],
|
|
58
|
+
};
|
|
59
|
+
const authInfo = reqWithAuth.auth;
|
|
60
|
+
logger.debug("Dev mode auth object created.", {
|
|
61
|
+
...context,
|
|
62
|
+
authDetails: authInfo,
|
|
63
|
+
});
|
|
64
|
+
return await authContext.run({ authInfo }, next);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
logger.error("FATAL: MCP_AUTH_SECRET_KEY is missing in production. Cannot bypass auth.", context);
|
|
68
|
+
throw new McpError(BaseErrorCode.INTERNAL_ERROR, "Server configuration error: Authentication key missing.");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const secretKey = new TextEncoder().encode(config.mcpAuthSecretKey);
|
|
72
|
+
const authHeader = c.req.header("Authorization");
|
|
73
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
74
|
+
logger.warning("Authentication failed: Missing or malformed Authorization header (Bearer scheme required).", context);
|
|
75
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Missing or invalid authentication token format.");
|
|
76
|
+
}
|
|
77
|
+
const tokenParts = authHeader.split(" ");
|
|
78
|
+
if (tokenParts.length !== 2 || tokenParts[0] !== "Bearer" || !tokenParts[1]) {
|
|
79
|
+
logger.warning("Authentication failed: Malformed Bearer token.", context);
|
|
80
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Malformed authentication token.");
|
|
81
|
+
}
|
|
82
|
+
const rawToken = tokenParts[1];
|
|
83
|
+
try {
|
|
84
|
+
const { payload: decoded } = await jwtVerify(rawToken, secretKey);
|
|
85
|
+
const clientIdFromToken = typeof decoded.cid === "string"
|
|
86
|
+
? decoded.cid
|
|
87
|
+
: typeof decoded.client_id === "string"
|
|
88
|
+
? decoded.client_id
|
|
89
|
+
: undefined;
|
|
90
|
+
if (!clientIdFromToken) {
|
|
91
|
+
logger.warning("Authentication failed: JWT 'cid' or 'client_id' claim is missing or not a string.", { ...context, jwtPayloadKeys: Object.keys(decoded) });
|
|
92
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Invalid token, missing client identifier.");
|
|
93
|
+
}
|
|
94
|
+
let scopesFromToken = [];
|
|
95
|
+
if (Array.isArray(decoded.scp) &&
|
|
96
|
+
decoded.scp.every((s) => typeof s === "string")) {
|
|
97
|
+
scopesFromToken = decoded.scp;
|
|
98
|
+
}
|
|
99
|
+
else if (typeof decoded.scope === "string" &&
|
|
100
|
+
decoded.scope.trim() !== "") {
|
|
101
|
+
scopesFromToken = decoded.scope.split(" ").filter((s) => s);
|
|
102
|
+
if (scopesFromToken.length === 0 && decoded.scope.trim() !== "") {
|
|
103
|
+
scopesFromToken = [decoded.scope.trim()];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (scopesFromToken.length === 0) {
|
|
107
|
+
logger.warning("Authentication failed: Token resulted in an empty scope array, and scopes are required.", { ...context, jwtPayloadKeys: Object.keys(decoded) });
|
|
108
|
+
throw new McpError(BaseErrorCode.UNAUTHORIZED, "Token must contain valid, non-empty scopes.");
|
|
109
|
+
}
|
|
110
|
+
reqWithAuth.auth = {
|
|
111
|
+
token: rawToken,
|
|
112
|
+
clientId: clientIdFromToken,
|
|
113
|
+
scopes: scopesFromToken,
|
|
114
|
+
};
|
|
115
|
+
const subClaimForLogging = typeof decoded.sub === "string" ? decoded.sub : undefined;
|
|
116
|
+
const authInfo = reqWithAuth.auth;
|
|
117
|
+
logger.debug("JWT verified successfully. AuthInfo attached to request.", {
|
|
118
|
+
...context,
|
|
119
|
+
mcpSessionIdContext: subClaimForLogging,
|
|
120
|
+
clientId: authInfo.clientId,
|
|
121
|
+
scopes: authInfo.scopes,
|
|
122
|
+
});
|
|
123
|
+
await authContext.run({ authInfo }, next);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
let errorMessage = "Invalid token.";
|
|
127
|
+
let errorCode = BaseErrorCode.UNAUTHORIZED;
|
|
128
|
+
if (error instanceof Error && error.name === "JWTExpired") {
|
|
129
|
+
errorMessage = "Token expired.";
|
|
130
|
+
logger.warning("Authentication failed: Token expired.", {
|
|
131
|
+
...context,
|
|
132
|
+
errorName: error.name,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else if (error instanceof Error) {
|
|
136
|
+
errorMessage = `Invalid token: ${error.message}`;
|
|
137
|
+
logger.warning(`Authentication failed: ${errorMessage}`, {
|
|
138
|
+
...context,
|
|
139
|
+
errorName: error.name,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
errorMessage = "Unknown verification error.";
|
|
144
|
+
errorCode = BaseErrorCode.INTERNAL_ERROR;
|
|
145
|
+
logger.error("Authentication failed: Unexpected non-error exception during token verification.", { ...context, error });
|
|
146
|
+
}
|
|
147
|
+
throw new McpError(errorCode, errorMessage);
|
|
148
|
+
}
|
|
149
|
+
}
|