staff-mcp 1.0.29 → 1.0.30

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.
@@ -7,6 +7,7 @@ import { registerSkillTools } from "./tools/skills.js";
7
7
  import { registerMcpClientTools } from "./tools/mcp-client-tools.js";
8
8
  import { getMcpInstructions } from "./tools/system-tools.js";
9
9
  import { FteServer } from "mcp-fte";
10
+ import { installFteTransportLifecycle } from "./transports/fte-lifecycle.js";
10
11
  /**
11
12
  * Creates and initializes a new McpServer with all functional tools.
12
13
  * @param name The server's identification name.
@@ -44,11 +45,8 @@ export function createServerFactory(name, version, workingDir, allowedDirs, prof
44
45
  registerSkillTools(server, workingDir, security, profile);
45
46
  registerMcpClientTools(server, { maxSessions: maxMcpSessions });
46
47
  // Intercept connect to wrap transport with FTE server-side processing
47
- const originalConnect = server.connect.bind(server);
48
- server.connect = (transport) => {
49
- const wrapped = FteServer.wrapTransport(transport, { sandbox });
50
- return originalConnect(wrapped);
51
- };
48
+ // while ensuring each reconnect also disposes the previous FTE wrapper.
49
+ installFteTransportLifecycle(server, sandbox);
52
50
  return server;
53
51
  };
54
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,OAAe,EACf,UAAkB,EAClB,WAAqB,EACrB,UAAkB,SAAS,EAC3B,iBAAyB,CAAC,EAC1B,YAAqB,KAAK;IAE1B,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,EAAE,CAAC;IAE1C,uEAAuE;IACvE,MAAM,YAAY,GAAG,kBAAkB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE9D,OAAO,GAAG,EAAE;QACV,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;YACE,IAAI;YACJ,OAAO;SACR,EACD;YACE,yDAAyD;YACzD,YAAY;YACZ,6BAA6B;YAC7B,YAAY,EAAE;gBACZ,GAAG,SAAS,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;aACvC;SACF,CACF,CAAC;QAEF,uCAAuC;QACvC,iBAAiB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACpC,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAErC,IAAI,SAAS,EAAE,CAAC;YACd,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACrC,CAAC;QAED,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC1D,sBAAsB,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;QAEhE,sEAAsE;QACtE,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,MAAM,CAAC,OAAO,GAAG,CAAC,SAAS,EAAE,EAAE;YAC7B,MAAM,OAAO,GAAG,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YAChE,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC,CAAC;QAEF,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAE7E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,OAAe,EACf,UAAkB,EAClB,WAAqB,EACrB,UAAkB,SAAS,EAC3B,iBAAyB,CAAC,EAC1B,YAAqB,KAAK;IAE1B,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,cAAc,EAAE,CAAC;IAE1C,uEAAuE;IACvE,MAAM,YAAY,GAAG,kBAAkB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE9D,OAAO,GAAG,EAAE;QACV,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;YACE,IAAI;YACJ,OAAO;SACR,EACD;YACE,yDAAyD;YACzD,YAAY;YACZ,6BAA6B;YAC7B,YAAY,EAAE;gBACZ,GAAG,SAAS,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;aACvC;SACF,CACF,CAAC;QAEF,uCAAuC;QACvC,iBAAiB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACpC,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAErC,IAAI,SAAS,EAAE,CAAC;YACd,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACrC,CAAC;QAED,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC1D,sBAAsB,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;QAEhE,sEAAsE;QACtE,wEAAwE;QACxE,4BAA4B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAE9C,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
3
+ import { type FteServerConfig } from "mcp-fte";
4
+ export type FteTransportFactory = (transport: Transport, config: FteServerConfig) => Transport;
5
+ /**
6
+ * Install FTE wrapping while preserving transport lifecycle semantics.
7
+ *
8
+ * mcp-reverse transports can close themselves before the MCP SDK calls the
9
+ * wrapper's close() method. Without this bridge, FteServer's timer and transfer
10
+ * sessions would survive every reconnect. The composed onclose handler clears
11
+ * the SDK binding first, then closes the FTE wrapper exactly once.
12
+ */
13
+ export declare function installFteTransportLifecycle(server: McpServer, sandbox: string[], wrapTransport?: FteTransportFactory): void;
@@ -0,0 +1,76 @@
1
+ import { FteServer } from "mcp-fte";
2
+ /**
3
+ * Install FTE wrapping while preserving transport lifecycle semantics.
4
+ *
5
+ * mcp-reverse transports can close themselves before the MCP SDK calls the
6
+ * wrapper's close() method. Without this bridge, FteServer's timer and transfer
7
+ * sessions would survive every reconnect. The composed onclose handler clears
8
+ * the SDK binding first, then closes the FTE wrapper exactly once.
9
+ */
10
+ export function installFteTransportLifecycle(server, sandbox, wrapTransport = (transport, config) => FteServer.wrapTransport(transport, config)) {
11
+ const originalConnect = server.connect.bind(server);
12
+ server.connect = async (transport) => {
13
+ const wrapped = wrapTransport(transport, { sandbox });
14
+ const rawWrappedClose = wrapped.close.bind(wrapped);
15
+ let closing = false;
16
+ let closePromise = Promise.resolve();
17
+ const closeWrappedOnce = () => {
18
+ if (closing)
19
+ return closePromise;
20
+ closing = true;
21
+ try {
22
+ closePromise = Promise.resolve(rawWrappedClose());
23
+ }
24
+ catch (error) {
25
+ closePromise = Promise.reject(error);
26
+ }
27
+ return closePromise;
28
+ };
29
+ // Any SDK-driven close must use the same idempotent cleanup path.
30
+ wrapped.close = closeWrappedOnce;
31
+ try {
32
+ await originalConnect(wrapped);
33
+ }
34
+ catch (error) {
35
+ await closeWrappedOnce().catch((cleanupError) => {
36
+ console.error("[staff-mcp] Failed to clean up FTE transport after connect failure:", cleanupError);
37
+ });
38
+ // Some transports reject start() without firing onclose. Release only if
39
+ // this exact wrapper is still bound: an asynchronous stale cleanup must
40
+ // never clear a newer reconnect that has already succeeded.
41
+ if (server.server.transport === wrapped) {
42
+ try {
43
+ wrapped.onclose?.();
44
+ }
45
+ catch (cleanupError) {
46
+ console.error("[staff-mcp] Failed to release MCP transport after connect failure:", cleanupError);
47
+ }
48
+ }
49
+ throw error;
50
+ }
51
+ // Protocol.connect() installs its callbacks during originalConnect().
52
+ // Compose only afterwards so both the SDK and FTE cleanup always run.
53
+ const protocolOnClose = wrapped.onclose;
54
+ let closeHandled = false;
55
+ wrapped.onclose = () => {
56
+ if (closeHandled)
57
+ return;
58
+ closeHandled = true;
59
+ try {
60
+ protocolOnClose?.();
61
+ }
62
+ finally {
63
+ void closeWrappedOnce().catch((cleanupError) => {
64
+ console.error("[staff-mcp] Failed to clean up closed FTE transport:", cleanupError);
65
+ });
66
+ }
67
+ };
68
+ // Cover the narrow race where this delegate closes after connect resolves
69
+ // but before the composed callback above is installed. Transport identity,
70
+ // rather than global connected state, protects a newer concurrent binding.
71
+ if (server.server.transport !== wrapped) {
72
+ await closeWrappedOnce();
73
+ }
74
+ };
75
+ }
76
+ //# sourceMappingURL=fte-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fte-lifecycle.js","sourceRoot":"","sources":["../../../src/transports/fte-lifecycle.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAwB,MAAM,SAAS,CAAC;AAO1D;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAC1C,MAAiB,EACjB,OAAiB,EACjB,gBAAqC,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CACzD,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC;IAE5C,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpD,MAAM,CAAC,OAAO,GAAG,KAAK,EAAE,SAAoB,EAAiB,EAAE;QAC7D,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACtD,MAAM,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEpD,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,YAAY,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;QACpD,MAAM,gBAAgB,GAAG,GAAkB,EAAE;YAC3C,IAAI,OAAO;gBAAE,OAAO,YAAY,CAAC;YACjC,OAAO,GAAG,IAAI,CAAC;YAEf,IAAI,CAAC;gBACH,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;YACpD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvC,CAAC;YACD,OAAO,YAAY,CAAC;QACtB,CAAC,CAAC;QAEF,kEAAkE;QAClE,OAAO,CAAC,KAAK,GAAG,gBAAgB,CAAC;QAEjC,IAAI,CAAC;YACH,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,gBAAgB,EAAE,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,EAAE;gBAC9C,OAAO,CAAC,KAAK,CAAC,qEAAqE,EAAE,YAAY,CAAC,CAAC;YACrG,CAAC,CAAC,CAAC;YAEH,yEAAyE;YACzE,wEAAwE;YACxE,4DAA4D;YAC5D,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC;gBACxC,IAAI,CAAC;oBACH,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;gBACtB,CAAC;gBAAC,OAAO,YAAY,EAAE,CAAC;oBACtB,OAAO,CAAC,KAAK,CAAC,oEAAoE,EAAE,YAAY,CAAC,CAAC;gBACpG,CAAC;YACH,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QAED,sEAAsE;QACtE,sEAAsE;QACtE,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;QACxC,IAAI,YAAY,GAAG,KAAK,CAAC;QACzB,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE;YACrB,IAAI,YAAY;gBAAE,OAAO;YACzB,YAAY,GAAG,IAAI,CAAC;YAEpB,IAAI,CAAC;gBACH,eAAe,EAAE,EAAE,CAAC;YACtB,CAAC;oBAAS,CAAC;gBACT,KAAK,gBAAgB,EAAE,CAAC,KAAK,CAAC,CAAC,YAAY,EAAE,EAAE;oBAC7C,OAAO,CAAC,KAAK,CAAC,sDAAsD,EAAE,YAAY,CAAC,CAAC;gBACtF,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC;QAEF,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC;YACxC,MAAM,gBAAgB,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -1,13 +1,19 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { Logger, ReconnectOptions } from "mcp-reverse";
3
+ import { ReverseMCPClient } from "mcp-reverse/client";
4
+ export interface ReverseServerOptions {
5
+ /** Timeout for establishing each SSE connection. 0 disables the timeout. */
6
+ connectTimeout?: number;
7
+ /** Override the automatic reconnect policy. */
8
+ reconnect?: ReconnectOptions;
9
+ /** Override reverse transport and lifecycle logging. */
10
+ logger?: Logger;
11
+ }
2
12
  /**
3
13
  * Start a reverse MCP connection to a public gateway.
4
14
  *
5
- * The ReverseMCPClient handles all transport lifecycle, reconnection,
6
- * and MCP protocol re-binding automatically.
7
- *
8
- * @param server The McpServer instance (with all tools registered)
9
- * @param url Base URL of the reverse MCP endpoint
10
- * @param token Authentication token
11
- * @param name Server name for identification
15
+ * The ReverseMCPClient owns transport lifecycle, reconnection, and MCP
16
+ * protocol re-binding. start() launches the connection loop; readiness is
17
+ * reported asynchronously through the connected event/log.
12
18
  */
