@cyanheads/git-mcp-server 2.2.2 → 2.2.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.
Files changed (35) hide show
  1. package/README.md +63 -124
  2. package/dist/config/index.js +248 -53
  3. package/dist/mcp-server/server.js +6 -6
  4. package/dist/mcp-server/tools/gitCherryPick/logic.js +39 -13
  5. package/dist/mcp-server/tools/gitCommit/logic.js +1 -1
  6. package/dist/mcp-server/tools/gitMerge/logic.js +38 -13
  7. package/dist/mcp-server/tools/gitTag/logic.js +38 -19
  8. package/dist/mcp-server/transports/auth/authFactory.js +41 -0
  9. package/dist/mcp-server/transports/auth/authMiddleware.js +57 -0
  10. package/dist/mcp-server/transports/auth/index.js +6 -4
  11. package/dist/mcp-server/transports/auth/lib/authTypes.js +8 -0
  12. package/dist/mcp-server/transports/auth/{core → lib}/authUtils.js +21 -14
  13. package/dist/mcp-server/transports/auth/strategies/authStrategy.js +1 -0
  14. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +113 -0
  15. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +102 -0
  16. package/dist/mcp-server/transports/core/baseTransportManager.js +19 -0
  17. package/dist/mcp-server/transports/core/honoNodeBridge.js +51 -0
  18. package/dist/mcp-server/transports/core/statefulTransportManager.js +234 -0
  19. package/dist/mcp-server/transports/core/statelessTransportManager.js +92 -0
  20. package/dist/mcp-server/transports/core/transportTypes.js +5 -0
  21. package/dist/mcp-server/transports/{httpErrorHandler.js → http/httpErrorHandler.js} +33 -8
  22. package/dist/mcp-server/transports/http/httpTransport.js +254 -0
  23. package/dist/mcp-server/transports/http/httpTypes.js +5 -0
  24. package/dist/mcp-server/transports/http/index.js +6 -0
  25. package/dist/mcp-server/transports/http/mcpTransportMiddleware.js +63 -0
  26. package/dist/mcp-server/transports/stdio/index.js +5 -0
  27. package/dist/mcp-server/transports/{stdioTransport.js → stdio/stdioTransport.js} +10 -5
  28. package/dist/types-global/errors.js +75 -19
  29. package/dist/utils/internal/errorHandler.js +11 -13
  30. package/package.json +18 -7
  31. package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
  32. package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
  33. package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -127
  34. package/dist/mcp-server/transports/httpTransport.js +0 -207
  35. /package/dist/mcp-server/transports/auth/{core → lib}/authContext.js +0 -0
