@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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Handles the setup and connection for the Stdio MCP transport.
2
+ * @fileoverview Handles the setup and connection for the Stdio MCP transport.
3
3
  * Implements the MCP Specification 2025-03-26 for stdio transport.
4
4
  * This transport communicates directly over standard input (stdin) and
5
5
  * standard output (stdout), typically used when the MCP server is launched
@@ -15,79 +15,49 @@
15
15
  * controlling the server process. This implementation follows that guideline.
16
16
  *
17
17
  * @see {@link https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/authorization.mdx | MCP Authorization Specification}
18
+ * @module src/mcp-server/transports/stdioTransport
18
19
  */
19
20
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
- // Import core utilities: ErrorHandler for centralized error management and logger for logging.
21
21
  import { ErrorHandler, logger } from "../../utils/index.js";
22
- // --- Stdio Session State ---
23
- // Since stdio typically involves a single, persistent connection managed by a parent,
24
- // we manage a single working directory state for the entire process.
25
- let currentWorkingDirectory = undefined; // Initialize as undefined
26
22
  /**
27
- * Gets the current working directory set for the stdio session.
28
- * @returns {string | undefined} The current working directory path or undefined if not set.
29
- */
30
- export function getStdioWorkingDirectory() {
31
- return currentWorkingDirectory;
32
- }
33
- /**
34
- * Sets the working directory for the stdio session.
35
- * @param {string} dir - The new working directory path.
36
- */
37
- export function setStdioWorkingDirectory(dir) {
38
- currentWorkingDirectory = dir;
39
- logger.info(`Stdio working directory set to: ${dir}`, {
40
- operation: "setStdioWorkingDirectory",
41
- });
42
- }
43
- /**
44
- * Connects a given McpServer instance to the Stdio transport. (Asynchronous)
45
- * Initializes the SDK's StdioServerTransport, which handles reading newline-delimited
46
- * JSON-RPC messages from process.stdin and writing corresponding messages to process.stdout,
47
- * adhering to the MCP stdio transport specification.
23
+ * Connects a given `McpServer` instance to the Stdio transport.
24
+ * This function initializes the SDK's `StdioServerTransport`, which manages
25
+ * communication over `process.stdin` and `process.stdout` according to the
26
+ * MCP stdio transport specification.
48
27
  *
49
- * MCP Spec Points Covered by SDK's StdioServerTransport:
28
+ * MCP Spec Points Covered by SDK's `StdioServerTransport`:
50
29
  * - Reads JSON-RPC messages (requests, notifications, responses, batches) from stdin.
51
30
  * - Writes JSON-RPC messages to stdout.
52
31
  * - Handles newline delimiters and ensures no embedded newlines in output messages.
53
32
  * - Ensures only valid MCP messages are written to stdout.
54
33
  *
55
- * Note: Logging via the `logger` utility MAY result in output to stderr, which is
34
+ * Logging via the `logger` utility MAY result in output to stderr, which is
56
35
  * permitted by the spec for logging purposes.
57
36
  *
58
- * @param {McpServer} server - The McpServer instance containing the core logic (tools, resources).
59
- * @param {Record<string, any>} context - Logging context for correlation.
60
- * @returns {Promise<void>} A promise that resolves when the connection is successfully established.
61
- * @throws {Error} Throws an error if the connection fails during setup (e.g., issues connecting server to transport).
37
+ * @param server - The `McpServer` instance.
38
+ * @param parentContext - The logging and tracing context from the calling function.
39
+ * @returns A promise that resolves when the Stdio transport is successfully connected.
40
+ * @throws {Error} If the connection fails during setup.
62
41
  */