13
- export declare function startReverseServer(server: McpServer, url: string, token: string, name: string): Promise<void>;
19
+ export declare function startReverseServer(server: McpServer, url: string, token: string, name: string, options?: ReverseServerOptions): Promise<ReverseMCPClient>;
@@ -1,43 +1,62 @@
1
1
  import { ReverseMCPClient } from "mcp-reverse/client";
2
+ const DEFAULT_CONNECT_TIMEOUT_MS = 15_000;
3
+ function createReverseLogger() {
4
+ const debugEnabled = process.env.STAFF_MCP_REVERSE_DEBUG === "1";
5
+ return {
6
+ debug: debugEnabled
7
+ ? (message, ...args) => console.error(`[staff-mcp][reverse][debug] ${message}`, ...args)
8
+ : () => { },
9
+ info: (message, ...args) => console.error(`[staff-mcp][reverse][info] ${message}`, ...args),
10
+ warn: (message, ...args) => console.error(`[staff-mcp][reverse][warn] ${message}`, ...args),
11
+ error: (message, ...args) => console.error(`[staff-mcp][reverse][error] ${message}`, ...args),
12
+ };
13
+ }
2
14
  /**
3
15
  * Start a reverse MCP connection to a public gateway.
4
16
  *
5
- * The ReverseMCPClient handles all transport lifecycle, reconnection,
6
- * and MCP protocol re-binding automatically.
7
- *
8
- * @param server The McpServer instance (with all tools registered)
9
- * @param url Base URL of the reverse MCP endpoint
10
- * @param token Authentication token
11
- * @param name Server name for identification
17
+ * The ReverseMCPClient owns transport lifecycle, reconnection, and MCP
18
+ * protocol re-binding. start() launches the connection loop; readiness is
19
+ * reported asynchronously through the connected event/log.
12
20
  */
