hoomanjs 1.58.0 → 1.59.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/dist/acp/acp-agent.js +17 -3
- package/dist/acp/acp-agent.js.map +1 -1
- package/dist/acp/approvals.js +11 -0
- package/dist/acp/approvals.js.map +1 -1
- package/dist/acp/meta/daemon.d.ts +12 -0
- package/dist/acp/meta/daemon.js +16 -0
- package/dist/acp/meta/daemon.js.map +1 -0
- package/dist/acp/meta/origin.d.ts +8 -0
- package/dist/acp/meta/origin.js +38 -0
- package/dist/acp/meta/origin.js.map +1 -0
- package/dist/acp/sessions/store.d.ts +6 -0
- package/dist/acp/sessions/store.js.map +1 -1
- package/dist/cli.js +78 -26
- package/dist/cli.js.map +1 -1
- package/dist/configure/app.js +1 -0
- package/dist/configure/app.js.map +1 -1
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.js +32 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mcp/manager.d.ts +15 -0
- package/dist/core/mcp/manager.js +28 -0
- package/dist/core/mcp/manager.js.map +1 -1
- package/dist/core/mcp/prefixed-mcp-tool.d.ts +1 -0
- package/dist/core/mcp/prefixed-mcp-tool.js +1 -1
- package/dist/core/mcp/prefixed-mcp-tool.js.map +1 -1
- package/dist/core/skills/built-in/hooman-config/SKILL.md +35 -3
- package/dist/daemon/acp-client.d.ts +49 -0
- package/dist/daemon/acp-client.js +130 -0
- package/dist/daemon/acp-client.js.map +1 -0
- package/dist/daemon/approvals.d.ts +9 -2
- package/dist/daemon/approvals.js +151 -6
- package/dist/daemon/approvals.js.map +1 -1
- package/dist/daemon/attachments.d.ts +9 -0
- package/dist/daemon/attachments.js +63 -0
- package/dist/daemon/attachments.js.map +1 -0
- package/dist/daemon/index.d.ts +16 -6
- package/dist/daemon/index.js +162 -71
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/mcproxy/catalog.d.ts +17 -0
- package/dist/daemon/mcproxy/catalog.js +71 -0
- package/dist/daemon/mcproxy/catalog.js.map +1 -0
- package/dist/daemon/mcproxy/http-server.d.ts +15 -0
- package/dist/daemon/mcproxy/http-server.js +150 -0
- package/dist/daemon/mcproxy/http-server.js.map +1 -0
- package/dist/daemon/mcproxy/index.d.ts +23 -0
- package/dist/daemon/mcproxy/index.js +23 -0
- package/dist/daemon/mcproxy/index.js.map +1 -0
- package/dist/daemon/mcproxy/session.d.ts +9 -0
- package/dist/daemon/mcproxy/session.js +24 -0
- package/dist/daemon/mcproxy/session.js.map +1 -0
- package/dist/daemon/queue.d.ts +14 -5
- package/dist/daemon/queue.js +31 -33
- package/dist/daemon/queue.js.map +1 -1
- package/dist/daemon/session-registry.d.ts +66 -0
- package/dist/daemon/session-registry.js +198 -0
- package/dist/daemon/session-registry.js.map +1 -0
- package/dist/daemon/session-store.d.ts +20 -0
- package/dist/daemon/session-store.js +63 -0
- package/dist/daemon/session-store.js.map +1 -0
- package/package.json +1 -1
- package/dist/daemon/questions.d.ts +0 -18
- package/dist/daemon/questions.js +0 -44
- package/dist/daemon/questions.js.map +0 -1
package/dist/daemon/index.js
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
import { stderr } from "node:process";
|
|
2
|
-
import {
|
|
2
|
+
import { CONFIG_ID_EFFORT, CONFIG_ID_MODE, CONFIG_ID_MODEL, CONFIG_ID_YOLO, } from "../acp/session-config.js";
|
|
3
3
|
import { HOOMAN_CHANNEL } from "../core/mcp/index.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { attachmentPathsToPromptBlocks } from "../core/utils/attachments.js";
|
|
7
|
-
import { createQueue } from "./queue.js";
|
|
8
|
-
const MAX_ATTACHMENT_BYTES = 1024 * 1024;
|
|
4
|
+
import { attachmentPathsToAcpBlocks } from "./attachments.js";
|
|
5
|
+
import { KeyedTurnQueue } from "./queue.js";
|
|
9
6
|
function debug(text) {
|
|
10
7
|
stderr.write(`[daemon] ${text}\n`);
|
|
11
8
|
}
|
|
12
|
-
function
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// Namespace per `server:channel` so the same chat id coming from two
|
|
17
|
-
// different MCP servers (or two channels on the same server) never collide.
|
|
18
|
-
return `${message.meta.subscription.server}:${message.meta.subscription.channel}:${raw}`;
|
|
9
|
+
function resolveExternalKey(message, cliSessionFallback) {
|
|
10
|
+
const { server, channel } = message.meta.subscription;
|
|
11
|
+
const raw = message.meta.session?.trim() || cliSessionFallback?.trim();
|
|
12
|
+
return raw ? `${server}:${channel}:${raw}` : `${server}:${channel}`;
|
|
19
13
|
}
|
|
20
|
-
function resolveUserId(message,
|
|
14
|
+
function resolveUserId(message, externalKey) {
|
|
21
15
|
const raw = message.meta.user?.trim();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
return raw ? `${message.meta.subscription.server}:${raw}` : externalKey;
|
|
17
|
+
}
|
|
18
|
+
function buildOrigin(message) {
|
|
19
|
+
return {
|
|
20
|
+
server: message.meta.subscription.server,
|
|
21
|
+
...(message.meta.source ? { source: message.meta.source } : {}),
|
|
22
|
+
...(message.meta.user ? { user: message.meta.user } : {}),
|
|
23
|
+
...(message.meta.session ? { session: message.meta.session } : {}),
|
|
24
|
+
...(message.meta.thread ? { thread: message.meta.thread } : {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function toPromptBlocks(message) {
|
|
28
|
+
const blocks = [{ type: "text", text: message.prompt }];
|
|
29
|
+
if (message.attachments.length > 0) {
|
|
30
|
+
blocks.push(...(await attachmentPathsToAcpBlocks(message.attachments)));
|
|
31
|
+
}
|
|
32
|
+
return blocks;
|
|
28
33
|
}
|
|
29
34
|
function formatSubscriptions(subscriptions) {
|
|
30
35
|
if (subscriptions.length === 0) {
|
|
@@ -35,80 +40,166 @@ function formatSubscriptions(subscriptions) {
|
|
|
35
40
|
].sort((left, right) => left.localeCompare(right));
|
|
36
41
|
return `${servers.length} MCP server(s): ${servers.join(", ")}`;
|
|
37
42
|
}
|
|
38
|
-
async function
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
async function applyCliOverrides(acpClient, sessionId, overrides) {
|
|
44
|
+
const requests = [];
|
|
45
|
+
if (overrides.mode) {
|
|
46
|
+
requests.push({
|
|
47
|
+
sessionId,
|
|
48
|
+
configId: CONFIG_ID_MODE,
|
|
49
|
+
value: overrides.mode,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (overrides.model) {
|
|
53
|
+
requests.push({
|
|
54
|
+
sessionId,
|
|
55
|
+
configId: CONFIG_ID_MODEL,
|
|
56
|
+
value: overrides.model,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (overrides.effort) {
|
|
60
|
+
requests.push({
|
|
61
|
+
sessionId,
|
|
62
|
+
configId: CONFIG_ID_EFFORT,
|
|
63
|
+
value: overrides.effort,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (overrides.yolo) {
|
|
67
|
+
requests.push({
|
|
68
|
+
sessionId,
|
|
69
|
+
configId: CONFIG_ID_YOLO,
|
|
70
|
+
type: "boolean",
|
|
71
|
+
value: true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
for (const request of requests) {
|
|
75
|
+
await acpClient.setConfigOption(request);
|
|
41
76
|
}
|
|
42
|
-
const blocks = [new TextBlock(message.prompt)];
|
|
43
|
-
const attachmentBlocks = await attachmentPathsToPromptBlocks(message.attachments, {
|
|
44
|
-
maxBytes: MAX_ATTACHMENT_BYTES,
|
|
45
|
-
metadata: options.metadata,
|
|
46
|
-
});
|
|
47
|
-
blocks.push(...attachmentBlocks);
|
|
48
|
-
return [new Message({ role: "user", content: blocks })];
|
|
49
77
|
}
|
|
50
78
|
export async function main(options) {
|
|
51
79
|
const channels = [HOOMAN_CHANNEL];
|
|
52
80
|
debug(`starting daemon for channel(s): ${channels.join(", ")}`);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
81
|
+
const queue = new KeyedTurnQueue();
|
|
82
|
+
async function ensureAcpSession(externalKey, userId, origin) {
|
|
83
|
+
if (options.registry.hasRuntime(externalKey)) {
|
|
84
|
+
options.registry.markBusy(externalKey);
|
|
85
|
+
return options.registry.acpSessionIdFor(externalKey);
|
|
86
|
+
}
|
|
87
|
+
const meta = { "hooman/userId": userId, "hooman/origin": origin };
|
|
88
|
+
const persisted = options.registry.persistedAcpSessionId(externalKey);
|
|
89
|
+
let acpSessionId;
|
|
90
|
+
if (persisted) {
|
|
91
|
+
try {
|
|
92
|
+
await options.acpClient.resumeSession({
|
|
93
|
+
sessionId: persisted,
|
|
94
|
+
cwd: options.cwd,
|
|
95
|
+
mcpServers: [options.mcpServer],
|
|
96
|
+
meta,
|
|
97
|
+
});
|
|
98
|
+
acpSessionId = persisted;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
debug(`resume failed for ${externalKey} (${persisted}); creating a replacement session`);
|
|
102
|
+
const created = await options.acpClient.newSession({
|
|
103
|
+
cwd: options.cwd,
|
|
104
|
+
mcpServers: [options.mcpServer],
|
|
105
|
+
meta,
|
|
106
|
+
});
|
|
107
|
+
acpSessionId = created.sessionId;
|
|
108
|
+
await options.registry.persistBinding({
|
|
109
|
+
externalKey,
|
|
110
|
+
acpSessionId,
|
|
111
|
+
cwd: options.cwd,
|
|
112
|
+
userId,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const created = await options.acpClient.newSession({
|
|
118
|
+
cwd: options.cwd,
|
|
119
|
+
mcpServers: [options.mcpServer],
|
|
120
|
+
meta,
|
|
121
|
+
});
|
|
122
|
+
acpSessionId = created.sessionId;
|
|
123
|
+
await options.registry.persistBinding({
|
|
124
|
+
externalKey,
|
|
125
|
+
acpSessionId,
|
|
126
|
+
cwd: options.cwd,
|
|
127
|
+
userId,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
await applyCliOverrides(options.acpClient, acpSessionId, options.cliOverrides);
|
|
131
|
+
options.registry.registerActive({
|
|
132
|
+
externalKey,
|
|
133
|
+
acpSessionId,
|
|
134
|
+
cwd: options.cwd,
|
|
135
|
+
userId,
|
|
136
|
+
origin,
|
|
137
|
+
});
|
|
138
|
+
return acpSessionId;
|
|
139
|
+
}
|
|
140
|
+
async function runTurn(message) {
|
|
58
141
|
const tag = `${message.meta.subscription.server}:${message.meta.subscription.channel}`;
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
142
|
+
const externalKey = resolveExternalKey(message, options.session);
|
|
143
|
+
const userId = resolveUserId(message, externalKey);
|
|
144
|
+
const origin = buildOrigin(message);
|
|
145
|
+
if (options.registry.isShuttingDown) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await options.registry.acquireSlot(externalKey);
|
|
149
|
+
if (options.registry.isShuttingDown) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
options.registry.updateOrigin(externalKey, origin);
|
|
153
|
+
debug(`dequeued → ${tag} session=${externalKey} user=${userId}`);
|
|
62
154
|
if (options.debug) {
|
|
63
155
|
debug(`raw → ${JSON.stringify(message.meta)}`);
|
|
64
156
|
}
|
|
65
|
-
|
|
66
|
-
options.agent.appState.set("sessionId", session);
|
|
67
|
-
const origin = {
|
|
68
|
-
server: message.meta.subscription.server,
|
|
69
|
-
channel: message.meta.subscription.channel,
|
|
70
|
-
...(message.meta.source ? { source: message.meta.source } : {}),
|
|
71
|
-
...(message.meta.user ? { user: message.meta.user } : {}),
|
|
72
|
-
...(message.meta.session ? { session: message.meta.session } : {}),
|
|
73
|
-
...(message.meta.thread ? { thread: message.meta.thread } : {}),
|
|
74
|
-
};
|
|
75
|
-
options.agent.appState.set("origin", {
|
|
76
|
-
...origin,
|
|
77
|
-
});
|
|
157
|
+
let acpSessionId;
|
|
78
158
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
159
|
+
acpSessionId = await ensureAcpSession(externalKey, userId, origin);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
163
|
+
debug(`session setup failed → ${externalKey}: ${text}`);
|
|
164
|
+
options.registry.markIdle(externalKey, queue.length(externalKey) > 1);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
debug(`invoking agent → ${tag} session=${externalKey} acp=${acpSessionId}`);
|
|
169
|
+
const prompt = await toPromptBlocks(message);
|
|
170
|
+
await options.acpClient.prompt({
|
|
171
|
+
sessionId: acpSessionId,
|
|
172
|
+
prompt,
|
|
173
|
+
meta: { "hooman/origin": origin },
|
|
82
174
|
});
|
|
83
|
-
|
|
84
|
-
await runWithAgentMemoryScope(options.agent, () => options.agent.invoke(invokeInput));
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
await runWithAgentMemoryScope(options.agent, async () => {
|
|
88
|
-
for await (const event of options.agent.stream(invokeInput)) {
|
|
89
|
-
void event;
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
debug(`completed → ${tag} session=${session} user=${user}`);
|
|
175
|
+
debug(`completed → ${tag} session=${externalKey} acp=${acpSessionId}`);
|
|
94
176
|
}
|
|
95
177
|
catch (error) {
|
|
96
178
|
const text = error instanceof Error ? error.message : String(error);
|
|
97
|
-
debug(`turn failed → ${tag} session=${
|
|
179
|
+
debug(`turn failed → ${tag} session=${externalKey} acp=${acpSessionId}: ${text}`);
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
options.registry.markIdle(externalKey, queue.length(externalKey) > 1);
|
|
98
183
|
}
|
|
99
|
-
}
|
|
184
|
+
}
|
|
100
185
|
const handle = await options.manager.subscribeToChannels(channels, (message) => {
|
|
101
186
|
debug(`received notification → ${message.meta.subscription.server}:${message.meta.subscription.channel}`);
|
|
102
|
-
|
|
187
|
+
queue.push(resolveExternalKey(message, options.session), () => runTurn(message));
|
|
103
188
|
});
|
|
104
|
-
unsubscribe = handle.unsubscribe;
|
|
105
189
|
debug(`subscribed → ${formatSubscriptions(handle.subscriptions)}`);
|
|
190
|
+
const stopper = new Promise((resolve) => {
|
|
191
|
+
const shutdown = () => resolve();
|
|
192
|
+
process.once("SIGINT", shutdown);
|
|
193
|
+
process.once("SIGTERM", shutdown);
|
|
194
|
+
});
|
|
106
195
|
try {
|
|
107
|
-
await
|
|
196
|
+
await stopper;
|
|
108
197
|
}
|
|
109
198
|
finally {
|
|
110
|
-
stopDownloadProgress();
|
|
111
199
|
debug("stopping daemon");
|
|
200
|
+
handle.unsubscribe();
|
|
201
|
+
await queue.drain();
|
|
202
|
+
await options.registry.shutdown();
|
|
112
203
|
}
|
|
113
204
|
}
|
|
114
205
|
//# sourceMappingURL=index.js.map
|
package/dist/daemon/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/daemon/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import type { Manager as McpManager } from "../../core/mcp/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Aggregates tools from every configured MCP server the daemon's `Manager`
|
|
5
|
+
* already has connected, exposing them under collision-safe proxied names
|
|
6
|
+
* (`${slugifiedServer}__${wireName}`, deterministically suffixed on slug
|
|
7
|
+
* collisions) and demultiplexing `tools/call` back to the exact upstream
|
|
8
|
+
* server + original wire tool name via an explicit route map — never by
|
|
9
|
+
* parsing caller-controlled names.
|
|
10
|
+
*/
|
|
11
|
+
export declare class DaemonToolCatalog {
|
|
12
|
+
#private;
|
|
13
|
+
private readonly manager;
|
|
14
|
+
constructor(manager: McpManager);
|
|
15
|
+
listTools(): Promise<Tool[]>;
|
|
16
|
+
callTool(name: string, args: Record<string, unknown> | undefined, signal?: AbortSignal): Promise<CallToolResult>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mcpServerPrefix } from "../../core/mcp/prefixed-mcp-tool.js";
|
|
2
|
+
/**
|
|
3
|
+
* Aggregates tools from every configured MCP server the daemon's `Manager`
|
|
4
|
+
* already has connected, exposing them under collision-safe proxied names
|
|
5
|
+
* (`${slugifiedServer}__${wireName}`, deterministically suffixed on slug
|
|
6
|
+
* collisions) and demultiplexing `tools/call` back to the exact upstream
|
|
7
|
+
* server + original wire tool name via an explicit route map — never by
|
|
8
|
+
* parsing caller-controlled names.
|
|
9
|
+
*/
|
|
10
|
+
export class DaemonToolCatalog {
|
|
11
|
+
manager;
|
|
12
|
+
#routes = new Map();
|
|
13
|
+
constructor(manager) {
|
|
14
|
+
this.manager = manager;
|
|
15
|
+
}
|
|
16
|
+
/** Deterministic proxied-name prefix per server, suffixing slug collisions by sorted server name order. */
|
|
17
|
+
#resolvePrefixes(serverNames) {
|
|
18
|
+
const sorted = [...serverNames].sort((a, b) => a.localeCompare(b));
|
|
19
|
+
const counts = new Map();
|
|
20
|
+
const prefixes = new Map();
|
|
21
|
+
for (const server of sorted) {
|
|
22
|
+
const base = mcpServerPrefix(server);
|
|
23
|
+
const count = (counts.get(base) ?? 0) + 1;
|
|
24
|
+
counts.set(base, count);
|
|
25
|
+
prefixes.set(server, count === 1 ? base : `${base}_${count}`);
|
|
26
|
+
}
|
|
27
|
+
return prefixes;
|
|
28
|
+
}
|
|
29
|
+
async listTools() {
|
|
30
|
+
const servers = this.manager.listServers().map((entry) => entry.name);
|
|
31
|
+
const prefixes = this.#resolvePrefixes(servers);
|
|
32
|
+
const perServer = await Promise.all(servers.map(async (server) => {
|
|
33
|
+
try {
|
|
34
|
+
return { server, tools: await this.manager.listServerTools(server) };
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { server, tools: [] };
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
const routes = new Map();
|
|
41
|
+
const tools = [];
|
|
42
|
+
for (const { server, tools: serverTools } of perServer) {
|
|
43
|
+
const prefix = prefixes.get(server) ?? mcpServerPrefix(server);
|
|
44
|
+
for (const tool of serverTools) {
|
|
45
|
+
const name = `${prefix}__${tool.name}`;
|
|
46
|
+
routes.set(name, { server, wireName: tool.name });
|
|
47
|
+
tools.push({
|
|
48
|
+
...tool,
|
|
49
|
+
name,
|
|
50
|
+
description: tool.description
|
|
51
|
+
? `${tool.description} (MCP server: ${server})`
|
|
52
|
+
: `MCP server: ${server}`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this.#routes = routes;
|
|
57
|
+
return tools;
|
|
58
|
+
}
|
|
59
|
+
async callTool(name, args, signal) {
|
|
60
|
+
let route = this.#routes.get(name);
|
|
61
|
+
if (!route) {
|
|
62
|
+
await this.listTools();
|
|
63
|
+
route = this.#routes.get(name);
|
|
64
|
+
}
|
|
65
|
+
if (!route) {
|
|
66
|
+
throw new Error(`Unknown tool "${name}".`);
|
|
67
|
+
}
|
|
68
|
+
return this.manager.callServerTool(route.server, { name: route.wireName, arguments: args }, signal);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../src/daemon/mcproxy/catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Server as McpServerInstance } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
export type DaemonMcpHttpHandle = {
|
|
3
|
+
/** `http://127.0.0.1:<port>/mcp` for the resolved (fixed or ephemeral) port. */
|
|
4
|
+
url: string;
|
|
5
|
+
close(): Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Hand-rolled loopback-only Streamable HTTP host for the daemon's aggregate
|
|
9
|
+
* MCP proxy: one stateful session per initialize handshake, no auth (the
|
|
10
|
+
* service is process-local and never reachable off-host), no CORS/legacy SSE.
|
|
11
|
+
*/
|
|
12
|
+
export declare function startDaemonMcpHttpServer(options: {
|
|
13
|
+
port: number;
|
|
14
|
+
createSession: () => McpServerInstance;
|
|
15
|
+
}): Promise<DaemonMcpHttpHandle>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createServer, } from "node:http";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
const ENDPOINT = "/mcp";
|
|
6
|
+
const MAX_BODY_BYTES = 1024 * 1024;
|
|
7
|
+
function jsonRpcError(id, code, message) {
|
|
8
|
+
return JSON.stringify({
|
|
9
|
+
jsonrpc: "2.0",
|
|
10
|
+
id: id ?? null,
|
|
11
|
+
error: { code, message },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function requestId(body) {
|
|
15
|
+
if (typeof body !== "object" ||
|
|
16
|
+
body === null ||
|
|
17
|
+
Array.isArray(body) ||
|
|
18
|
+
!("id" in body)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return body.id;
|
|
22
|
+
}
|
|
23
|
+
async function readJsonBody(req) {
|
|
24
|
+
const chunks = [];
|
|
25
|
+
let size = 0;
|
|
26
|
+
for await (const chunk of req) {
|
|
27
|
+
size += chunk.length;
|
|
28
|
+
if (size > MAX_BODY_BYTES) {
|
|
29
|
+
throw new Error("Request body exceeds the 1 MiB limit.");
|
|
30
|
+
}
|
|
31
|
+
chunks.push(chunk);
|
|
32
|
+
}
|
|
33
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
34
|
+
return raw ? JSON.parse(raw) : undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Hand-rolled loopback-only Streamable HTTP host for the daemon's aggregate
|
|
38
|
+
* MCP proxy: one stateful session per initialize handshake, no auth (the
|
|
39
|
+
* service is process-local and never reachable off-host), no CORS/legacy SSE.
|
|
40
|
+
*/
|
|
41
|
+
export async function startDaemonMcpHttpServer(options) {
|
|
42
|
+
const sessions = new Map();
|
|
43
|
+
async function handleInitialize(req, res, body) {
|
|
44
|
+
if (!isInitializeRequest(body)) {
|
|
45
|
+
res
|
|
46
|
+
.writeHead(400, { "Content-Type": "application/json" })
|
|
47
|
+
.end(jsonRpcError(requestId(body), -32600, "Missing Mcp-Session-Id and body is not an initialize request."));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const mcpServer = options.createSession();
|
|
51
|
+
const transport = new StreamableHTTPServerTransport({
|
|
52
|
+
sessionIdGenerator: () => randomUUID(),
|
|
53
|
+
onsessioninitialized: (sessionId) => {
|
|
54
|
+
sessions.set(sessionId, { server: mcpServer, transport });
|
|
55
|
+
},
|
|
56
|
+
onsessionclosed: (sessionId) => {
|
|
57
|
+
sessions.delete(sessionId);
|
|
58
|
+
void mcpServer.close().catch(() => undefined);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
await mcpServer.connect(transport);
|
|
62
|
+
await transport.handleRequest(req, res, body);
|
|
63
|
+
}
|
|
64
|
+
async function handle(req, res) {
|
|
65
|
+
let url;
|
|
66
|
+
try {
|
|
67
|
+
url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
res.writeHead(400).end();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (url.pathname !== ENDPOINT) {
|
|
74
|
+
res.writeHead(404).end();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (req.method !== "POST" &&
|
|
78
|
+
req.method !== "GET" &&
|
|
79
|
+
req.method !== "DELETE") {
|
|
80
|
+
res.writeHead(405, { Allow: "POST, GET, DELETE" }).end();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const sessionIdHeader = req.headers["mcp-session-id"];
|
|
84
|
+
const sessionId = Array.isArray(sessionIdHeader)
|
|
85
|
+
? sessionIdHeader[0]
|
|
86
|
+
: sessionIdHeader;
|
|
87
|
+
if (req.method === "POST") {
|
|
88
|
+
let body;
|
|
89
|
+
try {
|
|
90
|
+
body = await readJsonBody(req);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
res
|
|
94
|
+
.writeHead(400, { "Content-Type": "application/json" })
|
|
95
|
+
.end(jsonRpcError(null, -32700, error instanceof Error ? error.message : "Invalid JSON body."));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!sessionId) {
|
|
99
|
+
await handleInitialize(req, res, body);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const entry = sessions.get(sessionId);
|
|
103
|
+
if (!entry) {
|
|
104
|
+
res
|
|
105
|
+
.writeHead(404, { "Content-Type": "application/json" })
|
|
106
|
+
.end(jsonRpcError(requestId(body), -32001, "Unknown session."));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
await entry.transport.handleRequest(req, res, body);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// GET (SSE stream) / DELETE (session termination) require a known session.
|
|
113
|
+
if (!sessionId) {
|
|
114
|
+
res.writeHead(400).end();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const entry = sessions.get(sessionId);
|
|
118
|
+
if (!entry) {
|
|
119
|
+
res.writeHead(404).end();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
await entry.transport.handleRequest(req, res);
|
|
123
|
+
}
|
|
124
|
+
const httpServer = createServer((req, res) => {
|
|
125
|
+
handle(req, res).catch(() => {
|
|
126
|
+
if (!res.headersSent) {
|
|
127
|
+
res.writeHead(500).end();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
await new Promise((resolve, reject) => {
|
|
132
|
+
httpServer.once("error", reject);
|
|
133
|
+
httpServer.listen(options.port, "127.0.0.1", () => {
|
|
134
|
+
httpServer.off("error", reject);
|
|
135
|
+
resolve();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
const address = httpServer.address();
|
|
139
|
+
return {
|
|
140
|
+
url: `http://127.0.0.1:${address.port}${ENDPOINT}`,
|
|
141
|
+
async close() {
|
|
142
|
+
await Promise.all([...sessions.values()].map(({ transport }) => transport.close().catch(() => undefined)));
|
|
143
|
+
sessions.clear();
|
|
144
|
+
await new Promise((resolve) => {
|
|
145
|
+
httpServer.close(() => resolve());
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=http-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../../src/daemon/mcproxy/http-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Manager as McpManager } from "../../core/mcp/index.js";
|
|
2
|
+
/** ACP protocol `McpServerHttp` shape for the daemon's aggregate proxy (no auth/headers). */
|
|
3
|
+
export type DaemonMcpProxyServer = {
|
|
4
|
+
name: "daemon";
|
|
5
|
+
type: "http";
|
|
6
|
+
url: string;
|
|
7
|
+
headers: [];
|
|
8
|
+
};
|
|
9
|
+
export type DaemonMcpProxy = {
|
|
10
|
+
/** Session-scoped `mcpServers` entry to pass on every `session/new` / `session/resume` for daemon-hosted ACP sessions. */
|
|
11
|
+
mcpServer: DaemonMcpProxyServer;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Starts the daemon's local, loopback-only aggregate MCP tool proxy in front
|
|
16
|
+
* of the parent `Manager`'s already-connected upstream servers. Call once,
|
|
17
|
+
* after the parent has connected/subscribed to channels and before creating
|
|
18
|
+
* any ACP session; every daemon-hosted ACP session gets the one returned
|
|
19
|
+
* `mcpServer` entry instead of loading local `mcp.json`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function startDaemonMcpProxy(manager: McpManager, options?: {
|
|
22
|
+
port?: number;
|
|
23
|
+
}): Promise<DaemonMcpProxy>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DaemonToolCatalog } from "./catalog.js";
|
|
2
|
+
import { startDaemonMcpHttpServer } from "./http-server.js";
|
|
3
|
+
import { createDaemonProxyServer } from "./session.js";
|
|
4
|
+
/**
|
|
5
|
+
* Starts the daemon's local, loopback-only aggregate MCP tool proxy in front
|
|
6
|
+
* of the parent `Manager`'s already-connected upstream servers. Call once,
|
|
7
|
+
* after the parent has connected/subscribed to channels and before creating
|
|
8
|
+
* any ACP session; every daemon-hosted ACP session gets the one returned
|
|
9
|
+
* `mcpServer` entry instead of loading local `mcp.json`.
|
|
10
|
+
*/
|
|
11
|
+
export async function startDaemonMcpProxy(manager, options = {}) {
|
|
12
|
+
const catalog = new DaemonToolCatalog(manager);
|
|
13
|
+
const instructions = (await manager.listServerInstructions()).join("\n\n");
|
|
14
|
+
const handle = await startDaemonMcpHttpServer({
|
|
15
|
+
port: options.port ?? 0,
|
|
16
|
+
createSession: () => createDaemonProxyServer(catalog, instructions),
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
mcpServer: { name: "daemon", type: "http", url: handle.url, headers: [] },
|
|
20
|
+
close: handle.close,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/daemon/mcproxy/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAgBvD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAmB,EACnB,UAA6B,EAAE;IAE/B,MAAM,OAAO,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,CAAC,MAAM,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC;QAC5C,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC;QACvB,aAAa,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,OAAO,EAAE,YAAY,CAAC;KACpE,CAAC,CAAC;IACH,OAAO;QACL,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE;QACzE,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC"}
|