@cyanheads/git-mcp-server 2.8.2 → 2.8.4
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 +155 -134
- 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.4",
|
|
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",
|
|
@@ -169396,7 +169396,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
169396
169396
|
const schOrFunc = root.refs[ref];
|
|
169397
169397
|
if (schOrFunc)
|
|
169398
169398
|
return schOrFunc;
|
|
169399
|
-
let _sch =
|
|
169399
|
+
let _sch = resolve2.call(this, root, ref);
|
|
169400
169400
|
if (_sch === undefined) {
|
|
169401
169401
|
const schema2 = (_a4 = root.localRefs) === null || _a4 === undefined ? undefined : _a4[ref];
|
|
169402
169402
|
const { schemaId } = this.opts;
|
|
@@ -169423,7 +169423,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
169423
169423
|
function sameSchemaEnv(s1, s2) {
|
|
169424
169424
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
169425
169425
|
}
|
|
169426
|
-
function
|
|
169426
|
+
function resolve2(root, ref) {
|
|
169427
169427
|
let sch;
|
|
169428
169428
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
169429
169429
|
ref = sch;
|
|
@@ -169953,7 +169953,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
169953
169953
|
}
|
|
169954
169954
|
return uri;
|
|
169955
169955
|
}
|
|
169956
|
-
function
|
|
169956
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
169957
169957
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
169958
169958
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
169959
169959
|
schemelessOptions.skipEscape = true;
|
|
@@ -170181,7 +170181,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
170181
170181
|
var fastUri = {
|
|
170182
170182
|
SCHEMES,
|
|
170183
170183
|
normalize,
|
|
170184
|
-
resolve,
|
|
170184
|
+
resolve: resolve2,
|
|
170185
170185
|
resolveComponent,
|
|
170186
170186
|
equal,
|
|
170187
170187
|
serialize,
|
|
@@ -176024,9 +176024,12 @@ async function executeInit(options, context, execGit) {
|
|
|
176024
176024
|
}
|
|
176025
176025
|
}
|
|
176026
176026
|
// src/services/git/providers/cli/operations/core/clone.ts
|
|
176027
|
+
import { dirname, resolve } from "node:path";
|
|
176027
176028
|
async function executeClone(options, context, execGit) {
|
|
176028
176029
|
try {
|
|
176029
|
-
const
|
|
176030
|
+
const resolvedLocalPath = resolve(context.workingDirectory, options.localPath);
|
|
176031
|
+
const cloneCwd = dirname(resolvedLocalPath);
|
|
176032
|
+
const args = [options.remoteUrl, resolvedLocalPath];
|
|
176030
176033
|
if (options.branch) {
|
|
176031
176034
|
args.push("--branch", options.branch);
|
|
176032
176035
|
}
|
|
@@ -176043,7 +176046,7 @@ async function executeClone(options, context, execGit) {
|
|
|
176043
176046
|
args.push("--recurse-submodules");
|
|
176044
176047
|
}
|
|
176045
176048
|
const cmd = buildGitCommand({ command: "clone", args });
|
|
176046
|
-
await execGit(cmd,
|
|
176049
|
+
await execGit(cmd, cloneCwd, context.requestContext);
|
|
176047
176050
|
const result = {
|
|
176048
176051
|
success: true,
|
|
176049
176052
|
localPath: options.localPath,
|
|
@@ -177964,7 +177967,7 @@ var safeJSON = (text) => {
|
|
|
177964
177967
|
};
|
|
177965
177968
|
|
|
177966
177969
|
// node_modules/openai/internal/utils/sleep.mjs
|
|
177967
|
-
var sleep = (ms) => new Promise((
|
|
177970
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
177968
177971
|
|
|
177969
177972
|
// node_modules/openai/version.mjs
|
|
177970
177973
|
var VERSION = "6.21.0";
|
|
@@ -178968,8 +178971,8 @@ var _APIPromise_client;
|
|
|
178968
178971
|
|
|
178969
178972
|
class APIPromise extends Promise {
|
|
178970
178973
|
constructor(client, responsePromise, parseResponse = defaultParseResponse) {
|
|
178971
|
-
super((
|
|
178972
|
-
|
|
178974
|
+
super((resolve2) => {
|
|
178975
|
+
resolve2(null);
|
|
178973
178976
|
});
|
|
178974
178977
|
this.responsePromise = responsePromise;
|
|
178975
178978
|
this.parseResponse = parseResponse;
|
|
@@ -179487,12 +179490,12 @@ class EventStream {
|
|
|
179487
179490
|
_EventStream_errored.set(this, false);
|
|
179488
179491
|
_EventStream_aborted.set(this, false);
|
|
179489
179492
|
_EventStream_catchingPromiseCreated.set(this, false);
|
|
179490
|
-
__classPrivateFieldSet(this, _EventStream_connectedPromise, new Promise((
|
|
179491
|
-
__classPrivateFieldSet(this, _EventStream_resolveConnectedPromise,
|
|
179493
|
+
__classPrivateFieldSet(this, _EventStream_connectedPromise, new Promise((resolve2, reject) => {
|
|
179494
|
+
__classPrivateFieldSet(this, _EventStream_resolveConnectedPromise, resolve2, "f");
|
|
179492
179495
|
__classPrivateFieldSet(this, _EventStream_rejectConnectedPromise, reject, "f");
|
|
179493
179496
|
}), "f");
|
|
179494
|
-
__classPrivateFieldSet(this, _EventStream_endPromise, new Promise((
|
|
179495
|
-
__classPrivateFieldSet(this, _EventStream_resolveEndPromise,
|
|
179497
|
+
__classPrivateFieldSet(this, _EventStream_endPromise, new Promise((resolve2, reject) => {
|
|
179498
|
+
__classPrivateFieldSet(this, _EventStream_resolveEndPromise, resolve2, "f");
|
|
179496
179499
|
__classPrivateFieldSet(this, _EventStream_rejectEndPromise, reject, "f");
|
|
179497
179500
|
}), "f");
|
|
179498
179501
|
__classPrivateFieldGet(this, _EventStream_connectedPromise, "f").catch(() => {});
|
|
@@ -179544,11 +179547,11 @@ class EventStream {
|
|
|
179544
179547
|
return this;
|
|
179545
179548
|
}
|
|
179546
179549
|
emitted(event) {
|
|
179547
|
-
return new Promise((
|
|
179550
|
+
return new Promise((resolve2, reject) => {
|
|
179548
179551
|
__classPrivateFieldSet(this, _EventStream_catchingPromiseCreated, true, "f");
|
|
179549
179552
|
if (event !== "error")
|
|
179550
179553
|
this.once("error", reject);
|
|
179551
|
-
this.once(event,
|
|
179554
|
+
this.once(event, resolve2);
|
|
179552
179555
|
});
|
|
179553
179556
|
}
|
|
179554
179557
|
async done() {
|
|
@@ -180466,7 +180469,7 @@ class ChatCompletionStream extends AbstractChatCompletionRunner {
|
|
|
180466
180469
|
if (done) {
|
|
180467
180470
|
return { value: undefined, done: true };
|
|
180468
180471
|
}
|
|
180469
|
-
return new Promise((
|
|
180472
|
+
return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
|
|
180470
180473
|
}
|
|
180471
180474
|
const chunk = pushQueue.shift();
|
|
180472
180475
|
return { value: chunk, done: false };
|
|
@@ -181054,7 +181057,7 @@ class AssistantStream extends EventStream {
|
|
|
181054
181057
|
if (done) {
|
|
181055
181058
|
return { value: undefined, done: true };
|
|
181056
181059
|
}
|
|
181057
|
-
return new Promise((
|
|
181060
|
+
return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: undefined, done: true });
|
|
181058
181061
|
}
|
|
181059
181062
|
const chunk = pushQueue.shift();
|
|
181060
181063
|
return { value: chunk, done: false };
|
|
@@ -182485,7 +182488,7 @@ class ResponseStream extends EventStream {
|
|
|
182485
182488
|
if (done) {
|
|
182486
182489
|
return { value: undefined, done: true };
|
|
182487
182490
|
}
|
|
182488
|
-
return new Promise((
|
|
182491
|
+
return new Promise((resolve2, reject) => readQueue.push({ resolve: resolve2, reject })).then((event2) => event2 ? { value: event2, done: false } : { value: undefined, done: true });
|
|
182489
182492
|
}
|
|
182490
182493
|
const event = pushQueue.shift();
|
|
182491
182494
|
return { value: event, done: false };
|
|
@@ -191752,7 +191755,7 @@ class Protocol {
|
|
|
191752
191755
|
return;
|
|
191753
191756
|
}
|
|
191754
191757
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
|
|
191755
|
-
await new Promise((
|
|
191758
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
191756
191759
|
options?.signal?.throwIfAborted();
|
|
191757
191760
|
}
|
|
191758
191761
|
} catch (error49) {
|
|
@@ -191764,7 +191767,7 @@ class Protocol {
|
|
|
191764
191767
|
}
|
|
191765
191768
|
request(request, resultSchema, options) {
|
|
191766
191769
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
191767
|
-
return new Promise((
|
|
191770
|
+
return new Promise((resolve2, reject) => {
|
|
191768
191771
|
const earlyReject = (error49) => {
|
|
191769
191772
|
reject(error49);
|
|
191770
191773
|
};
|
|
@@ -191842,7 +191845,7 @@ class Protocol {
|
|
|
191842
191845
|
if (!parseResult.success) {
|
|
191843
191846
|
reject(parseResult.error);
|
|
191844
191847
|
} else {
|
|
191845
|
-
|
|
191848
|
+
resolve2(parseResult.data);
|
|
191846
191849
|
}
|
|
191847
191850
|
} catch (error49) {
|
|
191848
191851
|
reject(error49);
|
|
@@ -192033,12 +192036,12 @@ class Protocol {
|
|
|
192033
192036
|
interval = task.pollInterval;
|
|
192034
192037
|
}
|
|
192035
192038
|
} catch {}
|
|
192036
|
-
return new Promise((
|
|
192039
|
+
return new Promise((resolve2, reject) => {
|
|
192037
192040
|
if (signal.aborted) {
|
|
192038
192041
|
reject(new McpError2(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
192039
192042
|
return;
|
|
192040
192043
|
}
|
|
192041
|
-
const timeoutId = setTimeout(
|
|
192044
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
192042
192045
|
signal.addEventListener("abort", () => {
|
|
192043
192046
|
clearTimeout(timeoutId);
|
|
192044
192047
|
reject(new McpError2(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -193056,7 +193059,7 @@ class McpServer {
|
|
|
193056
193059
|
let task = createTaskResult.task;
|
|
193057
193060
|
const pollInterval = task.pollInterval ?? 5000;
|
|
193058
193061
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
193059
|
-
await new Promise((
|
|
193062
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
193060
193063
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
193061
193064
|
if (!updatedTask) {
|
|
193062
193065
|
throw new McpError2(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -194610,7 +194613,7 @@ async function gitCloneLogic(input, { provider, appContext }) {
|
|
|
194610
194613
|
cloneOptions.depth = input.depth;
|
|
194611
194614
|
}
|
|
194612
194615
|
const result = await provider.clone(cloneOptions, {
|
|
194613
|
-
workingDirectory:
|
|
194616
|
+
workingDirectory: process.cwd(),
|
|
194614
194617
|
requestContext: appContext,
|
|
194615
194618
|
tenantId: appContext.tenantId || "default-tenant"
|
|
194616
194619
|
});
|
|
@@ -199204,14 +199207,14 @@ var StreamableHTTPTransport = class {
|
|
|
199204
199207
|
if (!this.#enableJsonResponse && this.sessionId !== undefined)
|
|
199205
199208
|
ctx.header("mcp-session-id", this.sessionId);
|
|
199206
199209
|
if (this.#enableJsonResponse)
|
|
199207
|
-
return await new Promise((
|
|
199210
|
+
return await new Promise((resolve2) => {
|
|
199208
199211
|
for (const message of messages)
|
|
199209
199212
|
if (isJSONRPCRequest(message)) {
|
|
199210
199213
|
this.#streamMapping.set(streamId, {
|
|
199211
199214
|
ctx: {
|
|
199212
199215
|
header: ctx.header,
|
|
199213
199216
|
json: (data) => {
|
|
199214
|
-
|
|
199217
|
+
resolve2(ctx.json(data));
|
|
199215
199218
|
}
|
|
199216
199219
|
},
|
|
199217
199220
|
cleanup: () => {
|
|
@@ -199902,7 +199905,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
199902
199905
|
});
|
|
199903
199906
|
if (!chunk) {
|
|
199904
199907
|
if (i2 === 1) {
|
|
199905
|
-
await new Promise((
|
|
199908
|
+
await new Promise((resolve2) => setTimeout(resolve2));
|
|
199906
199909
|
maxReadCount = 3;
|
|
199907
199910
|
continue;
|
|
199908
199911
|
}
|
|
@@ -201975,6 +201978,7 @@ class SessionManager {
|
|
|
201975
201978
|
cleanupIntervalId = null;
|
|
201976
201979
|
staleTimeoutMs;
|
|
201977
201980
|
cleanupIntervalMs;
|
|
201981
|
+
onSessionExpired = null;
|
|
201978
201982
|
constructor(staleTimeoutMs = 30 * 60 * 1000, cleanupIntervalMs = 5 * 60 * 1000) {
|
|
201979
201983
|
this.staleTimeoutMs = staleTimeoutMs;
|
|
201980
201984
|
this.cleanupIntervalMs = cleanupIntervalMs;
|
|
@@ -202030,6 +202034,7 @@ class SessionManager {
|
|
|
202030
202034
|
staleTimeoutMs: this.staleTimeoutMs
|
|
202031
202035
|
});
|
|
202032
202036
|
this.sessions.delete(sessionId);
|
|
202037
|
+
this.onSessionExpired?.(sessionId);
|
|
202033
202038
|
return false;
|
|
202034
202039
|
}
|
|
202035
202040
|
return true;
|
|
@@ -202097,6 +202102,7 @@ class SessionManager {
|
|
|
202097
202102
|
const age = now2 - metadata.lastActivityAt;
|
|
202098
202103
|
if (age > this.staleTimeoutMs) {
|
|
202099
202104
|
this.sessions.delete(sessionId);
|
|
202105
|
+
this.onSessionExpired?.(sessionId);
|
|
202100
202106
|
removedCount++;
|
|
202101
202107
|
}
|
|
202102
202108
|
}
|
|
@@ -202125,21 +202131,27 @@ class SessionManager {
|
|
|
202125
202131
|
|
|
202126
202132
|
// src/mcp-server/transports/http/httpTransport.ts
|
|
202127
202133
|
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) {
|
|
202134
|
+
function createHttpApp(createMcpServer, parentContext) {
|
|
202137
202135
|
const app = new Hono2;
|
|
202138
202136
|
const transportContext = {
|
|
202139
202137
|
...parentContext,
|
|
202140
202138
|
component: "HttpTransportSetup"
|
|
202141
202139
|
};
|
|
202140
|
+
const transports = new Map;
|
|
202142
202141
|
const sessionManager = SessionManager.getInstance(config2.mcpStatefulSessionStaleTimeoutMs);
|
|
202142
|
+
sessionManager.onSessionExpired = (sessionId) => {
|
|
202143
|
+
const transport = transports.get(sessionId);
|
|
202144
|
+
if (transport) {
|
|
202145
|
+
transport.close().catch((err) => {
|
|
202146
|
+
logger.warning("Failed to close transport for expired session", {
|
|
202147
|
+
...transportContext,
|
|
202148
|
+
sessionId,
|
|
202149
|
+
error: err instanceof Error ? err.message : String(err)
|
|
202150
|
+
});
|
|
202151
|
+
});
|
|
202152
|
+
transports.delete(sessionId);
|
|
202153
|
+
}
|
|
202154
|
+
};
|
|
202143
202155
|
logger.info("Session manager initialized", {
|
|
202144
202156
|
...transportContext,
|
|
202145
202157
|
staleTimeoutMs: config2.mcpStatefulSessionStaleTimeoutMs
|
|
@@ -202175,18 +202187,6 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202175
202187
|
...config2.oauthJwksUri && { jwks_uri: config2.oauthJwksUri }
|
|
202176
202188
|
});
|
|
202177
202189
|
});
|
|
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
202190
|
const authStrategy = createAuthStrategy();
|
|
202191
202191
|
if (authStrategy) {
|
|
202192
202192
|
const authMiddleware = createAuthMiddleware(authStrategy);
|
|
@@ -202195,113 +202195,134 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202195
202195
|
} else {
|
|
202196
202196
|
logger.info("Authentication is disabled; MCP endpoint is unprotected.", transportContext);
|
|
202197
202197
|
}
|
|
202198
|
-
|
|
202199
|
-
|
|
202200
|
-
|
|
202201
|
-
|
|
202198
|
+
const getSessionTransport = (sessionId) => {
|
|
202199
|
+
if (!sessionManager.isSessionValid(sessionId)) {
|
|
202200
|
+
const stale = transports.get(sessionId);
|
|
202201
|
+
if (stale) {
|
|
202202
|
+
stale.close().catch(() => {});
|
|
202203
|
+
transports.delete(sessionId);
|
|
202204
|
+
}
|
|
202205
|
+
return Response.json({
|
|
202202
202206
|
jsonrpc: "2.0",
|
|
202203
202207
|
error: {
|
|
202204
|
-
code: -
|
|
202205
|
-
message: "
|
|
202208
|
+
code: -32001,
|
|
202209
|
+
message: "Session expired or invalid. Please reinitialize."
|
|
202206
202210
|
},
|
|
202207
202211
|
id: null
|
|
202208
|
-
},
|
|
202212
|
+
}, { status: 404 });
|
|
202209
202213
|
}
|
|
202210
|
-
const
|
|
202211
|
-
if (!
|
|
202212
|
-
return
|
|
202214
|
+
const transport = transports.get(sessionId);
|
|
202215
|
+
if (!transport) {
|
|
202216
|
+
return Response.json({
|
|
202213
202217
|
jsonrpc: "2.0",
|
|
202214
202218
|
error: {
|
|
202215
202219
|
code: -32001,
|
|
202216
|
-
message: "Session not found
|
|
202220
|
+
message: "Session not found. Please reinitialize."
|
|
202217
202221
|
},
|
|
202218
202222
|
id: null
|
|
202219
|
-
}, 404);
|
|
202223
|
+
}, { status: 404 });
|
|
202220
202224
|
}
|
|
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
|
|
202225
|
+
sessionManager.touchSession(sessionId);
|
|
202226
|
+
return transport;
|
|
202227
|
+
};
|
|
202228
|
+
app.get(config2.mcpHttpEndpointPath, async (c) => {
|
|
202229
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
202230
|
+
if (!sessionId) {
|
|
202231
|
+
return c.json({
|
|
202232
|
+
status: "ok",
|
|
202233
|
+
server: {
|
|
202234
|
+
name: config2.mcpServerName,
|
|
202235
|
+
version: config2.mcpServerVersion,
|
|
202236
|
+
description: config2.mcpServerDescription,
|
|
202237
|
+
transport: config2.mcpTransportType,
|
|
202238
|
+
sessionMode: config2.mcpSessionMode
|
|
202239
|
+
}
|
|
202241
202240
|
});
|
|
202241
|
+
}
|
|
202242
|
+
const transportOrError = getSessionTransport(sessionId);
|
|
202243
|
+
if (transportOrError instanceof Response)
|
|
202244
|
+
return transportOrError;
|
|
202245
|
+
const response = await transportOrError.handleRequest(c);
|
|
202246
|
+
return response ?? c.body(null, 204);
|
|
202247
|
+
});
|
|
202248
|
+
app.delete(config2.mcpHttpEndpointPath, async (c) => {
|
|
202249
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
202250
|
+
if (!sessionId) {
|
|
202242
202251
|
return c.json({
|
|
202243
202252
|
jsonrpc: "2.0",
|
|
202244
202253
|
error: {
|
|
202245
202254
|
code: -32600,
|
|
202246
|
-
message:
|
|
202247
|
-
data: {
|
|
202248
|
-
requested: protocolVersion,
|
|
202249
|
-
supported: supportedVersions
|
|
202250
|
-
}
|
|
202255
|
+
message: "Mcp-Session-Id header required for DELETE"
|
|
202251
202256
|
},
|
|
202252
202257
|
id: null
|
|
202253
202258
|
}, 400);
|
|
202254
202259
|
}
|
|
202255
|
-
const
|
|
202256
|
-
if (
|
|
202257
|
-
logger.warning("Invalid or expired session ID", {
|
|
202258
|
-
...transportContext,
|
|
202259
|
-
sessionId
|
|
202260
|
-
});
|
|
202260
|
+
const transport = transports.get(sessionId);
|
|
202261
|
+
if (!transport) {
|
|
202261
202262
|
return c.json({
|
|
202262
202263
|
jsonrpc: "2.0",
|
|
202263
202264
|
error: {
|
|
202264
202265
|
code: -32001,
|
|
202265
|
-
message: "Session
|
|
202266
|
+
message: "Session not found or already expired"
|
|
202266
202267
|
},
|
|
202267
202268
|
id: null
|
|
202268
202269
|
}, 404);
|
|
202269
202270
|
}
|
|
202270
|
-
|
|
202271
|
-
|
|
202272
|
-
|
|
202273
|
-
|
|
202271
|
+
const response = await transport.handleRequest(c);
|
|
202272
|
+
transports.delete(sessionId);
|
|
202273
|
+
sessionManager.terminateSession(sessionId);
|
|
202274
|
+
logger.info("Session terminated via DELETE", {
|
|
202275
|
+
...transportContext,
|
|
202276
|
+
sessionId
|
|
202277
|
+
});
|
|
202278
|
+
return response ?? c.body(null, 204);
|
|
202279
|
+
});
|
|
202280
|
+
app.post(config2.mcpHttpEndpointPath, async (c) => {
|
|
202281
|
+
logger.debug("Handling MCP POST request.", {
|
|
202282
|
+
...transportContext,
|
|
202283
|
+
path: c.req.path
|
|
202284
|
+
});
|
|
202285
|
+
const sessionId = c.req.header("mcp-session-id");
|
|
202286
|
+
const handleRequest = async () => {
|
|
202287
|
+
if (sessionId) {
|
|
202288
|
+
const transportOrError = getSessionTransport(sessionId);
|
|
202289
|
+
if (transportOrError instanceof Response)
|
|
202290
|
+
return transportOrError;
|
|
202291
|
+
const response2 = await transportOrError.handleRequest(c);
|
|
202292
|
+
return response2 ?? c.body(null, 204);
|
|
202293
|
+
}
|
|
202294
|
+
const server = await createMcpServer();
|
|
202295
|
+
const transport = new StreamableHTTPTransport({
|
|
202296
|
+
sessionIdGenerator: () => randomUUID(),
|
|
202297
|
+
onsessioninitialized: (sid) => {
|
|
202298
|
+
transports.set(sid, transport);
|
|
202299
|
+
const store = authContext.getStore();
|
|
202300
|
+
sessionManager.createSession(sid, store?.authInfo.clientId, store?.authInfo.tenantId);
|
|
202301
|
+
logger.debug("New MCP session initialized", {
|
|
202302
|
+
...transportContext,
|
|
202303
|
+
sessionId: sid
|
|
202304
|
+
});
|
|
202305
|
+
},
|
|
202306
|
+
onsessionclosed: (sid) => {
|
|
202307
|
+
transports.delete(sid);
|
|
202308
|
+
sessionManager.terminateSession(sid);
|
|
202309
|
+
logger.debug("MCP session closed via transport", {
|
|
202310
|
+
...transportContext,
|
|
202311
|
+
sessionId: sid
|
|
202312
|
+
});
|
|
202313
|
+
}
|
|
202274
202314
|
});
|
|
202275
|
-
|
|
202276
|
-
sessionManager.touchSession(sessionId);
|
|
202277
|
-
}
|
|
202278
|
-
const transport = new McpSessionTransport(sessionId);
|
|
202279
|
-
const handleRpc = async () => {
|
|
202280
|
-
await mcpServer.connect(transport);
|
|
202315
|
+
await server.connect(transport);
|
|
202281
202316
|
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);
|
|
202317
|
+
return response ?? c.body(null, 204);
|
|
202290
202318
|
};
|
|
202291
202319
|
try {
|
|
202292
202320
|
const store = authContext.getStore();
|
|
202293
202321
|
if (store) {
|
|
202294
|
-
return await authContext.run(store,
|
|
202322
|
+
return await authContext.run(store, handleRequest);
|
|
202295
202323
|
}
|
|
202296
|
-
return await
|
|
202324
|
+
return await handleRequest();
|
|
202297
202325
|
} 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
202326
|
throw err instanceof Error ? err : new Error(String(err));
|
|
202306
202327
|
}
|
|
202307
202328
|
});
|
|
@@ -202311,9 +202332,9 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202311
202332
|
async function isPortInUse(port, host, parentContext) {
|
|
202312
202333
|
const context = { ...parentContext, operation: "isPortInUse", port, host };
|
|
202313
202334
|
logger.debug(`Checking if port ${port} is in use...`, context);
|
|
202314
|
-
return new Promise((
|
|
202335
|
+
return new Promise((resolve2) => {
|
|
202315
202336
|
const tempServer = http.createServer();
|
|
202316
|
-
tempServer.once("error", (err) =>
|
|
202337
|
+
tempServer.once("error", (err) => resolve2(err.code === "EADDRINUSE")).once("listening", () => tempServer.close(() => resolve2(false))).listen(port, host);
|
|
202317
202338
|
});
|
|
202318
202339
|
}
|
|
202319
202340
|
function startHttpServerWithRetry(app, initialPort, host, maxRetries, parentContext) {
|
|
@@ -202322,7 +202343,7 @@ function startHttpServerWithRetry(app, initialPort, host, maxRetries, parentCont
|
|
|
202322
202343
|
operation: "startHttpServerWithRetry"
|
|
202323
202344
|
};
|
|
202324
202345
|
logger.info(`Attempting to start HTTP server on port ${initialPort} with ${maxRetries} retries.`, startContext);
|
|
202325
|
-
return new Promise((
|
|
202346
|
+
return new Promise((resolve2, reject) => {
|
|
202326
202347
|
const tryBind = (port, attempt) => {
|
|
202327
202348
|
if (attempt > maxRetries + 1) {
|
|
202328
202349
|
const error49 = new Error(`Failed to bind to any port after ${maxRetries} retries.`);
|
|
@@ -202350,7 +202371,7 @@ function startHttpServerWithRetry(app, initialPort, host, maxRetries, parentCont
|
|
|
202350
202371
|
logStartupBanner(`
|
|
202351
202372
|
\uD83D\uDE80 MCP Server running at: ${serverAddress}`, "http");
|
|
202352
202373
|
});
|
|
202353
|
-
|
|
202374
|
+
resolve2(serverInstance);
|
|
202354
202375
|
} catch (err) {
|
|
202355
202376
|
logger.warning(`Binding attempt failed for port ${port}, retrying...`, { ...startContext, port, attempt, error: String(err) });
|
|
202356
202377
|
setTimeout(() => tryBind(port + 1, attempt + 1), config2.mcpHttpPortRetryDelayMs);
|
|
@@ -202360,13 +202381,13 @@ function startHttpServerWithRetry(app, initialPort, host, maxRetries, parentCont
|
|
|
202360
202381
|
tryBind(initialPort, 1);
|
|
202361
202382
|
});
|
|
202362
202383
|
}
|
|
202363
|
-
async function startHttpTransport(
|
|
202384
|
+
async function startHttpTransport(createMcpServer, parentContext) {
|
|
202364
202385
|
const transportContext = {
|
|
202365
202386
|
...parentContext,
|
|
202366
202387
|
component: "HttpTransportStart"
|
|
202367
202388
|
};
|
|
202368
202389
|
logger.info("Starting HTTP transport.", transportContext);
|
|
202369
|
-
const app = createHttpApp(
|
|
202390
|
+
const app = createHttpApp(createMcpServer, transportContext);
|
|
202370
202391
|
const server = await startHttpServerWithRetry(app, config2.mcpHttpPort, config2.mcpHttpHost, config2.mcpHttpMaxPortRetries, transportContext);
|
|
202371
202392
|
logger.info("HTTP transport started successfully.", transportContext);
|
|
202372
202393
|
return server;
|
|
@@ -202381,14 +202402,14 @@ async function stopHttpTransport(server, parentContext) {
|
|
|
202381
202402
|
const sessionManager = SessionManager.getInstance();
|
|
202382
202403
|
sessionManager.stopCleanupInterval();
|
|
202383
202404
|
logger.info("Session cleanup interval stopped", operationContext);
|
|
202384
|
-
return new Promise((
|
|
202405
|
+
return new Promise((resolve2, reject) => {
|
|
202385
202406
|
server.close((err) => {
|
|
202386
202407
|
if (err) {
|
|
202387
202408
|
logger.error("Error closing HTTP server.", err, operationContext);
|
|
202388
202409
|
return reject(err);
|
|
202389
202410
|
}
|
|
202390
202411
|
logger.info("HTTP server closed successfully.", operationContext);
|
|
202391
|
-
|
|
202412
|
+
resolve2();
|
|
202392
202413
|
});
|
|
202393
202414
|
});
|
|
202394
202415
|
}
|
|
@@ -202473,12 +202494,12 @@ class StdioServerTransport {
|
|
|
202473
202494
|
this.onclose?.();
|
|
202474
202495
|
}
|
|
202475
202496
|
send(message2) {
|
|
202476
|
-
return new Promise((
|
|
202497
|
+
return new Promise((resolve2) => {
|
|
202477
202498
|
const json3 = serializeMessage(message2);
|
|
202478
202499
|
if (this._stdout.write(json3)) {
|
|
202479
|
-
|
|
202500
|
+
resolve2();
|
|
202480
202501
|
} else {
|
|
202481
|
-
this._stdout.once("drain",
|
|
202502
|
+
this._stdout.once("drain", resolve2);
|
|
202482
202503
|
}
|
|
202483
202504
|
});
|
|
202484
202505
|
}
|
|
@@ -202543,10 +202564,10 @@ class TransportManager {
|
|
|
202543
202564
|
transport: this.config.mcpTransportType
|
|
202544
202565
|
});
|
|
202545
202566
|
this.logger.info(`Starting transport: ${this.config.mcpTransportType}`, context);
|
|
202546
|
-
const mcpServer = await this.createMcpServer();
|
|
202547
202567
|
if (this.config.mcpTransportType === "http") {
|
|
202548
|
-
this.serverInstance = await startHttpTransport(
|
|
202568
|
+
this.serverInstance = await startHttpTransport(this.createMcpServer, context);
|
|
202549
202569
|
} else if (this.config.mcpTransportType === "stdio") {
|
|
202570
|
+
const mcpServer = await this.createMcpServer();
|
|
202550
202571
|
this.serverInstance = await startStdioTransport(mcpServer, context);
|
|
202551
202572
|
} else {
|
|
202552
202573
|
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.4",
|
|
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",
|