63
- export async function connectStdioTransport(server, context) {
64
- // Add a specific operation name to the context for better log filtering.
42
+ export async function connectStdioTransport(server, parentContext) {
65
43
  const operationContext = {
66
- ...context,
44
+ ...parentContext,
67
45
  operation: "connectStdioTransport",
68
46
  transportType: "Stdio",
69
47
  };
70
48
  logger.debug("Attempting to connect stdio transport...", operationContext);
71
49
  try {
72
50
  logger.debug("Creating StdioServerTransport instance...", operationContext);
73
- // Instantiate the transport provided by the SDK for standard I/O communication.
74
- // This class encapsulates the logic for reading from stdin and writing to stdout
75
- // according to the MCP stdio spec.
76
51
  const transport = new StdioServerTransport();
77
52
  logger.debug("Connecting McpServer instance to StdioServerTransport...", operationContext);
78
- // Establish the link between the server's core logic and the transport layer.
79
- // This internally starts the necessary listeners on process.stdin.
80
53
  await server.connect(transport);
81
- // Log successful connection. The server is now ready to process messages via stdio.
82
54
  logger.info("MCP Server connected and listening via stdio transport.", operationContext);
83
- // Use logger.notice for startup message to ensure MCP compliance and proper handling by clients.
84
- logger.notice(`\nšŸš€ MCP Server running in STDIO mode.\n (MCP Spec: 2025-03-26 Stdio Transport)\n`, operationContext);
55
+ if (process.stdout.isTTY) {
56
+ console.log(`\nšŸš€ MCP Server running in STDIO mode.\n (MCP Spec: 2025-03-26 Stdio Transport)\n`);
57
+ }
85
58
  }
86
59
  catch (err) {
87
- // Catch and handle any critical errors during the transport connection setup.
88
- // Mark as critical because the server cannot function without a connected transport.
89
60
  ErrorHandler.handleError(err, { ...operationContext, critical: true });
90
- // Rethrow the error to signal the failure to the calling code (e.g., the main server startup).
91
- throw err;
61
+ throw err; // Re-throw after handling to allow caller to react if necessary
92
62
  }
93
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "description": "An MCP (Model Context Protocol) server enabling LLMs and AI agents to interact with Git repositories. Provides tools for comprehensive Git operations including clone, commit, branch, diff, log, status, push, pull, merge, rebase, worktree, tag management, and more, via the MCP standard. STDIO & HTTP.",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -35,21 +35,23 @@
35
35
  "clean": "ts-node --esm scripts/clean.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@modelcontextprotocol/inspector": "^0.14.1",
39
- "@modelcontextprotocol/sdk": "^1.12.3",
40
- "@types/jsonwebtoken": "^9.0.9",
41
- "@types/node": "^24.0.1",
38
+ "@hono/node-server": "^1.14.4",
39
+ "@modelcontextprotocol/inspector": "^0.14.3",
40
+ "@modelcontextprotocol/sdk": "^1.13.0",
41
+ "@types/jsonwebtoken": "^9.0.10",
42
+ "@types/node": "^24.0.3",
42
43
  "@types/sanitize-html": "^2.16.0",
43
- "@types/validator": "^13.15.1",
44
+ "@types/validator": "^13.15.2",
44
45
  "chalk": "^5.4.1",
45
46
  "chrono-node": "2.8.0",
46
47
  "cli-table3": "^0.6.5",
47
48
  "dotenv": "^16.5.0",
48
49
  "express": "^5.1.0",
50
+ "hono": "^4.8.2",
49
51
  "ignore": "^7.0.5",
50
52
  "jose": "^6.0.11",
51
53
  "jsonwebtoken": "^9.0.2",
52
- "openai": "^5.3.0",
54
+ "openai": "^5.6.0",
53
55
  "partial-json": "^0.1.7",
54
56
  "sanitize-html": "^2.17.0",
55
57
  "tiktoken": "^1.0.21",
@@ -59,7 +61,7 @@
59
61
  "winston": "^3.17.0",
60
62
  "winston-daily-rotate-file": "^5.0.0",
61
63
  "yargs": "^18.0.0",
62
- "zod": "^3.25.64"
64
+ "zod": "^3.25.67"
63
65
  },
64
66
  "keywords": [
65
67
  "typescript",
@@ -1,167 +0,0 @@
1
- /**
2
- * @fileoverview MCP Authentication Middleware for Bearer Token Validation (JWT).
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
- * (expected to contain `token`, `clientId`, and `scopes`) is attached to `req.auth`.
11
- * If the token is missing, invalid, or expired, it sends an HTTP 401 Unauthorized response.
12
- *
13
- * @see {@link https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/authorization.mdx | MCP Authorization Specification}
14
- * @module src/mcp-server/transports/authentication/authMiddleware
15
- */
16
- import jwt from "jsonwebtoken";
17
- import { config, environment } from "../../../config/index.js";
18
- import { logger, requestContextService } from "../../../utils/index.js";
19
- // Startup Validation: Validate secret key presence on module load.
20
- if (environment === "production" && !config.security.mcpAuthSecretKey) {
21
- logger.fatal("CRITICAL: MCP_AUTH_SECRET_KEY is not set in production environment. Authentication cannot proceed securely.");
22
- throw new Error("MCP_AUTH_SECRET_KEY must be set in production environment for JWT authentication.");
23
- }
24
- else if (!config.security.mcpAuthSecretKey) {
25
- logger.warning("MCP_AUTH_SECRET_KEY is not set. Authentication middleware will bypass checks (DEVELOPMENT ONLY). This is insecure for production.");
26
- }
27
- /**
28
- * Express middleware for verifying JWT Bearer token authentication.
29
- */
30
- export function mcpAuthMiddleware(req, res, next) {
31
- const context = requestContextService.createRequestContext({
32
- operation: "mcpAuthMiddleware",
33
- method: req.method,
34
- path: req.path,
35
- });
36
- logger.debug("Running MCP Authentication Middleware (Bearer Token Validation)...", context);
37
- // Development Mode Bypass
38
- if (!config.security.mcpAuthSecretKey) {
39
- if (environment !== "production") {
40
- logger.warning("Bypassing JWT authentication: MCP_AUTH_SECRET_KEY is not set (DEVELOPMENT ONLY).", context);
41
- // Populate req.auth strictly according to SDK's AuthInfo
42
- req.auth = {
43
- token: "dev-mode-placeholder-token",
44
- clientId: "dev-client-id",
45
- scopes: ["dev-scope"],
46
- };
47
- // Log dev mode details separately, not attaching to req.auth if not part of AuthInfo
48
- logger.debug("Dev mode auth object created.", {
49
- ...context,
50
- authDetails: req.auth,
51
- });
52
- return next();
53
- }
54
- else {
55
- logger.error("FATAL: MCP_AUTH_SECRET_KEY is missing in production. Cannot bypass auth.", context);
56
- res.status(500).json({
57
- error: "Server configuration error: Authentication key missing.",
58
- });
59
- return;
60
- }
61
- }
62
- const authHeader = req.headers.authorization;
63
- if (!authHeader || !authHeader.startsWith("Bearer ")) {
64
- logger.warning("Authentication failed: Missing or malformed Authorization header (Bearer scheme required).", context);
65
- res.status(401).json({
66
- error: "Unauthorized: Missing or invalid authentication token format.",
67
- });
68
- return;
69
- }
70
- const tokenParts = authHeader.split(" ");
71
- if (tokenParts.length !== 2 || tokenParts[0] !== "Bearer" || !tokenParts[1]) {
72
- logger.warning("Authentication failed: Malformed Bearer token.", context);
73
- res
74
- .status(401)
75
- .json({ error: "Unauthorized: Malformed authentication token." });
76
- return;
77
- }
78
- const rawToken = tokenParts[1];
79
- try {
80
- const decoded = jwt.verify(rawToken, config.security.mcpAuthSecretKey);
81
- if (typeof decoded === "string") {
82
- logger.warning("Authentication failed: JWT decoded to a string, expected an object payload.", context);
83
- res
84
- .status(401)
85
- .json({ error: "Unauthorized: Invalid token payload format." });
86
- return;
87
- }
88
- // Extract and validate fields for SDK's AuthInfo
89
- const clientIdFromToken = typeof decoded.cid === "string"
90
- ? decoded.cid
91
- : typeof decoded.client_id === "string"
92
- ? decoded.client_id
93
- : undefined;
94
- if (!clientIdFromToken) {
95
- logger.warning("Authentication failed: JWT 'cid' or 'client_id' claim is missing or not a string.", { ...context, jwtPayloadKeys: Object.keys(decoded) });
96
- res.status(401).json({
97
- error: "Unauthorized: Invalid token, missing client identifier.",
98
- });
99
- return;
100
- }
101
- let scopesFromToken;
102
- if (Array.isArray(decoded.scp) &&
103
- decoded.scp.every((s) => typeof s === "string")) {
104
- scopesFromToken = decoded.scp;
105
- }
106
- else if (typeof decoded.scope === "string" &&
107
- decoded.scope.trim() !== "") {
108
- scopesFromToken = decoded.scope.split(" ").filter((s) => s);
109
- if (scopesFromToken.length === 0 && decoded.scope.trim() !== "") {
110
- // handles case " " -> [""]
111
- scopesFromToken = [decoded.scope.trim()];
112
- }
113
- else if (scopesFromToken.length === 0 && decoded.scope.trim() === "") {
114
- // If scope is an empty string, treat as no scopes rather than erroring, or use a default.
115
- // Depending on strictness, could also error here. For now, allow empty array if scope was empty string.
116
- logger.debug("JWT 'scope' claim was an empty string, resulting in empty scopes array.", context);
117
- }
118
- }
119
- else {
120
- // If scopes are strictly mandatory and not found or invalid format
121
- logger.warning("Authentication failed: JWT 'scp' or 'scope' claim is missing, not an array of strings, or not a valid space-separated string. Assigning default empty array.", { ...context, jwtPayloadKeys: Object.keys(decoded) });
122
- scopesFromToken = []; // Default to empty array if scopes are mandatory but not found/invalid
123
- // Or, if truly mandatory and must be non-empty:
124
- // res.status(401).json({ error: "Unauthorized: Invalid token, missing or invalid scopes." });
125
- // return;
126
- }
127
- // Construct req.auth with only the properties defined in SDK's AuthInfo
128
- // All other claims from 'decoded' are not part of req.auth for type safety.
129
- req.auth = {
130
- token: rawToken,
131
- clientId: clientIdFromToken,
132
- scopes: scopesFromToken,
133
- };
134
- // Log separately if other JWT claims like 'sub' (sessionId) are needed for app logic
135
- const subClaimForLogging = typeof decoded.sub === "string" ? decoded.sub : undefined;
136
- logger.debug("JWT verified successfully. AuthInfo attached to request.", {
137
- ...context,
138
- mcpSessionIdContext: subClaimForLogging,
139
- clientId: req.auth.clientId,
140
- scopes: req.auth.scopes,
141
- });
142
- next();
143
- }
144
- catch (error) {
145
- let errorMessage = "Invalid token";
146
- if (error instanceof jwt.TokenExpiredError) {
147
- errorMessage = "Token expired";
148
- logger.warning("Authentication failed: Token expired.", {
149
- ...context,
150
- expiredAt: error.expiredAt,
151
- });
152
- }
153
- else if (error instanceof jwt.JsonWebTokenError) {
154
- errorMessage = `Invalid token: ${error.message}`;
155
- logger.warning(`Authentication failed: ${errorMessage}`, { ...context });
156
- }
157
- else if (error instanceof Error) {
158
- errorMessage = `Verification error: ${error.message}`;
159
- logger.error("Authentication failed: Unexpected error during token verification.", { ...context, error: error.message });
160
- }
161
- else {
162
- errorMessage = "Unknown verification error";
163
- logger.error("Authentication failed: Unexpected non-error exception during token verification.", { ...context, error });
164
- }
165
- res.status(401).json({ error: `Unauthorized: ${errorMessage}.` });
166
- }
167
- }