aws-runtime-bridge 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +40 -37
- package/dist/index.js +33 -27
- package/dist/middleware/auth.d.ts +4 -2
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +36 -6
- package/dist/routes/runtime-binding.d.ts +3 -0
- package/dist/routes/runtime-binding.d.ts.map +1 -0
- package/dist/routes/runtime-binding.js +147 -0
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +2 -7
- package/dist/routes/terminal.test.js +0 -4
- package/dist/services/auto-register.d.ts +6 -0
- package/dist/services/auto-register.d.ts.map +1 -1
- package/dist/services/auto-register.js +123 -43
- package/dist/services/aws-client-agent-mcp.d.ts +1 -1
- package/dist/services/aws-client-agent-mcp.d.ts.map +1 -1
- package/dist/services/aws-client-agent-mcp.js +81 -49
- package/dist/services/runtime-binding.d.ts +32 -0
- package/dist/services/runtime-binding.d.ts.map +1 -0
- package/dist/services/runtime-binding.js +131 -0
- package/package.json +1 -1
|
@@ -55,6 +55,12 @@ export declare function autoRegister(customConfig?: Partial<AutoRegisterConfig>)
|
|
|
55
55
|
/**
|
|
56
56
|
* 注销实例
|
|
57
57
|
*/
|
|
58
|
+
export declare function requestRuntimeAccessTokenRefresh(): Promise<{
|
|
59
|
+
success: boolean;
|
|
60
|
+
runtimeAccessToken?: string;
|
|
61
|
+
updated?: boolean;
|
|
62
|
+
error?: string;
|
|
63
|
+
}>;
|
|
58
64
|
export declare function unregister(): Promise<boolean>;
|
|
59
65
|
/**
|
|
60
66
|
* 获取注册状态
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"auto-register.d.ts","sourceRoot":"","sources":["../../src/services/auto-register.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA4CH;;GAEG;AACH,UAAU,kBAAkB;IAC1B,eAAe;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,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,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAiKD;;;;;;GAMG;AACH,wBAAgB,UAAU,IAAI,kBAAkB,CAuC/C;AAuLD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,YAAY,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,GACzC,OAAO,CAAC,OAAO,CAAC,CAmHlB;AAED;;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,CAoED;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAuBnD;AAED;;GAEG;AACH,wBAAgB,oBAAoB;gBAxmBtB,OAAO;iBACN,MAAM;kBACL,IAAI;YACV,MAAM;EAumBf;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,CA0ED"}
|
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
* 1. 环境变量
|
|
8
8
|
* 2. 用户目录下的 .aws-bridge/config.json 文件
|
|
9
9
|
*/
|
|
10
|
-
import axios from
|
|
11
|
-
import os from
|
|
12
|
-
import path from
|
|
13
|
-
import fs from
|
|
14
|
-
import { logger } from
|
|
15
|
-
import { schedulerBaseUrl, runtimeToken } from
|
|
10
|
+
import axios from "axios";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import { logger } from "../utils/logger.js";
|
|
15
|
+
import { schedulerBaseUrl, runtimeToken } from "../config.js";
|
|
16
|
+
import { getRuntimeAccessToken, getRuntimeBindingPublicState, saveRuntimeBinding, } from "./runtime-binding.js";
|
|
16
17
|
// 默认配置
|
|
17
18
|
const DEFAULT_CONFIG = {
|
|
18
19
|
enabled: false,
|
|
@@ -27,7 +28,7 @@ let registrationState = {
|
|
|
27
28
|
*/
|
|
28
29
|
function getConfigFilePath() {
|
|
29
30
|
const homeDir = os.homedir();
|
|
30
|
-
return path.join(homeDir,
|
|
31
|
+
return path.join(homeDir, ".aws-bridge", "config.json");
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* 确保配置文件存在,如果不存在则创建空配置文件
|
|
@@ -46,7 +47,7 @@ function ensureConfigFile() {
|
|
|
46
47
|
}
|
|
47
48
|
// 创建空配置文件
|
|
48
49
|
const emptyConfig = {};
|
|
49
|
-
fs.writeFileSync(configPath, JSON.stringify(emptyConfig, null, 2),
|
|
50
|
+
fs.writeFileSync(configPath, JSON.stringify(emptyConfig, null, 2), "utf-8");
|
|
50
51
|
logger.info(`[AutoRegister] 已创建配置文件: ${configPath}`);
|
|
51
52
|
logger.info(`[AutoRegister] 要启用自动注册,请编辑配置文件设置 tenantId 和 userKey`);
|
|
52
53
|
return true;
|
|
@@ -84,7 +85,7 @@ function loadConfigFromFile() {
|
|
|
84
85
|
logger.debug(`[AutoRegister] 配置文件不存在: ${configPath}`);
|
|
85
86
|
return null;
|
|
86
87
|
}
|
|
87
|
-
const content = fs.readFileSync(configPath,
|
|
88
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
88
89
|
const config = JSON.parse(content);
|
|
89
90
|
logger.info(`[AutoRegister] 从配置文件加载配置: ${configPath}`);
|
|
90
91
|
return {
|
|
@@ -110,7 +111,7 @@ function loadConfigFromFile() {
|
|
|
110
111
|
*/
|
|
111
112
|
function loadConfigFromEnv() {
|
|
112
113
|
const envConfig = {};
|
|
113
|
-
if (process.env.AWS_AUTO_REGISTER ===
|
|
114
|
+
if (process.env.AWS_AUTO_REGISTER === "true") {
|
|
114
115
|
envConfig.enabled = true;
|
|
115
116
|
}
|
|
116
117
|
if (process.env.AWS_TENANT_ID) {
|
|
@@ -167,15 +168,15 @@ export function loadConfig() {
|
|
|
167
168
|
// 否则,如果配置文件存在(无论是否有内容),则启用自动注册
|
|
168
169
|
let enabled = false;
|
|
169
170
|
if (process.env.AWS_AUTO_REGISTER !== undefined) {
|
|
170
|
-
enabled = process.env.AWS_AUTO_REGISTER ===
|
|
171
|
+
enabled = process.env.AWS_AUTO_REGISTER === "true";
|
|
171
172
|
}
|
|
172
173
|
else if (fileConfig) {
|
|
173
174
|
enabled = true;
|
|
174
175
|
}
|
|
175
176
|
return {
|
|
176
177
|
enabled,
|
|
177
|
-
tenantId: merged.tenantId ||
|
|
178
|
-
userKey: merged.userKey ||
|
|
178
|
+
tenantId: merged.tenantId || "", // 保持为空字符串,后续会通过userKey自动推导
|
|
179
|
+
userKey: merged.userKey || "",
|
|
179
180
|
instanceName,
|
|
180
181
|
projectName: merged.projectName,
|
|
181
182
|
roleName: merged.roleName,
|
|
@@ -197,7 +198,7 @@ function getAllLocalIpAddresses() {
|
|
|
197
198
|
continue;
|
|
198
199
|
for (const net of nets) {
|
|
199
200
|
// 跳过内部和非 IPv4 地址
|
|
200
|
-
if (net.family ===
|
|
201
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
201
202
|
addresses.push({
|
|
202
203
|
address: net.address,
|
|
203
204
|
netmask: net.netmask,
|
|
@@ -212,7 +213,7 @@ function getAllLocalIpAddresses() {
|
|
|
212
213
|
* 将 IP 地址转换为整数
|
|
213
214
|
*/
|
|
214
215
|
function ipToInt(ip) {
|
|
215
|
-
const parts = ip.split(
|
|
216
|
+
const parts = ip.split(".").map(Number);
|
|
216
217
|
return (parts[0] << 24) + (parts[1] << 16) + (parts[2] << 8) + parts[3];
|
|
217
218
|
}
|
|
218
219
|
/**
|
|
@@ -231,8 +232,8 @@ function extractIpFromUrl(url) {
|
|
|
231
232
|
try {
|
|
232
233
|
const urlObj = new URL(url);
|
|
233
234
|
// 如果是主机名,尝试解析
|
|
234
|
-
if (urlObj.hostname ===
|
|
235
|
-
return
|
|
235
|
+
if (urlObj.hostname === "localhost" || urlObj.hostname === "127.0.0.1") {
|
|
236
|
+
return "127.0.0.1";
|
|
236
237
|
}
|
|
237
238
|
// 检查是否是有效的 IP 地址
|
|
238
239
|
const ipPattern = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
@@ -258,8 +259,8 @@ function extractIpFromUrl(url) {
|
|
|
258
259
|
function getPreferredIpAddress(schedulerUrl) {
|
|
259
260
|
const allIps = getAllLocalIpAddresses();
|
|
260
261
|
if (allIps.length === 0) {
|
|
261
|
-
logger.warn(
|
|
262
|
-
return
|
|
262
|
+
logger.warn("[AutoRegister] 未找到任何非内部 IP 地址,使用 127.0.0.1");
|
|
263
|
+
return "127.0.0.1";
|
|
263
264
|
}
|
|
264
265
|
// 尝试从调度中心 URL 提取 IP
|
|
265
266
|
const schedulerIp = extractIpFromUrl(schedulerUrl);
|
|
@@ -274,7 +275,7 @@ function getPreferredIpAddress(schedulerUrl) {
|
|
|
274
275
|
logger.warn(`[AutoRegister] 未找到与调度中心 (${schedulerIp}) 同子网的 IP,使用第一个可用 IP`);
|
|
275
276
|
}
|
|
276
277
|
else {
|
|
277
|
-
logger.info(
|
|
278
|
+
logger.info("[AutoRegister] 无法解析调度中心 IP,使用第一个可用 IP");
|
|
278
279
|
}
|
|
279
280
|
// 回退:返回第一个非内部 IP
|
|
280
281
|
return allIps[0].address;
|
|
@@ -305,12 +306,12 @@ async function doRegister(config) {
|
|
|
305
306
|
};
|
|
306
307
|
logger.info(`[AutoRegister] 正在注册实例: ${instanceName}`);
|
|
307
308
|
logger.info(`[AutoRegister] 调度中心: ${schedulerBaseUrl}`);
|
|
308
|
-
logger.info(`[AutoRegister] 用户 Key: ${config.userKey ?
|
|
309
|
-
logger.info(`[AutoRegister] 租户 ID: ${config.tenantId ||
|
|
309
|
+
logger.info(`[AutoRegister] 用户 Key: ${config.userKey ? "****" + config.userKey.slice(-4) : "(未设置)"}`);
|
|
310
|
+
logger.info(`[AutoRegister] 租户 ID: ${config.tenantId || "(自动推导)"}`);
|
|
310
311
|
const response = await axios.post(`${schedulerBaseUrl}/api/instances/register`, request, {
|
|
311
312
|
headers: {
|
|
312
|
-
|
|
313
|
-
|
|
313
|
+
"Content-Type": "application/json",
|
|
314
|
+
"X-Runtime-Token": runtimeToken,
|
|
314
315
|
},
|
|
315
316
|
timeout: 10000,
|
|
316
317
|
});
|
|
@@ -322,8 +323,8 @@ async function doRegister(config) {
|
|
|
322
323
|
async function doUnregister(instanceId) {
|
|
323
324
|
const response = await axios.post(`${schedulerBaseUrl}/api/instances/unregister`, { instanceId }, {
|
|
324
325
|
headers: {
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
"Content-Type": "application/json",
|
|
327
|
+
"X-Runtime-Token": runtimeToken,
|
|
327
328
|
},
|
|
328
329
|
timeout: 10000,
|
|
329
330
|
});
|
|
@@ -348,13 +349,13 @@ export async function autoRegister(customConfig) {
|
|
|
348
349
|
};
|
|
349
350
|
// 检查是否启用
|
|
350
351
|
if (!config.enabled) {
|
|
351
|
-
logger.info(
|
|
352
|
-
logger.info(
|
|
352
|
+
logger.info("[AutoRegister] 自动注册未启用");
|
|
353
|
+
logger.info("[AutoRegister] 要启用自动注册,请创建配置文件 ~/.aws-bridge/config.json 或设置环境变量 AWS_AUTO_REGISTER=true");
|
|
353
354
|
return false;
|
|
354
355
|
}
|
|
355
356
|
// 验证必填字段(userKey 为必填,tenantId 现在可选)
|
|
356
357
|
if (!config.userKey) {
|
|
357
|
-
logger.error(
|
|
358
|
+
logger.error("[AutoRegister] 缺少用户 Key(在配置文件中设置 userKey 或设置环境变量 AWS_USER_KEY)");
|
|
358
359
|
return false;
|
|
359
360
|
}
|
|
360
361
|
// tenantId 现在是可选的,如果没有提供,后端会根据 userKey 自动推导
|
|
@@ -372,7 +373,26 @@ export async function autoRegister(customConfig) {
|
|
|
372
373
|
logger.info(`[AutoRegister] ✓ 注册成功!`);
|
|
373
374
|
logger.info(`[AutoRegister] 实例 ID: ${response.instanceId}`);
|
|
374
375
|
logger.info(`[AutoRegister] 消息: ${response.message}`);
|
|
375
|
-
logger.info(`[AutoRegister] 更新: ${response.isUpdate ?
|
|
376
|
+
logger.info(`[AutoRegister] 更新: ${response.isUpdate ? "是" : "否"}`);
|
|
377
|
+
if (response.runtimeAccessToken) {
|
|
378
|
+
try {
|
|
379
|
+
saveRuntimeBinding({
|
|
380
|
+
accessToken: response.runtimeAccessToken,
|
|
381
|
+
instanceId: response.instanceId,
|
|
382
|
+
schedulerBaseUrl: response.serverUrl || schedulerBaseUrl,
|
|
383
|
+
});
|
|
384
|
+
logger.info("[AutoRegister] 已保存调度中心下发的 runtime access token");
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const err = error;
|
|
388
|
+
logger.error(`[AutoRegister] 保存 runtime access token 失败: ${err.message}`);
|
|
389
|
+
registrationState.error = err.message;
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
logger.warn("[AutoRegister] 注册响应未包含 runtime access token,MCP 将只能使用兼容的静态内部密钥访问调度中心");
|
|
395
|
+
}
|
|
376
396
|
return true;
|
|
377
397
|
}
|
|
378
398
|
else {
|
|
@@ -382,25 +402,27 @@ export async function autoRegister(customConfig) {
|
|
|
382
402
|
}
|
|
383
403
|
catch (error) {
|
|
384
404
|
const err = error;
|
|
385
|
-
const message = err.response?.data
|
|
405
|
+
const message = err.response?.data
|
|
406
|
+
? JSON.stringify(err.response.data)
|
|
407
|
+
: err.message;
|
|
386
408
|
logger.error(`[AutoRegister] 注册请求失败 (尝试 ${attempt}/${maxRetries}): ${message}`);
|
|
387
409
|
registrationState.error = message;
|
|
388
410
|
}
|
|
389
411
|
// 如果不是最后一次尝试,等待后重试
|
|
390
412
|
if (attempt < maxRetries) {
|
|
391
413
|
logger.info(`[AutoRegister] ${retryInterval / 1000} 秒后重试...`);
|
|
392
|
-
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
|
414
|
+
await new Promise((resolve) => setTimeout(resolve, retryInterval));
|
|
393
415
|
}
|
|
394
416
|
}
|
|
395
417
|
logger.error(`[AutoRegister] ✗ 注册失败,已达最大重试次数`);
|
|
396
418
|
logger.error(`[AutoRegister] ========================================`);
|
|
397
419
|
logger.error(`[AutoRegister] 自动注册已启用但注册失败,实例无法正常工作`);
|
|
398
420
|
logger.error(`[AutoRegister] 请检查以下配置:`);
|
|
399
|
-
logger.error(`[AutoRegister] - 用户 Key (userKey): ${config.userKey ?
|
|
400
|
-
logger.error(`[AutoRegister] - 租户 ID (tenantId): ${config.tenantId ||
|
|
401
|
-
logger.error(`[AutoRegister] - 实例名 (instanceName): ${config.instanceName ||
|
|
421
|
+
logger.error(`[AutoRegister] - 用户 Key (userKey): ${config.userKey ? "****" + config.userKey.slice(-4) : "(未设置)"}`);
|
|
422
|
+
logger.error(`[AutoRegister] - 租户 ID (tenantId): ${config.tenantId || "(可选,将自动推导)"}`);
|
|
423
|
+
logger.error(`[AutoRegister] - 实例名 (instanceName): ${config.instanceName || "(使用主机名)"}`);
|
|
402
424
|
logger.error(`[AutoRegister] - 调度中心地址: ${schedulerBaseUrl}`);
|
|
403
|
-
logger.error(`[AutoRegister] - 最后错误: ${registrationState.error ||
|
|
425
|
+
logger.error(`[AutoRegister] - 最后错误: ${registrationState.error || "未知"}`);
|
|
404
426
|
logger.error(`[AutoRegister] ========================================`);
|
|
405
427
|
logger.error(`[AutoRegister] 配置来源:~/.aws-bridge/config.json 或环境变量`);
|
|
406
428
|
return false;
|
|
@@ -408,9 +430,65 @@ export async function autoRegister(customConfig) {
|
|
|
408
430
|
/**
|
|
409
431
|
* 注销实例
|
|
410
432
|
*/
|
|
433
|
+
export async function requestRuntimeAccessTokenRefresh() {
|
|
434
|
+
const config = loadConfig();
|
|
435
|
+
if (!config.userKey) {
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
error: "userKey is required for runtime token refresh",
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
const state = getRuntimeBindingPublicState();
|
|
442
|
+
const localIp = getLocalIpAddress();
|
|
443
|
+
const runtimeBridgePort = process.env.AWS_RUNTIME_BRIDGE_PORT || 18081;
|
|
444
|
+
const runtimeBridgeBaseUrl = `http://${config.virtualIp || localIp}:${runtimeBridgePort}`;
|
|
445
|
+
try {
|
|
446
|
+
const response = await axios.post(`${schedulerBaseUrl}/api/instances/runtime-tokens/refresh`, {
|
|
447
|
+
tenantId: config.tenantId,
|
|
448
|
+
userKey: config.userKey,
|
|
449
|
+
instanceId: state.instanceId,
|
|
450
|
+
instanceName: config.instanceName,
|
|
451
|
+
runtimeBridgeBaseUrl,
|
|
452
|
+
}, {
|
|
453
|
+
headers: {
|
|
454
|
+
"Content-Type": "application/json",
|
|
455
|
+
"X-Runtime-Token": runtimeToken,
|
|
456
|
+
},
|
|
457
|
+
timeout: 10000,
|
|
458
|
+
});
|
|
459
|
+
if (!response.data.success || !response.data.runtimeAccessToken) {
|
|
460
|
+
return {
|
|
461
|
+
success: false,
|
|
462
|
+
error: response.data.message ||
|
|
463
|
+
"scheduler did not return runtimeAccessToken",
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
const previousToken = getRuntimeAccessToken();
|
|
467
|
+
const updated = previousToken !== response.data.runtimeAccessToken;
|
|
468
|
+
if (updated) {
|
|
469
|
+
saveRuntimeBinding({
|
|
470
|
+
accessToken: response.data.runtimeAccessToken,
|
|
471
|
+
instanceId: state.instanceId,
|
|
472
|
+
schedulerBaseUrl,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
success: true,
|
|
477
|
+
runtimeAccessToken: response.data.runtimeAccessToken,
|
|
478
|
+
updated,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
const err = error;
|
|
483
|
+
const message = err.response?.data
|
|
484
|
+
? JSON.stringify(err.response.data)
|
|
485
|
+
: err.message;
|
|
486
|
+
return { success: false, error: message };
|
|
487
|
+
}
|
|
488
|
+
}
|
|
411
489
|
export async function unregister() {
|
|
412
490
|
if (!registrationState.registered || !registrationState.instanceId) {
|
|
413
|
-
logger.info(
|
|
491
|
+
logger.info("[AutoRegister] 实例未注册,无需注销");
|
|
414
492
|
return true;
|
|
415
493
|
}
|
|
416
494
|
try {
|
|
@@ -477,17 +555,17 @@ export async function bridgeRestartCleanup() {
|
|
|
477
555
|
requestBody.runtimeBridgeBaseUrl = `http://${config.virtualIp || localIp}:${port}`;
|
|
478
556
|
}
|
|
479
557
|
if (!requestBody.instanceId && !requestBody.runtimeBridgeBaseUrl) {
|
|
480
|
-
logger.info(
|
|
558
|
+
logger.info("[AutoRegister] Bridge 重启清理:没有 instanceId 或 runtimeBridgeBaseUrl,跳过清理");
|
|
481
559
|
return { success: true, agentCount: 0 };
|
|
482
560
|
}
|
|
483
561
|
try {
|
|
484
562
|
logger.info(`[AutoRegister] Bridge 重启清理:正在通知调度中心...`);
|
|
485
|
-
logger.info(`[AutoRegister] instanceId: ${requestBody.instanceId ||
|
|
486
|
-
logger.info(`[AutoRegister] runtimeBridgeBaseUrl: ${requestBody.runtimeBridgeBaseUrl ||
|
|
563
|
+
logger.info(`[AutoRegister] instanceId: ${requestBody.instanceId || "(无)"}`);
|
|
564
|
+
logger.info(`[AutoRegister] runtimeBridgeBaseUrl: ${requestBody.runtimeBridgeBaseUrl || "(无)"}`);
|
|
487
565
|
const response = await axios.post(`${schedulerBaseUrl}/api/instances/bridge-restart-cleanup`, requestBody, {
|
|
488
566
|
headers: {
|
|
489
|
-
|
|
490
|
-
|
|
567
|
+
"Content-Type": "application/json",
|
|
568
|
+
"X-Runtime-Token": runtimeToken,
|
|
491
569
|
},
|
|
492
570
|
timeout: 10000,
|
|
493
571
|
});
|
|
@@ -503,7 +581,9 @@ export async function bridgeRestartCleanup() {
|
|
|
503
581
|
}
|
|
504
582
|
catch (error) {
|
|
505
583
|
const err = error;
|
|
506
|
-
const message = err.response?.data
|
|
584
|
+
const message = err.response?.data
|
|
585
|
+
? JSON.stringify(err.response.data)
|
|
586
|
+
: err.message;
|
|
507
587
|
logger.warn(`[AutoRegister] Bridge 重启清理请求失败:${message}`);
|
|
508
588
|
return { success: false, error: message };
|
|
509
589
|
}
|
|
@@ -15,7 +15,7 @@ export interface ResolveAwsClientAgentMcpCommandOptions {
|
|
|
15
15
|
release?: () => string | null;
|
|
16
16
|
}
|
|
17
17
|
export interface AwsMcpPreparedInfo extends AwsMcpCommandSpec {
|
|
18
|
-
source:
|
|
18
|
+
source: "override" | "bundled" | "global";
|
|
19
19
|
}
|
|
20
20
|
export declare function getReleasedMcpDistPath(): string;
|
|
21
21
|
export declare function releaseBundledAwsClientAgentMcp(): string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws-client-agent-mcp.d.ts","sourceRoot":"","sources":["../../src/services/aws-client-agent-mcp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"aws-client-agent-mcp.d.ts","sourceRoot":"","sources":["../../src/services/aws-client-agent-mcp.ts"],"names":[],"mappings":"AAWA,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,kBAAmB,SAAQ,iBAAiB;IAC3D,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC3C;AAqBD,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAMD,wBAAgB,+BAA+B,IAAI,MAAM,GAAG,IAAI,CAsB/D;AA2ED,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CAEpB;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,GAAE,sCAA2C,GACnD,kBAAkB,CA+BpB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,uBAAuB,GAC7B,kBAAkB,CAwBpB;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAgBtD"}
|
|
@@ -1,49 +1,52 @@
|
|
|
1
|
-
import { cpSync, existsSync, mkdirSync } from
|
|
2
|
-
import path from
|
|
3
|
-
import { fileURLToPath } from
|
|
4
|
-
import { getRuntimeHomeDir, schedulerBaseUrl } from
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
import { cpSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { getRuntimeHomeDir, port, schedulerBaseUrl } from "../config.js";
|
|
5
|
+
import { getRuntimeAccessToken, loadRuntimeBinding, } from "./runtime-binding.js";
|
|
6
|
+
import { logger } from "../utils/logger.js";
|
|
7
|
+
export const AWS_MCP_SERVER_NAME = "aws-mcp";
|
|
7
8
|
const AWS_MCP_ALLOWED_ENV_KEYS = [
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
"AWS_INTERNAL_API_KEY",
|
|
10
|
+
"AWS_PROJECT_NAME",
|
|
11
|
+
"AWS_PROMPT",
|
|
12
|
+
"AWS_ROLE_NAME",
|
|
13
|
+
"AWS_SERVER_URL",
|
|
14
|
+
"AWS_MCP_HTTP_URL",
|
|
15
|
+
"AWS_RUNTIME_ACCESS_TOKEN",
|
|
16
|
+
"AWS_RUNTIME_BRIDGE_BASE_URL",
|
|
17
|
+
"AWS_HEARTBEAT_INTERVAL",
|
|
18
|
+
"AWS_HEARTBEAT_TIMEOUT",
|
|
19
|
+
"PATH",
|
|
20
|
+
"HOME",
|
|
21
|
+
"USERPROFILE",
|
|
22
|
+
"TMP",
|
|
23
|
+
"TEMP",
|
|
24
|
+
"SystemRoot",
|
|
25
|
+
"COMSPEC",
|
|
26
|
+
"PATHEXT",
|
|
27
|
+
"WINDIR",
|
|
25
28
|
];
|
|
26
29
|
function getPackageRoot() {
|
|
27
30
|
const currentFile = fileURLToPath(import.meta.url);
|
|
28
|
-
return path.resolve(path.dirname(currentFile),
|
|
31
|
+
return path.resolve(path.dirname(currentFile), "..", "..");
|
|
29
32
|
}
|
|
30
33
|
function getBundledEntryPath() {
|
|
31
|
-
return path.join(getPackageRoot(),
|
|
34
|
+
return path.join(getPackageRoot(), "package", "aws-client-agent-mcp", "dist", "index.js");
|
|
32
35
|
}
|
|
33
36
|
function getReleasedMcpRoot() {
|
|
34
|
-
return path.join(getRuntimeHomeDir(),
|
|
37
|
+
return path.join(getRuntimeHomeDir(), ".aws-bridge", "mcp");
|
|
35
38
|
}
|
|
36
39
|
export function getReleasedMcpDistPath() {
|
|
37
|
-
return path.join(getReleasedMcpRoot(),
|
|
40
|
+
return path.join(getReleasedMcpRoot(), "dist");
|
|
38
41
|
}
|
|
39
42
|
function getReleasedEntryPath() {
|
|
40
|
-
return path.join(getReleasedMcpDistPath(),
|
|
43
|
+
return path.join(getReleasedMcpDistPath(), "index.js");
|
|
41
44
|
}
|
|
42
45
|
export function releaseBundledAwsClientAgentMcp() {
|
|
43
46
|
const bundledDist = path.dirname(getBundledEntryPath());
|
|
44
47
|
const releasedDist = getReleasedMcpDistPath();
|
|
45
|
-
const releasedEntry = path.join(releasedDist,
|
|
46
|
-
if (!existsSync(path.join(bundledDist,
|
|
48
|
+
const releasedEntry = path.join(releasedDist, "index.js");
|
|
49
|
+
if (!existsSync(path.join(bundledDist, "index.js"))) {
|
|
47
50
|
return null;
|
|
48
51
|
}
|
|
49
52
|
if (!existsSync(releasedEntry)) {
|
|
@@ -58,67 +61,90 @@ export function releaseBundledAwsClientAgentMcp() {
|
|
|
58
61
|
return releasedEntry;
|
|
59
62
|
}
|
|
60
63
|
function getStringEnv() {
|
|
61
|
-
return Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] ===
|
|
64
|
+
return Object.fromEntries(Object.entries(process.env).filter((entry) => typeof entry[1] === "string"));
|
|
62
65
|
}
|
|
63
66
|
function getAllowedAwsMcpEnv(baseEnv) {
|
|
64
67
|
return Object.fromEntries(AWS_MCP_ALLOWED_ENV_KEYS.flatMap((key) => {
|
|
65
68
|
const value = baseEnv[key];
|
|
66
|
-
return typeof value ===
|
|
69
|
+
return typeof value === "string" && value.length > 0
|
|
70
|
+
? [[key, value]]
|
|
71
|
+
: [];
|
|
67
72
|
}));
|
|
68
73
|
}
|
|
69
74
|
function parseAwsClientAgentMcpArgs(raw) {
|
|
70
75
|
try {
|
|
71
76
|
const parsed = JSON.parse(raw);
|
|
72
|
-
if (Array.isArray(parsed) &&
|
|
77
|
+
if (Array.isArray(parsed) &&
|
|
78
|
+
parsed.every((value) => typeof value === "string")) {
|
|
73
79
|
return parsed;
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
catch (error) {
|
|
77
83
|
logger.warn(`[runtime-bridge] AWS_CLIENT_AGENT_MCP_ARGS 不是合法 JSON,已忽略: ${error instanceof Error ? error.message : String(error)}`);
|
|
78
84
|
}
|
|
79
|
-
logger.warn(
|
|
85
|
+
logger.warn("[runtime-bridge] AWS_CLIENT_AGENT_MCP_ARGS 必须是 string[] JSON,已忽略");
|
|
80
86
|
return [];
|
|
81
87
|
}
|
|
82
88
|
function toWebSocketUrl(baseUrl) {
|
|
83
|
-
const url = new URL(
|
|
84
|
-
if (url.protocol ===
|
|
85
|
-
url.protocol =
|
|
89
|
+
const url = new URL("/ws/agent", baseUrl);
|
|
90
|
+
if (url.protocol === "https:") {
|
|
91
|
+
url.protocol = "wss:";
|
|
86
92
|
}
|
|
87
93
|
else {
|
|
88
|
-
url.protocol =
|
|
94
|
+
url.protocol = "ws:";
|
|
89
95
|
}
|
|
90
96
|
return url.toString();
|
|
91
97
|
}
|
|
92
98
|
function toMcpHttpUrl(baseUrl) {
|
|
93
|
-
return new URL(
|
|
99
|
+
return new URL("/mcp/call", baseUrl).toString();
|
|
100
|
+
}
|
|
101
|
+
function resolveSchedulerBaseUrlForMcp() {
|
|
102
|
+
const envSchedulerBaseUrl = String(process.env.AWS_RUNTIME_SCHEDULER_BASE_URL || "").trim();
|
|
103
|
+
if (envSchedulerBaseUrl) {
|
|
104
|
+
return envSchedulerBaseUrl;
|
|
105
|
+
}
|
|
106
|
+
const binding = loadRuntimeBinding();
|
|
107
|
+
const pairedSchedulerBaseUrl = String(binding.schedulerBaseUrl || "").trim();
|
|
108
|
+
if (binding.status === "paired" && pairedSchedulerBaseUrl) {
|
|
109
|
+
return pairedSchedulerBaseUrl;
|
|
110
|
+
}
|
|
111
|
+
return schedulerBaseUrl;
|
|
94
112
|
}
|
|
95
113
|
export function resolveAwsClientAgentMcpCommand(options = {}) {
|
|
96
114
|
return getAwsClientAgentMcpPreparedInfo(options);
|
|
97
115
|
}
|
|
98
116
|
export function getAwsClientAgentMcpPreparedInfo(options = {}) {
|
|
99
|
-
const overrideCommand = String(process.env.AWS_CLIENT_AGENT_MCP_COMMAND ||
|
|
117
|
+
const overrideCommand = String(process.env.AWS_CLIENT_AGENT_MCP_COMMAND || "").trim();
|
|
100
118
|
if (overrideCommand) {
|
|
101
119
|
return {
|
|
102
|
-
source:
|
|
120
|
+
source: "override",
|
|
103
121
|
command: overrideCommand,
|
|
104
|
-
args: process.env.AWS_CLIENT_AGENT_MCP_ARGS
|
|
122
|
+
args: process.env.AWS_CLIENT_AGENT_MCP_ARGS
|
|
123
|
+
? parseAwsClientAgentMcpArgs(process.env.AWS_CLIENT_AGENT_MCP_ARGS)
|
|
124
|
+
: [],
|
|
105
125
|
};
|
|
106
126
|
}
|
|
107
127
|
const exists = options.exists || existsSync;
|
|
108
128
|
const releasedEntry = getReleasedEntryPath();
|
|
109
129
|
if (exists(releasedEntry)) {
|
|
110
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
source: "bundled",
|
|
132
|
+
command: process.execPath,
|
|
133
|
+
args: [releasedEntry],
|
|
134
|
+
};
|
|
111
135
|
}
|
|
112
136
|
const release = options.release || releaseBundledAwsClientAgentMcp;
|
|
113
137
|
const released = release();
|
|
114
138
|
if (released && exists(released)) {
|
|
115
|
-
return { source:
|
|
139
|
+
return { source: "bundled", command: process.execPath, args: [released] };
|
|
116
140
|
}
|
|
117
|
-
return { source:
|
|
141
|
+
return { source: "global", command: "aws-client-agent-mcp", args: [] };
|
|
118
142
|
}
|
|
119
143
|
export function buildAwsMcpServerConfig(input) {
|
|
120
144
|
const env = getStringEnv();
|
|
121
145
|
const command = getAwsClientAgentMcpPreparedInfo();
|
|
146
|
+
const effectiveSchedulerBaseUrl = resolveSchedulerBaseUrlForMcp();
|
|
147
|
+
const issuedRuntimeAccessToken = getRuntimeAccessToken();
|
|
122
148
|
return {
|
|
123
149
|
command: command.command,
|
|
124
150
|
args: command.args,
|
|
@@ -126,15 +152,21 @@ export function buildAwsMcpServerConfig(input) {
|
|
|
126
152
|
...getAllowedAwsMcpEnv(env),
|
|
127
153
|
AWS_AGENT_ID: input.agentId,
|
|
128
154
|
AWS_WORKSPACE_PATH: input.workspacePath,
|
|
129
|
-
AWS_SERVER_URL: env.AWS_SERVER_URL || toWebSocketUrl(
|
|
130
|
-
AWS_MCP_HTTP_URL: env.AWS_MCP_HTTP_URL || toMcpHttpUrl(
|
|
155
|
+
AWS_SERVER_URL: env.AWS_SERVER_URL || toWebSocketUrl(effectiveSchedulerBaseUrl),
|
|
156
|
+
AWS_MCP_HTTP_URL: env.AWS_MCP_HTTP_URL || toMcpHttpUrl(effectiveSchedulerBaseUrl),
|
|
157
|
+
AWS_RUNTIME_BRIDGE_BASE_URL: env.AWS_RUNTIME_BRIDGE_BASE_URL || `http://127.0.0.1:${port}`,
|
|
158
|
+
...(issuedRuntimeAccessToken
|
|
159
|
+
? { AWS_RUNTIME_ACCESS_TOKEN: issuedRuntimeAccessToken }
|
|
160
|
+
: {}),
|
|
131
161
|
},
|
|
132
162
|
};
|
|
133
163
|
}
|
|
134
164
|
export function ensureAwsClientAgentMcpReleased() {
|
|
135
165
|
const prepared = getAwsClientAgentMcpPreparedInfo();
|
|
136
|
-
const commandLine = prepared.args[0]
|
|
137
|
-
|
|
166
|
+
const commandLine = prepared.args[0]
|
|
167
|
+
? `${prepared.command} ${prepared.args.join(" ")}`
|
|
168
|
+
: prepared.command;
|
|
169
|
+
if (prepared.source === "global") {
|
|
138
170
|
logger.warn(`[runtime-bridge] ${AWS_MCP_SERVER_NAME} MCP 未找到 bundled 产物,启动时将回退到 PATH 命令: ${commandLine}`);
|
|
139
171
|
return;
|
|
140
172
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type RuntimeBindingState = {
|
|
2
|
+
status: "unpaired" | "paired";
|
|
3
|
+
instanceId?: string;
|
|
4
|
+
schedulerBaseUrl?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Plain runtime access token issued by the scheduler.
|
|
7
|
+
* Stored locally with 0600 permissions so child MCP processes can authenticate
|
|
8
|
+
* scheduler calls without exposing the token in public status APIs.
|
|
9
|
+
*/
|
|
10
|
+
accessToken?: string;
|
|
11
|
+
tokenHash?: string;
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
updatedAt?: string;
|
|
14
|
+
revokedAt?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function getRuntimePairingCode(): string;
|
|
17
|
+
export declare function getRuntimeBindingFilePath(): string;
|
|
18
|
+
export declare function loadRuntimeBinding(): RuntimeBindingState;
|
|
19
|
+
export declare function getRuntimeBindingPublicState(): Omit<RuntimeBindingState, "tokenHash" | "accessToken"> & {
|
|
20
|
+
paired: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function hasRuntimeBinding(): boolean;
|
|
23
|
+
export declare function validateRuntimeBindingToken(token: unknown): boolean;
|
|
24
|
+
export declare function getRuntimeAccessToken(): string | undefined;
|
|
25
|
+
export declare function validateRuntimePairingCode(code: unknown): boolean;
|
|
26
|
+
export declare function saveRuntimeBinding(input: {
|
|
27
|
+
accessToken: string;
|
|
28
|
+
instanceId?: string;
|
|
29
|
+
schedulerBaseUrl?: string;
|
|
30
|
+
}): RuntimeBindingState;
|
|
31
|
+
export declare function clearRuntimeBinding(): void;
|
|
32
|
+
//# sourceMappingURL=runtime-binding.d.ts.map
|
|
@@ -0,0 +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,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;AAuCF,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,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,CAYnE;AAED,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,SAAS,CAM1D;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,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,mBAAmB,CA6BtB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAkB1C"}
|