@cyanheads/git-mcp-server 2.8.2 → 2.8.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.
- package/README.md +1 -1
- package/dist/index.js +115 -97
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [](https://modelcontextprotocol.io/) [](./LICENSE) [](https://github.com/cyanheads/git-mcp-server/issues) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
package/dist/index.js
CHANGED
|
@@ -15335,7 +15335,7 @@ var package_default;
|
|
|
15335
15335
|
var init_package = __esm(() => {
|
|
15336
15336
|
package_default = {
|
|
15337
15337
|
name: "@cyanheads/git-mcp-server",
|
|
15338
|
-
version: "2.8.
|
|
15338
|
+
version: "2.8.3",
|
|
15339
15339
|
mcpName: "io.github.cyanheads/git-mcp-server",
|
|
15340
15340
|
description: "A secure and scalable Git MCP server enabling AI agents to perform comprehensive Git version control operations via STDIO and Streamable HTTP.",
|
|
15341
15341
|
main: "dist/index.js",
|
|
@@ -201975,6 +201975,7 @@ class SessionManager {
|
|
|
201975
201975
|
cleanupIntervalId = null;
|
|
201976
201976
|
staleTimeoutMs;
|
|
201977
201977
|
cleanupIntervalMs;
|
|
201978
|
+
onSessionExpired = null;
|
|
201978
201979
|
constructor(staleTimeoutMs = 30 * 60 * 1000, cleanupIntervalMs = 5 * 60 * 1000) {
|
|
201979
201980
|
this.staleTimeoutMs = staleTimeoutMs;
|
|
201980
201981
|
this.cleanupIntervalMs = cleanupIntervalMs;
|
|
@@ -202030,6 +202031,7 @@ class SessionManager {
|
|
|
202030
202031
|
staleTimeoutMs: this.staleTimeoutMs
|
|
202031
202032
|
});
|
|
202032
202033
|
this.sessions.delete(sessionId);
|
|
202034
|
+
this.onSessionExpired?.(sessionId);
|
|
202033
202035
|
return false;
|
|
202034
202036
|
}
|
|
202035
202037
|
return true;
|
|
@@ -202097,6 +202099,7 @@ class SessionManager {
|
|
|
202097
202099
|
const age = now2 - metadata.lastActivityAt;
|
|
202098
202100
|
if (age > this.staleTimeoutMs) {
|
|
202099
202101
|
this.sessions.delete(sessionId);
|
|
202102
|
+
this.onSessionExpired?.(sessionId);
|
|
202100
202103
|
removedCount++;
|
|
202101
202104
|
}
|
|
202102
202105
|
}
|
|
@@ -202125,21 +202128,27 @@ class SessionManager {
|
|
|
202125
202128
|
|
|
202126
202129
|
// src/mcp-server/transports/http/httpTransport.ts
|
|
202127
202130
|
init_utils();
|
|
202128
|
-
|
|
202129
|
-
class McpSessionTransport extends StreamableHTTPTransport {
|
|
202130
|
-
sessionId;
|
|
202131
|
-
constructor(sessionId) {
|
|
202132
|
-
super();
|
|
202133
|
-
this.sessionId = sessionId;
|
|
202134
|
-
}
|
|
202135
|
-
}
|
|
202136
|
-
function createHttpApp(mcpServer, parentContext) {
|
|
202131
|
+
function createHttpApp(createMcpServer, parentContext) {
|
|
202137
202132
|
const app = new Hono2;
|
|
202138
202133
|
const transportContext = {
|
|
202139
202134
|
...parentContext,
|
|
202140
202135
|
component: "HttpTransportSetup"
|
|
202141
202136
|
};
|
|
202137
|
+
const transports = new Map;
|
|
202142
202138
|
const sessionManager = SessionManager.getInstance(config2.mcpStatefulSessionStaleTimeoutMs);
|
|
202139
|
+
sessionManager.onSessionExpired = (sessionId) => {
|
|
202140
|
+
const transport = transports.get(sessionId);
|
|
202141
|
+
if (transport) {
|
|
202142
|
+
transport.close().catch((err) => {
|
|
202143
|
+
logger.warning("Failed to close transport for expired session", {
|
|
202144
|
+
...transportContext,
|
|
202145
|
+
sessionId,
|
|
202146
|
+
error: err instanceof Error ? err.message : String(err)
|
|
202147
|
+
});
|
|
202148
|
+
});
|
|
202149
|
+
transports.delete(sessionId);
|
|
202150
|
+
}
|
|
202151
|
+
};
|
|
202143
202152
|
logger.info("Session manager initialized", {
|
|
202144
202153
|
...transportContext,
|
|
202145
202154
|
staleTimeoutMs: config2.mcpStatefulSessionStaleTimeoutMs
|
|
@@ -202175,18 +202184,6 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202175
202184
|
...config2.oauthJwksUri && { jwks_uri: config2.oauthJwksUri }
|
|
202176
202185
|
});
|
|
202177
202186
|
});
|
|
202178
|
-
app.get(config2.mcpHttpEndpointPath, (c) => {
|
|
202179
|
-
return c.json({
|
|
202180
|
-
status: "ok",
|
|
202181
|
-
server: {
|
|
202182
|
-
name: config2.mcpServerName,
|
|
202183
|
-
version: config2.mcpServerVersion,
|
|
202184
|
-
description: config2.mcpServerDescription,
|
|
202185
|
-
transport: config2.mcpTransportType,
|
|
202186
|
-
sessionMode: config2.mcpSessionMode
|
|
202187
|
-
}
|
|
202188
|
-
});
|
|
202189
|
-
});
|
|
202190
202187
|
const authStrategy = createAuthStrategy();
|
|
202191
202188
|
if (authStrategy) {
|
|
202192
202189
|
const authMiddleware = createAuthMiddleware(authStrategy);
|
|
@@ -202195,113 +202192,134 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202195
202192
|
} else {
|
|
202196
202193
|
logger.info("Authentication is disabled; MCP endpoint is unprotected.", transportContext);
|
|
202197
202194
|
}
|
|
202198
|
-
|
|
202199
|
-
|
|
202200
|
-
|
|
202201
|
-
|
|
202195
|
+
const getSessionTransport = (sessionId) => {
|
|
202196
|
+
if (!sessionManager.isSessionValid(sessionId)) {
|
|
202197
|
+
const stale = transports.get(sessionId);
|
|
202198
|
+
if (stale) {
|
|
202199
|
+
stale.close().catch(() => {});
|
|
202200
|
+
transports.delete(sessionId);
|
|
202201
|
+
}
|
|
202202
|
+
return Response.json({
|
|
202202
202203
|
jsonrpc: "2.0",
|
|
202203
202204
|
error: {
|
|
202204
|
-
code: -
|
|
202205
|
-
message: "
|
|
202205
|
+
code: -32001,
|
|
202206
|
+
message: "Session expired or invalid. Please reinitialize."
|
|
202206
202207
|
},
|
|
202207
202208
|
id: null
|
|
202208
|
-
},
|
|
202209
|
+
}, { status: 404 });
|
|
202209
202210
|
}
|
|
202210
|
-
const
|
|
202211
|
-
if (!
|
|
202212
|
-
return
|
|
202211
|
+
const transport = transports.get(sessionId);
|
|
202212
|
+
if (!transport) {
|
|
202213
|
+
return Response.json({
|
|
202213
202214
|
jsonrpc: "2.0",
|
|
202214
202215
|
error: {
|
|
202215
202216
|
code: -32001,
|
|
202216
|
-
message: "Session not found
|
|
202217
|
+
message: "Session not found. Please reinitialize."
|
|
202217
202218
|
},
|
|
202218
202219
|
id: null
|
|
202219
|
-
}, 404);
|
|
202220
|
+
}, { status: 404 });
|
|
202220
202221
|
}
|
|
202221
|
-
|
|
202222
|
-
|
|
202223
|
-
|
|
202224
|
-
|
|
202225
|
-
|
|
202226
|
-
|
|
202227
|
-
|
|
202228
|
-
|
|
202229
|
-
|
|
202230
|
-
|
|
202231
|
-
|
|
202232
|
-
|
|
202233
|
-
|
|
202234
|
-
|
|
202235
|
-
|
|
202236
|
-
if (!supportedVersions.includes(protocolVersion)) {
|
|
202237
|
-
logger.warning("Unsupported MCP protocol version requested.", {
|
|
202238
|
-
...transportContext,
|
|
202239
|
-
protocolVersion,
|
|
202240
|
-
supportedVersions
|
|
202222
|
+
sessionManager.touchSession(sessionId);
|
|
202223
|
+
return transport;
|
|
202224
|
+
};
|
|
202225
|
+
app.get(config2.mcpHttpEndpointPath, async (c) => {
|
|
202226
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
202227
|
+
if (!sessionId) {
|
|
202228
|
+
return c.json({
|
|
202229
|
+
status: "ok",
|
|
202230
|
+
server: {
|
|
202231
|
+
name: config2.mcpServerName,
|
|
202232
|
+
version: config2.mcpServerVersion,
|
|
202233
|
+
description: config2.mcpServerDescription,
|
|
202234
|
+
transport: config2.mcpTransportType,
|
|
202235
|
+
sessionMode: config2.mcpSessionMode
|
|
202236
|
+
}
|
|
202241
202237
|
});
|
|
202238
|
+
}
|
|
202239
|
+
const transportOrError = getSessionTransport(sessionId);
|
|
202240
|
+
if (transportOrError instanceof Response)
|
|
202241
|
+
return transportOrError;
|
|
202242
|
+
const response = await transportOrError.handleRequest(c);
|
|
202243
|
+
return response ?? c.body(null, 204);
|
|
202244
|
+
});
|
|
202245
|
+
app.delete(config2.mcpHttpEndpointPath, async (c) => {
|
|
202246
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
202247
|
+
if (!sessionId) {
|
|
202242
202248
|
return c.json({
|
|
202243
202249
|
jsonrpc: "2.0",
|
|
202244
202250
|
error: {
|
|
202245
202251
|
code: -32600,
|
|
202246
|
-
message:
|
|
202247
|
-
data: {
|
|
202248
|
-
requested: protocolVersion,
|
|
202249
|
-
supported: supportedVersions
|
|
202250
|
-
}
|
|
202252
|
+
message: "Mcp-Session-Id header required for DELETE"
|
|
202251
202253
|
},
|
|
202252
202254
|
id: null
|
|
202253
202255
|
}, 400);
|
|
202254
202256
|
}
|
|
202255
|
-
const
|
|
202256
|
-
if (
|
|
202257
|
-
logger.warning("Invalid or expired session ID", {
|
|
202258
|
-
...transportContext,
|
|
202259
|
-
sessionId
|
|
202260
|
-
});
|
|
202257
|
+
const transport = transports.get(sessionId);
|
|
202258
|
+
if (!transport) {
|
|
202261
202259
|
return c.json({
|
|
202262
202260
|
jsonrpc: "2.0",
|
|
202263
202261
|
error: {
|
|
202264
202262
|
code: -32001,
|
|
202265
|
-
message: "Session
|
|
202263
|
+
message: "Session not found or already expired"
|
|
202266
202264
|
},
|
|
202267
202265
|
id: null
|
|
202268
202266
|
}, 404);
|
|
202269
202267
|
}
|
|
202270
|
-
|
|
202271
|
-
|
|
202272
|
-
|
|
202273
|
-
|
|
202268
|
+
const response = await transport.handleRequest(c);
|
|
202269
|
+
transports.delete(sessionId);
|
|
202270
|
+
sessionManager.terminateSession(sessionId);
|
|
202271
|
+
logger.info("Session terminated via DELETE", {
|
|
202272
|
+
...transportContext,
|
|
202273
|
+
sessionId
|
|
202274
|
+
});
|
|
202275
|
+
return response ?? c.body(null, 204);
|
|
202276
|
+
});
|
|
202277
|
+
app.post(config2.mcpHttpEndpointPath, async (c) => {
|
|
202278
|
+
logger.debug("Handling MCP POST request.", {
|
|
202279
|
+
...transportContext,
|
|
202280
|
+
path: c.req.path
|
|
202281
|
+
});
|
|
202282
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
202283
|
+
const handleRequest = async () => {
|
|
202284
|
+
if (sessionId) {
|
|
202285
|
+
const transportOrError = getSessionTransport(sessionId);
|
|
202286
|
+
if (transportOrError instanceof Response)
|
|
202287
|
+
return transportOrError;
|
|
202288
|
+
const response2 = await transportOrError.handleRequest(c);
|
|
202289
|
+
return response2 ?? c.body(null, 204);
|
|
202290
|
+
}
|
|
202291
|
+
const server = await createMcpServer();
|
|
202292
|
+
const transport = new StreamableHTTPTransport({
|
|
202293
|
+
sessionIdGenerator: () => randomUUID(),
|
|
202294
|
+
onsessioninitialized: (sid) => {
|
|
202295
|
+
transports.set(sid, transport);
|
|
202296
|
+
const store = authContext.getStore();
|
|
202297
|
+
sessionManager.createSession(sid, store?.authInfo.clientId, store?.authInfo.tenantId);
|
|
202298
|
+
logger.debug("New MCP session initialized", {
|
|
202299
|
+
...transportContext,
|
|
202300
|
+
sessionId: sid
|
|
202301
|
+
});
|
|
202302
|
+
},
|
|
202303
|
+
onsessionclosed: (sid) => {
|
|
202304
|
+
transports.delete(sid);
|
|
202305
|
+
sessionManager.terminateSession(sid);
|
|
202306
|
+
logger.debug("MCP session closed via transport", {
|
|
202307
|
+
...transportContext,
|
|
202308
|
+
sessionId: sid
|
|
202309
|
+
});
|
|
202310
|
+
}
|
|
202274
202311
|
});
|
|
202275
|
-
|
|
202276
|
-
sessionManager.touchSession(sessionId);
|
|
202277
|
-
}
|
|
202278
|
-
const transport = new McpSessionTransport(sessionId);
|
|
202279
|
-
const handleRpc = async () => {
|
|
202280
|
-
await mcpServer.connect(transport);
|
|
202312
|
+
await server.connect(transport);
|
|
202281
202313
|
const response = await transport.handleRequest(c);
|
|
202282
|
-
|
|
202283
|
-
const store = authContext.getStore();
|
|
202284
|
-
sessionManager.createSession(sessionId, store?.authInfo.clientId, store?.authInfo.tenantId);
|
|
202285
|
-
}
|
|
202286
|
-
if (response) {
|
|
202287
|
-
return response;
|
|
202288
|
-
}
|
|
202289
|
-
return c.body(null, 204);
|
|
202314
|
+
return response ?? c.body(null, 204);
|
|
202290
202315
|
};
|
|
202291
202316
|
try {
|
|
202292
202317
|
const store = authContext.getStore();
|
|
202293
202318
|
if (store) {
|
|
202294
|
-
return await authContext.run(store,
|
|
202319
|
+
return await authContext.run(store, handleRequest);
|
|
202295
202320
|
}
|
|
202296
|
-
return await
|
|
202321
|
+
return await handleRequest();
|
|
202297
202322
|
} catch (err) {
|
|
202298
|
-
await transport.close?.().catch((closeErr) => {
|
|
202299
|
-
logger.warning("Failed to close transport after error", {
|
|
202300
|
-
...transportContext,
|
|
202301
|
-
sessionId,
|
|
202302
|
-
error: closeErr instanceof Error ? closeErr.message : String(closeErr)
|
|
202303
|
-
});
|
|
202304
|
-
});
|
|
202305
202323
|
throw err instanceof Error ? err : new Error(String(err));
|
|
202306
202324
|
}
|
|
202307
202325
|
});
|
|
@@ -202360,13 +202378,13 @@ function startHttpServerWithRetry(app, initialPort, host, maxRetries, parentCont
|
|
|
202360
202378
|
tryBind(initialPort, 1);
|
|
202361
202379
|
});
|
|
202362
202380
|
}
|
|
202363
|
-
async function startHttpTransport(
|
|
202381
|
+
async function startHttpTransport(createMcpServer, parentContext) {
|
|
202364
202382
|
const transportContext = {
|
|
202365
202383
|
...parentContext,
|
|
202366
202384
|
component: "HttpTransportStart"
|
|
202367
202385
|
};
|
|
202368
202386
|
logger.info("Starting HTTP transport.", transportContext);
|
|
202369
|
-
const app = createHttpApp(
|
|
202387
|
+
const app = createHttpApp(createMcpServer, transportContext);
|
|
202370
202388
|
const server = await startHttpServerWithRetry(app, config2.mcpHttpPort, config2.mcpHttpHost, config2.mcpHttpMaxPortRetries, transportContext);
|
|
202371
202389
|
logger.info("HTTP transport started successfully.", transportContext);
|
|
202372
202390
|
return server;
|
|
@@ -202543,10 +202561,10 @@ class TransportManager {
|
|
|
202543
202561
|
transport: this.config.mcpTransportType
|
|
202544
202562
|
});
|
|
202545
202563
|
this.logger.info(`Starting transport: ${this.config.mcpTransportType}`, context);
|
|
202546
|
-
const mcpServer = await this.createMcpServer();
|
|
202547
202564
|
if (this.config.mcpTransportType === "http") {
|
|
202548
|
-
this.serverInstance = await startHttpTransport(
|
|
202565
|
+
this.serverInstance = await startHttpTransport(this.createMcpServer, context);
|
|
202549
202566
|
} else if (this.config.mcpTransportType === "stdio") {
|
|
202567
|
+
const mcpServer = await this.createMcpServer();
|
|
202550
202568
|
this.serverInstance = await startStdioTransport(mcpServer, context);
|
|
202551
202569
|
} else {
|
|
202552
202570
|
const transportType = String(this.config.mcpTransportType);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/git-mcp-server",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.3",
|
|
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",
|