@airmcp-dev/gateway 0.1.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.
- package/LICENSE +17 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy/index.d.ts +5 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +7 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/mcp-proxy.d.ts +53 -0
- package/dist/proxy/mcp-proxy.d.ts.map +1 -0
- package/dist/proxy/mcp-proxy.js +131 -0
- package/dist/proxy/mcp-proxy.js.map +1 -0
- package/dist/proxy/protocol-adapter.d.ts +28 -0
- package/dist/proxy/protocol-adapter.d.ts.map +1 -0
- package/dist/proxy/protocol-adapter.js +56 -0
- package/dist/proxy/protocol-adapter.js.map +1 -0
- package/dist/proxy/session-pool.d.ts +39 -0
- package/dist/proxy/session-pool.d.ts.map +1 -0
- package/dist/proxy/session-pool.js +102 -0
- package/dist/proxy/session-pool.js.map +1 -0
- package/dist/registry/health-checker.d.ts +35 -0
- package/dist/registry/health-checker.d.ts.map +1 -0
- package/dist/registry/health-checker.js +121 -0
- package/dist/registry/health-checker.js.map +1 -0
- package/dist/registry/index.d.ts +4 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +7 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/server-registry.d.ts +37 -0
- package/dist/registry/server-registry.d.ts.map +1 -0
- package/dist/registry/server-registry.js +72 -0
- package/dist/registry/server-registry.js.map +1 -0
- package/dist/registry/tool-index.d.ts +35 -0
- package/dist/registry/tool-index.d.ts.map +1 -0
- package/dist/registry/tool-index.js +72 -0
- package/dist/registry/tool-index.js.map +1 -0
- package/dist/router/index.d.ts +3 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +6 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/load-balancer.d.ts +23 -0
- package/dist/router/load-balancer.d.ts.map +1 -0
- package/dist/router/load-balancer.js +78 -0
- package/dist/router/load-balancer.js.map +1 -0
- package/dist/router/request-router.d.ts +21 -0
- package/dist/router/request-router.d.ts.map +1 -0
- package/dist/router/request-router.js +50 -0
- package/dist/router/request-router.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/gateway — registry/health-checker.ts
|
|
3
|
+
//
|
|
4
|
+
// 등록된 서버들의 헬스체크를 주기적으로 수행한다.
|
|
5
|
+
// 응답 없는 서버는 status를 'error'로 전환.
|
|
6
|
+
export class HealthChecker {
|
|
7
|
+
registry;
|
|
8
|
+
checkIntervalMs;
|
|
9
|
+
timeoutMs;
|
|
10
|
+
interval = null;
|
|
11
|
+
results = new Map();
|
|
12
|
+
constructor(registry, checkIntervalMs = 30_000, timeoutMs = 5_000) {
|
|
13
|
+
this.registry = registry;
|
|
14
|
+
this.checkIntervalMs = checkIntervalMs;
|
|
15
|
+
this.timeoutMs = timeoutMs;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 주기적 헬스체크를 시작한다.
|
|
19
|
+
*/
|
|
20
|
+
start() {
|
|
21
|
+
if (this.interval)
|
|
22
|
+
return;
|
|
23
|
+
// 즉시 1회 실행
|
|
24
|
+
this.checkAll();
|
|
25
|
+
this.interval = setInterval(() => {
|
|
26
|
+
this.checkAll();
|
|
27
|
+
}, this.checkIntervalMs);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 헬스체크를 중지한다.
|
|
31
|
+
*/
|
|
32
|
+
stop() {
|
|
33
|
+
if (this.interval) {
|
|
34
|
+
clearInterval(this.interval);
|
|
35
|
+
this.interval = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 모든 connected 서버를 체크한다.
|
|
40
|
+
*/
|
|
41
|
+
async checkAll() {
|
|
42
|
+
const servers = this.registry.listAll();
|
|
43
|
+
const results = [];
|
|
44
|
+
for (const server of servers) {
|
|
45
|
+
if (server.status === 'stopped')
|
|
46
|
+
continue;
|
|
47
|
+
const result = await this.checkOne(server);
|
|
48
|
+
results.push(result);
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 단일 서버를 체크한다.
|
|
54
|
+
*/
|
|
55
|
+
async checkOne(server) {
|
|
56
|
+
const start = Date.now();
|
|
57
|
+
try {
|
|
58
|
+
if (server.connection.type === 'stdio') {
|
|
59
|
+
// stdio 서버는 프로세스 존재 여부로 판단
|
|
60
|
+
// (실제로는 PID 체크 또는 ping 메시지)
|
|
61
|
+
const result = {
|
|
62
|
+
serverId: server.id,
|
|
63
|
+
healthy: server.status === 'connected',
|
|
64
|
+
latencyMs: Date.now() - start,
|
|
65
|
+
checkedAt: new Date(),
|
|
66
|
+
};
|
|
67
|
+
this.results.set(server.id, result);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
// http/sse 서버는 URL로 health 체크
|
|
71
|
+
const url = server.connection.url;
|
|
72
|
+
const controller = new AbortController();
|
|
73
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(`${url}/health`, {
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
});
|
|
78
|
+
clearTimeout(timeout);
|
|
79
|
+
const healthy = res.ok;
|
|
80
|
+
const result = {
|
|
81
|
+
serverId: server.id,
|
|
82
|
+
healthy,
|
|
83
|
+
latencyMs: Date.now() - start,
|
|
84
|
+
checkedAt: new Date(),
|
|
85
|
+
};
|
|
86
|
+
this.registry.updateStatus(server.id, healthy ? 'connected' : 'error');
|
|
87
|
+
this.results.set(server.id, result);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
const result = {
|
|
97
|
+
serverId: server.id,
|
|
98
|
+
healthy: false,
|
|
99
|
+
latencyMs: Date.now() - start,
|
|
100
|
+
error: err.message,
|
|
101
|
+
checkedAt: new Date(),
|
|
102
|
+
};
|
|
103
|
+
this.registry.updateStatus(server.id, 'error');
|
|
104
|
+
this.results.set(server.id, result);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 마지막 헬스체크 결과를 조회한다.
|
|
110
|
+
*/
|
|
111
|
+
getLastResult(serverId) {
|
|
112
|
+
return this.results.get(serverId);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 전체 마지막 결과.
|
|
116
|
+
*/
|
|
117
|
+
getAllResults() {
|
|
118
|
+
return Array.from(this.results.values());
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=health-checker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-checker.js","sourceRoot":"","sources":["../../src/registry/health-checker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/gateway — registry/index.ts
|
|
3
|
+
// re-export only.
|
|
4
|
+
export { ServerRegistry } from './server-registry.js';
|
|
5
|
+
export { ToolIndex } from './tool-index.js';
|
|
6
|
+
export { HealthChecker } from './health-checker.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,0CAA0C;AAC1C,kBAAkB;AAElB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ServerEntry, ServerConnection, ServerStatus, ToolEntry } from '../types.js';
|
|
2
|
+
export declare class ServerRegistry {
|
|
3
|
+
private servers;
|
|
4
|
+
/**
|
|
5
|
+
* 서버를 등록한다.
|
|
6
|
+
*/
|
|
7
|
+
register(id: string, name: string, connection: ServerConnection): ServerEntry;
|
|
8
|
+
/**
|
|
9
|
+
* 서버를 해제한다.
|
|
10
|
+
*/
|
|
11
|
+
unregister(id: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* 서버를 ID로 조회한다.
|
|
14
|
+
*/
|
|
15
|
+
get(id: string): ServerEntry | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* 전체 서버 목록.
|
|
18
|
+
*/
|
|
19
|
+
listAll(): ServerEntry[];
|
|
20
|
+
/**
|
|
21
|
+
* 특정 상태의 서버만 조회.
|
|
22
|
+
*/
|
|
23
|
+
listByStatus(status: ServerStatus): ServerEntry[];
|
|
24
|
+
/**
|
|
25
|
+
* 서버 상태를 갱신한다.
|
|
26
|
+
*/
|
|
27
|
+
updateStatus(id: string, status: ServerStatus): void;
|
|
28
|
+
/**
|
|
29
|
+
* 서버의 도구 목록을 갱신한다 (발견 후 호출).
|
|
30
|
+
*/
|
|
31
|
+
updateTools(id: string, tools: ToolEntry[]): void;
|
|
32
|
+
/**
|
|
33
|
+
* 등록된 서버 수.
|
|
34
|
+
*/
|
|
35
|
+
get size(): number;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=server-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-registry.d.ts","sourceRoot":"","sources":["../../src/registry/server-registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE1F,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAkC;IAEjD;;OAEG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,GAAG,WAAW;IAa7E;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI/B;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIxC;;OAEG;IACH,OAAO,IAAI,WAAW,EAAE;IAIxB;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,EAAE;IAIjD;;OAEG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI;IAOpD;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI;IAOjD;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/gateway — registry/server-registry.ts
|
|
3
|
+
//
|
|
4
|
+
// MCP 서버 등록/해제/조회.
|
|
5
|
+
// Gateway에 하위 서버를 등록하면 자동으로 도구를 발견하고 인덱싱한다.
|
|
6
|
+
export class ServerRegistry {
|
|
7
|
+
servers = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* 서버를 등록한다.
|
|
10
|
+
*/
|
|
11
|
+
register(id, name, connection) {
|
|
12
|
+
const entry = {
|
|
13
|
+
id,
|
|
14
|
+
name,
|
|
15
|
+
transport: connection.type === 'stdio' ? 'stdio' : connection.type,
|
|
16
|
+
connection,
|
|
17
|
+
tools: [],
|
|
18
|
+
status: 'registered',
|
|
19
|
+
};
|
|
20
|
+
this.servers.set(id, entry);
|
|
21
|
+
return entry;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 서버를 해제한다.
|
|
25
|
+
*/
|
|
26
|
+
unregister(id) {
|
|
27
|
+
return this.servers.delete(id);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 서버를 ID로 조회한다.
|
|
31
|
+
*/
|
|
32
|
+
get(id) {
|
|
33
|
+
return this.servers.get(id);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 전체 서버 목록.
|
|
37
|
+
*/
|
|
38
|
+
listAll() {
|
|
39
|
+
return Array.from(this.servers.values());
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 특정 상태의 서버만 조회.
|
|
43
|
+
*/
|
|
44
|
+
listByStatus(status) {
|
|
45
|
+
return this.listAll().filter((s) => s.status === status);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 서버 상태를 갱신한다.
|
|
49
|
+
*/
|
|
50
|
+
updateStatus(id, status) {
|
|
51
|
+
const server = this.servers.get(id);
|
|
52
|
+
if (server) {
|
|
53
|
+
server.status = status;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 서버의 도구 목록을 갱신한다 (발견 후 호출).
|
|
58
|
+
*/
|
|
59
|
+
updateTools(id, tools) {
|
|
60
|
+
const server = this.servers.get(id);
|
|
61
|
+
if (server) {
|
|
62
|
+
server.tools = tools;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 등록된 서버 수.
|
|
67
|
+
*/
|
|
68
|
+
get size() {
|
|
69
|
+
return this.servers.size;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=server-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-registry.js","sourceRoot":"","sources":["../../src/registry/server-registry.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,oDAAoD;AACpD,EAAE;AACF,mBAAmB;AACnB,4CAA4C;AAI5C,MAAM,OAAO,cAAc;IACjB,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEjD;;OAEG;IACH,QAAQ,CAAC,EAAU,EAAE,IAAY,EAAE,UAA4B;QAC7D,MAAM,KAAK,GAAgB;YACzB,EAAE;YACF,IAAI;YACJ,SAAS,EAAE,UAAU,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI;YAClE,UAAU;YACV,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,YAAY;SACrB,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,EAAU;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,OAAO;QACL,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,MAAoB;QAC/B,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IAC3D,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,EAAU,EAAE,MAAoB;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACzB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,EAAU,EAAE,KAAkB;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ToolEntry, ServerEntry } from '../types.js';
|
|
2
|
+
export declare class ToolIndex {
|
|
3
|
+
/** toolName → ToolEntry[] (같은 이름의 도구가 여러 서버에 있을 수 있음) */
|
|
4
|
+
private index;
|
|
5
|
+
/**
|
|
6
|
+
* 서버의 도구 목록을 인덱스에 반영한다.
|
|
7
|
+
* 기존 항목은 제거 후 재등록.
|
|
8
|
+
*/
|
|
9
|
+
reindex(server: ServerEntry): void;
|
|
10
|
+
/**
|
|
11
|
+
* 서버의 모든 도구를 인덱스에서 제거한다.
|
|
12
|
+
*/
|
|
13
|
+
removeServer(serverId: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* 도구 이름으로 제공 서버를 조회한다.
|
|
16
|
+
*/
|
|
17
|
+
find(toolName: string): ToolEntry[];
|
|
18
|
+
/**
|
|
19
|
+
* 도구가 존재하는지 확인.
|
|
20
|
+
*/
|
|
21
|
+
has(toolName: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 전체 도구 목록 (중복 제거 없이).
|
|
24
|
+
*/
|
|
25
|
+
listAll(): ToolEntry[];
|
|
26
|
+
/**
|
|
27
|
+
* 고유 도구 이름 목록.
|
|
28
|
+
*/
|
|
29
|
+
listNames(): string[];
|
|
30
|
+
/**
|
|
31
|
+
* 전체 도구 수.
|
|
32
|
+
*/
|
|
33
|
+
get size(): number;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=tool-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-index.d.ts","sourceRoot":"","sources":["../../src/registry/tool-index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1D,qBAAa,SAAS;IACpB,yDAAyD;IACzD,OAAO,CAAC,KAAK,CAAkC;IAE/C;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAYlC;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWpC;;OAEG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE;IAInC;;OAEG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAI9B;;OAEG;IACH,OAAO,IAAI,SAAS,EAAE;IAQtB;;OAEG;IACH,SAAS,IAAI,MAAM,EAAE;IAIrB;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/gateway — registry/tool-index.ts
|
|
3
|
+
//
|
|
4
|
+
// 전체 하위 서버의 도구를 하나의 인덱스로 관리한다.
|
|
5
|
+
// 도구 이름으로 어떤 서버가 제공하는지 즉시 조회 가능.
|
|
6
|
+
export class ToolIndex {
|
|
7
|
+
/** toolName → ToolEntry[] (같은 이름의 도구가 여러 서버에 있을 수 있음) */
|
|
8
|
+
index = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* 서버의 도구 목록을 인덱스에 반영한다.
|
|
11
|
+
* 기존 항목은 제거 후 재등록.
|
|
12
|
+
*/
|
|
13
|
+
reindex(server) {
|
|
14
|
+
// 기존 항목 제거
|
|
15
|
+
this.removeServer(server.id);
|
|
16
|
+
// 새 도구 등록
|
|
17
|
+
for (const tool of server.tools) {
|
|
18
|
+
const existing = this.index.get(tool.name) || [];
|
|
19
|
+
existing.push(tool);
|
|
20
|
+
this.index.set(tool.name, existing);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 서버의 모든 도구를 인덱스에서 제거한다.
|
|
25
|
+
*/
|
|
26
|
+
removeServer(serverId) {
|
|
27
|
+
for (const [name, entries] of this.index) {
|
|
28
|
+
const filtered = entries.filter((e) => e.serverId !== serverId);
|
|
29
|
+
if (filtered.length === 0) {
|
|
30
|
+
this.index.delete(name);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
this.index.set(name, filtered);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 도구 이름으로 제공 서버를 조회한다.
|
|
39
|
+
*/
|
|
40
|
+
find(toolName) {
|
|
41
|
+
return this.index.get(toolName) || [];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 도구가 존재하는지 확인.
|
|
45
|
+
*/
|
|
46
|
+
has(toolName) {
|
|
47
|
+
return this.index.has(toolName);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 전체 도구 목록 (중복 제거 없이).
|
|
51
|
+
*/
|
|
52
|
+
listAll() {
|
|
53
|
+
const all = [];
|
|
54
|
+
for (const entries of this.index.values()) {
|
|
55
|
+
all.push(...entries);
|
|
56
|
+
}
|
|
57
|
+
return all;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 고유 도구 이름 목록.
|
|
61
|
+
*/
|
|
62
|
+
listNames() {
|
|
63
|
+
return Array.from(this.index.keys());
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 전체 도구 수.
|
|
67
|
+
*/
|
|
68
|
+
get size() {
|
|
69
|
+
return this.index.size;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=tool-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-index.js","sourceRoot":"","sources":["../../src/registry/tool-index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,+CAA+C;AAC/C,EAAE;AACF,+BAA+B;AAC/B,iCAAiC;AAIjC,MAAM,OAAO,SAAS;IACpB,yDAAyD;IACjD,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE/C;;;OAGG;IACH,OAAO,CAAC,MAAmB;QACzB,WAAW;QACX,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAE7B,UAAU;QACV,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACjD,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,QAAgB;QAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;YAChE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,IAAI,CAAC,QAAgB;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,QAAgB;QAClB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,OAAO;QACL,MAAM,GAAG,GAAgB,EAAE,CAAC;QAC5B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/router/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,wCAAwC;AACxC,kBAAkB;AAElB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ToolEntry, BalancerStrategy } from '../types.js';
|
|
2
|
+
export declare class LoadBalancer {
|
|
3
|
+
private strategy;
|
|
4
|
+
private counter;
|
|
5
|
+
private weights;
|
|
6
|
+
constructor(strategy?: BalancerStrategy);
|
|
7
|
+
/**
|
|
8
|
+
* 후보 중 하나를 선택한다.
|
|
9
|
+
*/
|
|
10
|
+
select(candidates: ToolEntry[]): ToolEntry;
|
|
11
|
+
/**
|
|
12
|
+
* 서버별 가중치를 설정한다 (weighted 전략용).
|
|
13
|
+
*/
|
|
14
|
+
setWeight(serverId: string, weight: number): void;
|
|
15
|
+
/**
|
|
16
|
+
* 전략을 변경한다.
|
|
17
|
+
*/
|
|
18
|
+
setStrategy(strategy: BalancerStrategy): void;
|
|
19
|
+
private roundRobin;
|
|
20
|
+
private random;
|
|
21
|
+
private weighted;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=load-balancer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-balancer.d.ts","sourceRoot":"","sources":["../../src/router/load-balancer.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/D,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ;IAH5B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAA6B;gBAExB,QAAQ,GAAE,gBAAgC;IAE9D;;OAEG;IACH,MAAM,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,SAAS;IAyB1C;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAIjD;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAK7C,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,QAAQ;CAkBjB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/gateway — router/load-balancer.ts
|
|
3
|
+
//
|
|
4
|
+
// 여러 후보 중 하나를 선택하는 로드밸런서.
|
|
5
|
+
// 전략: round-robin (기본), random, weighted.
|
|
6
|
+
export class LoadBalancer {
|
|
7
|
+
strategy;
|
|
8
|
+
counter = 0;
|
|
9
|
+
weights = new Map();
|
|
10
|
+
constructor(strategy = 'round-robin') {
|
|
11
|
+
this.strategy = strategy;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 후보 중 하나를 선택한다.
|
|
15
|
+
*/
|
|
16
|
+
select(candidates) {
|
|
17
|
+
if (candidates.length === 0) {
|
|
18
|
+
throw new Error('No candidates to balance.');
|
|
19
|
+
}
|
|
20
|
+
if (candidates.length === 1) {
|
|
21
|
+
return candidates[0];
|
|
22
|
+
}
|
|
23
|
+
switch (this.strategy) {
|
|
24
|
+
case 'round-robin':
|
|
25
|
+
return this.roundRobin(candidates);
|
|
26
|
+
case 'random':
|
|
27
|
+
return this.random(candidates);
|
|
28
|
+
case 'weighted':
|
|
29
|
+
return this.weighted(candidates);
|
|
30
|
+
case 'least-connections':
|
|
31
|
+
// least-connections는 세션 수 추적이 필요 → 향후 구현
|
|
32
|
+
// fallback to round-robin
|
|
33
|
+
return this.roundRobin(candidates);
|
|
34
|
+
default:
|
|
35
|
+
return this.roundRobin(candidates);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 서버별 가중치를 설정한다 (weighted 전략용).
|
|
40
|
+
*/
|
|
41
|
+
setWeight(serverId, weight) {
|
|
42
|
+
this.weights.set(serverId, Math.max(0, weight));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 전략을 변경한다.
|
|
46
|
+
*/
|
|
47
|
+
setStrategy(strategy) {
|
|
48
|
+
this.strategy = strategy;
|
|
49
|
+
this.counter = 0;
|
|
50
|
+
}
|
|
51
|
+
roundRobin(candidates) {
|
|
52
|
+
const idx = this.counter % candidates.length;
|
|
53
|
+
this.counter++;
|
|
54
|
+
return candidates[idx];
|
|
55
|
+
}
|
|
56
|
+
random(candidates) {
|
|
57
|
+
const idx = Math.floor(Math.random() * candidates.length);
|
|
58
|
+
return candidates[idx];
|
|
59
|
+
}
|
|
60
|
+
weighted(candidates) {
|
|
61
|
+
// 가중치 없는 서버는 기본 1
|
|
62
|
+
const entries = candidates.map((c) => ({
|
|
63
|
+
tool: c,
|
|
64
|
+
weight: this.weights.get(c.serverId) ?? 1,
|
|
65
|
+
}));
|
|
66
|
+
const totalWeight = entries.reduce((sum, e) => sum + e.weight, 0);
|
|
67
|
+
if (totalWeight === 0)
|
|
68
|
+
return this.roundRobin(candidates);
|
|
69
|
+
let random = Math.random() * totalWeight;
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
random -= entry.weight;
|
|
72
|
+
if (random <= 0)
|
|
73
|
+
return entry.tool;
|
|
74
|
+
}
|
|
75
|
+
return entries[entries.length - 1].tool;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=load-balancer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-balancer.js","sourceRoot":"","sources":["../../src/router/load-balancer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RouteRequest, RouteResult } from '../types.js';
|
|
2
|
+
import { ServerRegistry } from '../registry/server-registry.js';
|
|
3
|
+
import { ToolIndex } from '../registry/tool-index.js';
|
|
4
|
+
import { LoadBalancer } from './load-balancer.js';
|
|
5
|
+
export declare class RequestRouter {
|
|
6
|
+
private registry;
|
|
7
|
+
private toolIndex;
|
|
8
|
+
private balancer;
|
|
9
|
+
constructor(registry: ServerRegistry, toolIndex: ToolIndex, balancer: LoadBalancer);
|
|
10
|
+
/**
|
|
11
|
+
* 도구 호출 요청을 라우팅한다.
|
|
12
|
+
*
|
|
13
|
+
* @throws 도구를 찾을 수 없거나 사용 가능한 서버가 없으면 에러
|
|
14
|
+
*/
|
|
15
|
+
route(request: RouteRequest): RouteResult;
|
|
16
|
+
/**
|
|
17
|
+
* 도구가 라우팅 가능한지 확인 (dry-run).
|
|
18
|
+
*/
|
|
19
|
+
canRoute(toolName: string): boolean;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=request-router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-router.d.ts","sourceRoot":"","sources":["../../src/router/request-router.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAA0B,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,QAAQ;gBAFR,QAAQ,EAAE,cAAc,EACxB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,YAAY;IAGhC;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,WAAW;IAwBzC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAOpC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
|
|
2
|
+
// @airmcp-dev/gateway — router/request-router.ts
|
|
3
|
+
//
|
|
4
|
+
// 도구 호출 요청을 적절한 서버로 라우팅한다.
|
|
5
|
+
// ToolIndex에서 후보를 찾고, LoadBalancer로 하나를 선택.
|
|
6
|
+
export class RequestRouter {
|
|
7
|
+
registry;
|
|
8
|
+
toolIndex;
|
|
9
|
+
balancer;
|
|
10
|
+
constructor(registry, toolIndex, balancer) {
|
|
11
|
+
this.registry = registry;
|
|
12
|
+
this.toolIndex = toolIndex;
|
|
13
|
+
this.balancer = balancer;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 도구 호출 요청을 라우팅한다.
|
|
17
|
+
*
|
|
18
|
+
* @throws 도구를 찾을 수 없거나 사용 가능한 서버가 없으면 에러
|
|
19
|
+
*/
|
|
20
|
+
route(request) {
|
|
21
|
+
// ── 1. 도구 후보 찾기 ──
|
|
22
|
+
const candidates = this.toolIndex.find(request.toolName);
|
|
23
|
+
if (candidates.length === 0) {
|
|
24
|
+
throw new Error(`Tool "${request.toolName}" not found in any registered server.`);
|
|
25
|
+
}
|
|
26
|
+
// ── 2. 연결된 서버만 필터링 ──
|
|
27
|
+
const available = candidates.filter((t) => {
|
|
28
|
+
const server = this.registry.get(t.serverId);
|
|
29
|
+
return server && server.status === 'connected';
|
|
30
|
+
});
|
|
31
|
+
if (available.length === 0) {
|
|
32
|
+
throw new Error(`Tool "${request.toolName}" exists but no healthy server is available.`);
|
|
33
|
+
}
|
|
34
|
+
// ── 3. 로드밸런싱으로 하나 선택 ──
|
|
35
|
+
const selected = this.balancer.select(available);
|
|
36
|
+
const server = this.registry.get(selected.serverId);
|
|
37
|
+
return { server, tool: selected };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 도구가 라우팅 가능한지 확인 (dry-run).
|
|
41
|
+
*/
|
|
42
|
+
canRoute(toolName) {
|
|
43
|
+
const candidates = this.toolIndex.find(toolName);
|
|
44
|
+
return candidates.some((t) => {
|
|
45
|
+
const server = this.registry.get(t.serverId);
|
|
46
|
+
return server && server.status === 'connected';
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=request-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-router.js","sourceRoot":"","sources":["../../src/router/request-router.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,iDAAiD;AACjD,EAAE;AACF,2BAA2B;AAC3B,4CAA4C;AAO5C,MAAM,OAAO,aAAa;IAEd;IACA;IACA;IAHV,YACU,QAAwB,EACxB,SAAoB,EACpB,QAAsB;QAFtB,aAAQ,GAAR,QAAQ,CAAgB;QACxB,cAAS,GAAT,SAAS,CAAW;QACpB,aAAQ,GAAR,QAAQ,CAAc;IAC7B,CAAC;IAEJ;;;;OAIG;IACH,KAAK,CAAC,OAAqB;QACzB,oBAAoB;QACpB,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,SAAS,OAAO,CAAC,QAAQ,uCAAuC,CAAC,CAAC;QACpF,CAAC;QAED,uBAAuB;QACvB,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YACxC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC7C,OAAO,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,SAAS,OAAO,CAAC,QAAQ,8CAA8C,CAAC,CAAC;QAC3F,CAAC;QAED,yBAAyB;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAE,CAAC;QAErD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,QAAgB;QACvB,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;YAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC7C,OAAO,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|