aws-runtime-bridge 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +77 -77
  2. package/dist/adapter/ClaudeSdkAdapter.d.ts +1 -0
  3. package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
  4. package/dist/adapter/ClaudeSdkAdapter.js +7 -3
  5. package/dist/adapter/ClaudeSdkAdapter.test.js +2 -2
  6. package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
  7. package/dist/adapter/CodexSdkAdapter.js +7 -4
  8. package/dist/adapter/CodexSdkAdapter.test.js +4 -2
  9. package/dist/adapter/OpencodeSdkAdapter.d.ts +2 -0
  10. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
  11. package/dist/adapter/OpencodeSdkAdapter.js +15 -1
  12. package/dist/adapter/OpencodeSdkAdapter.test.js +5 -0
  13. package/dist/index.js +0 -0
  14. package/dist/routes/properties.test.js +4 -4
  15. package/dist/routes/runtime-binding.d.ts.map +1 -1
  16. package/dist/routes/runtime-binding.js +8 -13
  17. package/dist/routes/runtime-mcp-proxy.d.ts +3 -0
  18. package/dist/routes/runtime-mcp-proxy.d.ts.map +1 -0
  19. package/dist/routes/runtime-mcp-proxy.js +102 -0
  20. package/dist/routes/runtime-mcp-proxy.test.d.ts +2 -0
  21. package/dist/routes/runtime-mcp-proxy.test.d.ts.map +1 -0
  22. package/dist/routes/runtime-mcp-proxy.test.js +111 -0
  23. package/dist/routes/terminal.js +2 -5
  24. package/dist/routes/terminal.test.js +3 -4
  25. package/dist/services/auto-register.d.ts +6 -0
  26. package/dist/services/auto-register.d.ts.map +1 -1
  27. package/dist/services/auto-register.js +63 -1
  28. package/dist/services/aws-client-agent-mcp.d.ts.map +1 -1
  29. package/dist/services/aws-client-agent-mcp.js +4 -4
  30. package/dist/services/aws-client-agent-mcp.test.js +14 -0
  31. package/dist/services/mcp-launch-binding-queue.d.ts +0 -2
  32. package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
  33. package/dist/services/mcp-launch-binding-queue.js +44 -16
  34. package/dist/services/mcp-launch-binding-queue.test.js +42 -37
  35. package/dist/services/runtime-binding.d.ts +1 -0
  36. package/dist/services/runtime-binding.d.ts.map +1 -1
  37. package/dist/services/runtime-binding.js +39 -5
  38. package/dist/services/runtime-binding.test.d.ts +2 -0
  39. package/dist/services/runtime-binding.test.d.ts.map +1 -0
  40. package/dist/services/runtime-binding.test.js +11 -0
  41. package/dist/utils/yaml-utils.test.js +129 -129
  42. package/node_modules/@cc-switch/sdk/README.md +540 -540
  43. package/node_modules/@cc-switch/sdk/dist/sdk-import.test.d.ts +2 -0
  44. package/node_modules/@cc-switch/sdk/dist/sdk-import.test.d.ts.map +1 -0
  45. package/node_modules/@cc-switch/sdk/dist/sdk-import.test.js +119 -0
  46. package/node_modules/@cc-switch/sdk/package.json +31 -31
  47. package/package/aws-client-agent-mcp/README.md +288 -288
  48. package/package/aws-client-agent-mcp/dist/config.d.ts.map +1 -1
  49. package/package/aws-client-agent-mcp/dist/config.js +96 -13
  50. package/package/aws-client-agent-mcp/dist/config.js.map +1 -1
  51. package/package/aws-client-agent-mcp/dist/config.test.js +26 -8
  52. package/package/aws-client-agent-mcp/dist/config.test.js.map +1 -1
  53. package/package/aws-client-agent-mcp/dist/constants.d.ts +0 -1
  54. package/package/aws-client-agent-mcp/dist/constants.d.ts.map +1 -1
  55. package/package/aws-client-agent-mcp/dist/constants.js +0 -1
  56. package/package/aws-client-agent-mcp/dist/constants.js.map +1 -1
  57. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
  58. package/package/aws-client-agent-mcp/dist/http-client.js +49 -13
  59. package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
  60. package/package/aws-client-agent-mcp/dist/http-client.test.js +40 -13
  61. package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
  62. package/package/aws-client-agent-mcp/dist/index.js +11 -6
  63. package/package/aws-client-agent-mcp/dist/index.js.map +1 -1
  64. package/package/aws-client-agent-mcp/dist/logger.d.ts +11 -1
  65. package/package/aws-client-agent-mcp/dist/logger.d.ts.map +1 -1
  66. package/package/aws-client-agent-mcp/dist/logger.js +91 -6
  67. package/package/aws-client-agent-mcp/dist/logger.js.map +1 -1
  68. package/package/aws-client-agent-mcp/dist/logger.test.d.ts +2 -0
  69. package/package/aws-client-agent-mcp/dist/logger.test.d.ts.map +1 -0
  70. package/package/aws-client-agent-mcp/dist/logger.test.js +27 -0
  71. package/package/aws-client-agent-mcp/dist/logger.test.js.map +1 -0
  72. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
  73. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +18 -14
  74. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
  75. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +51 -21
  76. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +1 -1
  77. package/package/aws-client-agent-mcp/dist/types.d.ts +3 -2
  78. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  79. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  80. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +1 -0
  81. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  82. package/package/aws-client-agent-mcp/dist/websocket-client.js +18 -0
  83. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  84. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +53 -2
  85. package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
  86. package/package/aws-client-agent-mcp/package.json +52 -52
  87. package/package/cc-switch-sdk/README.md +540 -540
  88. package/package/cc-switch-sdk/dist/sdk-import.test.d.ts +2 -0
  89. package/package/cc-switch-sdk/dist/sdk-import.test.d.ts.map +1 -0
  90. package/package/cc-switch-sdk/dist/sdk-import.test.js +119 -0
  91. package/package/cc-switch-sdk/package.json +31 -31
  92. package/package.json +78 -78