13
- export async function startReverseServer(server, url, token, name) {
21
+ export async function startReverseServer(server, url, token, name, options = {}) {
22
+ const reconnectEnabled = options.reconnect?.enabled !== false;
23
+ const reverseLogger = options.logger ?? createReverseLogger();
14
24
  const client = await ReverseMCPClient.createSSE(server, {
15
25
  url,
16
26
  serverName: name,
17
27
  authToken: token,
28
+ connectTimeout: options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS,
18
29
  reconnect: {
19
30
  initialDelay: 1000,
20
31
  maxDelay: 30000,
21
32
  multiplier: 2,
22
33
  maxRetries: 0, // 0 = infinite
34
+ ...options.reconnect,
23
35
  },
24
- });
36
+ }, reverseLogger);
25
37
  client.on("connected", () => {
26
- console.log(`[staff-mcp] 🚀 Successfully connected to Reverse Gateway as "${name}"`);
38
+ reverseLogger.info(`SSE transport connected as "${name}"`);
27
39
  });
28
40
  client.on("disconnected", () => {
29
- console.log("[staff-mcp] Connection lost, reconnecting...");
41
+ reverseLogger.warn(reconnectEnabled
42
+ ? "Gateway connection closed; automatic reconnect is enabled"
43
+ : "Gateway connection closed; automatic reconnect is disabled");
30
44
  });
