@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.
Files changed (54) hide show
  1. package/LICENSE +17 -0
  2. package/dist/index.d.ts +9 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +14 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/proxy/index.d.ts +5 -0
  7. package/dist/proxy/index.d.ts.map +1 -0
  8. package/dist/proxy/index.js +7 -0
  9. package/dist/proxy/index.js.map +1 -0
  10. package/dist/proxy/mcp-proxy.d.ts +53 -0
  11. package/dist/proxy/mcp-proxy.d.ts.map +1 -0
  12. package/dist/proxy/mcp-proxy.js +131 -0
  13. package/dist/proxy/mcp-proxy.js.map +1 -0
  14. package/dist/proxy/protocol-adapter.d.ts +28 -0
  15. package/dist/proxy/protocol-adapter.d.ts.map +1 -0
  16. package/dist/proxy/protocol-adapter.js +56 -0
  17. package/dist/proxy/protocol-adapter.js.map +1 -0
  18. package/dist/proxy/session-pool.d.ts +39 -0
  19. package/dist/proxy/session-pool.d.ts.map +1 -0
  20. package/dist/proxy/session-pool.js +102 -0
  21. package/dist/proxy/session-pool.js.map +1 -0
  22. package/dist/registry/health-checker.d.ts +35 -0
  23. package/dist/registry/health-checker.d.ts.map +1 -0
  24. package/dist/registry/health-checker.js +121 -0
  25. package/dist/registry/health-checker.js.map +1 -0
  26. package/dist/registry/index.d.ts +4 -0
  27. package/dist/registry/index.d.ts.map +1 -0
  28. package/dist/registry/index.js +7 -0
  29. package/dist/registry/index.js.map +1 -0
  30. package/dist/registry/server-registry.d.ts +37 -0
  31. package/dist/registry/server-registry.d.ts.map +1 -0
  32. package/dist/registry/server-registry.js +72 -0
  33. package/dist/registry/server-registry.js.map +1 -0
  34. package/dist/registry/tool-index.d.ts +35 -0
  35. package/dist/registry/tool-index.d.ts.map +1 -0
  36. package/dist/registry/tool-index.js +72 -0
  37. package/dist/registry/tool-index.js.map +1 -0
  38. package/dist/router/index.d.ts +3 -0
  39. package/dist/router/index.d.ts.map +1 -0
  40. package/dist/router/index.js +6 -0
  41. package/dist/router/index.js.map +1 -0
  42. package/dist/router/load-balancer.d.ts +23 -0
  43. package/dist/router/load-balancer.d.ts.map +1 -0
  44. package/dist/router/load-balancer.js +78 -0
  45. package/dist/router/load-balancer.js.map +1 -0
  46. package/dist/router/request-router.d.ts +21 -0
  47. package/dist/router/request-router.d.ts.map +1 -0
  48. package/dist/router/request-router.js +50 -0
  49. package/dist/router/request-router.js.map +1 -0
  50. package/dist/types.d.ts +88 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +7 -0
  53. package/dist/types.js.map +1 -0
  54. 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":"AAAA,4DAA4D;AAC5D,mDAAmD;AACnD,EAAE;AACF,6BAA6B;AAC7B,iCAAiC;AAKjC,MAAM,OAAO,aAAa;IAKd;IACA;IACA;IANF,QAAQ,GAA0C,IAAI,CAAC;IACvD,OAAO,GAAG,IAAI,GAAG,EAA6B,CAAC;IAEvD,YACU,QAAwB,EACxB,kBAA0B,MAAM,EAChC,YAAoB,KAAK;QAFzB,aAAQ,GAAR,QAAQ,CAAgB;QACxB,oBAAe,GAAf,eAAe,CAAiB;QAChC,cAAS,GAAT,SAAS,CAAgB;IAChC,CAAC;IAEJ;;OAEG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAE1B,WAAW;QACX,IAAI,CAAC,QAAQ,EAAE,CAAC;QAEhB,IAAI,CAAC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAC/B,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,IAAI;QACF,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ;QACZ,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACxC,MAAM,OAAO,GAAwB,EAAE,CAAC;QAExC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;gBAAE,SAAS;YAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,MAAmB;QAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEzB,IAAI,CAAC;YACH,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBACvC,2BAA2B;gBAC3B,4BAA4B;gBAC5B,MAAM,MAAM,GAAsB;oBAChC,QAAQ,EAAE,MAAM,CAAC,EAAE;oBACnB,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,WAAW;oBACtC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;oBAC7B,SAAS,EAAE,IAAI,IAAI,EAAE;iBACtB,CAAC;gBACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;gBACpC,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,8BAA8B;YAC9B,MAAM,GAAG,GAAI,MAAM,CAAC,UAAkB,CAAC,GAAG,CAAC;YAC3C,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAErE,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,GAAG,SAAS,EAAE;oBACvC,MAAM,EAAE,UAAU,CAAC,MAAM;iBAC1B,CAAC,CAAC;gBACH,YAAY,CAAC,OAAO,CAAC,CAAC;gBAEtB,MAAM,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAsB;oBAChC,QAAQ,EAAE,MAAM,CAAC,EAAE;oBACnB,OAAO;oBACP,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;oBAC7B,SAAS,EAAE,IAAI,IAAI,EAAE;iBACtB,CAAC;gBAEF,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBACvE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;gBACpC,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,MAAM,MAAM,GAAsB;gBAChC,QAAQ,EAAE,MAAM,CAAC,EAAE;gBACnB,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;gBAC7B,KAAK,EAAE,GAAG,CAAC,OAAO;gBAClB,SAAS,EAAE,IAAI,IAAI,EAAE;aACtB,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YACpC,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,aAAa,CAAC,QAAgB;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED;;OAEG;IACH,aAAa;QACX,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ export { ServerRegistry } from './server-registry.js';
2
+ export { ToolIndex } from './tool-index.js';
3
+ export { HealthChecker } from './health-checker.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export { RequestRouter } from './request-router.js';
2
+ export { LoadBalancer } from './load-balancer.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,6 @@
1
+ // Copyright 2026 CodePedia Labs. Licensed under Apache-2.0.
2
+ // @airmcp-dev/gateway — router/index.ts
3
+ // re-export only.
4
+ export { RequestRouter } from './request-router.js';
5
+ export { LoadBalancer } from './load-balancer.js';
6
+ //# sourceMappingURL=index.js.map
@@ -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"}