@@ -80,7 +80,7 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
80
80
  });
81
81
  // SDK 模式
82
82
  if (mode === 'sdk') {
83
- await startSdkSession(req, res, queuedBinding?.id);
83
+ await startSdkSession(req, res);
84
84
  return;
85
85
  }
86
86
  // PTY 模式(原有逻辑)
@@ -110,7 +110,6 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
110
110
  env: {
111
111
  ...process.env,
112
112
  AWS_AGENT_ID: String(agentId),
113
- AWS_MCP_LAUNCH_BINDING_ID: queuedBinding?.id || '',
114
113
  AWS_MCP_CLAIM_LAUNCH_BINDING: 'true',
115
114
  },
116
115
  });
@@ -176,14 +175,13 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
176
175
  workspacePath,
177
176
  command: actualCommand,
178
177
  mode: 'pty',
179
- mcpLaunchBindingId: queuedBinding?.id,
180
178
  });
181
179
  });
182
180
  /**
183
181
  * 启动 SDK 会话
184
182
  * 支持 MCP 配置和空闲命令
185
183
  */
186
- async function startSdkSession(req, res, mcpLaunchBindingId) {
184
+ async function startSdkSession(req, res) {
187
185
  const { agentId, workspacePath, command, autoAccept = true, initialPrompt,
188
186
  // MCP 配置 - 加载 aws-client-agent-mcp
189
187
  mcpConfigPath, extraMcpServers,
@@ -213,7 +211,6 @@ async function startSdkSession(req, res, mcpLaunchBindingId) {
213
211
  envOverrides: {
214
212
  AWS_AGENT_ID: String(agentId),
215
213
  AWS_WORKSPACE_PATH: String(workspacePath),
216
- AWS_MCP_LAUNCH_BINDING_ID: mcpLaunchBindingId || '',
217
214
  AWS_MCP_CLAIM_LAUNCH_BINDING: 'true',
218
215
  },
219
216
  };
@@ -56,15 +56,14 @@ describe('terminal configuration', () => {
56
56
  expect(getShellConfig('linux').shell).toBe('bash');
57
57
  });
58
58
  it('builds correct terminal environment', () => {
59
- const buildTerminalEnv = (agentId, bindingId, baseEnv) => ({
59
+ const buildTerminalEnv = (agentId, baseEnv) => ({
60
60
  ...baseEnv,
61
61
  AWS_AGENT_ID: agentId,
62
- AWS_MCP_LAUNCH_BINDING_ID: bindingId,
63
62
  AWS_MCP_CLAIM_LAUNCH_BINDING: 'true',
64
63
  });
65
- const env = buildTerminalEnv('agent-123', 'binding-456', { PATH: '/usr/bin' });
64
+ const env = buildTerminalEnv('agent-123', { PATH: '/usr/bin' });
66
65
  expect(env.AWS_AGENT_ID).toBe('agent-123');
67
- expect(env.AWS_MCP_LAUNCH_BINDING_ID).toBe('binding-456');
66
+ expect(env.AWS_MCP_LAUNCH_BINDING_ID).toBeUndefined();
68
67
  expect(env.AWS_MCP_CLAIM_LAUNCH_BINDING).toBe('true');
69
68
  });
70
69
  });
@@ -75,6 +75,12 @@ export declare function requestRuntimeAccessTokenRefresh(): Promise<{
75
75
  updated?: boolean;
76
76
  error?: string;
77
77
  }>;
78
+ export declare function requestRuntimeAccessTokenRefreshForServer(serverBaseUrl: string): Promise<{
79
+ success: boolean;
80
+ runtimeAccessToken?: string;
81
+ updated?: boolean;
82
+ error?: string;
83
+ }>;
78
84
  export declare function unregister(): Promise<boolean>;
79
85
  /**
80
86
  * 获取注册状态
@@ -1 +1 @@
1
- {"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA+CH;;GAEG;AACH,UAAU,kBAAkB;IAC1B,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwBD,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAEtD;AA6DD;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,CAGtD;AAyJD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CA2C/C;AAED,wBAAgB,WAAW,IAAI,kBAAkB,EAAE,CA0ClD;AA4OD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACzC,OAAO,CAAC,OAAO,CAAC,CAalB;AAyHD;;GAEG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAqED;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBnD;AAED;;GAEG;AACH,wBAAgB,oBAAoB;gBAlzBtB,OAAO;iBACN,MAAM;mBACJ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;kBACvB,IAAI;YACV,MAAM;EAgzBf;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA2ED"}
1
+ {"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAiDH;;GAEG;AACH,UAAU,kBAAkB;IAC1B,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,oBAAoB;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwBD,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAEtD;AAiED;;GAEG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,CAGtD;AAyJD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CA2C/C;AAED,wBAAgB,WAAW,IAAI,kBAAkB,EAAE,CA0ClD;AA4OD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACzC,OAAO,CAAC,OAAO,CAAC,CAalB;AAyHD;;GAEG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC;IAChE,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAqED;AAED,wBAAsB,yCAAyC,CAC7D,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA2ED;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBnD;AAED;;GAEG;AACH,wBAAgB,oBAAoB;gBA14BtB,OAAO;iBACN,MAAM;mBACJ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;kBACvB,IAAI;YACV,MAAM;EAw4Bf;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CA2ED"}
@@ -13,7 +13,7 @@ import path from "node:path";
13
13
  import fs from "node:fs";
14
14
  import { logger } from "../utils/logger.js";
15
15
  import { schedulerBaseUrl, runtimeToken } from "../config.js";
16
- import { getRuntimeAccessToken, getRuntimeBindingPublicState, loadRuntimeBinding, saveRuntimeBinding, } from "./runtime-binding.js";
16
+ import { getRuntimeAccessToken, getRuntimeBindingPublicState, loadRuntimeBinding, normalizeSchedulerBaseUrl, saveRuntimeBinding, saveScopedRuntimeAccessToken, } from "./runtime-binding.js";
17
17
  // 默认配置
18
18
  const DEFAULT_CONFIG = {
19
19
  enabled: false,
@@ -37,6 +37,9 @@ function normalizeOptionalString(value) {
37
37
  const normalized = String(value).trim();
38
38
  return normalized || undefined;
39
39
  }
40
+ function normalizeSchedulerHttpBaseUrl(value) {
41
+ return normalizeSchedulerBaseUrl(value);
42
+ }
40
43
  function normalizeOptionalBoolean(value) {
41
44
  if (typeof value === "boolean") {
42
45
  return value;
@@ -649,6 +652,65 @@ export async function requestRuntimeAccessTokenRefresh() {
649
652
  return { success: false, error: message };
650
653
  }
651
654
  }
655
+ export async function requestRuntimeAccessTokenRefreshForServer(serverBaseUrl) {
656
+ const targetServerUrl = normalizeSchedulerHttpBaseUrl(serverBaseUrl);
657
+ if (!targetServerUrl) {
658
+ return { success: false, error: "serverUrl is required for runtime token refresh" };
659
+ }
660
+ const config = loadConfigs().find((item) => normalizeSchedulerHttpBaseUrl(item.serverUrl) === targetServerUrl) || loadPrimaryLifecycleConfig();
661
+ if (!config.userKey) {
662
+ return {
663
+ success: false,
664
+ error: "userKey is required for runtime token refresh",
665
+ };
666
+ }
667
+ const state = getRuntimeBindingPublicState();
668
+ const localIp = getLocalIpAddress(targetServerUrl);
669
+ const runtimeBridgePort = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
670
+ const runtimeBridgeBaseUrl = `http://${config.registerIp || config.virtualIp || localIp}:${runtimeBridgePort}`;
671
+ try {
672
+ const response = await axios.post(`${targetServerUrl}/api/instances/runtime-tokens/refresh`, {
673
+ tenantId: config.tenantId,
674
+ userKey: config.userKey,
675
+ instanceId: state.instanceId,
676
+ instanceName: config.instanceName,
677
+ runtimeBridgeBaseUrl,
678
+ }, {
679
+ headers: {
680
+ "Content-Type": "application/json",
681
+ "X-Runtime-Token": runtimeToken,
682
+ },
683
+ timeout: 10000,
684
+ });
685
+ if (!response.data.success || !response.data.runtimeAccessToken) {
686
+ return {
687
+ success: false,
688
+ error: response.data.message ||
689
+ "scheduler did not return runtimeAccessToken",
690
+ };
691
+ }
692
+ const userId = response.data.userId || config.userKey;
693
+ const previousToken = getRuntimeAccessToken(userId, targetServerUrl);
694
+ const updated = previousToken !== response.data.runtimeAccessToken;
695
+ saveScopedRuntimeAccessToken({
696
+ userId,
697
+ serverBaseUrl: targetServerUrl,
698
+ accessToken: response.data.runtimeAccessToken,
699
+ });
700
+ return {
701
+ success: true,
702
+ runtimeAccessToken: response.data.runtimeAccessToken,
703
+ updated,
704
+ };
705
+ }
706
+ catch (error) {
707
+ const err = error;
708
+ const message = err.response?.data
709
+ ? JSON.stringify(err.response.data)
710
+ : err.message;
711
+ return { success: false, error: message };
712
+ }
713
+ }
652
714
  export async function unregister() {
653
715
  if (!registrationState.registered || !registrationState.instanceId) {
654
716
  logger.info("[AutoRegister] 实例未注册,无需注销");
@@ -1 +1 @@
1
- {"version":3,"file":"aws-client-agent-mcp.d.ts","sourceRoot":"","sources":["../../src/services/aws-client-agent-mcp.ts"],"names":[],"mappings":"AAwBA,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAuB7C,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,sCAAsC;IACrD,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,sCAAsC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAaD,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC3C;AAuBD,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAgLD,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,MAAM,GAAG,IAAI,CAoDf;AA2ED,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CAEpB;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CAsBpB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,GAC7B,kBAAkB,CAwBpB;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAgBtD"}
1
+ {"version":3,"file":"aws-client-agent-mcp.d.ts","sourceRoot":"","sources":["../../src/services/aws-client-agent-mcp.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAsB7C,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,sCAAsC;IACrD,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,sCAAsC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAaD,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC3C;AAuBD,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAgLD,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,MAAM,GAAG,IAAI,CAoDf;AA2ED,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CAEpB;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CAsBpB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,GAC7B,kBAAkB,CA0BpB;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAgBtD"}
@@ -4,10 +4,9 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { getRuntimeHomeDir, port, schedulerBaseUrl } from "../config.js";
6
6
  import { logger } from "../utils/logger.js";
7
- import { getRuntimeAccessToken, loadRuntimeBinding, } from "./runtime-binding.js";
7
+ import { getRuntimeAccessToken, loadRuntimeBinding, normalizeSchedulerBaseUrl, } from "./runtime-binding.js";
8
8
  export const AWS_MCP_SERVER_NAME = "aws-mcp";
9
9
  const AWS_MCP_ALLOWED_ENV_KEYS = [
10
- "AWS_INTERNAL_API_KEY",
11
10
  "AWS_PROJECT_NAME",
12
11
  "AWS_PROMPT",
13
12
  "AWS_ROLE_NAME",
@@ -236,7 +235,7 @@ function parseAwsClientAgentMcpArgs(raw) {
236
235
  return [];
237
236
  }
238
237
  function toWebSocketUrl(baseUrl) {
239
- const url = new URL("/ws/agent", baseUrl);
238
+ const url = new URL("/ws/agent", normalizeSchedulerBaseUrl(baseUrl) || baseUrl);
240
239
  if (url.protocol === "https:") {
241
240
  url.protocol = "wss:";
242
241
  }
@@ -246,7 +245,7 @@ function toWebSocketUrl(baseUrl) {
246
245
  return url.toString();
247
246
  }
248
247
  function toMcpHttpUrl(baseUrl) {
249
- return new URL("/mcp/call", baseUrl).toString();
248
+ return new URL("/mcp/call", normalizeSchedulerBaseUrl(baseUrl) || baseUrl).toString();
250
249
  }
251
250
  function resolveSchedulerBaseUrlForMcp() {
252
251
  const envSchedulerBaseUrl = String(process.env.AWS_RUNTIME_SCHEDULER_BASE_URL || "").trim();
@@ -297,6 +296,7 @@ export function buildAwsMcpServerConfig(input) {
297
296
  AWS_SERVER_URL: env.AWS_SERVER_URL || toWebSocketUrl(effectiveSchedulerBaseUrl),
298
297
  AWS_MCP_HTTP_URL: env.AWS_MCP_HTTP_URL || toMcpHttpUrl(effectiveSchedulerBaseUrl),
299
298
  AWS_RUNTIME_BRIDGE_BASE_URL: env.AWS_RUNTIME_BRIDGE_BASE_URL || `http://127.0.0.1:${port}`,
299
+ AWS_MCP_CLAIM_LAUNCH_BINDING: env.AWS_MCP_CLAIM_LAUNCH_BINDING || "true",
300
300
  ...(issuedRuntimeAccessToken
301
301
  ? { AWS_RUNTIME_ACCESS_TOKEN: issuedRuntimeAccessToken }
302
302
  : {}),
@@ -216,6 +216,20 @@ describe('aws-client-agent-mcp service', () => {
216
216
  expect(config.env.AWS_RUNTIME_CALLBACK_TOKEN).toBeUndefined();
217
217
  expect(config.env.CUSTOM_SECRET).toBeUndefined();
218
218
  });
219
+ it('repairs duplicated scheduler ports when building MCP URLs', async () => {
220
+ process.env.AWS_CLIENT_AGENT_MCP_COMMAND = 'aws-client-agent-mcp';
221
+ process.env.AWS_RUNTIME_HOME_DIR = mkdtempSync(path.join(os.tmpdir(), 'aws-mcp-config-'));
222
+ process.env.AWS_RUNTIME_SCHEDULER_BASE_URL = 'http://127.0.0.1:8080:8080';
223
+ process.env.AWS_SERVER_URL = '';
224
+ process.env.AWS_MCP_HTTP_URL = '';
225
+ const mod = await import('./aws-client-agent-mcp.js');
226
+ const config = mod.buildAwsMcpServerConfig({
227
+ agentId: 'agent-1',
228
+ workspacePath: '/workspace/demo',
229
+ });
230
+ expect(config.env.AWS_SERVER_URL).toBe('ws://127.0.0.1:8080/ws/agent');
231
+ expect(config.env.AWS_MCP_HTTP_URL).toBe('http://127.0.0.1:8080/mcp/call');
232
+ });
219
233
  it('ignores malformed AWS_CLIENT_AGENT_MCP_ARGS JSON', async () => {
220
234
  process.env.AWS_CLIENT_AGENT_MCP_COMMAND = 'custom-aws-client-agent-mcp';
221
235
  process.env.AWS_CLIENT_AGENT_MCP_ARGS = '{bad json';
@@ -24,8 +24,6 @@ export interface ClaimedMcpLaunchBinding extends McpLaunchBinding {
24
24
  schedulerBaseUrl?: string;
25
25
  }
26
26
  export interface McpLaunchBindingClaimRequest {
27
- agentId: unknown;
28
- bindingId?: unknown;
29
27
  workspacePath: unknown;
30
28
  serverUrl?: unknown;
31
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-launch-binding-queue.d.ts","sourceRoot":"","sources":["../../src/services/mcp-launch-binding-queue.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAqCD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,GAAG,IAAI,CAoB7F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,uBAAuB,GAAG,IAAI,CAoC3G;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,MAAM,CAMpE;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
1
+ {"version":3,"file":"mcp-launch-binding-queue.d.ts","sourceRoot":"","sources":["../../src/services/mcp-launch-binding-queue.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,4BAA4B;IAC3C,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAmDD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,GAAG,IAAI,CA+B7F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,uBAAuB,GAAG,IAAI,CA0C3G;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,MAAM,CAOpE;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
@@ -1,5 +1,7 @@
1
1
  import path from "node:path";
2
- import { getRuntimeAccessToken, loadRuntimeBinding } from "./runtime-binding.js";
2
+ import { schedulerBaseUrl as defaultSchedulerBaseUrl } from "../config.js";
3
+ import { getRuntimeAccessToken, loadRuntimeBinding, normalizeSchedulerBaseUrl } from "./runtime-binding.js";
4
+ const LAUNCH_BINDING_TTL_MS = 5 * 60 * 1000;
3
5
  const launchBindings = [];
4
6
  function normalizeWorkspacePath(workspacePath) {
5
7
  const raw = String(workspacePath || "").trim();
@@ -11,57 +13,82 @@ function normalizeWorkspacePath(workspacePath) {
11
13
  function normalizeOptionalUrl(value) {
12
14
  return String(value || "").trim();
13
15
  }
14
- function normalizeServerUrl(serverUrl) {
15
- const raw = String(serverUrl || "").trim();
16
+ function normalizeSchedulerOrigin(serverUrl) {
17
+ const raw = normalizeSchedulerBaseUrl(serverUrl) || String(serverUrl || "").trim();
16
18
  if (!raw) {
17
19
  return "";
18
20
  }
19
21
  try {
20
22
  const url = new URL(raw);
21
- url.protocol = url.protocol.toLowerCase();
23
+ if (url.protocol === "ws:") {
24
+ url.protocol = "http:";
25
+ }
26
+ else if (url.protocol === "wss:") {
27
+ url.protocol = "https:";
28
+ }
29
+ else {
30
+ url.protocol = url.protocol.toLowerCase();
31
+ }
22
32
  url.hostname = url.hostname.toLowerCase();
23
- url.pathname = url.pathname.replace(/\/+$/, "");
24
- return url.toString().replace(/\/+$/, "");
33
+ return url.origin;
25
34
  }
26
35
  catch {
27
36
  return raw.replace(/\/+$/, "").toLowerCase();
28
37
  }
29
38
  }
39
+ function pruneExpiredBindings(now = Date.now()) {
40
+ for (let index = launchBindings.length - 1; index >= 0; index -= 1) {
41
+ const enqueuedAt = Date.parse(launchBindings[index]?.enqueuedAt || "");
42
+ if (!Number.isFinite(enqueuedAt) || now - enqueuedAt > LAUNCH_BINDING_TTL_MS) {
43
+ launchBindings.splice(index, 1);
44
+ }
45
+ }
46
+ }
30
47
  function createBindingId(agentId) {
31
48
  return `${agentId}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
32
49
  }
33
50
  export function enqueueMcpLaunchBinding(input) {
51
+ pruneExpiredBindings();
34
52
  const agentId = String(input.agentId || "").trim();
35
53
  const workspaceKey = normalizeWorkspacePath(input.workspacePath);
36
54
  if (!agentId || !workspaceKey) {
37
55
  return null;
38
56
  }
57
+ const runtimeState = loadRuntimeBinding();
58
+ const schedulerUrl = normalizeSchedulerBaseUrl(input.serverUrl || input.schedulerBaseUrl || runtimeState.schedulerBaseUrl || defaultSchedulerBaseUrl) || normalizeOptionalUrl(input.serverUrl || input.schedulerBaseUrl || runtimeState.schedulerBaseUrl || defaultSchedulerBaseUrl);
59
+ const schedulerBindingBaseUrl = normalizeSchedulerBaseUrl(input.schedulerBaseUrl || runtimeState.schedulerBaseUrl || defaultSchedulerBaseUrl) || normalizeOptionalUrl(input.schedulerBaseUrl || runtimeState.schedulerBaseUrl || defaultSchedulerBaseUrl);
39
60
  const binding = {
40
61
  id: createBindingId(agentId),
41
62
  agentId,
42
63
  workspacePath: path.resolve(String(input.workspacePath)),
43
- serverUrl: normalizeOptionalUrl(input.serverUrl || input.schedulerBaseUrl),
64
+ serverUrl: schedulerUrl,
44
65
  mcpHttpUrl: normalizeOptionalUrl(input.mcpHttpUrl),
45
66
  runtimeAccessToken: normalizeOptionalUrl(input.runtimeAccessToken),
46
67
  userId: normalizeOptionalUrl(input.userId),
47
- schedulerBaseUrl: normalizeOptionalUrl(input.schedulerBaseUrl),
68
+ schedulerBaseUrl: schedulerBindingBaseUrl,
48
69
  enqueuedAt: new Date().toISOString(),
49
70
  };
50
71
  launchBindings.push(binding);
51
72
  return binding;
52
73
  }
53
74
  export function claimMcpLaunchBinding(request) {
54
- const agentId = String(request.agentId || "").trim();
55
- const bindingId = String(request.bindingId || "").trim();
75
+ pruneExpiredBindings();
56
76
  const workspaceKey = normalizeWorkspacePath(request.workspacePath);
57
- const serverKey = normalizeServerUrl(request.serverUrl);
58
- if (!agentId || !bindingId || !workspaceKey || !serverKey) {
77
+ const serverKey = normalizeSchedulerOrigin(request.serverUrl);
78
+ if (!workspaceKey) {
59
79
  return null;
60
80
  }
61
- const bindingIndex = launchBindings.findIndex((binding) => binding.agentId === agentId &&
62
- binding.id === bindingId &&
63
- normalizeWorkspacePath(binding.workspacePath) === workspaceKey &&
64
- normalizeServerUrl(binding.serverUrl || binding.schedulerBaseUrl) === serverKey);
81
+ let bindingIndex = launchBindings.findIndex((binding) => normalizeWorkspacePath(binding.workspacePath) === workspaceKey &&
82
+ serverKey &&
83
+ normalizeSchedulerOrigin(binding.serverUrl || binding.schedulerBaseUrl) === serverKey);
84
+ if (bindingIndex < 0) {
85
+ const workspaceMatches = launchBindings
86
+ .map((binding, index) => ({ binding, index }))
87
+ .filter(({ binding }) => normalizeWorkspacePath(binding.workspacePath) === workspaceKey);
88
+ if (workspaceMatches.length === 1) {
89
+ bindingIndex = workspaceMatches[0]?.index ?? -1;
90
+ }
91
+ }
65
92
  if (bindingIndex < 0) {
66
93
  return null;
67
94
  }
@@ -81,6 +108,7 @@ export function claimMcpLaunchBinding(request) {
81
108
  };
82
109
  }
83
110
  export function getMcpLaunchQueueSize(workspacePath) {
111
+ pruneExpiredBindings();
84
112
  const workspaceKey = normalizeWorkspacePath(workspacePath);
85
113
  if (!workspaceKey) {
86
114
  return 0;
@@ -4,7 +4,7 @@ describe('mcp launch binding queue', () => {
4
4
  afterEach(() => {
5
5
  clearMcpLaunchBindingQueues();
6
6
  });
7
- it('claims same-workspace bindings only for the requested agent identity', () => {
7
+ it('claims same-workspace same-server bindings from the front of the queue', () => {
8
8
  const firstBinding = enqueueMcpLaunchBinding({
9
9
  agentId: 'agent-a',
10
10
  workspacePath: 'C:/repo/demo',
@@ -22,15 +22,11 @@ describe('mcp launch binding queue', () => {
22
22
  schedulerBaseUrl: 'http://server-a:7380',
23
23
  });
24
24
  expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(2);
25
- const second = claimMcpLaunchBinding({
26
- agentId: 'agent-b',
27
- bindingId: secondBinding?.id,
25
+ const first = claimMcpLaunchBinding({
28
26
  workspacePath: 'C:/repo/demo',
29
27
  serverUrl: 'ws://server-a:7380/ws/agent',
30
28
  });
31
- const first = claimMcpLaunchBinding({
32
- agentId: 'agent-a',
33
- bindingId: firstBinding?.id,
29
+ const second = claimMcpLaunchBinding({
34
30
  workspacePath: 'C:/repo/demo',
35
31
  serverUrl: 'ws://server-a:7380/ws/agent',
36
32
  });
@@ -40,68 +36,77 @@ describe('mcp launch binding queue', () => {
40
36
  expect(second?.runtimeAccessToken).toBe('token-b');
41
37
  expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(0);
42
38
  });
43
- it('does not consume a binding when the requested agent id does not match', () => {
39
+ it('claims by workspace path and server URL regardless of agent id', () => {
44
40
  const binding = enqueueMcpLaunchBinding({
45
41
  agentId: 'agent-a',
46
42
  workspacePath: 'C:/repo/demo',
47
43
  serverUrl: 'ws://server:7380/ws/agent',
48
44
  });
49
45
  expect(claimMcpLaunchBinding({
50
- agentId: 'agent-b',
51
- bindingId: binding?.id,
52
- workspacePath: 'C:/repo/demo',
53
- serverUrl: 'ws://server:7380/ws/agent',
54
- })).toBeNull();
55
- expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(1);
56
- expect(claimMcpLaunchBinding({
57
- agentId: 'agent-a',
58
- bindingId: binding?.id,
59
46
  workspacePath: 'C:/repo/demo',
60
47
  serverUrl: 'ws://server:7380/ws/agent',
61
48
  })?.agentId).toBe('agent-a');
49
+ expect(binding?.agentId).toBe('agent-a');
62
50
  });
63
- it('does not consume a binding when the binding id is missing or wrong', () => {
51
+ it('ignores explicit binding id and claims the first matching queue entry', () => {
64
52
  const binding = enqueueMcpLaunchBinding({
65
53
  agentId: 'agent-a',
66
54
  workspacePath: 'C:/repo/demo',
67
55
  serverUrl: 'ws://server:7380/ws/agent',
68
56
  });
69
57
  expect(claimMcpLaunchBinding({
70
- agentId: 'agent-a',
71
58
  workspacePath: 'C:/repo/demo',
72
59
  serverUrl: 'ws://server:7380/ws/agent',
73
- })).toBeNull();
60
+ })?.agentId).toBe('agent-a');
61
+ expect(binding?.id).toBeTruthy();
62
+ });
63
+ it('matches by server URL as well as workspace path', () => {
64
+ const firstBinding = enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' });
65
+ const secondBinding = enqueueMcpLaunchBinding({ agentId: 'agent-b', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' });
66
+ expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' })?.agentId).toBe('agent-b');
67
+ expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' })?.agentId).toBe('agent-a');
68
+ });
69
+ it('matches http scheduler URLs and websocket agent URLs by scheduler origin', () => {
70
+ enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/demo', schedulerBaseUrl: 'http://server-a:7380' });
74
71
  expect(claimMcpLaunchBinding({
72
+ workspacePath: 'C:/repo/demo',
73
+ serverUrl: 'ws://server-a:7380/ws/agent',
74
+ })?.agentId).toBe('agent-a');
75
+ });
76
+ it('repairs duplicated scheduler ports when enqueuing launch bindings', () => {
77
+ const binding = enqueueMcpLaunchBinding({
75
78
  agentId: 'agent-a',
76
- bindingId: 'wrong-binding',
77
79
  workspacePath: 'C:/repo/demo',
78
- serverUrl: 'ws://server:7380/ws/agent',
79
- })).toBeNull();
80
- expect(getMcpLaunchQueueSize('C:/repo/demo')).toBe(1);
80
+ schedulerBaseUrl: 'http://127.0.0.1:8080:8080',
81
+ });
82
+ expect(binding?.serverUrl).toBe('http://127.0.0.1:8080');
83
+ expect(binding?.schedulerBaseUrl).toBe('http://127.0.0.1:8080');
81
84
  expect(claimMcpLaunchBinding({
85
+ workspacePath: 'C:/repo/demo',
86
+ serverUrl: 'ws://127.0.0.1:8080/ws/agent',
87
+ })?.schedulerBaseUrl).toBe('http://127.0.0.1:8080');
88
+ });
89
+ it('falls back to the only same-workspace binding when scheduler URLs differ', () => {
90
+ enqueueMcpLaunchBinding({
82
91
  agentId: 'agent-a',
83
- bindingId: binding?.id,
84
92
  workspacePath: 'C:/repo/demo',
85
- serverUrl: 'ws://server:7380/ws/agent',
93
+ serverUrl: 'ws://127.0.0.1:7380/ws/agent',
94
+ });
95
+ expect(claimMcpLaunchBinding({
96
+ workspacePath: 'C:/repo/demo',
97
+ serverUrl: 'ws://101.42.200.84:7380/ws/agent',
86
98
  })?.agentId).toBe('agent-a');
87
99
  });
88
- it('matches by server URL as well as workspace path', () => {
89
- const firstBinding = enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' });
90
- const secondBinding = enqueueMcpLaunchBinding({ agentId: 'agent-b', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' });
91
- expect(claimMcpLaunchBinding({ agentId: 'agent-b', bindingId: secondBinding?.id, workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-b:7380/ws/agent' })?.agentId).toBe('agent-b');
92
- expect(claimMcpLaunchBinding({ agentId: 'agent-a', bindingId: firstBinding?.id, workspacePath: 'C:/repo/demo', serverUrl: 'ws://server-a:7380/ws/agent' })?.agentId).toBe('agent-a');
93
- });
94
100
  it('does not mix different workspace paths', () => {
95
101
  const firstBinding = enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/a', serverUrl: 'ws://server:7380/ws/agent' });
96
102
  const secondBinding = enqueueMcpLaunchBinding({ agentId: 'agent-b', workspacePath: 'C:/repo/b', serverUrl: 'ws://server:7380/ws/agent' });
97
- expect(claimMcpLaunchBinding({ agentId: 'agent-b', bindingId: secondBinding?.id, workspacePath: 'C:/repo/b', serverUrl: 'ws://server:7380/ws/agent' })?.agentId).toBe('agent-b');
98
- expect(claimMcpLaunchBinding({ agentId: 'agent-a', bindingId: firstBinding?.id, workspacePath: 'C:/repo/a', serverUrl: 'ws://server:7380/ws/agent' })?.agentId).toBe('agent-a');
103
+ expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/b', serverUrl: 'ws://server:7380/ws/agent' })?.agentId).toBe('agent-b');
104
+ expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/a', serverUrl: 'ws://server:7380/ws/agent' })?.agentId).toBe('agent-a');
99
105
  });
100
106
  it('returns null for missing or invalid workspaces', () => {
101
107
  expect(enqueueMcpLaunchBinding({ agentId: '', workspacePath: 'C:/repo/demo' })).toBeNull();
102
108
  expect(enqueueMcpLaunchBinding({ agentId: 'agent-a', workspacePath: '' })).toBeNull();
103
- expect(claimMcpLaunchBinding({ agentId: 'agent-a', workspacePath: 'C:/repo/missing', serverUrl: 'ws://server:7380/ws/agent' })).toBeNull();
104
- expect(claimMcpLaunchBinding({ agentId: '', workspacePath: 'C:/repo/demo', serverUrl: 'ws://server:7380/ws/agent' })).toBeNull();
105
- expect(claimMcpLaunchBinding({ agentId: 'agent-a', bindingId: 'binding-a', workspacePath: 'C:/repo/demo' })).toBeNull();
109
+ expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/missing', serverUrl: 'ws://server:7380/ws/agent' })).toBeNull();
110
+ expect(claimMcpLaunchBinding({ workspacePath: 'C:/repo/demo' })).toBeNull();
106
111
  });
107
112
  });
@@ -14,6 +14,7 @@ export type RuntimeBindingState = {
14
14
  updatedAt?: string;
15
15
  revokedAt?: string;
16
16
  };
17
+ export declare function normalizeSchedulerBaseUrl(value: unknown): string | undefined;
17
18
  export declare function getRuntimePairingCode(): string;
18
19
  export declare function getRuntimeBindingFilePath(): string;
19
20
  export declare function getRuntimeTokenStoreFilePath(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAoDF,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAoBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,CAYR;AAsCD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAWT;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,GAAG,SAAS,CAUpB;AAcD,wBAAgB,kBAAkB,IAAI,mBAAmB,CAWxD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAClD,mBAAmB,EACnB,WAAW,GAAG,aAAa,CAC5B,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAWtB;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAcnE;AAED,wBAAgB,qBAAqB,CACnC,MAAM,CAAC,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,OAAO,GACtB,MAAM,GAAG,SAAS,CAWpB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAEjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,mBAAmB,CAuCtB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAkB1C"}
1
+ {"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA2DF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAqB5E;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAoBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,CAYR;AAsCD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAWT;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,OAAO,EACf,aAAa,EAAE,OAAO,GACrB,MAAM,GAAG,SAAS,CAUpB;AAcD,wBAAgB,kBAAkB,IAAI,mBAAmB,CAgBxD;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAClD,mBAAmB,EACnB,WAAW,GAAG,aAAa,CAC5B,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAWtB;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAcnE;AAED,wBAAgB,qBAAqB,CACnC,MAAM,CAAC,EAAE,OAAO,EAChB,aAAa,CAAC,EAAE,OAAO,GACtB,MAAM,GAAG,SAAS,CAepB;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAEjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,mBAAmB,CAwCtB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAkB1C"}
@@ -46,6 +46,33 @@ function safeTextEqual(a, b) {
46
46
  function normalizeToken(token) {
47
47
  return String(token || "").trim();
48
48
  }
49
+ function repairDuplicatePort(raw) {
50
+ return raw.replace(/^((?:https?|wss?):\/\/(?:\[[^\]]+\]|[^/:?#]+):\d+):\d+(?=\/|$)/i, "$1");
51
+ }
52
+ export function normalizeSchedulerBaseUrl(value) {
53
+ const raw = normalizeToken(value);
54
+ if (!raw) {
55
+ return undefined;
56
+ }
57
+ const repaired = repairDuplicatePort(raw);
58
+ try {
59
+ const url = new URL(repaired);
60
+ if (url.protocol === "ws:") {
61
+ url.protocol = "http:";
62
+ }
63
+ else if (url.protocol === "wss:") {
64
+ url.protocol = "https:";
65
+ }
66
+ else {
67
+ url.protocol = url.protocol.toLowerCase();
68
+ }
69
+ url.hostname = url.hostname.toLowerCase();
70
+ return url.origin;
71
+ }
72
+ catch {
73
+ return repaired.replace(/\/+$/, "");
74
+ }
75
+ }
49
76
  export function getRuntimePairingCode() {
50
77
  return pairingCode;
51
78
  }
@@ -56,7 +83,7 @@ export function getRuntimeTokenStoreFilePath() {
56
83
  return tokenStoreFile();
57
84
  }
58
85
  function normalizeServerIp(serverBaseUrl) {
59
- const raw = String(serverBaseUrl || "").trim();
86
+ const raw = normalizeSchedulerBaseUrl(serverBaseUrl) || String(serverBaseUrl || "").trim();
60
87
  if (!raw) {
61
88
  return "";
62
89
  }
@@ -157,7 +184,11 @@ export function loadRuntimeBinding() {
157
184
  const raw = fs.readFileSync(bindingFile(), "utf8");
158
185
  const parsed = JSON.parse(raw);
159
186
  if (parsed?.status === "paired" && parsed.tokenHash) {
160
- return parsed;
187
+ return {
188
+ ...parsed,
189
+ schedulerBaseUrl: normalizeSchedulerBaseUrl(parsed.schedulerBaseUrl) ||
190
+ parsed.schedulerBaseUrl,
191
+ };
161
192
  }
162
193
  }
163
194
  catch {
@@ -195,6 +226,9 @@ export function getRuntimeAccessToken(userId, serverBaseUrl) {
195
226
  if (scopedToken) {
196
227
  return scopedToken;
197
228
  }
229
+ if (serverBaseUrl) {
230
+ return undefined;
231
+ }
198
232
  const state = loadRuntimeBinding();
199
233
  if (state.status !== "paired") {
200
234
  return undefined;
@@ -210,6 +244,8 @@ export function saveRuntimeBinding(input) {
210
244
  throw new Error("accessToken must be at least 16 characters");
211
245
  }
212
246
  const previous = loadRuntimeBinding();
247
+ const schedulerBaseUrl = normalizeSchedulerBaseUrl(input.schedulerBaseUrl) ||
248
+ normalizeSchedulerBaseUrl(previous.schedulerBaseUrl);
213
249
  const now = new Date().toISOString();
214
250
  const next = {
215
251
  status: "paired",
@@ -217,9 +253,7 @@ export function saveRuntimeBinding(input) {
217
253
  ? String(input.instanceId)
218
254
  : previous.instanceId,
219
255
  userId: input.userId ? String(input.userId) : previous.userId,
220
- schedulerBaseUrl: input.schedulerBaseUrl
221
- ? String(input.schedulerBaseUrl)
222
- : previous.schedulerBaseUrl,
256
+ schedulerBaseUrl,
223
257
  accessToken,
224
258
  tokenHash: hashToken(accessToken),
225
259
  createdAt: previous.createdAt || now,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runtime-binding.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-binding.test.d.ts","sourceRoot":"","sources":["../../src/services/runtime-binding.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { normalizeSchedulerBaseUrl } from './runtime-binding.js';
3
+ describe('runtime binding scheduler URL normalization', () => {
4
+ it('repairs duplicated scheduler ports', () => {
5
+ expect(normalizeSchedulerBaseUrl('http://127.0.0.1:8080:8080')).toBe('http://127.0.0.1:8080');
6
+ });
7
+ it('normalizes websocket agent URLs to scheduler HTTP origins', () => {
8
+ expect(normalizeSchedulerBaseUrl('ws://127.0.0.1:8080/ws/agent')).toBe('http://127.0.0.1:8080');
9
+ expect(normalizeSchedulerBaseUrl('wss://example.com/ws/agent')).toBe('https://example.com');
10
+ });
11
+ });