@cyanheads/git-mcp-server 2.4.9 → 2.5.1
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/dist/index.js +299 -37
- package/package.json +29 -31
package/dist/index.js
CHANGED
|
@@ -4261,7 +4261,7 @@ var package_default;
|
|
|
4261
4261
|
var init_package = __esm(() => {
|
|
4262
4262
|
package_default = {
|
|
4263
4263
|
name: "@cyanheads/git-mcp-server",
|
|
4264
|
-
version: "2.
|
|
4264
|
+
version: "2.5.1",
|
|
4265
4265
|
mcpName: "io.github.cyanheads/git-mcp-server",
|
|
4266
4266
|
description: "A secure and scalable Git MCP server enabling AI agents to perform comprehensive Git version control operations via STDIO and Streamable HTTP.",
|
|
4267
4267
|
main: "dist/index.js",
|
|
@@ -4327,33 +4327,12 @@ var init_package = __esm(() => {
|
|
|
4327
4327
|
zod: "3.23.8",
|
|
4328
4328
|
typescript: "5.9.3"
|
|
4329
4329
|
},
|
|
4330
|
-
|
|
4330
|
+
devDependencies: {
|
|
4331
|
+
"@cloudflare/workers-types": "^4.20251011.0",
|
|
4332
|
+
"@eslint/js": "^9.37.0",
|
|
4331
4333
|
"@hono/mcp": "^0.1.4",
|
|
4332
4334
|
"@hono/node-server": "^1.19.5",
|
|
4333
4335
|
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
4334
|
-
"@supabase/supabase-js": "^2.75.0",
|
|
4335
|
-
axios: "^1.12.2",
|
|
4336
|
-
"chrono-node": "^2.9.0",
|
|
4337
|
-
dotenv: "^17.2.3",
|
|
4338
|
-
"fast-xml-parser": "^5.3.0",
|
|
4339
|
-
hono: "^4.9.12",
|
|
4340
|
-
ignore: "^7.0.5",
|
|
4341
|
-
jose: "^6.1.0",
|
|
4342
|
-
"js-yaml": "^4.1.0",
|
|
4343
|
-
"node-cron": "^4.2.1",
|
|
4344
|
-
openai: "^6.3.0",
|
|
4345
|
-
papaparse: "^5.5.3",
|
|
4346
|
-
"partial-json": "^0.1.7",
|
|
4347
|
-
"pdf-lib": "^1.17.1",
|
|
4348
|
-
pino: "^10.0.0",
|
|
4349
|
-
"pino-pretty": "^13.1.2",
|
|
4350
|
-
"reflect-metadata": "^0.2.2",
|
|
4351
|
-
repomix: "^1.7.0",
|
|
4352
|
-
"sanitize-html": "^2.17.0",
|
|
4353
|
-
tslib: "^2.8.1",
|
|
4354
|
-
tsyringe: "^4.10.0",
|
|
4355
|
-
validator: "13.15.15",
|
|
4356
|
-
zod: "^3.23.8",
|
|
4357
4336
|
"@opentelemetry/api": "^1.9.0",
|
|
4358
4337
|
"@opentelemetry/auto-instrumentations-node": "^0.65.0",
|
|
4359
4338
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.206.0",
|
|
@@ -4363,11 +4342,8 @@ var init_package = __esm(() => {
|
|
|
4363
4342
|
"@opentelemetry/sdk-metrics": "^2.1.0",
|
|
4364
4343
|
"@opentelemetry/sdk-node": "^0.206.0",
|
|
4365
4344
|
"@opentelemetry/sdk-trace-node": "^2.1.0",
|
|
4366
|
-
"@opentelemetry/semantic-conventions": "^1.37.0"
|
|
4367
|
-
|
|
4368
|
-
devDependencies: {
|
|
4369
|
-
"@cloudflare/workers-types": "^4.20251011.0",
|
|
4370
|
-
"@eslint/js": "^9.37.0",
|
|
4345
|
+
"@opentelemetry/semantic-conventions": "^1.37.0",
|
|
4346
|
+
"@supabase/supabase-js": "^2.75.0",
|
|
4371
4347
|
"@types/bun": "^1.3.0",
|
|
4372
4348
|
"@types/js-yaml": "^4.0.9",
|
|
4373
4349
|
"@types/node": "^24.7.2",
|
|
@@ -4378,21 +4354,43 @@ var init_package = __esm(() => {
|
|
|
4378
4354
|
"@vitest/coverage-v8": "3.2.4",
|
|
4379
4355
|
ajv: "^8.17.1",
|
|
4380
4356
|
"ajv-formats": "^3.0.1",
|
|
4357
|
+
axios: "^1.12.2",
|
|
4381
4358
|
"bun-types": "^1.3.0",
|
|
4359
|
+
"chrono-node": "^2.9.0",
|
|
4382
4360
|
clipboardy: "^5.0.0",
|
|
4383
4361
|
depcheck: "^1.4.7",
|
|
4362
|
+
dotenv: "^17.2.3",
|
|
4384
4363
|
eslint: "^9.37.0",
|
|
4385
4364
|
execa: "^9.6.0",
|
|
4365
|
+
"fast-xml-parser": "^5.3.0",
|
|
4386
4366
|
globals: "^16.4.0",
|
|
4367
|
+
hono: "^4.9.12",
|
|
4387
4368
|
husky: "^9.1.7",
|
|
4369
|
+
ignore: "^7.0.5",
|
|
4370
|
+
jose: "^6.1.0",
|
|
4371
|
+
"js-yaml": "^4.1.0",
|
|
4388
4372
|
msw: "^2.11.5",
|
|
4373
|
+
"node-cron": "^4.2.1",
|
|
4374
|
+
openai: "^6.3.0",
|
|
4375
|
+
papaparse: "^5.5.3",
|
|
4376
|
+
"partial-json": "^0.1.7",
|
|
4377
|
+
"pdf-lib": "^1.17.1",
|
|
4378
|
+
pino: "^10.0.0",
|
|
4379
|
+
"pino-pretty": "^13.1.2",
|
|
4389
4380
|
prettier: "^3.6.2",
|
|
4381
|
+
"reflect-metadata": "^0.2.2",
|
|
4382
|
+
repomix: "^1.7.0",
|
|
4383
|
+
"sanitize-html": "^2.17.0",
|
|
4384
|
+
tslib: "^2.8.1",
|
|
4385
|
+
tsyringe: "^4.10.0",
|
|
4390
4386
|
typedoc: "^0.28.14",
|
|
4391
4387
|
typescript: "^5.9.3",
|
|
4392
4388
|
"typescript-eslint": "8.46.0",
|
|
4389
|
+
validator: "13.15.15",
|
|
4393
4390
|
vite: "7.1.9",
|
|
4394
4391
|
"vite-tsconfig-paths": "^5.1.4",
|
|
4395
|
-
vitest: "^3.2.4"
|
|
4392
|
+
vitest: "^3.2.4",
|
|
4393
|
+
zod: "^3.23.8"
|
|
4396
4394
|
},
|
|
4397
4395
|
keywords: [
|
|
4398
4396
|
"ai-agent",
|
|
@@ -151931,13 +151929,24 @@ function detectRuntime2() {
|
|
|
151931
151929
|
}
|
|
151932
151930
|
return "node";
|
|
151933
151931
|
}
|
|
151934
|
-
async function spawnWithBun(args, cwd, env, timeout) {
|
|
151932
|
+
async function spawnWithBun(args, cwd, env, timeout, signal) {
|
|
151935
151933
|
const bunApi = globalThis.Bun;
|
|
151934
|
+
if (signal?.aborted) {
|
|
151935
|
+
throw new Error(`Git command cancelled before execution: git ${args.join(" ")}`);
|
|
151936
|
+
}
|
|
151936
151937
|
const proc = bunApi.spawn(["git", ...args], {
|
|
151937
151938
|
cwd,
|
|
151938
151939
|
env,
|
|
151939
151940
|
stdio: ["ignore", "pipe", "pipe"]
|
|
151940
151941
|
});
|
|
151942
|
+
const abortPromise = new Promise((_, reject) => {
|
|
151943
|
+
if (signal) {
|
|
151944
|
+
signal.addEventListener("abort", () => {
|
|
151945
|
+
proc.kill();
|
|
151946
|
+
reject(new Error(`Git command cancelled: git ${args.join(" ")}`));
|
|
151947
|
+
}, { once: true });
|
|
151948
|
+
}
|
|
151949
|
+
});
|
|
151941
151950
|
const timeoutPromise = new Promise((_, reject) => {
|
|
151942
151951
|
const timeoutId = setTimeout(() => {
|
|
151943
151952
|
proc.kill();
|
|
@@ -151945,7 +151954,11 @@ async function spawnWithBun(args, cwd, env, timeout) {
|
|
|
151945
151954
|
}, timeout);
|
|
151946
151955
|
proc.exited.finally(() => clearTimeout(timeoutId));
|
|
151947
151956
|
});
|
|
151948
|
-
const exitCode = await Promise.race([
|
|
151957
|
+
const exitCode = await Promise.race([
|
|
151958
|
+
proc.exited,
|
|
151959
|
+
timeoutPromise,
|
|
151960
|
+
...signal ? [abortPromise] : []
|
|
151961
|
+
]);
|
|
151949
151962
|
const [stdout, stderr] = await Promise.all([
|
|
151950
151963
|
proc.stdout.text(),
|
|
151951
151964
|
proc.stderr.text()
|
|
@@ -151958,8 +151971,12 @@ Stdout: ${stdout}`;
|
|
|
151958
151971
|
}
|
|
151959
151972
|
return { stdout, stderr };
|
|
151960
151973
|
}
|
|
151961
|
-
async function spawnWithNode(args, cwd, env, timeout) {
|
|
151974
|
+
async function spawnWithNode(args, cwd, env, timeout, signal) {
|
|
151962
151975
|
return new Promise((resolve, reject) => {
|
|
151976
|
+
if (signal?.aborted) {
|
|
151977
|
+
reject(new Error(`Git command cancelled before execution: ${args.join(" ")}`));
|
|
151978
|
+
return;
|
|
151979
|
+
}
|
|
151963
151980
|
const proc = spawn("git", args, {
|
|
151964
151981
|
cwd,
|
|
151965
151982
|
env,
|
|
@@ -151973,16 +151990,29 @@ async function spawnWithNode(args, cwd, env, timeout) {
|
|
|
151973
151990
|
proc.stderr.on("data", (chunk) => {
|
|
151974
151991
|
stderrChunks.push(chunk);
|
|
151975
151992
|
});
|
|
151993
|
+
const abortHandler = () => {
|
|
151994
|
+
proc.kill("SIGTERM");
|
|
151995
|
+
reject(new Error(`Git command cancelled: ${args.join(" ")}`));
|
|
151996
|
+
};
|
|
151997
|
+
if (signal) {
|
|
151998
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
151999
|
+
}
|
|
151976
152000
|
const timeoutHandle = setTimeout(() => {
|
|
151977
152001
|
proc.kill("SIGTERM");
|
|
151978
152002
|
reject(new Error(`Git command timed out after ${timeout / 1000}s: ${args.join(" ")}`));
|
|
151979
152003
|
}, timeout);
|
|
151980
152004
|
proc.on("error", (error) => {
|
|
151981
152005
|
clearTimeout(timeoutHandle);
|
|
152006
|
+
if (signal) {
|
|
152007
|
+
signal.removeEventListener("abort", abortHandler);
|
|
152008
|
+
}
|
|
151982
152009
|
reject(error);
|
|
151983
152010
|
});
|
|
151984
152011
|
proc.on("close", (exitCode) => {
|
|
151985
152012
|
clearTimeout(timeoutHandle);
|
|
152013
|
+
if (signal) {
|
|
152014
|
+
signal.removeEventListener("abort", abortHandler);
|
|
152015
|
+
}
|
|
151986
152016
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
151987
152017
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
151988
152018
|
if (exitCode !== 0) {
|
|
@@ -151996,12 +152026,12 @@ Stdout: ${stdout}`;
|
|
|
151996
152026
|
});
|
|
151997
152027
|
});
|
|
151998
152028
|
}
|
|
151999
|
-
async function spawnGitCommand(args, cwd, env, timeout = 60000) {
|
|
152029
|
+
async function spawnGitCommand(args, cwd, env, timeout = 60000, signal) {
|
|
152000
152030
|
const runtime2 = detectRuntime2();
|
|
152001
152031
|
if (runtime2 === "bun") {
|
|
152002
|
-
return spawnWithBun(args, cwd, env, timeout);
|
|
152032
|
+
return spawnWithBun(args, cwd, env, timeout, signal);
|
|
152003
152033
|
} else {
|
|
152004
|
-
return spawnWithNode(args, cwd, env, timeout);
|
|
152034
|
+
return spawnWithNode(args, cwd, env, timeout, signal);
|
|
152005
152035
|
}
|
|
152006
152036
|
}
|
|
152007
152037
|
|
|
@@ -172126,6 +172156,163 @@ var httpErrorHandler = async (err, c) => {
|
|
|
172126
172156
|
return c.json(errorResponse);
|
|
172127
172157
|
};
|
|
172128
172158
|
|
|
172159
|
+
// src/mcp-server/transports/http/sessionManager.ts
|
|
172160
|
+
init_utils();
|
|
172161
|
+
|
|
172162
|
+
class SessionManager {
|
|
172163
|
+
static instance = null;
|
|
172164
|
+
sessions = new Map;
|
|
172165
|
+
cleanupIntervalId = null;
|
|
172166
|
+
staleTimeoutMs;
|
|
172167
|
+
cleanupIntervalMs;
|
|
172168
|
+
constructor(staleTimeoutMs = 30 * 60 * 1000, cleanupIntervalMs = 5 * 60 * 1000) {
|
|
172169
|
+
this.staleTimeoutMs = staleTimeoutMs;
|
|
172170
|
+
this.cleanupIntervalMs = cleanupIntervalMs;
|
|
172171
|
+
this.startCleanupInterval();
|
|
172172
|
+
}
|
|
172173
|
+
static getInstance(staleTimeoutMs, cleanupIntervalMs) {
|
|
172174
|
+
if (!SessionManager.instance) {
|
|
172175
|
+
SessionManager.instance = new SessionManager(staleTimeoutMs, cleanupIntervalMs);
|
|
172176
|
+
}
|
|
172177
|
+
return SessionManager.instance;
|
|
172178
|
+
}
|
|
172179
|
+
static resetInstance() {
|
|
172180
|
+
if (SessionManager.instance) {
|
|
172181
|
+
SessionManager.instance.stopCleanupInterval();
|
|
172182
|
+
SessionManager.instance = null;
|
|
172183
|
+
}
|
|
172184
|
+
}
|
|
172185
|
+
createSession(sessionId, clientId, tenantId) {
|
|
172186
|
+
const now2 = Date.now();
|
|
172187
|
+
const metadata = {
|
|
172188
|
+
sessionId,
|
|
172189
|
+
createdAt: now2,
|
|
172190
|
+
lastActivityAt: now2,
|
|
172191
|
+
...clientId !== undefined && { clientId },
|
|
172192
|
+
...tenantId !== undefined && { tenantId }
|
|
172193
|
+
};
|
|
172194
|
+
this.sessions.set(sessionId, metadata);
|
|
172195
|
+
logger.debug("Created new MCP session", {
|
|
172196
|
+
...requestContextService.createRequestContext({
|
|
172197
|
+
operation: "SessionManager.createSession"
|
|
172198
|
+
}),
|
|
172199
|
+
sessionId,
|
|
172200
|
+
...clientId !== undefined && { clientId },
|
|
172201
|
+
...tenantId !== undefined && { tenantId },
|
|
172202
|
+
totalSessions: this.sessions.size
|
|
172203
|
+
});
|
|
172204
|
+
return sessionId;
|
|
172205
|
+
}
|
|
172206
|
+
isSessionValid(sessionId) {
|
|
172207
|
+
const session = this.sessions.get(sessionId);
|
|
172208
|
+
if (!session) {
|
|
172209
|
+
return false;
|
|
172210
|
+
}
|
|
172211
|
+
const now2 = Date.now();
|
|
172212
|
+
const age = now2 - session.lastActivityAt;
|
|
172213
|
+
if (age > this.staleTimeoutMs) {
|
|
172214
|
+
logger.info("Session expired due to inactivity", {
|
|
172215
|
+
...requestContextService.createRequestContext({
|
|
172216
|
+
operation: "SessionManager.isSessionValid"
|
|
172217
|
+
}),
|
|
172218
|
+
sessionId,
|
|
172219
|
+
ageMs: age,
|
|
172220
|
+
staleTimeoutMs: this.staleTimeoutMs
|
|
172221
|
+
});
|
|
172222
|
+
this.sessions.delete(sessionId);
|
|
172223
|
+
return false;
|
|
172224
|
+
}
|
|
172225
|
+
return true;
|
|
172226
|
+
}
|
|
172227
|
+
touchSession(sessionId) {
|
|
172228
|
+
const session = this.sessions.get(sessionId);
|
|
172229
|
+
if (session) {
|
|
172230
|
+
session.lastActivityAt = Date.now();
|
|
172231
|
+
}
|
|
172232
|
+
}
|
|
172233
|
+
terminateSession(sessionId) {
|
|
172234
|
+
const existed = this.sessions.has(sessionId);
|
|
172235
|
+
this.sessions.delete(sessionId);
|
|
172236
|
+
if (existed) {
|
|
172237
|
+
logger.info("Session explicitly terminated", {
|
|
172238
|
+
...requestContextService.createRequestContext({
|
|
172239
|
+
operation: "SessionManager.terminateSession"
|
|
172240
|
+
}),
|
|
172241
|
+
sessionId,
|
|
172242
|
+
remainingSessions: this.sessions.size
|
|
172243
|
+
});
|
|
172244
|
+
}
|
|
172245
|
+
return existed;
|
|
172246
|
+
}
|
|
172247
|
+
getSessionMetadata(sessionId) {
|
|
172248
|
+
if (!this.isSessionValid(sessionId)) {
|
|
172249
|
+
return null;
|
|
172250
|
+
}
|
|
172251
|
+
return this.sessions.get(sessionId) ?? null;
|
|
172252
|
+
}
|
|
172253
|
+
getActiveSessionCount() {
|
|
172254
|
+
return this.sessions.size;
|
|
172255
|
+
}
|
|
172256
|
+
startCleanupInterval() {
|
|
172257
|
+
if (this.cleanupIntervalId) {
|
|
172258
|
+
return;
|
|
172259
|
+
}
|
|
172260
|
+
this.cleanupIntervalId = setInterval(() => {
|
|
172261
|
+
this.cleanupStaleSessions();
|
|
172262
|
+
}, this.cleanupIntervalMs);
|
|
172263
|
+
logger.info("Session cleanup interval started", {
|
|
172264
|
+
...requestContextService.createRequestContext({
|
|
172265
|
+
operation: "SessionManager.startCleanupInterval"
|
|
172266
|
+
}),
|
|
172267
|
+
cleanupIntervalMs: this.cleanupIntervalMs,
|
|
172268
|
+
staleTimeoutMs: this.staleTimeoutMs
|
|
172269
|
+
});
|
|
172270
|
+
}
|
|
172271
|
+
stopCleanupInterval() {
|
|
172272
|
+
if (this.cleanupIntervalId) {
|
|
172273
|
+
clearInterval(this.cleanupIntervalId);
|
|
172274
|
+
this.cleanupIntervalId = null;
|
|
172275
|
+
logger.info("Session cleanup interval stopped", {
|
|
172276
|
+
...requestContextService.createRequestContext({
|
|
172277
|
+
operation: "SessionManager.stopCleanupInterval"
|
|
172278
|
+
})
|
|
172279
|
+
});
|
|
172280
|
+
}
|
|
172281
|
+
}
|
|
172282
|
+
cleanupStaleSessions() {
|
|
172283
|
+
const now2 = Date.now();
|
|
172284
|
+
const sessionsBefore = this.sessions.size;
|
|
172285
|
+
let removedCount = 0;
|
|
172286
|
+
for (const [sessionId, metadata] of this.sessions.entries()) {
|
|
172287
|
+
const age = now2 - metadata.lastActivityAt;
|
|
172288
|
+
if (age > this.staleTimeoutMs) {
|
|
172289
|
+
this.sessions.delete(sessionId);
|
|
172290
|
+
removedCount++;
|
|
172291
|
+
}
|
|
172292
|
+
}
|
|
172293
|
+
if (removedCount > 0) {
|
|
172294
|
+
logger.notice("Cleaned up stale sessions", {
|
|
172295
|
+
...requestContextService.createRequestContext({
|
|
172296
|
+
operation: "SessionManager.cleanupStaleSessions"
|
|
172297
|
+
}),
|
|
172298
|
+
removedCount,
|
|
172299
|
+
sessionsBefore,
|
|
172300
|
+
sessionsAfter: this.sessions.size
|
|
172301
|
+
});
|
|
172302
|
+
}
|
|
172303
|
+
}
|
|
172304
|
+
clearAllSessions() {
|
|
172305
|
+
const count = this.sessions.size;
|
|
172306
|
+
this.sessions.clear();
|
|
172307
|
+
logger.warning("All sessions cleared", {
|
|
172308
|
+
...requestContextService.createRequestContext({
|
|
172309
|
+
operation: "SessionManager.clearAllSessions"
|
|
172310
|
+
}),
|
|
172311
|
+
clearedCount: count
|
|
172312
|
+
});
|
|
172313
|
+
}
|
|
172314
|
+
}
|
|
172315
|
+
|
|
172129
172316
|
// src/mcp-server/transports/http/httpTransport.ts
|
|
172130
172317
|
init_utils();
|
|
172131
172318
|
|
|
@@ -172142,6 +172329,11 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
172142
172329
|
...parentContext,
|
|
172143
172330
|
component: "HttpTransportSetup"
|
|
172144
172331
|
};
|
|
172332
|
+
const sessionManager = SessionManager.getInstance(config.mcpStatefulSessionStaleTimeoutMs);
|
|
172333
|
+
logger.info("Session manager initialized", {
|
|
172334
|
+
...transportContext,
|
|
172335
|
+
staleTimeoutMs: config.mcpStatefulSessionStaleTimeoutMs
|
|
172336
|
+
});
|
|
172145
172337
|
const allowedOrigin = Array.isArray(config.mcpAllowedOrigins) && config.mcpAllowedOrigins.length > 0 ? config.mcpAllowedOrigins : "*";
|
|
172146
172338
|
app.use("*", cors({
|
|
172147
172339
|
origin: allowedOrigin,
|
|
@@ -172190,6 +172382,35 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
172190
172382
|
} else {
|
|
172191
172383
|
logger.info("Authentication is disabled; MCP endpoint is unprotected.", transportContext);
|
|
172192
172384
|
}
|
|
172385
|
+
app.delete(config.mcpHttpEndpointPath, (c) => {
|
|
172386
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
172387
|
+
if (!sessionId) {
|
|
172388
|
+
return c.json({
|
|
172389
|
+
jsonrpc: "2.0",
|
|
172390
|
+
error: {
|
|
172391
|
+
code: -32600,
|
|
172392
|
+
message: "Mcp-Session-Id header required for DELETE"
|
|
172393
|
+
},
|
|
172394
|
+
id: null
|
|
172395
|
+
}, 400);
|
|
172396
|
+
}
|
|
172397
|
+
const terminated = sessionManager.terminateSession(sessionId);
|
|
172398
|
+
if (!terminated) {
|
|
172399
|
+
return c.json({
|
|
172400
|
+
jsonrpc: "2.0",
|
|
172401
|
+
error: {
|
|
172402
|
+
code: -32001,
|
|
172403
|
+
message: "Session not found or already expired"
|
|
172404
|
+
},
|
|
172405
|
+
id: null
|
|
172406
|
+
}, 404);
|
|
172407
|
+
}
|
|
172408
|
+
logger.info("Session terminated via DELETE", {
|
|
172409
|
+
...transportContext,
|
|
172410
|
+
sessionId
|
|
172411
|
+
});
|
|
172412
|
+
return c.body(null, 204);
|
|
172413
|
+
});
|
|
172193
172414
|
app.all(config.mcpHttpEndpointPath, async (c) => {
|
|
172194
172415
|
const protocolVersion = c.req.header("mcp-protocol-version") ?? "2025-03-26";
|
|
172195
172416
|
logger.debug("Handling MCP request.", {
|
|
@@ -172205,12 +172426,50 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
172205
172426
|
protocolVersion,
|
|
172206
172427
|
supportedVersions
|
|
172207
172428
|
});
|
|
172429
|
+
return c.json({
|
|
172430
|
+
jsonrpc: "2.0",
|
|
172431
|
+
error: {
|
|
172432
|
+
code: -32600,
|
|
172433
|
+
message: `Unsupported MCP protocol version: ${protocolVersion}`,
|
|
172434
|
+
data: {
|
|
172435
|
+
requested: protocolVersion,
|
|
172436
|
+
supported: supportedVersions
|
|
172437
|
+
}
|
|
172438
|
+
},
|
|
172439
|
+
id: null
|
|
172440
|
+
}, 400);
|
|
172208
172441
|
}
|
|
172209
172442
|
const sessionId = c.req.header("mcp-session-id") ?? randomUUID();
|
|
172443
|
+
if (c.req.header("mcp-session-id") && !sessionManager.isSessionValid(sessionId)) {
|
|
172444
|
+
logger.warning("Invalid or expired session ID", {
|
|
172445
|
+
...transportContext,
|
|
172446
|
+
sessionId
|
|
172447
|
+
});
|
|
172448
|
+
return c.json({
|
|
172449
|
+
jsonrpc: "2.0",
|
|
172450
|
+
error: {
|
|
172451
|
+
code: -32001,
|
|
172452
|
+
message: "Session expired or invalid. Please reinitialize."
|
|
172453
|
+
},
|
|
172454
|
+
id: null
|
|
172455
|
+
}, 404);
|
|
172456
|
+
}
|
|
172457
|
+
if (!c.req.header("mcp-session-id")) {
|
|
172458
|
+
logger.debug("New session will be created", {
|
|
172459
|
+
...transportContext,
|
|
172460
|
+
sessionId
|
|
172461
|
+
});
|
|
172462
|
+
} else {
|
|
172463
|
+
sessionManager.touchSession(sessionId);
|
|
172464
|
+
}
|
|
172210
172465
|
const transport = new McpSessionTransport(sessionId);
|
|
172211
172466
|
const handleRpc = async () => {
|
|
172212
172467
|
await mcpServer.connect(transport);
|
|
172213
172468
|
const response = await transport.handleRequest(c);
|
|
172469
|
+
if (response && !c.req.header("mcp-session-id")) {
|
|
172470
|
+
const store = authContext.getStore();
|
|
172471
|
+
sessionManager.createSession(sessionId, store?.authInfo.clientId, store?.authInfo.tenantId);
|
|
172472
|
+
}
|
|
172214
172473
|
if (response) {
|
|
172215
172474
|
return response;
|
|
172216
172475
|
}
|
|
@@ -172306,6 +172565,9 @@ async function stopHttpTransport(server, parentContext) {
|
|
|
172306
172565
|
transportType: "Http"
|
|
172307
172566
|
};
|
|
172308
172567
|
logger.info("Attempting to stop http transport...", operationContext);
|
|
172568
|
+
const sessionManager = SessionManager.getInstance();
|
|
172569
|
+
sessionManager.stopCleanupInterval();
|
|
172570
|
+
logger.info("Session cleanup interval stopped", operationContext);
|
|
172309
172571
|
return new Promise((resolve, reject) => {
|
|
172310
172572
|
server.close((err) => {
|
|
172311
172573
|
if (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/git-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"mcpName": "io.github.cyanheads/git-mcp-server",
|
|
5
5
|
"description": "A secure and scalable Git MCP server enabling AI agents to perform comprehensive Git version control operations via STDIO and Streamable HTTP.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -66,33 +66,12 @@
|
|
|
66
66
|
"zod": "3.23.8",
|
|
67
67
|
"typescript": "5.9.3"
|
|
68
68
|
},
|
|
69
|
-
"
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@cloudflare/workers-types": "^4.20251011.0",
|
|
71
|
+
"@eslint/js": "^9.37.0",
|
|
70
72
|
"@hono/mcp": "^0.1.4",
|
|
71
73
|
"@hono/node-server": "^1.19.5",
|
|
72
74
|
"@modelcontextprotocol/sdk": "^1.20.0",
|
|
73
|
-
"@supabase/supabase-js": "^2.75.0",
|
|
74
|
-
"axios": "^1.12.2",
|
|
75
|
-
"chrono-node": "^2.9.0",
|
|
76
|
-
"dotenv": "^17.2.3",
|
|
77
|
-
"fast-xml-parser": "^5.3.0",
|
|
78
|
-
"hono": "^4.9.12",
|
|
79
|
-
"ignore": "^7.0.5",
|
|
80
|
-
"jose": "^6.1.0",
|
|
81
|
-
"js-yaml": "^4.1.0",
|
|
82
|
-
"node-cron": "^4.2.1",
|
|
83
|
-
"openai": "^6.3.0",
|
|
84
|
-
"papaparse": "^5.5.3",
|
|
85
|
-
"partial-json": "^0.1.7",
|
|
86
|
-
"pdf-lib": "^1.17.1",
|
|
87
|
-
"pino": "^10.0.0",
|
|
88
|
-
"pino-pretty": "^13.1.2",
|
|
89
|
-
"reflect-metadata": "^0.2.2",
|
|
90
|
-
"repomix": "^1.7.0",
|
|
91
|
-
"sanitize-html": "^2.17.0",
|
|
92
|
-
"tslib": "^2.8.1",
|
|
93
|
-
"tsyringe": "^4.10.0",
|
|
94
|
-
"validator": "13.15.15",
|
|
95
|
-
"zod": "^3.23.8",
|
|
96
75
|
"@opentelemetry/api": "^1.9.0",
|
|
97
76
|
"@opentelemetry/auto-instrumentations-node": "^0.65.0",
|
|
98
77
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.206.0",
|
|
@@ -102,11 +81,8 @@
|
|
|
102
81
|
"@opentelemetry/sdk-metrics": "^2.1.0",
|
|
103
82
|
"@opentelemetry/sdk-node": "^0.206.0",
|
|
104
83
|
"@opentelemetry/sdk-trace-node": "^2.1.0",
|
|
105
|
-
"@opentelemetry/semantic-conventions": "^1.37.0"
|
|
106
|
-
|
|
107
|
-
"devDependencies": {
|
|
108
|
-
"@cloudflare/workers-types": "^4.20251011.0",
|
|
109
|
-
"@eslint/js": "^9.37.0",
|
|
84
|
+
"@opentelemetry/semantic-conventions": "^1.37.0",
|
|
85
|
+
"@supabase/supabase-js": "^2.75.0",
|
|
110
86
|
"@types/bun": "^1.3.0",
|
|
111
87
|
"@types/js-yaml": "^4.0.9",
|
|
112
88
|
"@types/node": "^24.7.2",
|
|
@@ -117,21 +93,43 @@
|
|
|
117
93
|
"@vitest/coverage-v8": "3.2.4",
|
|
118
94
|
"ajv": "^8.17.1",
|
|
119
95
|
"ajv-formats": "^3.0.1",
|
|
96
|
+
"axios": "^1.12.2",
|
|
120
97
|
"bun-types": "^1.3.0",
|
|
98
|
+
"chrono-node": "^2.9.0",
|
|
121
99
|
"clipboardy": "^5.0.0",
|
|
122
100
|
"depcheck": "^1.4.7",
|
|
101
|
+
"dotenv": "^17.2.3",
|
|
123
102
|
"eslint": "^9.37.0",
|
|
124
103
|
"execa": "^9.6.0",
|
|
104
|
+
"fast-xml-parser": "^5.3.0",
|
|
125
105
|
"globals": "^16.4.0",
|
|
106
|
+
"hono": "^4.9.12",
|
|
126
107
|
"husky": "^9.1.7",
|
|
108
|
+
"ignore": "^7.0.5",
|
|
109
|
+
"jose": "^6.1.0",
|
|
110
|
+
"js-yaml": "^4.1.0",
|
|
127
111
|
"msw": "^2.11.5",
|
|
112
|
+
"node-cron": "^4.2.1",
|
|
113
|
+
"openai": "^6.3.0",
|
|
114
|
+
"papaparse": "^5.5.3",
|
|
115
|
+
"partial-json": "^0.1.7",
|
|
116
|
+
"pdf-lib": "^1.17.1",
|
|
117
|
+
"pino": "^10.0.0",
|
|
118
|
+
"pino-pretty": "^13.1.2",
|
|
128
119
|
"prettier": "^3.6.2",
|
|
120
|
+
"reflect-metadata": "^0.2.2",
|
|
121
|
+
"repomix": "^1.7.0",
|
|
122
|
+
"sanitize-html": "^2.17.0",
|
|
123
|
+
"tslib": "^2.8.1",
|
|
124
|
+
"tsyringe": "^4.10.0",
|
|
129
125
|
"typedoc": "^0.28.14",
|
|
130
126
|
"typescript": "^5.9.3",
|
|
131
127
|
"typescript-eslint": "8.46.0",
|
|
128
|
+
"validator": "13.15.15",
|
|
132
129
|
"vite": "7.1.9",
|
|
133
130
|
"vite-tsconfig-paths": "^5.1.4",
|
|
134
|
-
"vitest": "^3.2.4"
|
|
131
|
+
"vitest": "^3.2.4",
|
|
132
|
+
"zod": "^3.23.8"
|
|
135
133
|
},
|
|
136
134
|
"keywords": [
|
|
137
135
|
"ai-agent",
|