@cyanheads/git-mcp-server 2.0.9 ā 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
|
[](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)
|
|
@@ -204,6 +204,9 @@ npm run build
|
|
|
204
204
|
# Test the server locally using the MCP inspector tool (stdio transport)
|
|
205
205
|
npm run inspector
|
|
206
206
|
|
|
207
|
+
# Test the server locally using the MCP inspector tool (http transport)
|
|
208
|
+
npm run inspector:http
|
|
209
|
+
|
|
207
210
|
# Clean build artifacts (runs scripts/clean.ts)
|
|
208
211
|
npm run clean
|
|
209
212
|
|
|
@@ -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
|
|
426
|
-
|
|
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
|
|
78
|
-
|
|
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
|
|
35
|
-
console
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
logsDirCreatedMessage = `Created logs directory: ${resolvedLogsDir}`;
|
|
67
100
|
}
|
|
68
101
|
}
|
|
69
102
|
catch (err) {
|
|
70
|
-
console
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
133
|
+
consoleLoggingEnabledMessage = 'Console logging enabled at level: debug (stdout is TTY)';
|
|
115
134
|
}
|
|
116
135
|
else if (this.currentMcpLevel === 'debug' && !process.stdout.isTTY) {
|
|
117
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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.
|
|
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",
|
|
@@ -29,16 +29,18 @@
|
|
|
29
29
|
"start:http": "MCP_LOG_LEVEL=debug MCP_TRANSPORT_TYPE=http node dist/index.js",
|
|
30
30
|
"rebuild": "ts-node --esm scripts/clean.ts && npm run build",
|
|
31
31
|
"tree": "ts-node --esm scripts/tree.ts",
|
|
32
|
-
"inspector": "npx @modelcontextprotocol/inspector
|
|
32
|
+
"inspector": "npx @modelcontextprotocol/inspector --config mcp.json --server git-mcp-server",
|
|
33
|
+
"inspector:http": "npx @modelcontextprotocol/inspector --config mcp.json --server git-mcp-server-http",
|
|
33
34
|
"clean": "ts-node --esm scripts/clean.ts"
|
|
34
35
|
},
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@modelcontextprotocol/
|
|
40
|
+
"@modelcontextprotocol/inspector": "^0.12.0",
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.11.2",
|
|
40
42
|
"@types/jsonwebtoken": "^9.0.9",
|
|
41
|
-
"@types/node": "^22.15.
|
|
43
|
+
"@types/node": "^22.15.18",
|
|
42
44
|
"@types/sanitize-html": "^2.16.0",
|
|
43
45
|
"@types/validator": "^13.15.0",
|
|
44
46
|
"chrono-node": "^2.8.0",
|
|
@@ -46,9 +48,9 @@
|
|
|
46
48
|
"express": "^5.1.0",
|
|
47
49
|
"ignore": "^7.0.4",
|
|
48
50
|
"jsonwebtoken": "^9.0.2",
|
|
49
|
-
"openai": "^4.
|
|
51
|
+
"openai": "^4.98.0",
|
|
50
52
|
"partial-json": "^0.1.7",
|
|
51
|
-
"sanitize-html": "^2.
|
|
53
|
+
"sanitize-html": "^2.17.0",
|
|
52
54
|
"tiktoken": "^1.0.21",
|
|
53
55
|
"ts-node": "^10.9.2",
|
|
54
56
|
"typescript": "^5.8.3",
|