@cyanheads/git-mcp-server 2.0.10 → 2.0.11

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # Git MCP Server
2
2
 
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-^5.8.3-blue.svg)](https://www.typescriptlang.org/)
4
- [![Model Context Protocol](https://img.shields.io/badge/MCP%20SDK-^1.11.0-green.svg)](https://modelcontextprotocol.io/)
5
- [![Version](https://img.shields.io/badge/Version-2.0.10-blue.svg)](./CHANGELOG.md)
4
+ [![Model Context Protocol](https://img.shields.io/badge/MCP%20SDK-^1.11.2-green.svg)](https://modelcontextprotocol.io/)
5
+ [![Version](https://img.shields.io/badge/Version-2.0.11-blue.svg)](./CHANGELOG.md)
6
6
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
7
  [![Status](https://img.shields.io/badge/Status-Stable-green.svg)](https://github.com/cyanheads/git-mcp-server/issues)
8
8
  [![GitHub](https://img.shields.io/github/stars/cyanheads/git-mcp-server?style=social)](https://github.com/cyanheads/git-mcp-server)
@@ -422,8 +422,8 @@ export async function startHttpTransport(createServerInstanceFn, context) {
422
422
  // Determine protocol for logging (basic assumption based on HSTS possibility)
423
423
  const protocol = config.environment === 'production' ? 'https' : 'http';
424
424
  const serverAddress = `${protocol}://${config.mcpHttpHost}:${actualPort}${MCP_ENDPOINT_PATH}`;
425
- // Use console.log for prominent startup message.
426
- console.log(`\nšŸš€ MCP Server running in HTTP mode at: ${serverAddress}\n (MCP Spec: 2025-03-26 Streamable HTTP Transport)\n`);
425
+ // Use logger.notice for startup message to ensure MCP compliance and proper handling by clients.
426
+ logger.notice(`\nšŸš€ MCP Server running in HTTP mode at: ${serverAddress}\n (MCP Spec: 2025-03-26 Streamable HTTP Transport)\n`, transportContext);
427
427
  }
428
428
  catch (err) {
429
429
  logger.fatal('HTTP server failed to start after multiple port retries.', { ...transportContext, error: err instanceof Error ? err.message : String(err) });
@@ -74,8 +74,8 @@ export async function connectStdioTransport(server, context) {
74
74
  await server.connect(transport);
75
75
  // Log successful connection. The server is now ready to process messages via stdio.
76
76
  logger.info('MCP Server connected and listening via stdio transport.', operationContext);
77
- // Use console.log for prominent startup message visibility when run directly.
78
- console.log(`\nšŸš€ MCP Server running in STDIO mode.\n (MCP Spec: 2025-03-26 Stdio Transport)\n`);
77
+ // Use logger.notice for startup message to ensure MCP compliance and proper handling by clients.
78
+ logger.notice(`\nšŸš€ MCP Server running in STDIO mode.\n (MCP Spec: 2025-03-26 Stdio Transport)\n`, operationContext);
79
79
  }
80
80
  catch (err) {
81
81
  // Catch and handle any critical errors during the transport connection setup.
@@ -31,8 +31,40 @@ const logsDir = path.join(projectRoot, 'logs');
31
31
  const resolvedLogsDir = path.resolve(logsDir);
32
32
  const isLogsDirSafe = resolvedLogsDir === projectRoot || resolvedLogsDir.startsWith(projectRoot + path.sep);
33
33
  if (!isLogsDirSafe) {
34
- // Use console.error here as logger might not be initialized or safe
35
- console.error(`FATAL: logs directory "${resolvedLogsDir}" is outside project root "${projectRoot}". File logging disabled.`);
34
+ // Use console.error for critical pre-init errors.
35
+ // Only log to console if TTY to avoid polluting stdout for stdio MCP clients.
36
+ if (process.stdout.isTTY) {
37
+ console.error(`FATAL: logs directory "${resolvedLogsDir}" is outside project root "${projectRoot}". File logging disabled.`);
38
+ }
39
+ }
40
+ /**
41
+ * Helper function to create the Winston console format.
42
+ * This is extracted to avoid duplication between initialize and setLevel.
43
+ */
44
+ function createWinstonConsoleFormat() {
45
+ return winston.format.combine(winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf(({ timestamp, level, message, ...meta }) => {
46
+ let metaString = '';
47
+ const metaCopy = { ...meta };
48
+ if (metaCopy.error && typeof metaCopy.error === 'object') {
49
+ const errorObj = metaCopy.error;
50
+ if (errorObj.message)
51
+ metaString += `\n Error: ${errorObj.message}`;
52
+ if (errorObj.stack)
53
+ metaString += `\n Stack: ${String(errorObj.stack).split('\n').map((l) => ` ${l}`).join('\n')}`;
54
+ delete metaCopy.error;
55
+ }
56
+ if (Object.keys(metaCopy).length > 0) {
57
+ try {
58
+ const remainingMetaJson = JSON.stringify(metaCopy, null, 2);
59
+ if (remainingMetaJson !== '{}')
60
+ metaString += `\n Meta: ${remainingMetaJson}`;
61
+ }
62
+ catch (stringifyError) {
63
+ metaString += `\n Meta: [Error stringifying metadata: ${stringifyError.message}]`;
64
+ }
65
+ }
66
+ return `${timestamp} ${level}: ${message}${metaString}`;
67
+ }));
36
68
  }
37
69
  /**
38
70
  * Singleton Logger wrapping Winston, adapted for MCP.
@@ -53,21 +85,25 @@ class Logger {
53
85
  */
54
86
  async initialize(level = 'info') {
55
87
  if (this.initialized) {
56
- console.warn('Logger already initialized.');
88
+ this.warning('Logger already initialized.', { loggerSetup: true });
57
89
  return;
58
90
  }
59
91
  this.currentMcpLevel = level;
60
92
  this.currentWinstonLevel = mcpToWinstonLevel[level];
93
+ let logsDirCreatedMessage = null;
61
94
  // Ensure logs directory exists
62
95
  if (isLogsDirSafe) {
63
96
  try {
64
97
  if (!fs.existsSync(resolvedLogsDir)) {
65
98
  fs.mkdirSync(resolvedLogsDir, { recursive: true });
66
- console.log(`Created logs directory: ${resolvedLogsDir}`);
99
+ logsDirCreatedMessage = `Created logs directory: ${resolvedLogsDir}`;
67
100
  }
68
101
  }
69
102
  catch (err) {
70
- console.error(`Error creating logs directory at ${resolvedLogsDir}: ${err.message}. File logging disabled.`);
103
+ // Conditional console output for pre-init errors to avoid issues with stdio MCP clients.
104
+ if (process.stdout.isTTY) {
105
+ console.error(`Error creating logs directory at ${resolvedLogsDir}: ${err.message}. File logging disabled.`);
106
+ }
71
107
  }
72
108
  }
73
109
  // Common format for files
@@ -78,43 +114,26 @@ class Logger {
78
114
  transports.push(new winston.transports.File({ filename: path.join(resolvedLogsDir, 'error.log'), level: 'error', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'warn.log'), level: 'warn', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'info.log'), level: 'info', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'debug.log'), level: 'debug', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'combined.log'), format: fileFormat }));
79
115
  }
80
116
  else {
81
- console.warn("File logging disabled due to unsafe logs directory path.");
117
+ // Conditional console output for pre-init warnings.
118
+ if (process.stdout.isTTY) {
119
+ console.warn("File logging disabled due to unsafe logs directory path.");
120
+ }
82
121
  }
122
+ let consoleLoggingEnabledMessage = null;
123
+ let consoleLoggingSkippedMessage = null;
83
124
  // Conditionally add Console transport only if:
84
125
  // 1. MCP level is 'debug'
85
126
  // 2. stdout is a TTY (interactive terminal, not piped)
86
127
  if (this.currentMcpLevel === 'debug' && process.stdout.isTTY) {
87
- const consoleFormat = winston.format.combine(winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf(({ timestamp, level, message, ...meta }) => {
88
- let metaString = '';
89
- const metaCopy = { ...meta };
90
- if (metaCopy.error && typeof metaCopy.error === 'object') {
91
- const errorObj = metaCopy.error;
92
- if (errorObj.message)
93
- metaString += `\n Error: ${errorObj.message}`;
94
- if (errorObj.stack)
95
- metaString += `\n Stack: ${String(errorObj.stack).split('\n').map((l) => ` ${l}`).join('\n')}`;
96
- delete metaCopy.error;
97
- }
98
- if (Object.keys(metaCopy).length > 0) {
99
- try {
100
- const remainingMetaJson = JSON.stringify(metaCopy, null, 2);
101
- if (remainingMetaJson !== '{}')
102
- metaString += `\n Meta: ${remainingMetaJson}`;
103
- }
104
- catch (stringifyError) {
105
- metaString += `\n Meta: [Error stringifying metadata: ${stringifyError.message}]`;
106
- }
107
- }
108
- return `${timestamp} ${level}: ${message}${metaString}`;
109
- }));
128
+ const consoleFormat = createWinstonConsoleFormat();
110
129
  transports.push(new winston.transports.Console({
111
130
  level: 'debug',
112
131
  format: consoleFormat,
113
132
  }));
114
- console.log(`Console logging enabled at level: debug (stdout is TTY)`);
133
+ consoleLoggingEnabledMessage = 'Console logging enabled at level: debug (stdout is TTY)';
115
134
  }
116
135
  else if (this.currentMcpLevel === 'debug' && !process.stdout.isTTY) {
117
- console.log(`Console logging skipped: Level is debug, but stdout is not a TTY (likely stdio transport).`);
136
+ consoleLoggingSkippedMessage = 'Console logging skipped: Level is debug, but stdout is not a TTY (likely stdio transport).';
118
137
  }
119
138
  // Create logger with the initial Winston level and configured transports
120
139
  this.winstonLogger = winston.createLogger({
@@ -122,9 +141,19 @@ class Logger {
122
141
  transports,
123
142
  exitOnError: false
124
143
  });
144
+ // Log deferred messages now that winstonLogger is initialized
145
+ if (logsDirCreatedMessage) {
146
+ this.info(logsDirCreatedMessage, { loggerSetup: true });
147
+ }
148
+ if (consoleLoggingEnabledMessage) {
149
+ this.info(consoleLoggingEnabledMessage, { loggerSetup: true });
150
+ }
151
+ if (consoleLoggingSkippedMessage) {
152
+ this.info(consoleLoggingSkippedMessage, { loggerSetup: true });
153
+ }
125
154
  this.initialized = true;
126
155
  await Promise.resolve(); // Yield to event loop
127
- this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${process.stdout.isTTY && this.currentMcpLevel === 'debug' ? 'enabled' : 'disabled'}`);
156
+ this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${process.stdout.isTTY && this.currentMcpLevel === 'debug' ? 'enabled' : 'disabled'}`, { loggerSetup: true });
128
157
  }
129
158
  /**
130
159
  * Sets the function used to send MCP 'notifications/message'.
@@ -132,14 +161,17 @@ class Logger {
132
161
  setMcpNotificationSender(sender) {
133
162
  this.mcpNotificationSender = sender;
134
163
  const status = sender ? 'enabled' : 'disabled';
135
- this.info(`MCP notification sending ${status}.`);
164
+ this.info(`MCP notification sending ${status}.`, { loggerSetup: true });
136
165
  }
137
166
  /**
138
167
  * Dynamically sets the minimum logging level.
139
168
  */
140
169
  setLevel(newLevel) {
141
170
  if (!this.ensureInitialized()) {
142
- console.error("Cannot set level: Logger not initialized.");
171
+ // Conditional console output if logger not usable.
172
+ if (process.stdout.isTTY) {
173
+ console.error("Cannot set level: Logger not initialized.");
174
+ }
143
175
  return;
144
176
  }
145
177
  if (!(newLevel in mcpLevelSeverity)) {
@@ -155,17 +187,17 @@ class Logger {
155
187
  const shouldHaveConsole = newLevel === 'debug' && process.stdout.isTTY;
156
188
  if (shouldHaveConsole && !consoleTransport) {
157
189
  // Add console transport
158
- const consoleFormat = winston.format.combine( /* ... same format as in initialize ... */); // TODO: Extract format to avoid duplication
190
+ const consoleFormat = createWinstonConsoleFormat();
159
191
  this.winstonLogger.add(new winston.transports.Console({ level: 'debug', format: consoleFormat }));
160
- this.info('Console logging dynamically enabled.');
192
+ this.info('Console logging dynamically enabled.', { loggerSetup: true });
161
193
  }
162
194
  else if (!shouldHaveConsole && consoleTransport) {
163
195
  // Remove console transport
164
196
  this.winstonLogger.remove(consoleTransport);
165
- this.info('Console logging dynamically disabled.');
197
+ this.info('Console logging dynamically disabled.', { loggerSetup: true });
166
198
  }
167
199
  if (oldLevel !== newLevel) {
168
- this.info(`Log level changed. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${shouldHaveConsole ? 'enabled' : 'disabled'}`);
200
+ this.info(`Log level changed. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${shouldHaveConsole ? 'enabled' : 'disabled'}`, { loggerSetup: true });
169
201
  }
170
202
  }
171
203
  /** Get singleton instance. */
@@ -178,7 +210,10 @@ class Logger {
178
210
  /** Ensures the logger has been initialized. */
179
211
  ensureInitialized() {
180
212
  if (!this.initialized || !this.winstonLogger) {
181
- console.warn('Logger not initialized; message dropped.');
213
+ // Conditional console output if logger not usable.
214
+ if (process.stdout.isTTY) {
215
+ console.warn('Logger not initialized; message dropped.');
216
+ }
182
217
  return false;
183
218
  }
184
219
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.0.10",
3
+ "version": "2.0.11",
4
4
  "description": "An MCP (Model Context Protocol) server providing tools to interact with Git repositories. Enables LLMs and AI agents to perform Git operations like clone, commit, push, pull, branch, diff, log, status, and more via the MCP standard.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -37,10 +37,10 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@modelcontextprotocol/inspector": "^0.11.0",
41
- "@modelcontextprotocol/sdk": "^1.11.0",
40
+ "@modelcontextprotocol/inspector": "^0.12.0",
41
+ "@modelcontextprotocol/sdk": "^1.11.2",
42
42
  "@types/jsonwebtoken": "^9.0.9",
43
- "@types/node": "^22.15.15",
43
+ "@types/node": "^22.15.18",
44
44
  "@types/sanitize-html": "^2.16.0",
45
45
  "@types/validator": "^13.15.0",
46
46
  "chrono-node": "^2.8.0",
@@ -48,9 +48,9 @@
48
48
  "express": "^5.1.0",
49
49
  "ignore": "^7.0.4",
50
50
  "jsonwebtoken": "^9.0.2",
51
- "openai": "^4.97.0",
51
+ "openai": "^4.98.0",
52
52
  "partial-json": "^0.1.7",
53
- "sanitize-html": "^2.16.0",
53
+ "sanitize-html": "^2.17.0",
54
54
  "tiktoken": "^1.0.21",
55
55
  "ts-node": "^10.9.2",
56
56
  "typescript": "^5.8.3",