@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +115 -97
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-2.8.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.26.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Status](https://img.shields.io/badge/Status-Stable-brightgreen.svg?style=flat-square)](https://github.com/cyanheads/git-mcp-server/issues) [![TypeScript](https://img.shields.io/badge/TypeScript-^5.9.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.2.21-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-2.8.3-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.26.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Status](https://img.shields.io/badge/Status-Stable-brightgreen.svg?style=flat-square)](https://github.com/cyanheads/git-mcp-server/issues) [![TypeScript](https://img.shields.io/badge/TypeScript-^5.9.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.2.21-blueviolet.svg?style=flat-square)](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.2",
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
- app.delete(config2.mcpHttpEndpointPath, (c) => {
202199
- const sessionId = c.req.header("mcp-session-id");
202200
- if (!sessionId) {
202201
- return c.json({
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: -32600,
202205
- message: "Mcp-Session-Id header required for DELETE"
202205
+ code: -32001,
202206
+ message: "Session expired or invalid. Please reinitialize."
202206
202207
  },
202207
202208
  id: null
202208
- }, 400);
202209
+ }, { status: 404 });
202209
202210
  }
202210
- const terminated = sessionManager.terminateSession(sessionId);
202211
- if (!terminated) {
202212
- return c.json({
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 or already expired"
202217
+ message: "Session not found. Please reinitialize."
202217
202218
  },
202218
202219
  id: null
202219
- }, 404);
202220
+ }, { status: 404 });
202220
202221
  }
202221
- logger.info("Session terminated via DELETE", {
202222
- ...transportContext,
202223
- sessionId
202224
- });
202225
- return c.body(null, 204);
202226
- });
202227
- app.all(config2.mcpHttpEndpointPath, async (c) => {
202228
- const protocolVersion = c.req.header("mcp-protocol-version") ?? "2025-03-26";
202229
- logger.debug("Handling MCP request.", {
202230
- ...transportContext,
202231
- path: c.req.path,
202232
- method: c.req.method,
202233
- protocolVersion
202234
- });
202235
- const supportedVersions = ["2025-03-26", "2025-06-18"];
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: `Unsupported MCP protocol version: ${protocolVersion}`,
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 sessionId = c.req.header("mcp-session-id") ?? randomUUID();
202256
- if (c.req.header("mcp-session-id") && !sessionManager.isSessionValid(sessionId)) {
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 expired or invalid. Please reinitialize."
202263
+ message: "Session not found or already expired"
202266
202264
  },
202267
202265
  id: null
202268
202266
  }, 404);
202269
202267
  }
202270
- if (!c.req.header("mcp-session-id")) {
202271
- logger.debug("New session will be created", {
202272
- ...transportContext,
202273
- sessionId
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
- } else {
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
- if (response && !c.req.header("mcp-session-id")) {
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, handleRpc);
202319
+ return await authContext.run(store, handleRequest);
202295
202320
  }
202296
- return await handleRpc();
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(mcpServer, parentContext) {
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(mcpServer, transportContext);
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(mcpServer, context);
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.2",
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",