31
- client.on("reconnecting", (attempt) => {
32
- console.log(`[staff-mcp] Reconnecting, attempt ${attempt}...`);
45
+ client.on("reconnecting", (completedAttempts) => {
46
+ if (completedAttempts === 0) {
47
+ reverseLogger.info("Reconnect cycle started");
48
+ }
49
+ else {
50
+ reverseLogger.info(`${completedAttempts} reconnect attempt(s) have failed`);
51
+ }
33
52
  });
34
53
  client.on("error", (error) => {
35
- console.error(`[staff-mcp] Transport error:`, error.message);
54
+ reverseLogger.error(`Transport reported an error: ${error.message}`);
36
55
  });
37
- client.on("failed", () => {
38
- console.error("[staff-mcp] Permanent connection failure, exiting");
39
- process.exit(1);
56
+ client.on("failed", (error) => {
57
+ reverseLogger.error(`Reconnect budget exhausted: ${error.message}`);
40
58
  });
41
59
  await client.start();
60
+ return client;
42
61
  }
43
62
  //# sourceMappingURL=reverse.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reverse.js","sourceRoot":"","sources":["../../../src/transports/reverse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACpC,MAAiB,EACjB,GAAW,EACX,KAAa,EACb,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,SAAS,CAAC,MAAM,EAAE;QACpD,GAAG;QACH,UAAU,EAAE,IAAI;QAChB,SAAS,EAAE,KAAK;QAChB,SAAS,EAAE;YACP,YAAY,EAAE,IAAI;YAClB,QAAQ,EAAE,KAAK;YACf,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,CAAC,EAAE,eAAe;SACjC;KACJ,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,GAAG,CAAC,gEAAgE,IAAI,GAAG,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QAC3B,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,OAAe,EAAE,EAAE;QAC1C,OAAO,CAAC,GAAG,CAAC,qCAAqC,OAAO,KAAK,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;QAChC,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACrB,OAAO,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC"}
1
+ {"version":3,"file":"reverse.js","sourceRoot":"","sources":["../../../src/transports/reverse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,118 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { FteServer } from "mcp-fte";
5
+ import { installFteTransportLifecycle } from "../src/transports/fte-lifecycle.js";
6
+ class TestTransport {
7
+ startFailure;
8
+ closeBeforeReject;
9
+ closeGate;
10
+ onclose;
11
+ onerror;
12
+ onmessage;
13
+ closeCalls = 0;
14
+ constructor(startFailure, closeBeforeReject = false, closeGate) {
15
+ this.startFailure = startFailure;
16
+ this.closeBeforeReject = closeBeforeReject;
17
+ this.closeGate = closeGate;
18
+ }
19
+ async start() {
20
+ if (this.startFailure) {
21
+ if (this.closeBeforeReject)
22
+ this.onclose?.();
23
+ throw this.startFailure;
24
+ }
25
+ }
26
+ async send() { }
27
+ async close() {
28
+ this.closeCalls++;
29
+ this.onclose?.();
30
+ this.closeGate?.started();
31
+ await this.closeGate?.wait;
32
+ }
33
+ disconnect() {
34
+ this.onclose?.();
35
+ }
36
+ }
37
+ function createServer(cleanupCounter) {
38
+ const server = new McpServer({ name: "fte-lifecycle-test", version: "1.0.0" });
39
+ installFteTransportLifecycle(server, [], (transport, config) => {
40
+ const wrapped = FteServer.wrapTransport(transport, config);
41
+ const close = wrapped.close.bind(wrapped);
42
+ wrapped.close = async () => {
43
+ cleanupCounter.value++;
44
+ await close();
45
+ };
46
+ return wrapped;
47
+ });
48
+ return server;
49
+ }
50
+ async function flushCleanup() {
51
+ await new Promise((resolve) => setImmediate(resolve));
52
+ }
53
+ test("self-closing reverse transports dispose the FTE wrapper exactly once and allow reconnect", async () => {
54
+ const cleanupCounter = { value: 0 };
55
+ const server = createServer(cleanupCounter);
56
+ const first = new TestTransport();
57
+ await server.connect(first);
58
+ assert.equal(server.isConnected(), true);
59
+ first.disconnect();
60
+ await flushCleanup();
61
+ assert.equal(server.isConnected(), false);
62
+ assert.equal(cleanupCounter.value, 1);
63
+ const second = new TestTransport();
64
+ await server.connect(second);
65
+ assert.equal(server.isConnected(), true);
66
+ await server.close();
67
+ await flushCleanup();
68
+ assert.equal(server.isConnected(), false);
69
+ assert.equal(cleanupCounter.value, 2);
70
+ });
71
+ test("failed starts clean up FTE state even when the transport does not emit onclose", async () => {
72
+ const cleanupCounter = { value: 0 };
73
+ const server = createServer(cleanupCounter);
74
+ const failed = new TestTransport(new Error("connect failed"), false);
75
+ await assert.rejects(server.connect(failed), /connect failed/);
76
+ await flushCleanup();
77
+ assert.equal(server.isConnected(), false);
78
+ assert.equal(cleanupCounter.value, 1);
79
+ const next = new TestTransport();
80
+ await server.connect(next);
81
+ assert.equal(server.isConnected(), true);
82
+ await server.close();
83
+ });
84
+ test("failed starts that already emitted onclose still clean up only once", async () => {
85
+ const cleanupCounter = { value: 0 };
86
+ const server = createServer(cleanupCounter);
87
+ const failed = new TestTransport(new Error("closed while connecting"), true);
88
+ await assert.rejects(server.connect(failed), /closed while connecting/);
89
+ await flushCleanup();
90
+ assert.equal(server.isConnected(), false);
91
+ assert.equal(cleanupCounter.value, 1);
92
+ });
93
+ test("stale failed-transport cleanup cannot detach a newer connection", async () => {
94
+ const cleanupCounter = { value: 0 };
95
+ const server = createServer(cleanupCounter);
96
+ let markCloseStarted;
97
+ const closeStarted = new Promise((resolve) => {
98
+ markCloseStarted = resolve;
99
+ });
100
+ let releaseClose;
101
+ const closeWait = new Promise((resolve) => {
102
+ releaseClose = resolve;
103
+ });
104
+ const failed = new TestTransport(new Error("first connect failed"), false, { started: markCloseStarted, wait: closeWait });
105
+ const firstConnect = server.connect(failed);
106
+ await closeStarted;
107
+ // The first delegate has synchronously fired onclose, so Protocol is free to
108
+ // bind a replacement while the old FTE close remains asynchronously pending.
109
+ const replacement = new TestTransport();
110
+ await server.connect(replacement);
111
+ assert.equal(server.isConnected(), true);
112
+ releaseClose();
113
+ await assert.rejects(firstConnect, /first connect failed/);
114
+ assert.equal(server.isConnected(), true);
115
+ assert.equal(server.server.transport !== undefined, true);
116
+ await server.close();
117
+ });
118
+ //# sourceMappingURL=fte-lifecycle.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fte-lifecycle.test.js","sourceRoot":"","sources":["../../tests/fte-lifecycle.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ import assert from "node:assert/strict";
2
+ import { createServer as createNetServer } from "node:net";
3
+ import { test } from "node:test";
4
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { SSEAcceptor } from "mcp-reverse";
7
+ import { installFteTransportLifecycle } from "../src/transports/fte-lifecycle.js";
8
+ import { startReverseServer } from "../src/transports/reverse.js";
9
+ const noopLogger = {
10
+ debug: () => { },
11
+ info: () => { },
12
+ warn: () => { },
13
+ error: () => { },
14
+ };
15
+ async function freePort() {
16
+ const server = createNetServer();
17
+ await new Promise((resolve, reject) => {
18
+ server.once("error", reject);
19
+ server.listen(0, "127.0.0.1", resolve);
20
+ });
21
+ const address = server.address();
22
+ if (!address || typeof address === "string")
23
+ throw new Error("Could not allocate port");
24
+ await new Promise((resolve) => server.close(() => resolve()));
25
+ return address.port;
26
+ }
27
+ async function waitFor(predicate, timeout = 10_000) {
28
+ const deadline = Date.now() + timeout;
29
+ while (!predicate()) {
30
+ if (Date.now() >= deadline)
31
+ throw new Error("Timed out waiting for condition");
32
+ await new Promise((resolve) => setTimeout(resolve, 10));
33
+ }
34
+ }
35
+ test("staff reverse mode reconnects through a gateway restart with FTE wrapping enabled", async () => {
36
+ const port = await freePort();
37
+ const serverName = "staff-restart-test";
38
+ const token = "staff-restart-token";
39
+ const gatewayClients = [];
40
+ const createAcceptor = () => {
41
+ const acceptor = new SSEAcceptor({
42
+ port,
43
+ host: "127.0.0.1",
44
+ authTokens: { [serverName]: token },
45
+ heartbeat: { enabled: false },
46
+ sessionTimeout: 5_000,
47
+ }, noopLogger);
48
+ acceptor.onConnection(async (connection) => {
49
+ const gatewayClient = new Client({ name: "gateway-test", version: "1.0.0" });
50
+ gatewayClients.push(gatewayClient);
51
+ await gatewayClient.connect(connection.transport);
52
+ });
53
+ return acceptor;
54
+ };
55
+ let acceptor = createAcceptor();
56
+ await acceptor.start();
57
+ const mcpServer = new McpServer({ name: serverName, version: "1.0.0" });
58
+ installFteTransportLifecycle(mcpServer, []);
59
+ const reverseClient = await startReverseServer(mcpServer, `http://127.0.0.1:${port}/mcp-reverse`, token, serverName, {
60
+ connectTimeout: 500,
61
+ reconnect: {
62
+ initialDelay: 10,
63
+ maxDelay: 20,
64
+ multiplier: 1,
65
+ jitter: false,
66
+ maxRetries: 0,
67
+ },
68
+ logger: noopLogger,
69
+ });
70
+ try {
71
+ await waitFor(() => gatewayClients.length === 1 && gatewayClients[0].getServerVersion() !== undefined);
72
+ assert.equal(acceptor.sessionCount, 1);
73
+ await acceptor.close();
74
+ await waitFor(() => !mcpServer.isConnected());
75
+ // Leave the gateway down long enough for at least one failed attempt.
76
+ await new Promise((resolve) => setTimeout(resolve, 100));
77
+ acceptor = createAcceptor();
78
+ await acceptor.start();
79
+ await waitFor(() => gatewayClients.length >= 2 && gatewayClients[1].getServerVersion() !== undefined);
80
+ assert.equal(acceptor.sessionCount, 1);
81
+ assert.equal(mcpServer.isConnected(), true);
82
+ }
83
+ finally {
84
+ await reverseClient.stop();
85
+ await Promise.allSettled(gatewayClients.map((client) => client.close()));
86
+ await acceptor.close();
87
+ }
88
+ });
89
+ //# sourceMappingURL=reverse-reconnect.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reverse-reconnect.test.js","sourceRoot":"","sources":["../../tests/reverse-reconnect.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "staff-mcp",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/ChineseAStar/staff-mcp.git"
@@ -32,7 +32,7 @@
32
32
  "express": "^4.21.1",
33
33
  "js-yaml": "^4.1.1",
34
34
  "mcp-fte": "^1.0.2",
35
- "mcp-reverse": "^1.1.0",
35
+ "mcp-reverse": "1.3.0",
36
36
  "zod": "^3.23.8"
37
37
  },
38
38
  "devDependencies": {
@@ -42,5 +42,8 @@
42
42
  "@types/node": "^22.9.0",
43
43
  "tsx": "^4.19.2",
44
44
  "typescript": "^5.6.3"
45
+ },
46
+ "engines": {
47
+ "node": ">=20"
45
48
  }
46
49
  }