@taptap/instant-games-open-mcp 1.24.11 → 1.24.13

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/proxy.js CHANGED
@@ -28925,10 +28925,13 @@ var CookieJar = class {
28925
28925
  return this.cookies.size > 0;
28926
28926
  }
28927
28927
  };
28928
- function createCookieFetch(cookieJar) {
28928
+ function createCookieFetch(cookieJar, options = {}) {
28929
28929
  return async (input, init) => {
28930
+ if (options.rejectStandaloneSse && isStandaloneSseRequest(input, init)) {
28931
+ return new Response(null, { status: 405, statusText: "Method Not Allowed" });
28932
+ }
28930
28933
  const modifiedInit = { ...init };
28931
- const cookieHeader = cookieJar.getCookieHeader();
28934
+ const cookieHeader = options.enableCookies === false ? void 0 : cookieJar.getCookieHeader();
28932
28935
  if (cookieHeader) {
28933
28936
  const headers = new Headers(modifiedInit.headers);
28934
28937
  headers.set("Cookie", cookieHeader);
@@ -28939,6 +28942,17 @@ function createCookieFetch(cookieJar) {
28939
28942
  return response;
28940
28943
  };
28941
28944
  }
28945
+ function isStandaloneSseRequest(input, init) {
28946
+ var _a3;
28947
+ const method = ((init == null ? void 0 : init.method) || (input instanceof Request ? input.method : "GET")).toUpperCase();
28948
+ if (method !== "GET") {
28949
+ return false;
28950
+ }
28951
+ const headers = new Headers(
28952
+ (init == null ? void 0 : init.headers) || (input instanceof Request ? input.headers : void 0)
28953
+ );
28954
+ return ((_a3 = headers.get("accept")) == null ? void 0 : _a3.split(",").some((value) => value.trim().split(";", 1)[0].toLowerCase() === "text/event-stream")) === true;
28955
+ }
28942
28956
 
28943
28957
  // src/core/utils/logWriter.ts
28944
28958
  import * as fs from "node:fs";
@@ -29136,9 +29150,224 @@ var LogWriter = class {
29136
29150
  }
29137
29151
  };
29138
29152
 
29153
+ // src/mcp-proxy/config.ts
29154
+ var DEFAULT_TOOL_CALL_TIMEOUT_MS = 60 * 60 * 1e3;
29155
+ function readStdin() {
29156
+ return new Promise((resolve, reject) => {
29157
+ const chunks = [];
29158
+ process.stdin.on("data", (chunk) => {
29159
+ chunks.push(chunk);
29160
+ });
29161
+ process.stdin.on("end", () => {
29162
+ resolve(Buffer.concat(chunks).toString("utf-8"));
29163
+ });
29164
+ process.stdin.on("error", reject);
29165
+ setTimeout(() => {
29166
+ reject(new Error("Timeout reading from stdin"));
29167
+ }, 5e3);
29168
+ });
29169
+ }
29170
+ async function loadConfig() {
29171
+ let configJson;
29172
+ let source;
29173
+ if (process.argv[2]) {
29174
+ configJson = process.argv[2];
29175
+ source = "command line argument";
29176
+ } else if (process.env.PROXY_CONFIG) {
29177
+ configJson = process.env.PROXY_CONFIG;
29178
+ source = "PROXY_CONFIG environment variable";
29179
+ } else if (!process.stdin.isTTY) {
29180
+ configJson = await readStdin();
29181
+ source = "stdin";
29182
+ } else {
29183
+ throw new Error(
29184
+ `No configuration provided. Please use one of:
29185
+ 1. Command line: node index.js '{"server":{...}}'
29186
+ 2. Env var: PROXY_CONFIG='{"server":{...}}' node index.js
29187
+ 3. Stdin: echo '{"server":{...}}' | node index.js
29188
+
29189
+ See config.example.json for configuration format.`
29190
+ );
29191
+ }
29192
+ console.error(`[Proxy] Loading config from ${source}`);
29193
+ let config2;
29194
+ try {
29195
+ config2 = JSON.parse(configJson);
29196
+ } catch (error2) {
29197
+ throw new Error(
29198
+ `Failed to parse configuration JSON: ${error2 instanceof Error ? error2.message : String(error2)}
29199
+ Received: ${configJson.substring(0, 100)}...`
29200
+ );
29201
+ }
29202
+ validateConfig(config2);
29203
+ return applyDefaults(config2);
29204
+ }
29205
+ function validateConfig(config2) {
29206
+ var _a3, _b;
29207
+ const errors = [];
29208
+ if (!config2.server) {
29209
+ errors.push("- Missing required field: server");
29210
+ } else {
29211
+ if (!config2.server.url) {
29212
+ errors.push("- Missing required field: server.url");
29213
+ }
29214
+ if (config2.server.env && !["rnd", "production"].includes(config2.server.env)) {
29215
+ errors.push('- Invalid server.env: must be "rnd" or "production"');
29216
+ }
29217
+ }
29218
+ if (!config2.tenant) {
29219
+ errors.push("- Missing required field: tenant");
29220
+ } else {
29221
+ if (config2.tenant.custom_fields !== void 0) {
29222
+ const cf = config2.tenant.custom_fields;
29223
+ if (typeof cf !== "object" || cf === null || Array.isArray(cf) || Object.getPrototypeOf(cf) !== Object.prototype) {
29224
+ errors.push("- tenant.custom_fields must be a plain object (Record<string, string>)");
29225
+ } else if (!Object.values(cf).every((v) => typeof v === "string")) {
29226
+ errors.push("- tenant.custom_fields values must all be strings");
29227
+ }
29228
+ }
29229
+ }
29230
+ if (!config2.auth) {
29231
+ errors.push("- Missing required field: auth");
29232
+ } else {
29233
+ if (!config2.auth.kid) {
29234
+ errors.push("- Missing required field: auth.kid");
29235
+ }
29236
+ if (!config2.auth.mac_key) {
29237
+ errors.push("- Missing required field: auth.mac_key");
29238
+ }
29239
+ if (config2.auth.token_type !== "mac") {
29240
+ errors.push('- Invalid auth.token_type: must be "mac"');
29241
+ }
29242
+ if (config2.auth.mac_algorithm !== "hmac-sha-1") {
29243
+ errors.push('- Invalid auth.mac_algorithm: must be "hmac-sha-1"');
29244
+ }
29245
+ }
29246
+ if (((_a3 = config2.options) == null ? void 0 : _a3.exposed_tools) !== void 0) {
29247
+ const exposedTools = config2.options.exposed_tools;
29248
+ if (!Array.isArray(exposedTools)) {
29249
+ errors.push("- options.exposed_tools must be an array of tool names");
29250
+ } else if (!exposedTools.every((toolName) => typeof toolName === "string" && toolName.trim().length > 0)) {
29251
+ errors.push("- options.exposed_tools values must be non-empty strings");
29252
+ }
29253
+ }
29254
+ if (((_b = config2.options) == null ? void 0 : _b.replayable_tools) !== void 0) {
29255
+ const replayableTools = config2.options.replayable_tools;
29256
+ if (!Array.isArray(replayableTools)) {
29257
+ errors.push("- options.replayable_tools must be an array of tool names");
29258
+ } else if (!replayableTools.every(
29259
+ (toolName) => typeof toolName === "string" && toolName.trim().length > 0
29260
+ )) {
29261
+ errors.push("- options.replayable_tools values must be non-empty strings");
29262
+ }
29263
+ }
29264
+ if (errors.length > 0) {
29265
+ throw new Error("Invalid configuration:\n" + errors.join("\n"));
29266
+ }
29267
+ }
29268
+ var DEFAULT_LOG_ROOT = "/tmp/taptap-mcp/logs";
29269
+ function applyDefaults(config2) {
29270
+ var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
29271
+ const verbose = ((_a3 = config2.options) == null ? void 0 : _a3.verbose) ?? false;
29272
+ return {
29273
+ server: {
29274
+ url: config2.server.url,
29275
+ env: config2.server.env || "rnd"
29276
+ },
29277
+ tenant: {
29278
+ project_path: config2.tenant.project_path || ".",
29279
+ user_id: config2.tenant.user_id,
29280
+ project_id: config2.tenant.project_id,
29281
+ custom_fields: config2.tenant.custom_fields
29282
+ },
29283
+ auth: config2.auth,
29284
+ options: {
29285
+ verbose,
29286
+ reconnect_interval: ((_b = config2.options) == null ? void 0 : _b.reconnect_interval) ?? 5e3,
29287
+ request_timeout: ((_c = config2.options) == null ? void 0 : _c.request_timeout) ?? 3e4,
29288
+ tool_call_timeout: ((_d = config2.options) == null ? void 0 : _d.tool_call_timeout) ?? DEFAULT_TOOL_CALL_TIMEOUT_MS,
29289
+ reset_timeout_on_progress: ((_e = config2.options) == null ? void 0 : _e.reset_timeout_on_progress) ?? true,
29290
+ health_check_interval: ((_f = config2.options) == null ? void 0 : _f.health_check_interval) ?? 3e4,
29291
+ enable_cookie_sticky: ((_g = config2.options) == null ? void 0 : _g.enable_cookie_sticky) ?? true,
29292
+ disable_standalone_sse: ((_h = config2.options) == null ? void 0 : _h.disable_standalone_sse) ?? false,
29293
+ inject_params_per_call: ((_i = config2.options) == null ? void 0 : _i.inject_params_per_call) ?? true,
29294
+ force_inject_progress_token: ((_j = config2.options) == null ? void 0 : _j.force_inject_progress_token) ?? false,
29295
+ exposed_tools: (_k = config2.options) == null ? void 0 : _k.exposed_tools,
29296
+ replayable_tools: (_l = config2.options) == null ? void 0 : _l.replayable_tools,
29297
+ log: {
29298
+ root: ((_n = (_m = config2.options) == null ? void 0 : _m.log) == null ? void 0 : _n.root) ?? DEFAULT_LOG_ROOT,
29299
+ enabled: ((_p = (_o = config2.options) == null ? void 0 : _o.log) == null ? void 0 : _p.enabled) ?? false,
29300
+ // verbose=true 时自动使用 debug 级别
29301
+ level: verbose ? "debug" : ((_r = (_q = config2.options) == null ? void 0 : _q.log) == null ? void 0 : _r.level) ?? "info",
29302
+ max_days: ((_t = (_s = config2.options) == null ? void 0 : _s.log) == null ? void 0 : _t.max_days) ?? 7
29303
+ }
29304
+ }
29305
+ };
29306
+ }
29307
+
29139
29308
  // src/mcp-proxy/proxy.ts
29140
- var VERSION = true ? "1.24.11" : "dev";
29309
+ var VERSION = true ? "1.24.13" : "dev";
29141
29310
  var LOCAL_PROXY_TAG = "local";
29311
+ var DEFAULT_RECONNECT_INTERVAL_MS = 5e3;
29312
+ var MAX_RECONNECT_INTERVAL_MS = 60 * 1e3;
29313
+ function createNonReplayableToolResult(name, executionState) {
29314
+ const text = executionState === "not_executed" ? `${name} was not sent because the upstream MCP connection is unavailable. It was not executed. Automatic retry is disabled; retry explicitly after the connection recovers.` : `${name} may have completed before the upstream MCP response was interrupted. Its execution state is unknown. Automatic retry is disabled; verify remote output, state, and usage before deciding whether to retry explicitly.`;
29315
+ return {
29316
+ isError: true,
29317
+ content: [{ type: "text", text }],
29318
+ structuredContent: {
29319
+ execution_state: executionState,
29320
+ automatic_retry: false
29321
+ }
29322
+ };
29323
+ }
29324
+ function convertMcpApplicationErrorToToolResult(error2) {
29325
+ if (!(error2 instanceof Error)) {
29326
+ return void 0;
29327
+ }
29328
+ const data = error2.data;
29329
+ if (!isRecord(data) || !("remote_result" in data) || data.remote_result === void 0) {
29330
+ return void 0;
29331
+ }
29332
+ return {
29333
+ isError: true,
29334
+ content: [
29335
+ {
29336
+ type: "text",
29337
+ text: [
29338
+ error2.message,
29339
+ "remote_result:",
29340
+ formatProxyDiagnosticValue(data.remote_result)
29341
+ ].join("\n")
29342
+ }
29343
+ ]
29344
+ };
29345
+ }
29346
+ function isRecord(value) {
29347
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29348
+ }
29349
+ function formatProxyDiagnosticValue(value) {
29350
+ if (typeof value === "string") {
29351
+ return value;
29352
+ }
29353
+ try {
29354
+ return JSON.stringify(value, null, 2) || String(value);
29355
+ } catch {
29356
+ return String(value);
29357
+ }
29358
+ }
29359
+ function isTransientHttpServerError(error2) {
29360
+ const code = error2.code;
29361
+ if (typeof code === "number" && code >= 500 && code < 600) {
29362
+ return true;
29363
+ }
29364
+ return /\bHTTP\s+5\d\d\b|bad gateway|service unavailable|gateway timeout/i.test(error2.message);
29365
+ }
29366
+ function isHttpClientError(error2) {
29367
+ const code = error2.code;
29368
+ const numericCode = typeof code === "number" ? code : typeof code === "string" && /^4\d\d$/.test(code) ? Number(code) : void 0;
29369
+ return numericCode !== void 0 && numericCode >= 400 && numericCode < 500 || /\bHTTP\s+4\d\d\b/i.test(error2.message);
29370
+ }
29142
29371
  var TapTapMCPProxy = class {
29143
29372
  constructor(config2) {
29144
29373
  this.connected = false;
@@ -29146,6 +29375,7 @@ var TapTapMCPProxy = class {
29146
29375
  this.reconnectTimer = null;
29147
29376
  this.healthCheckTimer = null;
29148
29377
  this.pendingRequests = [];
29378
+ this.reconnectDelayMs = DEFAULT_RECONNECT_INTERVAL_MS;
29149
29379
  // 会话验证相关
29150
29380
  this.sessionValidated = false;
29151
29381
  this.lastValidationTime = 0;
@@ -29154,15 +29384,15 @@ var TapTapMCPProxy = class {
29154
29384
  this.config = config2;
29155
29385
  this.cookieJar = new CookieJar(((_a3 = config2.options) == null ? void 0 : _a3.verbose) ?? false);
29156
29386
  this.logWriter = this.createLogWriter();
29157
- this.client = new Client(
29158
- { name: "taptap-proxy-client", version: "1.0.0" },
29159
- { capabilities: {} }
29160
- );
29387
+ this.client = this.createClient();
29161
29388
  this.server = new Server(
29162
29389
  { name: "taptap-proxy", version: "1.0.0" },
29163
- { capabilities: { tools: {}, resources: {} } }
29390
+ { capabilities: { tools: { listChanged: true }, resources: {} } }
29164
29391
  );
29165
29392
  }
29393
+ createClient() {
29394
+ return new Client({ name: "taptap-proxy-client", version: "1.0.0" }, { capabilities: {} });
29395
+ }
29166
29396
  /**
29167
29397
  * 创建文件日志写入器
29168
29398
  */
@@ -29201,6 +29431,12 @@ var TapTapMCPProxy = class {
29201
29431
  `;
29202
29432
  this.logWriter.writeSync(level, formattedMessage);
29203
29433
  }
29434
+ logLifecycleEvent(source, message) {
29435
+ try {
29436
+ this.log("info", `lifecycle:${source}: ${message}`);
29437
+ } catch {
29438
+ }
29439
+ }
29204
29440
  /**
29205
29441
  * 启动 Proxy
29206
29442
  */
@@ -29327,18 +29563,30 @@ var TapTapMCPProxy = class {
29327
29563
  throw new McpError(ErrorCode.MethodNotFound, `Tool is not exposed by this proxy: ${name}`);
29328
29564
  }
29329
29565
  }
29566
+ isToolReplayable(name) {
29567
+ var _a3;
29568
+ const replayableTools = (_a3 = this.config.options) == null ? void 0 : _a3.replayable_tools;
29569
+ return replayableTools === void 0 || replayableTools.includes(name);
29570
+ }
29330
29571
  /**
29331
29572
  * 连接到 TapTap MCP Server
29332
29573
  */
29333
29574
  async connectToServer() {
29334
- var _a3, _b;
29575
+ var _a3, _b, _c, _d, _e;
29335
29576
  this.log("info", `Connecting to ${this.config.server.url}...`);
29336
29577
  try {
29337
29578
  const cookieEnabled = ((_a3 = this.config.options) == null ? void 0 : _a3.enable_cookie_sticky) ?? true;
29338
- const customFetch = cookieEnabled ? createCookieFetch(this.cookieJar) : void 0;
29339
- if (cookieEnabled && ((_b = this.config.options) == null ? void 0 : _b.verbose)) {
29579
+ const standaloneSseDisabled = ((_b = this.config.options) == null ? void 0 : _b.disable_standalone_sse) ?? false;
29580
+ const customFetch = cookieEnabled || standaloneSseDisabled ? createCookieFetch(this.cookieJar, {
29581
+ enableCookies: cookieEnabled,
29582
+ rejectStandaloneSse: standaloneSseDisabled
29583
+ }) : void 0;
29584
+ if (cookieEnabled && ((_c = this.config.options) == null ? void 0 : _c.verbose)) {
29340
29585
  this.log("debug", "Cookie sticky session enabled");
29341
29586
  }
29587
+ if (standaloneSseDisabled && ((_d = this.config.options) == null ? void 0 : _d.verbose)) {
29588
+ this.log("debug", "Standalone SSE disabled; using POST response streams");
29589
+ }
29342
29590
  const sessionHeaders = this.buildSessionHeaders();
29343
29591
  const transport = new StreamableHTTPClientTransport(new URL(this.config.server.url), {
29344
29592
  fetch: customFetch,
@@ -29352,11 +29600,12 @@ var TapTapMCPProxy = class {
29352
29600
  this.connected = true;
29353
29601
  this.sessionValidated = true;
29354
29602
  this.lastValidationTime = Date.now();
29603
+ this.reconnectDelayMs = ((_e = this.config.options) == null ? void 0 : _e.reconnect_interval) ?? DEFAULT_RECONNECT_INTERVAL_MS;
29355
29604
  this.log("info", "✅ Connected and session validated");
29356
29605
  this.startHealthCheck();
29357
29606
  if (this.reconnecting) {
29358
- await this.notifyReconnected();
29359
29607
  await this.processPendingRequests();
29608
+ await this.notifyReconnected();
29360
29609
  this.reconnecting = false;
29361
29610
  }
29362
29611
  } catch (error2) {
@@ -29402,7 +29651,7 @@ var TapTapMCPProxy = class {
29402
29651
  * 启动定期健康检查
29403
29652
  */
29404
29653
  startHealthCheck() {
29405
- var _a3;
29654
+ var _a3, _b, _c;
29406
29655
  this.stopHealthCheck();
29407
29656
  const interval = ((_a3 = this.config.options) == null ? void 0 : _a3.health_check_interval) ?? this.SESSION_VALIDATION_INTERVAL;
29408
29657
  this.healthCheckTimer = setInterval(async () => {
@@ -29419,6 +29668,7 @@ var TapTapMCPProxy = class {
29419
29668
  }
29420
29669
  }
29421
29670
  }, interval);
29671
+ (_c = (_b = this.healthCheckTimer).unref) == null ? void 0 : _c.call(_b);
29422
29672
  this.log("debug", `Health check started (interval: ${interval}ms)`);
29423
29673
  }
29424
29674
  /**
@@ -29478,10 +29728,11 @@ var TapTapMCPProxy = class {
29478
29728
  * 重连到 TapTap Server
29479
29729
  */
29480
29730
  async reconnectToServer() {
29481
- var _a3, _b, _c;
29731
+ var _a3, _b;
29482
29732
  if (this.reconnecting) return;
29483
29733
  this.reconnecting = true;
29484
29734
  this.clearReconnectTimer();
29735
+ this.stopHealthCheck();
29485
29736
  const cookieEnabled = ((_a3 = this.config.options) == null ? void 0 : _a3.enable_cookie_sticky) ?? true;
29486
29737
  if (cookieEnabled && this.cookieJar.hasCookies) {
29487
29738
  this.cookieJar.clear();
@@ -29490,14 +29741,12 @@ var TapTapMCPProxy = class {
29490
29741
  }
29491
29742
  }
29492
29743
  try {
29493
- this.client = new Client(
29494
- { name: "taptap-proxy-client", version: "1.0.0" },
29495
- { capabilities: {} }
29496
- );
29744
+ await this.closeClientSafely("reconnect-old-client");
29745
+ this.client = this.createClient();
29497
29746
  await this.connectToServer();
29498
29747
  this.log("info", "✅ Reconnected successfully");
29499
29748
  } catch (error2) {
29500
- const interval = ((_c = this.config.options) == null ? void 0 : _c.reconnect_interval) ?? 5e3;
29749
+ const interval = this.reconnectDelayMs;
29501
29750
  this.log("error", `❌ Reconnect failed: ${this.formatError(error2)}`);
29502
29751
  this.log("info", `Will retry in ${interval / 1e3}s...`);
29503
29752
  this.reconnecting = false;
@@ -29508,12 +29757,18 @@ var TapTapMCPProxy = class {
29508
29757
  * 调度重连
29509
29758
  */
29510
29759
  scheduleReconnect() {
29511
- var _a3;
29760
+ var _a3, _b, _c;
29512
29761
  this.clearReconnectTimer();
29513
- const interval = ((_a3 = this.config.options) == null ? void 0 : _a3.reconnect_interval) ?? 5e3;
29762
+ const configuredInterval = ((_a3 = this.config.options) == null ? void 0 : _a3.reconnect_interval) ?? DEFAULT_RECONNECT_INTERVAL_MS;
29763
+ const interval = this.reconnectDelayMs || configuredInterval;
29514
29764
  this.reconnectTimer = setTimeout(() => {
29515
29765
  this.reconnectToServer();
29516
29766
  }, interval);
29767
+ (_c = (_b = this.reconnectTimer).unref) == null ? void 0 : _c.call(_b);
29768
+ this.reconnectDelayMs = Math.min(
29769
+ Math.max(interval * 2, configuredInterval),
29770
+ MAX_RECONNECT_INTERVAL_MS
29771
+ );
29517
29772
  }
29518
29773
  /**
29519
29774
  * 清除重连定时器
@@ -29538,12 +29793,32 @@ var TapTapMCPProxy = class {
29538
29793
  const req = this.pendingRequests.shift();
29539
29794
  req.reject(timeout);
29540
29795
  }
29796
+ void this.closeClientSafely("cleanup");
29797
+ }
29798
+ async closeClientSafely(source) {
29799
+ try {
29800
+ await this.client.close();
29801
+ } catch (error2) {
29802
+ this.log(
29803
+ "warning",
29804
+ `Failed to close proxy client during ${source}: ${this.formatError(error2)}`
29805
+ );
29806
+ }
29541
29807
  }
29542
29808
  /**
29543
29809
  * 检测是否是需要重连的错误(网络错误或会话无效)
29544
29810
  */
29545
29811
  isNetworkError(error2) {
29546
29812
  if (!(error2 instanceof Error)) return false;
29813
+ if (convertMcpApplicationErrorToToolResult(error2)) {
29814
+ return false;
29815
+ }
29816
+ if (isHttpClientError(error2)) {
29817
+ return false;
29818
+ }
29819
+ if (isTransientHttpServerError(error2)) {
29820
+ return true;
29821
+ }
29547
29822
  const errorMsg = error2.message.toLowerCase();
29548
29823
  const errorCode = error2.code;
29549
29824
  const networkErrorCodes = [
@@ -29567,6 +29842,15 @@ var TapTapMCPProxy = class {
29567
29842
  if (errorCode && networkErrorCodes.includes(errorCode)) {
29568
29843
  return true;
29569
29844
  }
29845
+ if (errorCode === ErrorCode.ConnectionClosed || errorCode === ErrorCode.RequestTimeout) {
29846
+ return true;
29847
+ }
29848
+ if (errorMsg.includes("not connected") || this.isSessionInvalidError(error2)) {
29849
+ return true;
29850
+ }
29851
+ if (this.isMcpApplicationError(error2)) {
29852
+ return false;
29853
+ }
29570
29854
  const networkErrorKeywords = [
29571
29855
  "fetch failed",
29572
29856
  "socket hang up",
@@ -29574,15 +29858,18 @@ var TapTapMCPProxy = class {
29574
29858
  "connection refused",
29575
29859
  "connection reset",
29576
29860
  "timeout",
29577
- "dns",
29578
- "not connected"
29579
- // 连接断开
29861
+ "dns"
29580
29862
  ];
29581
29863
  if (networkErrorKeywords.some((keyword) => errorMsg.includes(keyword))) {
29582
29864
  return true;
29583
29865
  }
29584
29866
  return this.isSessionInvalidError(error2);
29585
29867
  }
29868
+ isMcpApplicationError(error2) {
29869
+ const message = error2.message.toLowerCase();
29870
+ const code = error2.code;
29871
+ return typeof code === "number" && code < 0 || /\bmcp error -\d+/.test(message);
29872
+ }
29586
29873
  /**
29587
29874
  * 处理待处理的请求队列
29588
29875
  */
@@ -29593,6 +29880,10 @@ var TapTapMCPProxy = class {
29593
29880
  this.log("info", `Processing ${this.pendingRequests.length} pending requests...`);
29594
29881
  while (this.pendingRequests.length > 0) {
29595
29882
  const req = this.pendingRequests.shift();
29883
+ if (!this.isToolReplayable(req.name)) {
29884
+ req.resolve(createNonReplayableToolResult(req.name, req.executionState ?? "unknown"));
29885
+ continue;
29886
+ }
29596
29887
  if (now - req.timestamp > timeout) {
29597
29888
  req.reject(new Error("Request timeout while waiting for reconnection"));
29598
29889
  continue;
@@ -29606,13 +29897,24 @@ var TapTapMCPProxy = class {
29606
29897
  void 0,
29607
29898
  // resultSchema
29608
29899
  {
29609
- timeout: ((_b = this.config.options) == null ? void 0 : _b.tool_call_timeout) ?? 3e5,
29900
+ timeout: ((_b = this.config.options) == null ? void 0 : _b.tool_call_timeout) ?? DEFAULT_TOOL_CALL_TIMEOUT_MS,
29610
29901
  resetTimeoutOnProgress: ((_c = this.config.options) == null ? void 0 : _c.reset_timeout_on_progress) ?? true,
29611
29902
  onprogress: req.onprogress
29612
29903
  }
29613
29904
  );
29614
29905
  req.resolve(result);
29615
29906
  } catch (error2) {
29907
+ const toolErrorResult = convertMcpApplicationErrorToToolResult(error2);
29908
+ if (toolErrorResult) {
29909
+ req.resolve(toolErrorResult);
29910
+ continue;
29911
+ }
29912
+ if (this.isNetworkError(error2)) {
29913
+ this.connected = false;
29914
+ this.sessionValidated = false;
29915
+ this.pendingRequests.unshift(req);
29916
+ throw error2;
29917
+ }
29616
29918
  req.reject(error2 instanceof Error ? error2 : new Error(String(error2)));
29617
29919
  }
29618
29920
  }
@@ -29692,7 +29994,7 @@ var TapTapMCPProxy = class {
29692
29994
  }
29693
29995
  const progressToken = (_c = request.params._meta) == null ? void 0 : _c.progressToken;
29694
29996
  const callToolOptions = {
29695
- timeout: ((_d = this.config.options) == null ? void 0 : _d.tool_call_timeout) ?? 3e5,
29997
+ timeout: ((_d = this.config.options) == null ? void 0 : _d.tool_call_timeout) ?? DEFAULT_TOOL_CALL_TIMEOUT_MS,
29696
29998
  resetTimeoutOnProgress: ((_e = this.config.options) == null ? void 0 : _e.reset_timeout_on_progress) ?? true
29697
29999
  };
29698
30000
  const forceInjectProgressToken = ((_f = this.config.options) == null ? void 0 : _f.force_inject_progress_token) ?? false;
@@ -29715,6 +30017,9 @@ var TapTapMCPProxy = class {
29715
30017
  }
29716
30018
  }
29717
30019
  if (!this.connected) {
30020
+ if (!this.isToolReplayable(name)) {
30021
+ return createNonReplayableToolResult(name, "not_executed");
30022
+ }
29718
30023
  if (this.reconnecting) {
29719
30024
  this.log("info", `⏳ Queueing request: ${name} (reconnecting...)`);
29720
30025
  return new Promise((resolve, reject) => {
@@ -29724,6 +30029,7 @@ var TapTapMCPProxy = class {
29724
30029
  resolve,
29725
30030
  reject,
29726
30031
  timestamp: Date.now(),
30032
+ executionState: "not_executed",
29727
30033
  onprogress: callToolOptions.onprogress
29728
30034
  });
29729
30035
  });
@@ -29745,12 +30051,19 @@ var TapTapMCPProxy = class {
29745
30051
  );
29746
30052
  return result;
29747
30053
  } catch (error2) {
30054
+ const toolErrorResult = convertMcpApplicationErrorToToolResult(error2);
30055
+ if (toolErrorResult) {
30056
+ return toolErrorResult;
30057
+ }
29748
30058
  if (this.isNetworkError(error2)) {
29749
30059
  this.log("error", "❌ Network error detected, marking connection as lost");
29750
30060
  this.connected = false;
29751
30061
  if (!this.reconnecting) {
29752
30062
  this.log("info", "Triggering immediate reconnection...");
29753
30063
  this.reconnectToServer();
30064
+ if (!this.isToolReplayable(name)) {
30065
+ return createNonReplayableToolResult(name, "unknown");
30066
+ }
29754
30067
  this.log("info", `⏳ Queueing current request: ${name}`);
29755
30068
  return new Promise((resolve, reject) => {
29756
30069
  this.pendingRequests.push({
@@ -29759,10 +30072,14 @@ var TapTapMCPProxy = class {
29759
30072
  resolve,
29760
30073
  reject,
29761
30074
  timestamp: Date.now(),
30075
+ executionState: "unknown",
29762
30076
  onprogress: callToolOptions.onprogress
29763
30077
  });
29764
30078
  });
29765
30079
  }
30080
+ if (!this.isToolReplayable(name)) {
30081
+ return createNonReplayableToolResult(name, "unknown");
30082
+ }
29766
30083
  }
29767
30084
  throw error2;
29768
30085
  }
@@ -29770,147 +30087,176 @@ var TapTapMCPProxy = class {
29770
30087
  }
29771
30088
  };
29772
30089
 
29773
- // src/mcp-proxy/config.ts
29774
- function readStdin() {
29775
- return new Promise((resolve, reject) => {
29776
- const chunks = [];
29777
- process.stdin.on("data", (chunk) => {
29778
- chunks.push(chunk);
29779
- });
29780
- process.stdin.on("end", () => {
29781
- resolve(Buffer.concat(chunks).toString("utf-8"));
29782
- });
29783
- process.stdin.on("error", reject);
29784
- setTimeout(() => {
29785
- reject(new Error("Timeout reading from stdin"));
29786
- }, 5e3);
29787
- });
30090
+ // src/core/utils/processLifecycle.ts
30091
+ var DISCONNECTED_STDIO_CODES = /* @__PURE__ */ new Set([
30092
+ "EPIPE",
30093
+ "ERR_STREAM_DESTROYED",
30094
+ "ECONNRESET",
30095
+ "EIO",
30096
+ "ENXIO"
30097
+ ]);
30098
+ function isDisconnectedStdioError(error2) {
30099
+ if (!error2 || typeof error2 !== "object") {
30100
+ return false;
30101
+ }
30102
+ const code = error2.code;
30103
+ if (typeof code === "string" && DISCONNECTED_STDIO_CODES.has(code)) {
30104
+ return true;
30105
+ }
30106
+ const message = error2 instanceof Error ? error2.message.toLowerCase() : "";
30107
+ return message.includes("epipe") || message.includes("stream destroyed");
29788
30108
  }
29789
- async function loadConfig() {
29790
- let configJson;
29791
- let source;
29792
- if (process.argv[2]) {
29793
- configJson = process.argv[2];
29794
- source = "command line argument";
29795
- } else if (process.env.PROXY_CONFIG) {
29796
- configJson = process.env.PROXY_CONFIG;
29797
- source = "PROXY_CONFIG environment variable";
29798
- } else if (!process.stdin.isTTY) {
29799
- configJson = await readStdin();
29800
- source = "stdin";
29801
- } else {
29802
- throw new Error(
29803
- `No configuration provided. Please use one of:
29804
- 1. Command line: node index.js '{"server":{...}}'
29805
- 2. Env var: PROXY_CONFIG='{"server":{...}}' node index.js
29806
- 3. Stdin: echo '{"server":{...}}' | node index.js
29807
-
29808
- See config.example.json for configuration format.`
29809
- );
30109
+ function shouldExitForParentDeath(options) {
30110
+ if ((options.platform === "darwin" || options.platform === "linux") && options.initialPpid > 1 && options.currentPpid === 1) {
30111
+ return true;
29810
30112
  }
29811
- console.error(`[Proxy] Loading config from ${source}`);
29812
- let config2;
29813
30113
  try {
29814
- config2 = JSON.parse(configJson);
30114
+ options.probeParent();
30115
+ return false;
29815
30116
  } catch (error2) {
29816
- throw new Error(
29817
- `Failed to parse configuration JSON: ${error2 instanceof Error ? error2.message : String(error2)}
29818
- Received: ${configJson.substring(0, 100)}...`
30117
+ return Boolean(
30118
+ error2 && typeof error2 === "object" && error2.code === "ESRCH" && options.initialPpid > 1
29819
30119
  );
29820
30120
  }
29821
- validateConfig(config2);
29822
- return applyDefaults(config2);
29823
30121
  }
29824
- function validateConfig(config2) {
29825
- var _a3;
29826
- const errors = [];
29827
- if (!config2.server) {
29828
- errors.push("- Missing required field: server");
29829
- } else {
29830
- if (!config2.server.url) {
29831
- errors.push("- Missing required field: server.url");
29832
- }
29833
- if (config2.server.env && !["rnd", "production"].includes(config2.server.env)) {
29834
- errors.push('- Invalid server.env: must be "rnd" or "production"');
29835
- }
29836
- }
29837
- if (!config2.tenant) {
29838
- errors.push("- Missing required field: tenant");
29839
- } else {
29840
- if (config2.tenant.custom_fields !== void 0) {
29841
- const cf = config2.tenant.custom_fields;
29842
- if (typeof cf !== "object" || cf === null || Array.isArray(cf) || Object.getPrototypeOf(cf) !== Object.prototype) {
29843
- errors.push("- tenant.custom_fields must be a plain object (Record<string, string>)");
29844
- } else if (!Object.values(cf).every((v) => typeof v === "string")) {
29845
- errors.push("- tenant.custom_fields values must all be strings");
29846
- }
29847
- }
29848
- }
29849
- if (!config2.auth) {
29850
- errors.push("- Missing required field: auth");
29851
- } else {
29852
- if (!config2.auth.kid) {
29853
- errors.push("- Missing required field: auth.kid");
30122
+ function installStdinExitHandler(options) {
30123
+ const stdin = options.stdin ?? process.stdin;
30124
+ const exit = options.exit ?? ((code) => process.exit(code));
30125
+ let exiting = false;
30126
+ const exitOnStdinClosed = () => {
30127
+ var _a3;
30128
+ if (exiting) {
30129
+ return;
29854
30130
  }
29855
- if (!config2.auth.mac_key) {
29856
- errors.push("- Missing required field: auth.mac_key");
30131
+ exiting = true;
30132
+ if (options.onClosed) {
30133
+ options.onClosed(options.source, options.message);
30134
+ return;
29857
30135
  }
29858
- if (config2.auth.token_type !== "mac") {
29859
- errors.push('- Invalid auth.token_type: must be "mac"');
30136
+ (_a3 = options.log) == null ? void 0 : _a3.call(options, options.source, options.message);
30137
+ options.cleanup();
30138
+ exit(0);
30139
+ };
30140
+ stdin.once("end", exitOnStdinClosed);
30141
+ stdin.once("close", exitOnStdinClosed);
30142
+ }
30143
+ function installParentDeathWatchdog(options) {
30144
+ var _a3;
30145
+ const initialPpid = options.initialPpid ?? process.ppid;
30146
+ const getCurrentPpid = options.getCurrentPpid ?? (() => process.ppid);
30147
+ const platform = options.platform ?? process.platform;
30148
+ const probeParent = options.probeParent ?? (() => process.kill(initialPpid, 0));
30149
+ const exit = options.exit ?? ((code) => process.exit(code));
30150
+ let exiting = false;
30151
+ const timer = setInterval(() => {
30152
+ var _a4;
30153
+ if (exiting) {
30154
+ return;
29860
30155
  }
29861
- if (config2.auth.mac_algorithm !== "hmac-sha-1") {
29862
- errors.push('- Invalid auth.mac_algorithm: must be "hmac-sha-1"');
30156
+ const shouldExit = shouldExitForParentDeath({
30157
+ initialPpid,
30158
+ currentPpid: getCurrentPpid(),
30159
+ platform,
30160
+ probeParent
30161
+ });
30162
+ if (!shouldExit) {
30163
+ return;
29863
30164
  }
30165
+ exiting = true;
30166
+ (_a4 = options.log) == null ? void 0 : _a4.call(options, options.source, options.message);
30167
+ options.cleanup();
30168
+ exit(0);
30169
+ }, options.intervalMs ?? 15e3);
30170
+ (_a3 = timer.unref) == null ? void 0 : _a3.call(timer);
30171
+ return timer;
30172
+ }
30173
+ function installStdioErrorHandlers(options) {
30174
+ const exit = options.exit ?? ((code) => process.exit(code));
30175
+ let exiting = false;
30176
+ for (const stream of options.streams) {
30177
+ stream.on("error", (error2) => {
30178
+ var _a3, _b;
30179
+ if (!isDisconnectedStdioError(error2)) {
30180
+ (_a3 = options.log) == null ? void 0 : _a3.call(
30181
+ options,
30182
+ options.ignoredSource ?? "stdio-error",
30183
+ options.ignoredMessage ? options.ignoredMessage(error2) : `stdio error ignored: ${formatError2(error2)}`
30184
+ );
30185
+ return;
30186
+ }
30187
+ if (exiting) {
30188
+ return;
30189
+ }
30190
+ exiting = true;
30191
+ if (options.onDisconnected) {
30192
+ options.onDisconnected(options.disconnectedSource, options.disconnectedMessage);
30193
+ return;
30194
+ }
30195
+ (_b = options.log) == null ? void 0 : _b.call(options, options.disconnectedSource, options.disconnectedMessage);
30196
+ options.cleanup();
30197
+ exit(0);
30198
+ });
29864
30199
  }
29865
- if (((_a3 = config2.options) == null ? void 0 : _a3.exposed_tools) !== void 0) {
29866
- const exposedTools = config2.options.exposed_tools;
29867
- if (!Array.isArray(exposedTools)) {
29868
- errors.push("- options.exposed_tools must be an array of tool names");
29869
- } else if (!exposedTools.every((toolName) => typeof toolName === "string" && toolName.trim().length > 0)) {
29870
- errors.push("- options.exposed_tools values must be non-empty strings");
30200
+ }
30201
+ function formatError2(error2) {
30202
+ return error2 instanceof Error ? error2.message : String(error2);
30203
+ }
30204
+
30205
+ // src/mcp-proxy/lifecycle.ts
30206
+ function installStandaloneProxyLifecycleHandlers(options) {
30207
+ const stdin = options.stdin ?? process.stdin;
30208
+ const stdout = options.stdout ?? process.stdout;
30209
+ const stderr = options.stderr ?? process.stderr;
30210
+ const exit = options.exit ?? ((code) => process.exit(code));
30211
+ const log = options.log;
30212
+ let exiting = false;
30213
+ const exitOnce = (source, message) => {
30214
+ if (exiting) {
30215
+ return;
29871
30216
  }
30217
+ exiting = true;
30218
+ log == null ? void 0 : log(source, message);
30219
+ options.proxy.cleanup();
30220
+ exit(0);
30221
+ };
30222
+ installStdinExitHandler({
30223
+ stdin,
30224
+ cleanup: () => void 0,
30225
+ exit: () => void 0,
30226
+ source: "standalone-proxy-stdin-closed",
30227
+ message: "Standalone proxy stdin closed; exiting.",
30228
+ onClosed: exitOnce
30229
+ });
30230
+ installStdioErrorHandlers({
30231
+ streams: [stdin, stdout, stderr].filter(Boolean),
30232
+ cleanup: () => void 0,
30233
+ exit: () => void 0,
30234
+ log,
30235
+ disconnectedSource: "standalone-proxy-stdio-disconnected",
30236
+ disconnectedMessage: "Standalone proxy stdio disconnected; exiting.",
30237
+ onDisconnected: exitOnce,
30238
+ ignoredSource: "standalone-proxy-stdio-error",
30239
+ ignoredMessage: (error2) => `Standalone proxy stdio error ignored: ${formatError3(error2)}`
30240
+ });
30241
+ if (options.installSignals !== false) {
30242
+ const signalCleanup = () => {
30243
+ exitOnce("standalone-proxy-signal", "Standalone proxy received shutdown signal; exiting.");
30244
+ };
30245
+ process.on("SIGINT", signalCleanup);
30246
+ process.on("SIGTERM", signalCleanup);
29872
30247
  }
29873
- if (errors.length > 0) {
29874
- throw new Error("Invalid configuration:\n" + errors.join("\n"));
30248
+ if (options.installParentWatchdog === false) {
30249
+ return void 0;
29875
30250
  }
30251
+ return installParentDeathWatchdog({
30252
+ cleanup: () => exitOnce("standalone-proxy-parent-dead", "Standalone proxy parent process is gone; exiting."),
30253
+ exit: () => void 0,
30254
+ source: "standalone-proxy-parent-dead",
30255
+ message: "Standalone proxy parent process is gone; exiting."
30256
+ });
29876
30257
  }
29877
- var DEFAULT_LOG_ROOT = "/tmp/taptap-mcp/logs";
29878
- function applyDefaults(config2) {
29879
- var _a3, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
29880
- const verbose = ((_a3 = config2.options) == null ? void 0 : _a3.verbose) ?? false;
29881
- return {
29882
- server: {
29883
- url: config2.server.url,
29884
- env: config2.server.env || "rnd"
29885
- },
29886
- tenant: {
29887
- project_path: config2.tenant.project_path || ".",
29888
- user_id: config2.tenant.user_id,
29889
- project_id: config2.tenant.project_id,
29890
- custom_fields: config2.tenant.custom_fields
29891
- },
29892
- auth: config2.auth,
29893
- options: {
29894
- verbose,
29895
- reconnect_interval: ((_b = config2.options) == null ? void 0 : _b.reconnect_interval) ?? 5e3,
29896
- request_timeout: ((_c = config2.options) == null ? void 0 : _c.request_timeout) ?? 3e4,
29897
- tool_call_timeout: ((_d = config2.options) == null ? void 0 : _d.tool_call_timeout) ?? 3e5,
29898
- // 5 分钟
29899
- reset_timeout_on_progress: ((_e = config2.options) == null ? void 0 : _e.reset_timeout_on_progress) ?? true,
29900
- health_check_interval: ((_f = config2.options) == null ? void 0 : _f.health_check_interval) ?? 3e4,
29901
- enable_cookie_sticky: ((_g = config2.options) == null ? void 0 : _g.enable_cookie_sticky) ?? true,
29902
- inject_params_per_call: ((_h = config2.options) == null ? void 0 : _h.inject_params_per_call) ?? true,
29903
- force_inject_progress_token: ((_i = config2.options) == null ? void 0 : _i.force_inject_progress_token) ?? false,
29904
- exposed_tools: (_j = config2.options) == null ? void 0 : _j.exposed_tools,
29905
- log: {
29906
- root: ((_l = (_k = config2.options) == null ? void 0 : _k.log) == null ? void 0 : _l.root) ?? DEFAULT_LOG_ROOT,
29907
- enabled: ((_n = (_m = config2.options) == null ? void 0 : _m.log) == null ? void 0 : _n.enabled) ?? false,
29908
- // verbose=true 时自动使用 debug 级别
29909
- level: verbose ? "debug" : ((_p = (_o = config2.options) == null ? void 0 : _o.log) == null ? void 0 : _p.level) ?? "info",
29910
- max_days: ((_r = (_q = config2.options) == null ? void 0 : _q.log) == null ? void 0 : _r.max_days) ?? 7
29911
- }
29912
- }
29913
- };
30258
+ function formatError3(error2) {
30259
+ return error2 instanceof Error ? error2.message : String(error2);
29914
30260
  }
29915
30261
 
29916
30262
  // src/mcp-proxy/index.ts
@@ -29931,13 +30277,10 @@ async function main() {
29931
30277
  console.error(`[Proxy] Verbose: ${(_a3 = config2.options) == null ? void 0 : _a3.verbose}`);
29932
30278
  const proxy = new TapTapMCPProxy(config2);
29933
30279
  await proxy.start();
29934
- const cleanup = () => {
29935
- console.error("[Proxy] Shutting down...");
29936
- proxy.cleanup();
29937
- process.exit(0);
29938
- };
29939
- process.on("SIGINT", cleanup);
29940
- process.on("SIGTERM", cleanup);
30280
+ installStandaloneProxyLifecycleHandlers({
30281
+ proxy,
30282
+ log: (source, message) => proxy.logLifecycleEvent(source, message)
30283
+ });
29941
30284
  } catch (error2) {
29942
30285
  console.error("[Proxy] Fatal error:", error2 instanceof Error ? error2.message : String(error2));
29943
30286
  process.exit(1);