@@ -0,0 +1,254 @@
1
+ /**
2
+ * @fileoverview Configures and starts the HTTP MCP transport using Hono.
3
+ * This file has been refactored to correctly integrate Hono's streaming
4
+ * capabilities with the Model Context Protocol SDK's transport layer.
5
+ * @module src/mcp-server/transports/http/httpTransport
6
+ */
7
+ import { serve } from "@hono/node-server";
8
+ import { Hono } from "hono";
9
+ import { cors } from "hono/cors";
10
+ import { stream } from "hono/streaming";
11
+ import http from "http";
12
+ import { config } from "../../../config/index.js";
13
+ import { logger, rateLimiter, requestContextService, } from "../../../utils/index.js";
14
+ import { createAuthMiddleware, createAuthStrategy } from "../auth/index.js";
15
+ import { StatefulTransportManager } from "../core/statefulTransportManager.js";
16
+ import { StatelessTransportManager } from "../core/statelessTransportManager.js";
17
+ import { httpErrorHandler } from "./httpErrorHandler.js";
18
+ import { mcpTransportMiddleware } from "./mcpTransportMiddleware.js";
19
+ const HTTP_PORT = config.mcpHttpPort;
20
+ const HTTP_HOST = config.mcpHttpHost;
21
+ const MCP_ENDPOINT_PATH = config.mcpHttpEndpointPath;
22
+ /**
23
+ * Converts a Fetch API Headers object to Node.js IncomingHttpHeaders.
24
+ * Hono uses Fetch API Headers, but the underlying transport managers expect
25
+ * Node's native IncomingHttpHeaders.
26
+ * @param headers - The Headers object to convert.
27
+ * @returns An object compatible with IncomingHttpHeaders.
28
+ */
29
+ async function isPortInUse(port, host, parentContext) {
30
+ const context = { ...parentContext, operation: "isPortInUse", port, host };
31
+ logger.debug(`Checking if port ${port} is in use...`, context);
32
+ return new Promise((resolve) => {
33
+ const tempServer = http.createServer();
34
+ tempServer
35
+ .once("error", (err) => {
36
+ const inUse = err.code === "EADDRINUSE";
37
+ logger.debug(`Port check resulted in error: ${err.code}. Port in use: ${inUse}`, context);
38
+ resolve(inUse);
39
+ })
40
+ .once("listening", () => {
41
+ logger.debug(`Successfully bound to port ${port} temporarily. Port is not in use.`, context);
42
+ tempServer.close(() => resolve(false));
43
+ })
44
+ .listen(port, host);
45
+ });
46
+ }
47
+ function startHttpServerWithRetry(app, initialPort, host, maxRetries, parentContext) {
48
+ const startContext = {
49
+ ...parentContext,
50
+ operation: "startHttpServerWithRetry",
51
+ };
52
+ logger.info(`Attempting to start HTTP server on port ${initialPort} with ${maxRetries} retries.`, startContext);
53
+ return new Promise((resolve, reject) => {
54
+ const tryBind = (port, attempt) => {
55
+ const attemptContext = { ...startContext, port, attempt };
56
+ if (attempt > maxRetries + 1) {
57
+ const error = new Error(`Failed to bind to any port after ${maxRetries} retries.`);
58
+ logger.fatal(error.message, attemptContext);
59
+ return reject(error);
60
+ }
61
+ isPortInUse(port, host, attemptContext)
62
+ .then((inUse) => {
63
+ if (inUse) {
64
+ logger.warning(`Port ${port} is in use, retrying on port ${port + 1}...`, attemptContext);
65
+ setTimeout(() => tryBind(port + 1, attempt + 1), config.mcpHttpPortRetryDelayMs);
66
+ return;
67
+ }
68
+ try {
69
+ const serverInstance = serve({ fetch: app.fetch, port, hostname: host }, (info) => {
70
+ const serverAddress = `http://${info.address}:${info.port}${MCP_ENDPOINT_PATH}`;
71
+ logger.info(`HTTP transport listening at ${serverAddress}`, {
72
+ ...attemptContext,
73
+ address: serverAddress,
74
+ sessionMode: config.mcpSessionMode,
75
+ });
76
+ if (process.stdout.isTTY) {
77
+ console.log(`\n🚀 MCP Server running at: ${serverAddress}`);
78
+ console.log(` Session Mode: ${config.mcpSessionMode}\n`);
79
+ }
80
+ });
81
+ resolve(serverInstance);
82
+ }
83
+ catch (err) {
84
+ if (err &&
85
+ typeof err === "object" &&
86
+ "code" in err &&
87
+ err.code !== "EADDRINUSE") {
88
+ const errorToLog = err instanceof Error ? err : new Error(String(err));
89
+ logger.error("An unexpected error occurred while starting the server.", { ...attemptContext, error: errorToLog });
90
+ return reject(err);
91
+ }
92
+ logger.warning(`Encountered EADDRINUSE race condition on port ${port}, retrying...`, attemptContext);
93
+ setTimeout(() => tryBind(port + 1, attempt + 1), config.mcpHttpPortRetryDelayMs);
94
+ }
95
+ })
96
+ .catch((err) => {
97
+ logger.fatal("Failed to check if port is in use.", {
98
+ ...attemptContext,
99
+ error: err,
100
+ });
101
+ reject(err);
102
+ });
103
+ };
104
+ tryBind(initialPort, 1);
105
+ });
106
+ }
107
+ function createTransportManager(createServerInstanceFn, sessionMode, context) {
108
+ const opContext = {
109
+ ...context,
110
+ operation: "createTransportManager",
111
+ sessionMode,
112
+ };
113
+ logger.info(`Creating transport manager for session mode: ${sessionMode}`, opContext);
114
+ switch (sessionMode) {
115
+ case "stateless":
116
+ return new StatelessTransportManager(createServerInstanceFn);
117
+ case "stateful":
118
+ return new StatefulTransportManager(createServerInstanceFn);
119
+ case "auto":
120
+ default:
121
+ logger.info("Defaulting to 'auto' mode (stateful with stateless fallback).", opContext);
122
+ return new StatefulTransportManager(createServerInstanceFn);
123
+ }
124
+ }
125
+ export function createHttpApp(transportManager, createServerInstanceFn, parentContext) {
126
+ const app = new Hono();
127
+ const transportContext = {
128
+ ...parentContext,
129
+ component: "HttpTransportSetup",
130
+ };
131
+ logger.info("Creating Hono HTTP application.", transportContext);
132
+ app.use("*", cors({
133
+ origin: config.mcpAllowedOrigins || [],
134
+ allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
135
+ allowHeaders: [
136
+ "Content-Type",
137
+ "Mcp-Session-Id",
138
+ "Last-Event-ID",
139
+ "Authorization",
140
+ ],
141
+ credentials: true,
142
+ }));
143
+ app.use("*", async (c, next) => {
144
+ c.env.outgoing.setHeader("X-Content-Type-Options", "nosniff");
145
+ await next();
146
+ });
147
+ app.use(MCP_ENDPOINT_PATH, async (c, next) => {
148
+ const clientIp = c.req.header("x-forwarded-for")?.split(",")[0].trim() || "unknown_ip";
149
+ const context = requestContextService.createRequestContext({
150
+ operation: "httpRateLimitCheck",
151
+ ipAddress: clientIp,
152
+ });
153
+ try {
154
+ rateLimiter.check(clientIp, context);
155
+ logger.debug("Rate limit check passed.", context);
156
+ }
157
+ catch (error) {
158
+ logger.warning("Rate limit check failed.", {
159
+ ...context,
160
+ error: error instanceof Error ? error.message : String(error),
161
+ });
162
+ throw error;
163
+ }
164
+ await next();
165
+ });
166
+ const authStrategy = createAuthStrategy();
167
+ if (authStrategy) {
168
+ logger.info("Authentication strategy found, enabling auth middleware.", transportContext);
169
+ app.use(MCP_ENDPOINT_PATH, createAuthMiddleware(authStrategy));
170
+ }
171
+ else {
172
+ logger.info("No authentication strategy found, auth middleware disabled.", transportContext);
173
+ }
174
+ app.onError(httpErrorHandler);
175
+ app.get("/healthz", (c) => {
176
+ return c.json({
177
+ status: "ok",
178
+ timestamp: new Date().toISOString(),
179
+ });
180
+ });
181
+ app.get(MCP_ENDPOINT_PATH, (c) => {
182
+ const sessionId = c.req.header("mcp-session-id");
183
+ if (sessionId) {
184
+ return c.text("GET requests to existing sessions are not supported.", 405);
185
+ }
186
+ return c.json({
187
+ status: "ok",
188
+ mode: "stateless",
189
+ message: "Server is running. Provide a Mcp-Session-Id header to stream from a session.",
190
+ });
191
+ });
192
+ app.post(MCP_ENDPOINT_PATH, mcpTransportMiddleware(transportManager, createServerInstanceFn), (c) => {
193
+ const response = c.get("mcpResponse");
194
+ if (response.sessionId) {
195
+ c.header("Mcp-Session-Id", response.sessionId);
196
+ }
197
+ response.headers.forEach((value, key) => {
198
+ c.header(key, value);
199
+ });
200
+ c.status(response.statusCode);
201
+ if (response.stream) {
202
+ return stream(c, async (s) => {
203
+ if (response.stream) {
204
+ await s.pipe(response.stream);
205
+ }
206
+ });
207
+ }
208
+ else {
209
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
+ return c.json(response.body);
211
+ }
212
+ });
213
+ app.delete(MCP_ENDPOINT_PATH, async (c) => {
214
+ const sessionId = c.req.header("mcp-session-id");
215
+ const context = requestContextService.createRequestContext({
216
+ ...transportContext,
217
+ operation: "handleDeleteRequest",
218
+ sessionId,
219
+ });
220
+ if (sessionId) {
221
+ if (transportManager instanceof StatefulTransportManager) {
222
+ const response = await transportManager.handleDeleteRequest(sessionId, context);
223
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
+ return c.json(response.body, response.statusCode);
225
+ }
226
+ else {
227
+ return c.json({
228
+ error: "Method Not Allowed",
229
+ message: "DELETE operations are not supported in this mode.",
230
+ }, 405);
231
+ }
232
+ }
233
+ else {
234
+ return c.json({
235
+ status: "stateless_mode",
236
+ message: "No sessions to delete in stateless mode",
237
+ });
238
+ }
239
+ });
240
+ logger.info("Hono application setup complete.", transportContext);
241
+ return app;
242
+ }
243
+ export async function startHttpTransport(createServerInstanceFn, parentContext) {
244
+ const transportContext = {
245
+ ...parentContext,
246
+ component: "HttpTransportStart",
247
+ };
248
+ logger.info("Starting HTTP transport.", transportContext);
249
+ const transportManager = createTransportManager(createServerInstanceFn, config.mcpSessionMode, transportContext);
250
+ const app = createHttpApp(transportManager, createServerInstanceFn, transportContext);
251
+ const server = await startHttpServerWithRetry(app, HTTP_PORT, HTTP_HOST, config.mcpHttpMaxPortRetries, transportContext);
252
+ logger.info("HTTP transport started successfully.", transportContext);
253
+ return { app, server, transportManager };
254
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @fileoverview Defines custom types for the Hono HTTP transport layer.
3
+ * @module src/mcp-server/transports/http/httpTypes
4
+ */
5
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @fileoverview Barrel file for the HTTP transport module.
3
+ * @module src/mcp-server/transports/http/index
4
+ */
5
+ export { createHttpApp, startHttpTransport } from "./httpTransport.js";
6
+ export { httpErrorHandler } from "./httpErrorHandler.js";
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @fileoverview Hono middleware for handling MCP transport logic.
3
+ * This middleware encapsulates the logic for processing MCP requests,
4
+ * delegating to the appropriate transport manager, and preparing the
5
+ * response for Hono to send.
6
+ * @module src/mcp-server/transports/http/mcpTransportMiddleware
7
+ */
8
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
9
+ import { createMiddleware } from "hono/factory";
10
+ import { config } from "../../../config/index.js";
11
+ import { requestContextService } from "../../../utils/index.js";
12
+ import { StatelessTransportManager } from "../core/statelessTransportManager.js";
13
+ /**
14
+ * Converts a Fetch API Headers object to Node.js IncomingHttpHeaders.
15
+ * @param headers - The Headers object to convert.
16
+ * @returns An object compatible with IncomingHttpHeaders.
17
+ */
18
+ function toIncomingHttpHeaders(headers) {
19
+ const result = {};
20
+ headers.forEach((value, key) => {
21
+ result[key] = value;
22
+ });
23
+ return result;
24
+ }
25
+ /**
26
+ * Handles a stateless request by creating an ephemeral transport manager.
27
+ * @param createServerInstanceFn - Function to create an McpServer instance.
28
+ * @param headers - The request headers.
29
+ * @param body - The request body.
30
+ * @param context - The request context.
31
+ * @returns A promise resolving with the transport response.
32
+ */
33
+ async function handleStatelessRequest(createServerInstanceFn, headers, body, context) {
34
+ const statelessManager = new StatelessTransportManager(createServerInstanceFn);
35
+ return statelessManager.handleRequest(toIncomingHttpHeaders(headers), body, context);
36
+ }
37
+ export const mcpTransportMiddleware = (transportManager, createServerInstanceFn) => createMiddleware(async (c, next) => {
38
+ const sessionId = c.req.header("mcp-session-id");
39
+ const context = requestContextService.createRequestContext({
40
+ operation: "mcpTransportMiddleware",
41
+ sessionId,
42
+ });
43
+ const body = await c.req.json();
44
+ let response;
45
+ if (isInitializeRequest(body)) {
46
+ if (config.mcpSessionMode === "stateless") {
47
+ response = await handleStatelessRequest(createServerInstanceFn, c.req.raw.headers, body, context);
48
+ }
49
+ else {
50
+ response = await transportManager.initializeAndHandle(toIncomingHttpHeaders(c.req.raw.headers), body, context);
51
+ }
52
+ }
53
+ else {
54
+ if (sessionId) {
55
+ response = await transportManager.handleRequest(toIncomingHttpHeaders(c.req.raw.headers), body, context, sessionId);
56
+ }
57
+ else {
58
+ response = await handleStatelessRequest(createServerInstanceFn, c.req.raw.headers, body, context);
59
+ }
60
+ }
61
+ c.set("mcpResponse", response);
62
+ await next();
63
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @fileoverview Barrel file for the Stdio transport module.
3
+ * @module src/mcp-server/transports/stdio/index
4
+ */
5
+ export { startStdioTransport } from "./stdioTransport.js";
@@ -18,7 +18,7 @@
18
18
  * @module src/mcp-server/transports/stdioTransport
19
19
  */
20
20
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
21
- import { ErrorHandler, logger } from "../../utils/index.js";
21
+ import { ErrorHandler, logger } from "../../../utils/index.js";
22
22
  /**
23
23
  * Connects a given `McpServer` instance to the Stdio transport.
24
24
  * This function initializes the SDK's `StdioServerTransport`, which manages
@@ -39,13 +39,13 @@ import { ErrorHandler, logger } from "../../utils/index.js";
39
39
  * @returns A promise that resolves when the Stdio transport is successfully connected.
40
40
  * @throws {Error} If the connection fails during setup.
41
41
  */
42
- export async function connectStdioTransport(server, parentContext) {
42
+ export async function startStdioTransport(server, parentContext) {
43
43
  const operationContext = {
44
44
  ...parentContext,
45
45
  operation: "connectStdioTransport",
46
46
  transportType: "Stdio",
47
47
  };
48
- logger.debug("Attempting to connect stdio transport...", operationContext);
48
+ logger.info("Attempting to connect stdio transport...", operationContext);
49
49
  try {
50
50
  logger.debug("Creating StdioServerTransport instance...", operationContext);
51
51
  const transport = new StdioServerTransport();
@@ -57,7 +57,12 @@ export async function connectStdioTransport(server, parentContext) {
57
57
  }
58
58
  }
59
59
  catch (err) {
60
- ErrorHandler.handleError(err, { ...operationContext, critical: true });
61
- throw err; // Re-throw after handling to allow caller to react if necessary
60
+ // Let the ErrorHandler log the error with all context, then rethrow.
61
+ throw ErrorHandler.handleError(err, {
62
+ operation: "connectStdioTransport",
63
+ context: operationContext,
64
+ critical: true,
65
+ rethrow: true,
66
+ });
62
67
  }
63
68
  }
@@ -1,7 +1,16 @@
1
+ /**
2
+ * @fileoverview Defines standardized error codes, a custom error class, and related schemas
3
+ * for handling errors within the Model Context Protocol (MCP) server and its components.
4
+ * This module provides a structured way to represent and communicate errors, ensuring
5
+ * consistency and clarity for both server-side operations and client-side error handling.
6
+ * @module src/types-global/errors
7
+ */
1
8
  import { z } from "zod";
2
9
  /**
3
- * Defines a set of standardized error codes for common issues within MCP servers or tools.
4
- * These codes help clients understand the nature of an error programmatically.
10
+ * Defines a comprehensive set of standardized error codes for common issues encountered
11
+ * within MCP servers, tools, or related operations. These codes are designed to help
12
+ * clients and developers programmatically understand the nature of an error, facilitating
13
+ * more precise error handling and debugging.
5
14
  */
6
15
  export var BaseErrorCode;
7
16
  (function (BaseErrorCode) {
@@ -15,6 +24,8 @@ export var BaseErrorCode;
15
24
  BaseErrorCode["CONFLICT"] = "CONFLICT";
16
25
  /** The request failed due to invalid input parameters or data. */
17
26
  BaseErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
27
+ /** The provided input is invalid for the operation. */
28
+ BaseErrorCode["INVALID_INPUT"] = "INVALID_INPUT";
18
29
  /** An error occurred while parsing input data (e.g., date string, JSON). */
19
30
  BaseErrorCode["PARSING_ERROR"] = "PARSING_ERROR";
20
31
  /** The request was rejected because the client has exceeded rate limits. */
@@ -29,45 +40,90 @@ export var BaseErrorCode;
29
40
  BaseErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
30
41
  /** An error occurred during the loading or validation of configuration data. */
31
42
  BaseErrorCode["CONFIGURATION_ERROR"] = "CONFIGURATION_ERROR";
43
+ /** An error occurred during the initialization phase of a service or module. */
44
+ BaseErrorCode["INITIALIZATION_FAILED"] = "INITIALIZATION_FAILED";
45
+ /** A service was used before it was properly initialized. */
46
+ BaseErrorCode["SERVICE_NOT_INITIALIZED"] = "SERVICE_NOT_INITIALIZED";
47
+ /** A generic error occurred during a database operation. */
48
+ BaseErrorCode["DATABASE_ERROR"] = "DATABASE_ERROR";
49
+ /** An error occurred while loading or interacting with an extension. */
50
+ BaseErrorCode["EXTENSION_ERROR"] = "EXTENSION_ERROR";
51
+ /** An error occurred during the shutdown phase of a service or module. */
52
+ BaseErrorCode["SHUTDOWN_ERROR"] = "SHUTDOWN_ERROR";
53
+ /** A generic error occurred during the execution of an agent's task. */
54
+ BaseErrorCode["AGENT_EXECUTION_ERROR"] = "AGENT_EXECUTION_ERROR";
32
55
  })(BaseErrorCode || (BaseErrorCode = {}));
33
56
  /**
34
- * Custom error class for MCP-specific errors.
35
- * Encapsulates a `BaseErrorCode`, a descriptive message, and optional details.
36
- * Provides a method to format the error into a standard MCP tool response.
57
+ * Custom error class for MCP-specific errors, extending the built-in `Error` class.
58
+ * It standardizes error reporting by encapsulating a `BaseErrorCode`, a descriptive
59
+ * human-readable message, and optional structured details for more context.
60
+ *
61
+ * This class is central to error handling within the MCP framework, allowing for
62
+ * consistent error creation and propagation.
37
63
  */
38
64
  export class McpError extends Error {
65
+ /**
66
+ * The standardized error code from {@link BaseErrorCode}.
67
+ */
39
68
  code;
69
+ /**
70
+ * Optional additional details or context about the error.
71
+ * This can be any structured data that helps in understanding or debugging the error.
72
+ */
40
73
  details;
41
74
  /**
42
75
  * Creates an instance of McpError.
43
- * @param {BaseErrorCode} code - The standardized error code.
44
- * @param {string} message - A human-readable description of the error.
45
- * @param {Record<string, unknown>} [details] - Optional additional details about the error.
76
+ *
77
+ * @param code - The standardized error code that categorizes the error.
78
+ * @param message - A human-readable description of the error.
79
+ * @param details - Optional. A record containing additional structured details about the error.
46
80
  */
47
81
  constructor(code, message, details) {
48
82
  super(message);
49
83
  this.code = code;
50
84
  this.details = details;
51
- // Set the error name for identification
52
85
  this.name = "McpError";
53
- // Ensure the prototype chain is correct
86
+ // Maintain a proper prototype chain.
54
87
  Object.setPrototypeOf(this, McpError.prototype);
88
+ // Capture the stack trace, excluding the constructor call from it, if available.
89
+ if (Error.captureStackTrace) {
90
+ Error.captureStackTrace(this, McpError);
91
+ }
55
92
  }
56
93
  }
57
94
  /**
58
- * Zod schema for validating error objects, potentially used for parsing
59
- * error responses or validating error structures internally.
95
+ * Zod schema for validating error objects. This schema can be used for:
96
+ * - Validating error structures when parsing error responses from external services.
97
+ * - Ensuring consistency when creating or handling error objects internally.
98
+ * - Generating TypeScript types for error objects.
99
+ *
100
+ * The schema enforces the presence of a `code` (from {@link BaseErrorCode}) and a `message`,
101
+ * and allows for optional `details`.
60
102
  */
61
103
  export const ErrorSchema = z
62
104
  .object({
63
- /** The error code, corresponding to BaseErrorCode enum values. */
64
- code: z.nativeEnum(BaseErrorCode).describe("Standardized error code"),
65
- /** A human-readable description of the error. */
66
- message: z.string().describe("Detailed error message"),
67
- /** Optional additional details or context about the error. */
105
+ /**
106
+ * The error code, corresponding to one of the {@link BaseErrorCode} enum values.
107
+ * This field is required and helps in programmatically identifying the error type.
108
+ */
109
+ code: z
110
+ .nativeEnum(BaseErrorCode)
111
+ .describe("Standardized error code from BaseErrorCode enum"),
112
+ /**
113
+ * A human-readable, descriptive message explaining the error.
114
+ * This field is required and provides context to developers or users.
115
+ */
116
+ message: z
117
+ .string()
118
+ .min(1, "Error message cannot be empty.")
119
+ .describe("Detailed human-readable error message"),
120
+ /**
121
+ * Optional. A record containing additional structured details or context about the error.
122
+ * This can include things like invalid field names, specific values that caused issues, or other relevant data.
123
+ */
68
124
  details: z
69
125
  .record(z.unknown())
70
126
  .optional()
71
- .describe("Optional structured error details"),
127
+ .describe("Optional structured details providing more context about the error"),
72
128
  })
73
- .describe("Schema for validating structured error objects.");
129
+ .describe("Schema for validating structured error objects, ensuring consistency in error reporting.");
@@ -133,28 +133,26 @@ export class ErrorHandler {
133
133
  const { context, operation, input, rethrow = false, errorCode: explicitErrorCode, includeStack = true, critical = false, } = options;
134
134
  // If it's already an McpError, use it directly but apply additional context
135
135
  if (error instanceof McpError) {
136
- // Add any additional context
137
- if (context && Object.keys(context).length > 0) {
138
- // Ensure details is an object before spreading
139
- const existingDetails = typeof error.details === "object" && error.details !== null
140
- ? error.details
141
- : {};
142
- error.details = { ...existingDetails, ...context };
143
- }
136
+ const existingDetails = typeof error.details === "object" && error.details !== null
137
+ ? error.details
138
+ : {};
139
+ const newDetails = { ...existingDetails, ...context };
140
+ // Create a new error to avoid mutating a readonly property
141
+ const updatedError = new McpError(error.code, error.message, newDetails);
144
142
  // Log the error with sanitized input
145
- logger.error(`Error ${operation}: ${error.message}`, {
146
- errorCode: error.code,
143
+ logger.error(`Error ${operation}: ${updatedError.message}`, {
144
+ errorCode: updatedError.code,
147
145
  requestId: context?.requestId,
148
146
  input: input ? sanitizeInputForLogging(input) : undefined,
149
- stack: includeStack ? error.stack : undefined,
147
+ stack: includeStack ? updatedError.stack : undefined,
150
148
  critical,
151
149
  ...context,
152
150
  });
153
151
  if (rethrow) {
154
- throw error;
152
+ throw updatedError;
155
153
  }
156
154
  // Ensure the function returns an Error type
157
- return error;
155
+ return updatedError;
158
156
  }
159
157
  // Sanitize input for logging
160
158
  const sanitizedInput = input ? sanitizeInputForLogging(input) : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
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": [
@@ -32,19 +32,22 @@
32
32
  "format": "prettier --write \"**/*.{ts,js,json,md,html,css}\"",
33
33
  "inspector": "npx @modelcontextprotocol/inspector --config mcp.json --server git-mcp-server",
34
34
  "inspector:http": "npx @modelcontextprotocol/inspector --config mcp.json --server git-mcp-server-http",
35
- "clean": "ts-node --esm scripts/clean.ts"
35
+ "clean": "ts-node --esm scripts/clean.ts",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "test:coverage": "vitest run --coverage"
36
39
  },
37
40
  "dependencies": {
38
41
  "@hono/node-server": "^1.17.1",
39
- "@modelcontextprotocol/sdk": "^1.16.0",
40
- "@types/node": "^24.0.15",
42
+ "@modelcontextprotocol/sdk": "^1.17.0",
43
+ "@types/node": "^24.1.0",
41
44
  "@types/sanitize-html": "^2.16.0",
42
45
  "@types/validator": "^13.15.2",
43
46
  "chrono-node": "2.8.0",
44
47
  "dotenv": "16.6.1",
45
- "hono": "^4.8.5",
48
+ "hono": "^4.8.10",
46
49
  "jose": "^6.0.12",
47
- "openai": "^5.10.1",
50
+ "openai": "^5.10.2",
48
51
  "partial-json": "^0.1.7",
49
52
  "sanitize-html": "^2.17.0",
50
53
  "tiktoken": "^1.0.21",
@@ -105,8 +108,16 @@
105
108
  "node": ">=20.0.0"
106
109
  },
107
110
  "devDependencies": {
111
+ "@anatine/zod-mock": "^3.14.0",
112
+ "@faker-js/faker": "^9.9.0",
113
+ "@types/supertest": "^6.0.3",
114
+ "@vitest/coverage-v8": "^3.2.4",
115
+ "msw": "^2.10.4",
108
116
  "prettier": "^3.6.2",
109
- "typedoc": "^0.28.7"
117
+ "supertest": "^7.1.4",
118
+ "typedoc": "^0.28.8",
119
+ "vite-tsconfig-paths": "^5.1.4",
120
+ "vitest": "^3.2.4"
110
121
  },
111
122
  "publishConfig": {
112
123
  "access": "public"
@@ -1,5 +0,0 @@
1
- /**
2
- * @fileoverview Shared types for authentication middleware.
3
- * @module src/mcp-server/transports/auth/core/auth.types
4
- */
5
- export {};