@cyanheads/git-mcp-server 2.4.9 → 2.5.2
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 +305 -42
- 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",
|
|
@@ -101104,7 +101102,7 @@ var init_logger = __esm(() => {
|
|
|
101104
101102
|
static getInstance() {
|
|
101105
101103
|
return Logger.instance;
|
|
101106
101104
|
}
|
|
101107
|
-
async createPinoLogger(level) {
|
|
101105
|
+
async createPinoLogger(level, transportType) {
|
|
101108
101106
|
const pinoLevel = mcpToPinoLevel[level] || "info";
|
|
101109
101107
|
const pinoOptions = {
|
|
101110
101108
|
level: pinoLevel,
|
|
@@ -101126,7 +101124,8 @@ var init_logger = __esm(() => {
|
|
|
101126
101124
|
const transports = [];
|
|
101127
101125
|
const isDevelopment = config.environment === "development";
|
|
101128
101126
|
const isTest = config.environment === "testing";
|
|
101129
|
-
|
|
101127
|
+
const useColoredOutput = isDevelopment && transportType !== "stdio";
|
|
101128
|
+
if (useColoredOutput && !isServerless2) {
|
|
101130
101129
|
try {
|
|
101131
101130
|
const { createRequire: createRequire2 } = await import("node:module");
|
|
101132
101131
|
const require2 = createRequire2(import.meta.url);
|
|
@@ -101183,7 +101182,7 @@ var init_logger = __esm(() => {
|
|
|
101183
101182
|
}
|
|
101184
101183
|
});
|
|
101185
101184
|
}
|
|
101186
|
-
async initialize(level = "info") {
|
|
101185
|
+
async initialize(level = "info", transportType) {
|
|
101187
101186
|
if (this.initialized) {
|
|
101188
101187
|
this.warning("Logger already initialized.", requestContextService.createRequestContext({
|
|
101189
101188
|
operation: "loggerReinit"
|
|
@@ -101191,7 +101190,7 @@ var init_logger = __esm(() => {
|
|
|
101191
101190
|
return;
|
|
101192
101191
|
}
|
|
101193
101192
|
this.currentMcpLevel = level;
|
|
101194
|
-
this.pinoLogger = await this.createPinoLogger(level);
|
|
101193
|
+
this.pinoLogger = await this.createPinoLogger(level, transportType);
|
|
101195
101194
|
this.interactionLogger = await this.createInteractionLogger();
|
|
101196
101195
|
if (!isServerless2 && !this.cleanupTimer) {
|
|
101197
101196
|
this.cleanupTimer = setInterval(() => this.flushSuppressedMessages(), this.rateLimitWindow);
|
|
@@ -151931,13 +151930,24 @@ function detectRuntime2() {
|
|
|
151931
151930
|
}
|
|
151932
151931
|
return "node";
|
|
151933
151932
|
}
|
|
151934
|
-
async function spawnWithBun(args, cwd, env, timeout) {
|
|
151933
|
+
async function spawnWithBun(args, cwd, env, timeout, signal) {
|
|
151935
151934
|
const bunApi = globalThis.Bun;
|
|
151935
|
+
if (signal?.aborted) {
|
|
151936
|
+
throw new Error(`Git command cancelled before execution: git ${args.join(" ")}`);
|
|
151937
|
+
}
|
|
151936
151938
|
const proc = bunApi.spawn(["git", ...args], {
|
|
151937
151939
|
cwd,
|
|
151938
151940
|
env,
|
|
151939
151941
|
stdio: ["ignore", "pipe", "pipe"]
|
|
151940
151942
|
});
|
|
151943
|
+
const abortPromise = new Promise((_, reject) => {
|
|
151944
|
+
if (signal) {
|
|
151945
|
+
signal.addEventListener("abort", () => {
|
|
151946
|
+
proc.kill();
|
|
151947
|
+
reject(new Error(`Git command cancelled: git ${args.join(" ")}`));
|
|
151948
|
+
}, { once: true });
|
|
151949
|
+
}
|
|
151950
|
+
});
|
|
151941
151951
|
const timeoutPromise = new Promise((_, reject) => {
|
|
151942
151952
|
const timeoutId = setTimeout(() => {
|
|
151943
151953
|
proc.kill();
|
|
@@ -151945,7 +151955,11 @@ async function spawnWithBun(args, cwd, env, timeout) {
|
|
|
151945
151955
|
}, timeout);
|
|
151946
151956
|
proc.exited.finally(() => clearTimeout(timeoutId));
|
|
151947
151957
|
});
|
|
151948
|
-
const exitCode = await Promise.race([
|
|
151958
|
+
const exitCode = await Promise.race([
|
|
151959
|
+
proc.exited,
|
|
151960
|
+
timeoutPromise,
|
|
151961
|
+
...signal ? [abortPromise] : []
|
|
151962
|
+
]);
|
|
151949
151963
|
const [stdout, stderr] = await Promise.all([
|
|
151950
151964
|
proc.stdout.text(),
|
|
151951
151965
|
proc.stderr.text()
|
|
@@ -151958,8 +151972,12 @@ Stdout: ${stdout}`;
|
|
|
151958
151972
|
}
|
|
151959
151973
|
return { stdout, stderr };
|
|
151960
151974
|
}
|
|
151961
|
-
async function spawnWithNode(args, cwd, env, timeout) {
|
|
151975
|
+
async function spawnWithNode(args, cwd, env, timeout, signal) {
|
|
151962
151976
|
return new Promise((resolve, reject) => {
|
|
151977
|
+
if (signal?.aborted) {
|
|
151978
|
+
reject(new Error(`Git command cancelled before execution: ${args.join(" ")}`));
|
|
151979
|
+
return;
|
|
151980
|
+
}
|
|
151963
151981
|
const proc = spawn("git", args, {
|
|
151964
151982
|
cwd,
|
|
151965
151983
|
env,
|
|
@@ -151973,16 +151991,29 @@ async function spawnWithNode(args, cwd, env, timeout) {
|
|
|
151973
151991
|
proc.stderr.on("data", (chunk) => {
|
|
151974
151992
|
stderrChunks.push(chunk);
|
|
151975
151993
|
});
|
|
151994
|
+
const abortHandler = () => {
|
|
151995
|
+
proc.kill("SIGTERM");
|
|
151996
|
+
reject(new Error(`Git command cancelled: ${args.join(" ")}`));
|
|
151997
|
+
};
|
|
151998
|
+
if (signal) {
|
|
151999
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
152000
|
+
}
|
|
151976
152001
|
const timeoutHandle = setTimeout(() => {
|
|
151977
152002
|
proc.kill("SIGTERM");
|
|
151978
152003
|
reject(new Error(`Git command timed out after ${timeout / 1000}s: ${args.join(" ")}`));
|
|
151979
152004
|
}, timeout);
|
|
151980
152005
|
proc.on("error", (error) => {
|
|
151981
152006
|
clearTimeout(timeoutHandle);
|
|
152007
|
+
if (signal) {
|
|
152008
|
+
signal.removeEventListener("abort", abortHandler);
|
|
152009
|
+
}
|
|
151982
152010
|
reject(error);
|
|
151983
152011
|
});
|
|
151984
152012
|
proc.on("close", (exitCode) => {
|
|
151985
152013
|
clearTimeout(timeoutHandle);
|
|
152014
|
+
if (signal) {
|
|
152015
|
+
signal.removeEventListener("abort", abortHandler);
|
|
152016
|
+
}
|
|
151986
152017
|
const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
151987
152018
|
const stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
151988
152019
|
if (exitCode !== 0) {
|
|
@@ -151996,12 +152027,12 @@ Stdout: ${stdout}`;
|
|
|
151996
152027
|
});
|
|
151997
152028
|
});
|
|
151998
152029
|
}
|
|
151999
|
-
async function spawnGitCommand(args, cwd, env, timeout = 60000) {
|
|
152030
|
+
async function spawnGitCommand(args, cwd, env, timeout = 60000, signal) {
|
|
152000
152031
|
const runtime2 = detectRuntime2();
|
|
152001
152032
|
if (runtime2 === "bun") {
|
|
152002
|
-
return spawnWithBun(args, cwd, env, timeout);
|
|
152033
|
+
return spawnWithBun(args, cwd, env, timeout, signal);
|
|
152003
152034
|
} else {
|
|
152004
|
-
return spawnWithNode(args, cwd, env, timeout);
|
|
152035
|
+
return spawnWithNode(args, cwd, env, timeout, signal);
|
|
152005
152036
|
}
|
|
152006
152037
|
}
|
|
152007
152038
|
|
|
@@ -172126,6 +172157,163 @@ var httpErrorHandler = async (err, c) => {
|
|
|
172126
172157
|
return c.json(errorResponse);
|
|
172127
172158
|
};
|
|
172128
172159
|
|
|
172160
|
+
// src/mcp-server/transports/http/sessionManager.ts
|
|
172161
|
+
init_utils();
|
|
172162
|
+
|
|
172163
|
+
class SessionManager {
|
|
172164
|
+
static instance = null;
|
|
172165
|
+
sessions = new Map;
|
|
172166
|
+
cleanupIntervalId = null;
|
|
172167
|
+
staleTimeoutMs;
|
|
172168
|
+
cleanupIntervalMs;
|
|
172169
|
+
constructor(staleTimeoutMs = 30 * 60 * 1000, cleanupIntervalMs = 5 * 60 * 1000) {
|
|
172170
|
+
this.staleTimeoutMs = staleTimeoutMs;
|
|
172171
|
+
this.cleanupIntervalMs = cleanupIntervalMs;
|
|
172172
|
+
this.startCleanupInterval();
|
|
172173
|
+
}
|
|
172174
|
+
static getInstance(staleTimeoutMs, cleanupIntervalMs) {
|
|
172175
|
+
if (!SessionManager.instance) {
|
|
172176
|
+
SessionManager.instance = new SessionManager(staleTimeoutMs, cleanupIntervalMs);
|
|
172177
|
+
}
|
|
172178
|
+
return SessionManager.instance;
|
|
172179
|
+
}
|
|
172180
|
+
static resetInstance() {
|
|
172181
|
+
if (SessionManager.instance) {
|
|
172182
|
+
SessionManager.instance.stopCleanupInterval();
|
|
172183
|
+
SessionManager.instance = null;
|
|
172184
|
+
}
|
|
172185
|
+
}
|
|
172186
|
+
createSession(sessionId, clientId, tenantId) {
|
|
172187
|
+
const now2 = Date.now();
|
|
172188
|
+
const metadata = {
|
|
172189
|
+
sessionId,
|
|
172190
|
+
createdAt: now2,
|
|
172191
|
+
lastActivityAt: now2,
|
|
172192
|
+
...clientId !== undefined && { clientId },
|
|
172193
|
+
...tenantId !== undefined && { tenantId }
|
|
172194
|
+
};
|
|
172195
|
+
this.sessions.set(sessionId, metadata);
|
|
172196
|
+
logger.debug("Created new MCP session", {
|
|
172197
|
+
...requestContextService.createRequestContext({
|
|
172198
|
+
operation: "SessionManager.createSession"
|
|
172199
|
+
}),
|
|
172200
|
+
sessionId,
|
|
172201
|
+
...clientId !== undefined && { clientId },
|
|
172202
|
+
...tenantId !== undefined && { tenantId },
|
|
172203
|
+
totalSessions: this.sessions.size
|
|
172204
|
+
});
|
|
172205
|
+
return sessionId;
|
|
172206
|
+
}
|
|
172207
|
+
isSessionValid(sessionId) {
|
|
172208
|
+
const session = this.sessions.get(sessionId);
|
|
172209
|
+
if (!session) {
|
|
172210
|
+
return false;
|
|
172211
|
+
}
|
|
172212
|
+
const now2 = Date.now();
|
|
172213
|
+
const age = now2 - session.lastActivityAt;
|
|
172214
|
+
if (age > this.staleTimeoutMs) {
|
|
172215
|
+
logger.info("Session expired due to inactivity", {
|
|
172216
|
+
...requestContextService.createRequestContext({
|
|
172217
|
+
operation: "SessionManager.isSessionValid"
|
|
172218
|
+
}),
|
|
172219
|
+
sessionId,
|
|
172220
|
+
ageMs: age,
|
|
172221
|
+
staleTimeoutMs: this.staleTimeoutMs
|
|
172222
|
+
});
|
|
172223
|
+
this.sessions.delete(sessionId);
|
|
172224
|
+
return false;
|
|
172225
|
+
}
|
|
172226
|
+
return true;
|
|
172227
|
+
}
|
|
172228
|
+
touchSession(sessionId) {
|
|
172229
|
+
const session = this.sessions.get(sessionId);
|
|
172230
|
+
if (session) {
|
|
172231
|
+
session.lastActivityAt = Date.now();
|
|
172232
|
+
}
|
|
172233
|
+
}
|
|
172234
|
+
terminateSession(sessionId) {
|
|
172235
|
+
const existed = this.sessions.has(sessionId);
|
|
172236
|
+
this.sessions.delete(sessionId);
|
|
172237
|
+
if (existed) {
|
|
172238
|
+
logger.info("Session explicitly terminated", {
|
|
172239
|
+
...requestContextService.createRequestContext({
|
|
172240
|
+
operation: "SessionManager.terminateSession"
|
|
172241
|
+
}),
|
|
172242
|
+
sessionId,
|
|
172243
|
+
remainingSessions: this.sessions.size
|
|
172244
|
+
});
|
|
172245
|
+
}
|
|
172246
|
+
return existed;
|
|
172247
|
+
}
|
|
172248
|
+
getSessionMetadata(sessionId) {
|
|
172249
|
+
if (!this.isSessionValid(sessionId)) {
|
|
172250
|
+
return null;
|
|
172251
|
+
}
|
|
172252
|
+
return this.sessions.get(sessionId) ?? null;
|
|
172253
|
+
}
|
|
172254
|
+
getActiveSessionCount() {
|
|
172255
|
+
return this.sessions.size;
|
|
172256
|
+
}
|
|
172257
|
+
startCleanupInterval() {
|
|
172258
|
+
if (this.cleanupIntervalId) {
|
|
172259
|
+
return;
|
|
172260
|
+
}
|
|
172261
|
+
this.cleanupIntervalId = setInterval(() => {
|
|
172262
|
+
this.cleanupStaleSessions();
|
|
172263
|
+
}, this.cleanupIntervalMs);
|
|
172264
|
+
logger.info("Session cleanup interval started", {
|
|
172265
|
+
...requestContextService.createRequestContext({
|
|
172266
|
+
operation: "SessionManager.startCleanupInterval"
|
|
172267
|
+
}),
|
|
172268
|
+
cleanupIntervalMs: this.cleanupIntervalMs,
|
|
172269
|
+
staleTimeoutMs: this.staleTimeoutMs
|
|
172270
|
+
});
|
|
172271
|
+
}
|
|
172272
|
+
stopCleanupInterval() {
|
|
172273
|
+
if (this.cleanupIntervalId) {
|
|
172274
|
+
clearInterval(this.cleanupIntervalId);
|
|
172275
|
+
this.cleanupIntervalId = null;
|
|
172276
|
+
logger.info("Session cleanup interval stopped", {
|
|
172277
|
+
...requestContextService.createRequestContext({
|
|
172278
|
+
operation: "SessionManager.stopCleanupInterval"
|
|
172279
|
+
})
|
|
172280
|
+
});
|
|
172281
|
+
}
|
|
172282
|
+
}
|
|
172283
|
+
cleanupStaleSessions() {
|
|
172284
|
+
const now2 = Date.now();
|
|
172285
|
+
const sessionsBefore = this.sessions.size;
|
|
172286
|
+
let removedCount = 0;
|
|
172287
|
+
for (const [sessionId, metadata] of this.sessions.entries()) {
|
|
172288
|
+
const age = now2 - metadata.lastActivityAt;
|
|
172289
|
+
if (age > this.staleTimeoutMs) {
|
|
172290
|
+
this.sessions.delete(sessionId);
|
|
172291
|
+
removedCount++;
|
|
172292
|
+
}
|
|
172293
|
+
}
|
|
172294
|
+
if (removedCount > 0) {
|
|
172295
|
+
logger.notice("Cleaned up stale sessions", {
|
|
172296
|
+
...requestContextService.createRequestContext({
|
|
172297
|
+
operation: "SessionManager.cleanupStaleSessions"
|
|
172298
|
+
}),
|
|
172299
|
+
removedCount,
|
|
172300
|
+
sessionsBefore,
|
|
172301
|
+
sessionsAfter: this.sessions.size
|
|
172302
|
+
});
|
|
172303
|
+
}
|
|
172304
|
+
}
|
|
172305
|
+
clearAllSessions() {
|
|
172306
|
+
const count = this.sessions.size;
|
|
172307
|
+
this.sessions.clear();
|
|
172308
|
+
logger.warning("All sessions cleared", {
|
|
172309
|
+
...requestContextService.createRequestContext({
|
|
172310
|
+
operation: "SessionManager.clearAllSessions"
|
|
172311
|
+
}),
|
|
172312
|
+
clearedCount: count
|
|
172313
|
+
});
|
|
172314
|
+
}
|
|
172315
|
+
}
|
|
172316
|
+
|
|
172129
172317
|
// src/mcp-server/transports/http/httpTransport.ts
|
|
172130
172318
|
init_utils();
|
|
172131
172319
|
|
|
@@ -172142,6 +172330,11 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
172142
172330
|
...parentContext,
|
|
172143
172331
|
component: "HttpTransportSetup"
|
|
172144
172332
|
};
|
|
172333
|
+
const sessionManager = SessionManager.getInstance(config.mcpStatefulSessionStaleTimeoutMs);
|
|
172334
|
+
logger.info("Session manager initialized", {
|
|
172335
|
+
...transportContext,
|
|
172336
|
+
staleTimeoutMs: config.mcpStatefulSessionStaleTimeoutMs
|
|
172337
|
+
});
|
|
172145
172338
|
const allowedOrigin = Array.isArray(config.mcpAllowedOrigins) && config.mcpAllowedOrigins.length > 0 ? config.mcpAllowedOrigins : "*";
|
|
172146
172339
|
app.use("*", cors({
|
|
172147
172340
|
origin: allowedOrigin,
|
|
@@ -172190,6 +172383,35 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
172190
172383
|
} else {
|
|
172191
172384
|
logger.info("Authentication is disabled; MCP endpoint is unprotected.", transportContext);
|
|
172192
172385
|
}
|
|
172386
|
+
app.delete(config.mcpHttpEndpointPath, (c) => {
|
|
172387
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
172388
|
+
if (!sessionId) {
|
|
172389
|
+
return c.json({
|
|
172390
|
+
jsonrpc: "2.0",
|
|
172391
|
+
error: {
|
|
172392
|
+
code: -32600,
|
|
172393
|
+
message: "Mcp-Session-Id header required for DELETE"
|
|
172394
|
+
},
|
|
172395
|
+
id: null
|
|
172396
|
+
}, 400);
|
|
172397
|
+
}
|
|
172398
|
+
const terminated = sessionManager.terminateSession(sessionId);
|
|
172399
|
+
if (!terminated) {
|
|
172400
|
+
return c.json({
|
|
172401
|
+
jsonrpc: "2.0",
|
|
172402
|
+
error: {
|
|
172403
|
+
code: -32001,
|
|
172404
|
+
message: "Session not found or already expired"
|
|
172405
|
+
},
|
|
172406
|
+
id: null
|
|
172407
|
+
}, 404);
|
|
172408
|
+
}
|
|
172409
|
+
logger.info("Session terminated via DELETE", {
|
|
172410
|
+
...transportContext,
|
|
172411
|
+
sessionId
|
|
172412
|
+
});
|
|
172413
|
+
return c.body(null, 204);
|
|
172414
|
+
});
|
|
172193
172415
|
app.all(config.mcpHttpEndpointPath, async (c) => {
|
|
172194
172416
|
const protocolVersion = c.req.header("mcp-protocol-version") ?? "2025-03-26";
|
|
172195
172417
|
logger.debug("Handling MCP request.", {
|
|
@@ -172205,12 +172427,50 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
172205
172427
|
protocolVersion,
|
|
172206
172428
|
supportedVersions
|
|
172207
172429
|
});
|
|
172430
|
+
return c.json({
|
|
172431
|
+
jsonrpc: "2.0",
|
|
172432
|
+
error: {
|
|
172433
|
+
code: -32600,
|
|
172434
|
+
message: `Unsupported MCP protocol version: ${protocolVersion}`,
|
|
172435
|
+
data: {
|
|
172436
|
+
requested: protocolVersion,
|
|
172437
|
+
supported: supportedVersions
|
|
172438
|
+
}
|
|
172439
|
+
},
|
|
172440
|
+
id: null
|
|
172441
|
+
}, 400);
|
|
172208
172442
|
}
|
|
172209
172443
|
const sessionId = c.req.header("mcp-session-id") ?? randomUUID();
|
|
172444
|
+
if (c.req.header("mcp-session-id") && !sessionManager.isSessionValid(sessionId)) {
|
|
172445
|
+
logger.warning("Invalid or expired session ID", {
|
|
172446
|
+
...transportContext,
|
|
172447
|
+
sessionId
|
|
172448
|
+
});
|
|
172449
|
+
return c.json({
|
|
172450
|
+
jsonrpc: "2.0",
|
|
172451
|
+
error: {
|
|
172452
|
+
code: -32001,
|
|
172453
|
+
message: "Session expired or invalid. Please reinitialize."
|
|
172454
|
+
},
|
|
172455
|
+
id: null
|
|
172456
|
+
}, 404);
|
|
172457
|
+
}
|
|
172458
|
+
if (!c.req.header("mcp-session-id")) {
|
|
172459
|
+
logger.debug("New session will be created", {
|
|
172460
|
+
...transportContext,
|
|
172461
|
+
sessionId
|
|
172462
|
+
});
|
|
172463
|
+
} else {
|
|
172464
|
+
sessionManager.touchSession(sessionId);
|
|
172465
|
+
}
|
|
172210
172466
|
const transport = new McpSessionTransport(sessionId);
|
|
172211
172467
|
const handleRpc = async () => {
|
|
172212
172468
|
await mcpServer.connect(transport);
|
|
172213
172469
|
const response = await transport.handleRequest(c);
|
|
172470
|
+
if (response && !c.req.header("mcp-session-id")) {
|
|
172471
|
+
const store = authContext.getStore();
|
|
172472
|
+
sessionManager.createSession(sessionId, store?.authInfo.clientId, store?.authInfo.tenantId);
|
|
172473
|
+
}
|
|
172214
172474
|
if (response) {
|
|
172215
172475
|
return response;
|
|
172216
172476
|
}
|
|
@@ -172306,6 +172566,9 @@ async function stopHttpTransport(server, parentContext) {
|
|
|
172306
172566
|
transportType: "Http"
|
|
172307
172567
|
};
|
|
172308
172568
|
logger.info("Attempting to stop http transport...", operationContext);
|
|
172569
|
+
const sessionManager = SessionManager.getInstance();
|
|
172570
|
+
sessionManager.stopCleanupInterval();
|
|
172571
|
+
logger.info("Session cleanup interval stopped", operationContext);
|
|
172309
172572
|
return new Promise((resolve, reject) => {
|
|
172310
172573
|
server.close((err) => {
|
|
172311
172574
|
if (err) {
|
|
@@ -172602,7 +172865,7 @@ var start = async () => {
|
|
|
172602
172865
|
console.warn(`[Startup Warning] Invalid MCP_LOG_LEVEL "${initialLogLevelConfig}". Defaulting to "info".`);
|
|
172603
172866
|
}
|
|
172604
172867
|
}
|
|
172605
|
-
await logger.initialize(validatedMcpLogLevel);
|
|
172868
|
+
await logger.initialize(validatedMcpLogLevel, config2.mcpTransportType);
|
|
172606
172869
|
logger.info(`Logger initialized. Effective MCP logging level: ${validatedMcpLogLevel}.`, requestContextService.createRequestContext({ operation: "LoggerInit" }));
|
|
172607
172870
|
const runtime2 = detectRuntime();
|
|
172608
172871
|
const runtimeDesc = getRuntimeDescription();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/git-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.2",
|
|
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",
|