agent-recorder 0.0.9 → 0.0.10
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/package.json +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/configure-wrap.d.ts.map +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/configure-wrap.js +5 -31
- package/vendor/node_modules/@agent-recorder/cli/commands/configure-wrap.js.map +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/configure.d.ts +2 -0
- package/vendor/node_modules/@agent-recorder/cli/commands/configure.d.ts.map +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/configure.js +117 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/configure.js.map +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/doctor.d.ts.map +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/doctor.js +113 -19
- package/vendor/node_modules/@agent-recorder/cli/commands/doctor.js.map +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/doctor.test.d.ts +5 -0
- package/vendor/node_modules/@agent-recorder/cli/commands/doctor.test.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/cli/commands/doctor.test.js +152 -0
- package/vendor/node_modules/@agent-recorder/cli/commands/doctor.test.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/cli/commands/install.d.ts +7 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/install.d.ts.map +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/install.js +89 -14
- package/vendor/node_modules/@agent-recorder/cli/commands/install.js.map +1 -1
- package/vendor/node_modules/@agent-recorder/cli/commands/install.test.d.ts +5 -0
- package/vendor/node_modules/@agent-recorder/cli/commands/install.test.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/cli/commands/install.test.js +173 -0
- package/vendor/node_modules/@agent-recorder/cli/commands/install.test.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/cli/config/hubify.d.ts +51 -0
- package/vendor/node_modules/@agent-recorder/cli/config/hubify.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/cli/config/hubify.js +125 -0
- package/vendor/node_modules/@agent-recorder/cli/config/hubify.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/cli/config/hubify.test.d.ts +5 -0
- package/vendor/node_modules/@agent-recorder/cli/config/hubify.test.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/cli/config/hubify.test.js +329 -0
- package/vendor/node_modules/@agent-recorder/cli/config/hubify.test.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/cli/index.js +7 -3
- package/vendor/node_modules/@agent-recorder/cli/index.js.map +1 -1
- package/vendor/node_modules/@agent-recorder/core/claude-config.d.ts +32 -0
- package/vendor/node_modules/@agent-recorder/core/claude-config.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/claude-config.js +65 -0
- package/vendor/node_modules/@agent-recorder/core/claude-config.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/index.d.ts +3 -0
- package/vendor/node_modules/@agent-recorder/core/index.d.ts.map +1 -1
- package/vendor/node_modules/@agent-recorder/core/index.js +3 -0
- package/vendor/node_modules/@agent-recorder/core/index.js.map +1 -1
- package/vendor/node_modules/@agent-recorder/core/providers/index.d.ts +9 -0
- package/vendor/node_modules/@agent-recorder/core/providers/index.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/providers/index.js +9 -0
- package/vendor/node_modules/@agent-recorder/core/providers/index.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/providers/io.d.ts +36 -0
- package/vendor/node_modules/@agent-recorder/core/providers/io.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/providers/io.js +88 -0
- package/vendor/node_modules/@agent-recorder/core/providers/io.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/providers/io.test.d.ts +5 -0
- package/vendor/node_modules/@agent-recorder/core/providers/io.test.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/providers/io.test.js +248 -0
- package/vendor/node_modules/@agent-recorder/core/providers/io.test.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/providers/types.d.ts +36 -0
- package/vendor/node_modules/@agent-recorder/core/providers/types.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/providers/types.js +6 -0
- package/vendor/node_modules/@agent-recorder/core/providers/types.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/wrap-utils.d.ts +42 -0
- package/vendor/node_modules/@agent-recorder/core/wrap-utils.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/core/wrap-utils.js +80 -0
- package/vendor/node_modules/@agent-recorder/core/wrap-utils.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/service/index.d.ts.map +1 -1
- package/vendor/node_modules/@agent-recorder/service/index.js +19 -0
- package/vendor/node_modules/@agent-recorder/service/index.js.map +1 -1
- package/vendor/node_modules/@agent-recorder/service/mcp/auto-wrap-manager.d.ts +43 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/auto-wrap-manager.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/auto-wrap-manager.js +221 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/auto-wrap-manager.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/hub.test.d.ts +6 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/hub.test.d.ts.map +1 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/hub.test.js +319 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/hub.test.js.map +1 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/proxy.d.ts +1 -0
- package/vendor/node_modules/@agent-recorder/service/mcp/proxy.d.ts.map +1 -1
- package/vendor/node_modules/@agent-recorder/service/mcp/proxy.js +239 -34
- package/vendor/node_modules/@agent-recorder/service/mcp/proxy.js.map +1 -1
- package/vendor/node_modules/@agent-recorder/service/routes/events.d.ts.map +1 -1
- package/vendor/node_modules/@agent-recorder/service/routes/events.js +1 -0
- package/vendor/node_modules/@agent-recorder/service/routes/events.js.map +1 -1
|
@@ -9,6 +9,7 @@ import { loadConfig, openDatabase, runMigrations, getDefaultMigrationsDir, getDa
|
|
|
9
9
|
import { createServer, startServer } from "./server.js";
|
|
10
10
|
import { createMcpProxy } from "./mcp/index.js";
|
|
11
11
|
import { createSessionManager } from "./session-manager.js";
|
|
12
|
+
import { AutoWrapManager } from "./mcp/auto-wrap-manager.js";
|
|
12
13
|
export { createServer, startServer } from "./server.js";
|
|
13
14
|
export { createMcpProxy } from "./mcp/index.js";
|
|
14
15
|
export { createSessionManager } from "./session-manager.js";
|
|
@@ -51,6 +52,20 @@ export async function startDaemon(options = {}) {
|
|
|
51
52
|
// Store for health endpoint
|
|
52
53
|
daemonSessionId = sessionManager.sessionId;
|
|
53
54
|
daemonStartedAt = startedAt;
|
|
55
|
+
// Initialize auto-wrap manager (fail-open: errors logged, not thrown)
|
|
56
|
+
let autoWrapManager = null;
|
|
57
|
+
try {
|
|
58
|
+
autoWrapManager = new AutoWrapManager({
|
|
59
|
+
config,
|
|
60
|
+
sessionId: sessionManager.sessionId,
|
|
61
|
+
db,
|
|
62
|
+
});
|
|
63
|
+
await autoWrapManager.initialize();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error("[AutoWrap] Initialization failed:", error instanceof Error ? error.message : "Unknown error");
|
|
67
|
+
console.error("Continuing in manual wrap mode...");
|
|
68
|
+
}
|
|
54
69
|
// Create and start REST API server (pass currentSessionId for /api/sessions/current)
|
|
55
70
|
const app = await createServer({
|
|
56
71
|
db,
|
|
@@ -89,6 +104,10 @@ export async function startDaemon(options = {}) {
|
|
|
89
104
|
sessionManager.shutdown(status);
|
|
90
105
|
await proxy.close();
|
|
91
106
|
await app.close();
|
|
107
|
+
// Cleanup auto-wrap manager
|
|
108
|
+
if (autoWrapManager) {
|
|
109
|
+
await autoWrapManager.cleanup();
|
|
110
|
+
}
|
|
92
111
|
db.close();
|
|
93
112
|
// Clean up PID file and lock in daemon mode
|
|
94
113
|
if (daemonMode) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,UAAU,EACV,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,WAAW,GAEZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Wrap Manager
|
|
3
|
+
* Automatically discovers and wraps MCP servers from Claude Code config.
|
|
4
|
+
* Supports URL-based servers (stdio support in later phase).
|
|
5
|
+
*/
|
|
6
|
+
import type Database from "better-sqlite3";
|
|
7
|
+
import type { Config } from "@agent-recorder/core";
|
|
8
|
+
export interface AutoWrapManagerOptions {
|
|
9
|
+
config: Config;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
db: Database.Database;
|
|
12
|
+
}
|
|
13
|
+
export declare class AutoWrapManager {
|
|
14
|
+
private wrappedServers;
|
|
15
|
+
private config;
|
|
16
|
+
private sessionId;
|
|
17
|
+
private db;
|
|
18
|
+
private claudeConfigPath;
|
|
19
|
+
private originalClaudeConfig;
|
|
20
|
+
constructor(options: AutoWrapManagerOptions);
|
|
21
|
+
/**
|
|
22
|
+
* Discover servers from Claude config and wrap them.
|
|
23
|
+
* Fail-open: logs errors but doesn't throw.
|
|
24
|
+
*/
|
|
25
|
+
initialize(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Re-discover and wrap new servers (for hot-reload).
|
|
28
|
+
*/
|
|
29
|
+
handleConfigChange(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Cleanup: restore config, unregister servers.
|
|
32
|
+
*/
|
|
33
|
+
cleanup(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Discover MCP servers from Claude config.
|
|
36
|
+
*/
|
|
37
|
+
private discoverServers;
|
|
38
|
+
/**
|
|
39
|
+
* Wrap a single URL-based MCP server.
|
|
40
|
+
*/
|
|
41
|
+
private wrapUrlServer;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=auto-wrap-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-wrap-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/auto-wrap-manager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAsBnD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;CACvB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,cAAc,CAAyC;IAC/D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,oBAAoB,CAAiB;gBAEjC,OAAO,EAAE,sBAAsB;IAM3C;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiEjC;;OAEG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAuDzC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B9B;;OAEG;YACW,eAAe;IAqD7B;;OAEG;YACW,aAAa;CAuD5B"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-Wrap Manager
|
|
3
|
+
* Automatically discovers and wraps MCP servers from Claude Code config.
|
|
4
|
+
* Supports URL-based servers (stdio support in later phase).
|
|
5
|
+
*/
|
|
6
|
+
import { registerUrlServer, unregisterServer, isAlreadyWrapped, detectClaudeConfig, readJsonFile, writeJsonFileAtomic, } from "@agent-recorder/core";
|
|
7
|
+
export class AutoWrapManager {
|
|
8
|
+
wrappedServers = new Map();
|
|
9
|
+
config;
|
|
10
|
+
sessionId;
|
|
11
|
+
db;
|
|
12
|
+
claudeConfigPath = null;
|
|
13
|
+
originalClaudeConfig = null;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.config = options.config;
|
|
16
|
+
this.sessionId = options.sessionId;
|
|
17
|
+
this.db = options.db;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Discover servers from Claude config and wrap them.
|
|
21
|
+
* Fail-open: logs errors but doesn't throw.
|
|
22
|
+
*/
|
|
23
|
+
async initialize() {
|
|
24
|
+
try {
|
|
25
|
+
// Detect Claude config
|
|
26
|
+
const detected = detectClaudeConfig();
|
|
27
|
+
if (detected.kind === "none" || !detected.path) {
|
|
28
|
+
console.log("[AutoWrap] No Claude Code config found - skipping auto-wrap");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.claudeConfigPath = detected.path;
|
|
32
|
+
console.log(`[AutoWrap] Found Claude config: ${detected.path}`);
|
|
33
|
+
// Read and backup original config
|
|
34
|
+
const configData = readJsonFile(this.claudeConfigPath);
|
|
35
|
+
if (!configData) {
|
|
36
|
+
console.error("[AutoWrap] Could not read Claude config - skipping");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.originalClaudeConfig = structuredClone(configData);
|
|
40
|
+
// Discover servers
|
|
41
|
+
const discovered = await this.discoverServers(configData);
|
|
42
|
+
console.log(`[AutoWrap] Discovered ${discovered.url.size} URL server(s), ${discovered.stdio.size} stdio server(s)`);
|
|
43
|
+
// Wrap URL servers
|
|
44
|
+
let wrappedCount = 0;
|
|
45
|
+
for (const [key, url] of discovered.url.entries()) {
|
|
46
|
+
try {
|
|
47
|
+
await this.wrapUrlServer(key, url);
|
|
48
|
+
wrappedCount++;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.error(`[AutoWrap] Failed to wrap server "${key}":`, error instanceof Error ? error.message : "Unknown error");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (wrappedCount > 0) {
|
|
55
|
+
console.log(`[AutoWrap] Successfully wrapped ${wrappedCount} server(s)`);
|
|
56
|
+
}
|
|
57
|
+
// TODO: Wrap stdio servers (later phase)
|
|
58
|
+
if (discovered.stdio.size > 0) {
|
|
59
|
+
console.log(`[AutoWrap] Skipping ${discovered.stdio.size} stdio server(s) (not yet supported)`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error("[AutoWrap] Initialization failed:", error instanceof Error ? error.message : "Unknown error");
|
|
64
|
+
console.error("Continuing in manual wrap mode...");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Re-discover and wrap new servers (for hot-reload).
|
|
69
|
+
*/
|
|
70
|
+
async handleConfigChange() {
|
|
71
|
+
console.log("[AutoWrap] Config change detected - re-wrapping...");
|
|
72
|
+
try {
|
|
73
|
+
if (!this.claudeConfigPath) {
|
|
74
|
+
console.error("[AutoWrap] No config path - cannot reload");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Read updated config
|
|
78
|
+
const configData = readJsonFile(this.claudeConfigPath);
|
|
79
|
+
if (!configData) {
|
|
80
|
+
console.error("[AutoWrap] Could not read updated config");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Discover servers
|
|
84
|
+
const discovered = await this.discoverServers(configData);
|
|
85
|
+
// Find new servers (not already wrapped)
|
|
86
|
+
const newServers = new Map();
|
|
87
|
+
for (const [key, url] of discovered.url.entries()) {
|
|
88
|
+
if (!this.wrappedServers.has(key)) {
|
|
89
|
+
newServers.set(key, url);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (newServers.size === 0) {
|
|
93
|
+
console.log("[AutoWrap] No new servers to wrap");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Wrap new servers
|
|
97
|
+
let wrappedCount = 0;
|
|
98
|
+
for (const [key, url] of newServers.entries()) {
|
|
99
|
+
try {
|
|
100
|
+
await this.wrapUrlServer(key, url);
|
|
101
|
+
wrappedCount++;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`[AutoWrap] Failed to wrap new server "${key}":`, error instanceof Error ? error.message : "Unknown error");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
console.log(`[AutoWrap] Wrapped ${wrappedCount} new server(s)`);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error("[AutoWrap] Config reload failed:", error instanceof Error ? error.message : "Unknown error");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Cleanup: restore config, unregister servers.
|
|
115
|
+
*/
|
|
116
|
+
async cleanup() {
|
|
117
|
+
try {
|
|
118
|
+
console.log("[AutoWrap] Cleaning up...");
|
|
119
|
+
// Unregister all wrapped servers
|
|
120
|
+
for (const [key, server] of this.wrappedServers.entries()) {
|
|
121
|
+
if (server.type === "url") {
|
|
122
|
+
unregisterServer(key, this.config.upstreamsPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Restore original Claude config
|
|
126
|
+
if (this.claudeConfigPath && this.originalClaudeConfig) {
|
|
127
|
+
writeJsonFileAtomic(this.claudeConfigPath, this.originalClaudeConfig);
|
|
128
|
+
console.log("[AutoWrap] Restored original Claude config");
|
|
129
|
+
}
|
|
130
|
+
this.wrappedServers.clear();
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error("[AutoWrap] Cleanup failed:", error instanceof Error ? error.message : "Unknown error");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Discover MCP servers from Claude config.
|
|
138
|
+
*/
|
|
139
|
+
async discoverServers(configData) {
|
|
140
|
+
const url = new Map();
|
|
141
|
+
const stdio = new Map();
|
|
142
|
+
if (!configData ||
|
|
143
|
+
typeof configData !== "object" ||
|
|
144
|
+
Array.isArray(configData)) {
|
|
145
|
+
return { url, stdio };
|
|
146
|
+
}
|
|
147
|
+
const config = configData;
|
|
148
|
+
const mcpServers = config.mcpServers;
|
|
149
|
+
if (!mcpServers ||
|
|
150
|
+
typeof mcpServers !== "object" ||
|
|
151
|
+
Array.isArray(mcpServers)) {
|
|
152
|
+
return { url, stdio };
|
|
153
|
+
}
|
|
154
|
+
const servers = mcpServers;
|
|
155
|
+
for (const [key, entry] of Object.entries(servers)) {
|
|
156
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const serverEntry = entry;
|
|
160
|
+
// URL-based server
|
|
161
|
+
if (typeof serverEntry.url === "string") {
|
|
162
|
+
url.set(key, serverEntry.url);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// Command-based server (stdio)
|
|
166
|
+
if (typeof serverEntry.command === "string") {
|
|
167
|
+
const args = Array.isArray(serverEntry.args)
|
|
168
|
+
? serverEntry.args
|
|
169
|
+
: [];
|
|
170
|
+
stdio.set(key, { command: serverEntry.command, args });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { url, stdio };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Wrap a single URL-based MCP server.
|
|
178
|
+
*/
|
|
179
|
+
async wrapUrlServer(key, url) {
|
|
180
|
+
// Check if already wrapped
|
|
181
|
+
if (isAlreadyWrapped(key, this.config.upstreamsPath)) {
|
|
182
|
+
console.log(`[AutoWrap] Server "${key}" already wrapped - skipping`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Register in upstreams registry
|
|
186
|
+
const proxyUrl = registerUrlServer(key, url, this.config.upstreamsPath, this.config.mcpProxyPort);
|
|
187
|
+
// Update Claude config to use proxy URL
|
|
188
|
+
if (!this.claudeConfigPath) {
|
|
189
|
+
throw new Error("Claude config path not set");
|
|
190
|
+
}
|
|
191
|
+
const configData = readJsonFile(this.claudeConfigPath);
|
|
192
|
+
if (!configData) {
|
|
193
|
+
throw new Error("Could not read Claude config");
|
|
194
|
+
}
|
|
195
|
+
const config = configData;
|
|
196
|
+
const mcpServers = config.mcpServers;
|
|
197
|
+
const serverEntry = mcpServers[key];
|
|
198
|
+
// Update URL to proxy
|
|
199
|
+
const newEntry = {
|
|
200
|
+
...serverEntry,
|
|
201
|
+
url: proxyUrl,
|
|
202
|
+
};
|
|
203
|
+
const newConfig = {
|
|
204
|
+
...config,
|
|
205
|
+
mcpServers: {
|
|
206
|
+
...mcpServers,
|
|
207
|
+
[key]: newEntry,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
// Write updated config atomically
|
|
211
|
+
writeJsonFileAtomic(this.claudeConfigPath, newConfig);
|
|
212
|
+
// Track wrapped server
|
|
213
|
+
this.wrappedServers.set(key, {
|
|
214
|
+
key,
|
|
215
|
+
type: "url",
|
|
216
|
+
originalUrl: url,
|
|
217
|
+
});
|
|
218
|
+
console.log(`[AutoWrap] Wrapped "${key}": ${url} -> ${proxyUrl}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=auto-wrap-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-wrap-manager.js","sourceRoot":"","sources":["../../src/mcp/auto-wrap-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub.test.d.ts","sourceRoot":"","sources":["../../src/mcp/hub.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for MCP Hub mode.
|
|
3
|
+
* Verifies tools/list aggregation and tools/call routing with multiple providers.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
6
|
+
import Fastify, {} from "fastify";
|
|
7
|
+
import { loadConfig, openMemoryDatabase, runMigrations, writeProvidersFile, getDefaultProvidersPath, } from "@agent-recorder/core";
|
|
8
|
+
import { createMcpProxy } from "./proxy.js";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
/**
|
|
15
|
+
* Create a mock MCP server that responds to tools/list and tools/call.
|
|
16
|
+
*/
|
|
17
|
+
async function createMockMcpServer(port, tools) {
|
|
18
|
+
const app = Fastify({ logger: false });
|
|
19
|
+
// Mock tools/list endpoint
|
|
20
|
+
app.post("/", async (request, reply) => {
|
|
21
|
+
const body = request.body;
|
|
22
|
+
if (body.method === "tools/list") {
|
|
23
|
+
return reply.code(200).send({
|
|
24
|
+
jsonrpc: "2.0",
|
|
25
|
+
result: {
|
|
26
|
+
tools: tools.map((t) => ({
|
|
27
|
+
name: t.name,
|
|
28
|
+
description: t.description,
|
|
29
|
+
inputSchema: { type: "object", properties: {} },
|
|
30
|
+
})),
|
|
31
|
+
},
|
|
32
|
+
id: body.id ?? null,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (body.method === "tools/call") {
|
|
36
|
+
const params = body.params;
|
|
37
|
+
const tool = tools.find((t) => t.name === params.name);
|
|
38
|
+
if (!tool) {
|
|
39
|
+
return reply.code(200).send({
|
|
40
|
+
jsonrpc: "2.0",
|
|
41
|
+
error: {
|
|
42
|
+
code: -32602,
|
|
43
|
+
message: `Unknown tool: ${params.name}`,
|
|
44
|
+
},
|
|
45
|
+
id: body.id ?? null,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return reply.code(200).send({
|
|
49
|
+
jsonrpc: "2.0",
|
|
50
|
+
result: { success: true, tool: params.name },
|
|
51
|
+
id: body.id ?? null,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return reply.code(400).send({
|
|
55
|
+
jsonrpc: "2.0",
|
|
56
|
+
error: { code: -32601, message: "Method not found" },
|
|
57
|
+
id: body.id ?? null,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
await app.listen({ port, host: "127.0.0.1" });
|
|
61
|
+
return {
|
|
62
|
+
app,
|
|
63
|
+
close: async () => {
|
|
64
|
+
await app.close();
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
describe("MCP Hub Mode", () => {
|
|
69
|
+
let db;
|
|
70
|
+
let sessionId;
|
|
71
|
+
let mockServer1;
|
|
72
|
+
let mockServer2;
|
|
73
|
+
beforeEach(async () => {
|
|
74
|
+
// Create in-memory database with migrations
|
|
75
|
+
db = openMemoryDatabase();
|
|
76
|
+
const migrationsDir = join(__dirname, "..", "..", "..", "core", "migrations");
|
|
77
|
+
runMigrations(db, migrationsDir);
|
|
78
|
+
// Create session
|
|
79
|
+
sessionId = "test-session-hub";
|
|
80
|
+
db.prepare("INSERT INTO sessions (id, started_at, status, created_at) VALUES (?, datetime('now'), ?, datetime('now'))").run(sessionId, "active");
|
|
81
|
+
// Start mock MCP servers
|
|
82
|
+
mockServer1 = await createMockMcpServer(9991, [
|
|
83
|
+
{ name: "echo", description: "Echo tool" },
|
|
84
|
+
{ name: "uppercase", description: "Uppercase tool" },
|
|
85
|
+
]);
|
|
86
|
+
mockServer2 = await createMockMcpServer(9992, [
|
|
87
|
+
{ name: "reverse", description: "Reverse tool" },
|
|
88
|
+
{ name: "lowercase", description: "Lowercase tool" },
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
afterEach(async () => {
|
|
92
|
+
if (mockServer1)
|
|
93
|
+
await mockServer1.close();
|
|
94
|
+
if (mockServer2)
|
|
95
|
+
await mockServer2.close();
|
|
96
|
+
if (db)
|
|
97
|
+
db.close();
|
|
98
|
+
});
|
|
99
|
+
it("aggregates tools/list from multiple providers with namespacing", async () => {
|
|
100
|
+
// Write providers BEFORE creating proxy so it loads them during init
|
|
101
|
+
writeProvidersFile({
|
|
102
|
+
version: 1,
|
|
103
|
+
providers: [
|
|
104
|
+
{ id: "server1", type: "http", url: "http://127.0.0.1:9991/" },
|
|
105
|
+
{ id: "server2", type: "http", url: "http://127.0.0.1:9992/" },
|
|
106
|
+
],
|
|
107
|
+
}, getDefaultProvidersPath());
|
|
108
|
+
const config = loadConfig();
|
|
109
|
+
const { app, close } = await createMcpProxy({
|
|
110
|
+
db,
|
|
111
|
+
config: {
|
|
112
|
+
...config,
|
|
113
|
+
// Override providers path for test
|
|
114
|
+
mcpProxyPort: 19991,
|
|
115
|
+
},
|
|
116
|
+
sessionId,
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
await app.listen({ port: 19991, host: "127.0.0.1" });
|
|
120
|
+
// Call tools/list
|
|
121
|
+
const response = await fetch("http://127.0.0.1:19991/", {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { "Content-Type": "application/json" },
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
jsonrpc: "2.0",
|
|
126
|
+
method: "tools/list",
|
|
127
|
+
id: 1,
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
expect(response.status).toBe(200);
|
|
131
|
+
const data = (await response.json());
|
|
132
|
+
const tools = data.result.tools;
|
|
133
|
+
// Verify namespaced tool names from both providers
|
|
134
|
+
expect(tools).toHaveLength(4);
|
|
135
|
+
const toolNames = tools.map((t) => t.name).sort();
|
|
136
|
+
expect(toolNames).toEqual([
|
|
137
|
+
"server1.echo",
|
|
138
|
+
"server1.uppercase",
|
|
139
|
+
"server2.lowercase",
|
|
140
|
+
"server2.reverse",
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
await close();
|
|
145
|
+
// Cleanup providers file
|
|
146
|
+
if (fs.existsSync(getDefaultProvidersPath())) {
|
|
147
|
+
fs.unlinkSync(getDefaultProvidersPath());
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
it("routes tools/call to correct provider and records with upstreamKey", async () => {
|
|
152
|
+
// Write providers BEFORE creating proxy so it loads them during init
|
|
153
|
+
writeProvidersFile({
|
|
154
|
+
version: 1,
|
|
155
|
+
providers: [
|
|
156
|
+
{ id: "server1", type: "http", url: "http://127.0.0.1:9991/" },
|
|
157
|
+
{ id: "server2", type: "http", url: "http://127.0.0.1:9992/" },
|
|
158
|
+
],
|
|
159
|
+
}, getDefaultProvidersPath());
|
|
160
|
+
const config = loadConfig();
|
|
161
|
+
const { app, close } = await createMcpProxy({
|
|
162
|
+
db,
|
|
163
|
+
config: {
|
|
164
|
+
...config,
|
|
165
|
+
mcpProxyPort: 19992,
|
|
166
|
+
},
|
|
167
|
+
sessionId,
|
|
168
|
+
});
|
|
169
|
+
try {
|
|
170
|
+
await app.listen({ port: 19992, host: "127.0.0.1" });
|
|
171
|
+
// Call namespaced tool from server1
|
|
172
|
+
const response1 = await fetch("http://127.0.0.1:19992/", {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: { "Content-Type": "application/json" },
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
jsonrpc: "2.0",
|
|
177
|
+
method: "tools/call",
|
|
178
|
+
params: { name: "server1.echo", arguments: { text: "hello" } },
|
|
179
|
+
id: 2,
|
|
180
|
+
}),
|
|
181
|
+
});
|
|
182
|
+
expect(response1.status).toBe(200);
|
|
183
|
+
const data1 = (await response1.json());
|
|
184
|
+
expect(data1.result.tool).toBe("echo"); // Tool name without namespace
|
|
185
|
+
// Call namespaced tool from server2
|
|
186
|
+
const response2 = await fetch("http://127.0.0.1:19992/", {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: { "Content-Type": "application/json" },
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
jsonrpc: "2.0",
|
|
191
|
+
method: "tools/call",
|
|
192
|
+
params: { name: "server2.reverse", arguments: { text: "world" } },
|
|
193
|
+
id: 3,
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
expect(response2.status).toBe(200);
|
|
197
|
+
const data2 = (await response2.json());
|
|
198
|
+
expect(data2.result.tool).toBe("reverse");
|
|
199
|
+
// Verify events recorded with correct upstreamKey
|
|
200
|
+
const events = db
|
|
201
|
+
.prepare("SELECT * FROM events WHERE session_id = ? ORDER BY sequence ASC")
|
|
202
|
+
.all(sessionId);
|
|
203
|
+
expect(events).toHaveLength(2);
|
|
204
|
+
// First event: server1.echo -> routed to server1
|
|
205
|
+
expect(events[0].tool_name).toBe("echo");
|
|
206
|
+
expect(events[0].upstream_key).toBe("server1");
|
|
207
|
+
expect(events[0].status).toBe("success");
|
|
208
|
+
// Second event: server2.reverse -> routed to server2
|
|
209
|
+
expect(events[1].tool_name).toBe("reverse");
|
|
210
|
+
expect(events[1].upstream_key).toBe("server2");
|
|
211
|
+
expect(events[1].status).toBe("success");
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
await close();
|
|
215
|
+
if (fs.existsSync(getDefaultProvidersPath())) {
|
|
216
|
+
fs.unlinkSync(getDefaultProvidersPath());
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
it("handles unknown provider gracefully", async () => {
|
|
221
|
+
// Write providers BEFORE creating proxy so it loads them during init
|
|
222
|
+
writeProvidersFile({
|
|
223
|
+
version: 1,
|
|
224
|
+
providers: [
|
|
225
|
+
{ id: "server1", type: "http", url: "http://127.0.0.1:9991/" },
|
|
226
|
+
],
|
|
227
|
+
}, getDefaultProvidersPath());
|
|
228
|
+
const config = loadConfig();
|
|
229
|
+
const { app, close } = await createMcpProxy({
|
|
230
|
+
db,
|
|
231
|
+
config: {
|
|
232
|
+
...config,
|
|
233
|
+
mcpProxyPort: 19993,
|
|
234
|
+
},
|
|
235
|
+
sessionId,
|
|
236
|
+
});
|
|
237
|
+
try {
|
|
238
|
+
await app.listen({ port: 19993, host: "127.0.0.1" });
|
|
239
|
+
// Call tool with unknown provider ID
|
|
240
|
+
const response = await fetch("http://127.0.0.1:19993/", {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: { "Content-Type": "application/json" },
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
jsonrpc: "2.0",
|
|
245
|
+
method: "tools/call",
|
|
246
|
+
params: { name: "unknown.tool", arguments: {} },
|
|
247
|
+
id: 4,
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
expect(response.status).toBe(404);
|
|
251
|
+
const data = (await response.json());
|
|
252
|
+
expect(data.error.message).toContain("Unknown provider");
|
|
253
|
+
expect(data.error.data.category).toBe("downstream_unreachable");
|
|
254
|
+
// Verify error event recorded
|
|
255
|
+
const events = db
|
|
256
|
+
.prepare("SELECT * FROM events WHERE session_id = ?")
|
|
257
|
+
.all(sessionId);
|
|
258
|
+
expect(events).toHaveLength(1);
|
|
259
|
+
expect(events[0].tool_name).toBe("tool");
|
|
260
|
+
expect(events[0].upstream_key).toBe("unknown");
|
|
261
|
+
expect(events[0].status).toBe("error");
|
|
262
|
+
expect(events[0].error_category).toBe("downstream_unreachable");
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
await close();
|
|
266
|
+
if (fs.existsSync(getDefaultProvidersPath())) {
|
|
267
|
+
fs.unlinkSync(getDefaultProvidersPath());
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
it("handles provider failure during tools/list gracefully", async () => {
|
|
272
|
+
// Write providers BEFORE creating proxy so it loads them during init
|
|
273
|
+
// Include one unreachable provider
|
|
274
|
+
writeProvidersFile({
|
|
275
|
+
version: 1,
|
|
276
|
+
providers: [
|
|
277
|
+
{ id: "server1", type: "http", url: "http://127.0.0.1:9991/" },
|
|
278
|
+
{ id: "unreachable", type: "http", url: "http://127.0.0.1:19999/" },
|
|
279
|
+
],
|
|
280
|
+
}, getDefaultProvidersPath());
|
|
281
|
+
const config = loadConfig();
|
|
282
|
+
const { app, close } = await createMcpProxy({
|
|
283
|
+
db,
|
|
284
|
+
config: {
|
|
285
|
+
...config,
|
|
286
|
+
mcpProxyPort: 19994,
|
|
287
|
+
debugProxy: true, // Enable debug logging for failure test
|
|
288
|
+
},
|
|
289
|
+
sessionId,
|
|
290
|
+
});
|
|
291
|
+
try {
|
|
292
|
+
await app.listen({ port: 19994, host: "127.0.0.1" });
|
|
293
|
+
// Call tools/list - should return tools from server1 only
|
|
294
|
+
const response = await fetch("http://127.0.0.1:19994/", {
|
|
295
|
+
method: "POST",
|
|
296
|
+
headers: { "Content-Type": "application/json" },
|
|
297
|
+
body: JSON.stringify({
|
|
298
|
+
jsonrpc: "2.0",
|
|
299
|
+
method: "tools/list",
|
|
300
|
+
id: 5,
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
expect(response.status).toBe(200);
|
|
304
|
+
const data = (await response.json());
|
|
305
|
+
const tools = data.result.tools;
|
|
306
|
+
// Should only have tools from server1 (unreachable provider omitted)
|
|
307
|
+
expect(tools).toHaveLength(2);
|
|
308
|
+
const toolNames = tools.map((t) => t.name).sort();
|
|
309
|
+
expect(toolNames).toEqual(["server1.echo", "server1.uppercase"]);
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
await close();
|
|
313
|
+
if (fs.existsSync(getDefaultProvidersPath())) {
|
|
314
|
+
fs.unlinkSync(getDefaultProvidersPath());
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
//# sourceMappingURL=hub.test.js.map
|