@thinkwell/conductor 0.5.4 → 0.5.6
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/conductor.js +627 -935
- package/dist/connectors/channel.js +75 -142
- package/dist/connectors/index.js +1 -5
- package/dist/connectors/stdio.js +100 -202
- package/dist/generated/features.d.ts +5 -0
- package/dist/generated/features.d.ts.map +1 -0
- package/dist/generated/features.js +4 -0
- package/dist/generated/features.js.map +1 -0
- package/dist/index.js +5 -73
- package/dist/instantiators.js +38 -171
- package/dist/logger.js +80 -148
- package/dist/mcp-bridge/http-listener.js +116 -212
- package/dist/mcp-bridge/index.js +0 -6
- package/dist/mcp-bridge/mcp-bridge.js +117 -164
- package/dist/mcp-bridge/types.js +0 -7
- package/dist/message-queue.js +49 -86
- package/dist/types.js +4 -11
- package/package.json +4 -3
|
@@ -1,224 +1,128 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP Listener for MCP Bridge
|
|
3
|
-
*
|
|
4
|
-
* Listens on an ephemeral HTTP port for MCP connections from agents
|
|
5
|
-
* and bridges them to ACP `_mcp/*` messages.
|
|
6
|
-
*
|
|
7
|
-
* The listener handles the Streamable HTTP transport for MCP:
|
|
8
|
-
* - POST requests for JSON-RPC messages
|
|
9
|
-
* - SSE responses for streaming
|
|
10
|
-
*/
|
|
11
1
|
import { createServer } from "node:http";
|
|
12
2
|
import { randomUUID } from "node:crypto";
|
|
13
|
-
import { isJsonRpcRequest, isJsonRpcNotification, createResponder
|
|
14
|
-
/**
|
|
15
|
-
* Create an HTTP listener for MCP connections
|
|
16
|
-
*
|
|
17
|
-
* The listener waits for the session ID before accepting connections,
|
|
18
|
-
* ensuring we can always correlate connections with sessions.
|
|
19
|
-
*/
|
|
3
|
+
import { isJsonRpcRequest, isJsonRpcNotification, createResponder } from "@thinkwell/protocol";
|
|
20
4
|
export async function createHttpListener(options) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
30
|
-
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS, GET");
|
|
31
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
|
32
|
-
if (req.method === "OPTIONS") {
|
|
33
|
-
res.writeHead(204);
|
|
34
|
-
res.end();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (req.method === "GET") {
|
|
38
|
-
// GET request is for SSE stream - return 204 to acknowledge
|
|
39
|
-
res.writeHead(204);
|
|
40
|
-
res.end();
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (req.method !== "POST") {
|
|
44
|
-
res.writeHead(405, { "Content-Type": "application/json" });
|
|
45
|
-
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
// Parse the request body first - don't wait for session ID
|
|
50
|
-
const body = await readBody(req);
|
|
51
|
-
const message = JSON.parse(body);
|
|
52
|
-
// Get or create connection for this request
|
|
53
|
-
// For simplicity, we use a single connection per listener
|
|
54
|
-
// (in practice, MCP typically uses one connection per session)
|
|
55
|
-
let connection = connections.get("default");
|
|
56
|
-
if (!connection) {
|
|
57
|
-
const connectionId = randomUUID();
|
|
58
|
-
// Use a placeholder session ID - it will be set later
|
|
59
|
-
// The session ID is not needed for MCP protocol messages
|
|
60
|
-
connection = {
|
|
61
|
-
connectionId,
|
|
62
|
-
sessionId: sessionId ?? "pending",
|
|
63
|
-
pendingResponses: new Map(),
|
|
64
|
-
};
|
|
65
|
-
connections.set("default", connection);
|
|
66
|
-
// Notify conductor of new connection
|
|
67
|
-
onMessage({
|
|
68
|
-
type: "connection-received",
|
|
69
|
-
acpUrl,
|
|
70
|
-
sessionId: connection.sessionId,
|
|
71
|
-
connectionId,
|
|
72
|
-
send: (responseData) => {
|
|
73
|
-
// This is called when conductor sends a response back
|
|
74
|
-
// We need to route it to the right pending response
|
|
75
|
-
},
|
|
76
|
-
close: () => {
|
|
77
|
-
connections.delete("default");
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
// Handle the message
|
|
82
|
-
await handleMessage(connection, message, res, onMessage);
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
console.error("MCP bridge HTTP error:", error);
|
|
86
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
87
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
// Listen on an ephemeral port
|
|
91
|
-
await new Promise((resolve, reject) => {
|
|
92
|
-
server.listen(0, "127.0.0.1", () => resolve());
|
|
93
|
-
server.on("error", reject);
|
|
94
|
-
});
|
|
95
|
-
// Track all socket connections so we can destroy them on close
|
|
96
|
-
server.on('connection', (socket) => {
|
|
97
|
-
sockets.add(socket);
|
|
98
|
-
socket.on('close', () => {
|
|
99
|
-
sockets.delete(socket);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
const address = server.address();
|
|
103
|
-
if (!address || typeof address === "string") {
|
|
104
|
-
throw new Error("Failed to get server address");
|
|
5
|
+
const { acpUrl, onMessage } = options;
|
|
6
|
+
let sessionId = null;
|
|
7
|
+
const connections = /* @__PURE__ */ new Map(), sockets = /* @__PURE__ */ new Set();
|
|
8
|
+
let server = null;
|
|
9
|
+
server = createServer(async (req, res) => {
|
|
10
|
+
if (res.setHeader("Access-Control-Allow-Origin", "*"), res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS, GET"), res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id"), req.method === "OPTIONS") {
|
|
11
|
+
res.writeHead(204), res.end();
|
|
12
|
+
return;
|
|
105
13
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
await new Promise((resolve, reject) => {
|
|
134
|
-
server.close((err) => {
|
|
135
|
-
if (err) {
|
|
136
|
-
reject(err);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
resolve();
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Handle an incoming MCP message
|
|
149
|
-
*/
|
|
150
|
-
async function handleMessage(connection, message, res, onMessage) {
|
|
151
|
-
if (isJsonRpcRequest(message)) {
|
|
152
|
-
// Use string key for the pending responses map
|
|
153
|
-
const idKey = String(message.id);
|
|
154
|
-
// Create a promise to wait for the response
|
|
155
|
-
const responsePromise = new Promise((resolve) => {
|
|
156
|
-
connection.pendingResponses.set(idKey, resolve);
|
|
157
|
-
});
|
|
158
|
-
// Create a dispatch with a responder that sends the response to HTTP
|
|
159
|
-
const dispatch = {
|
|
160
|
-
type: "request",
|
|
161
|
-
id: message.id,
|
|
162
|
-
method: message.method,
|
|
163
|
-
params: message.params,
|
|
164
|
-
responder: createResponder((result) => {
|
|
165
|
-
const resolver = connection.pendingResponses.get(idKey);
|
|
166
|
-
connection.pendingResponses.delete(idKey);
|
|
167
|
-
resolver?.({ jsonrpc: "2.0", id: message.id, result });
|
|
168
|
-
}, (error) => {
|
|
169
|
-
const resolver = connection.pendingResponses.get(idKey);
|
|
170
|
-
connection.pendingResponses.delete(idKey);
|
|
171
|
-
resolver?.({ jsonrpc: "2.0", id: message.id, error });
|
|
172
|
-
}),
|
|
173
|
-
};
|
|
174
|
-
// Notify conductor of the request
|
|
175
|
-
onMessage({
|
|
176
|
-
type: "client-message",
|
|
177
|
-
connectionId: connection.connectionId,
|
|
178
|
-
dispatch,
|
|
179
|
-
});
|
|
180
|
-
// Wait for the response
|
|
181
|
-
const response = await responsePromise;
|
|
182
|
-
// Return JSON-RPC response directly
|
|
183
|
-
// Include Mcp-Session-Id header per MCP Streamable HTTP spec
|
|
184
|
-
res.writeHead(200, {
|
|
185
|
-
"Content-Type": "application/json",
|
|
186
|
-
"Mcp-Session-Id": connection.connectionId,
|
|
14
|
+
if (req.method === "GET") {
|
|
15
|
+
res.writeHead(204), res.end();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (req.method !== "POST") {
|
|
19
|
+
res.writeHead(405, { "Content-Type": "application/json" }), res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const body = await readBody(req), message = JSON.parse(body);
|
|
24
|
+
let connection = connections.get("default");
|
|
25
|
+
if (!connection) {
|
|
26
|
+
const connectionId = randomUUID();
|
|
27
|
+
connection = {
|
|
28
|
+
connectionId,
|
|
29
|
+
sessionId: sessionId ?? "pending",
|
|
30
|
+
pendingResponses: /* @__PURE__ */ new Map()
|
|
31
|
+
}, connections.set("default", connection), onMessage({
|
|
32
|
+
type: "connection-received",
|
|
33
|
+
acpUrl,
|
|
34
|
+
sessionId: connection.sessionId,
|
|
35
|
+
connectionId,
|
|
36
|
+
send: (responseData) => {
|
|
37
|
+
},
|
|
38
|
+
close: () => {
|
|
39
|
+
connections.delete("default");
|
|
40
|
+
}
|
|
187
41
|
});
|
|
188
|
-
|
|
42
|
+
}
|
|
43
|
+
await handleMessage(connection, message, res, onMessage);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("MCP bridge HTTP error:", error), res.writeHead(500, { "Content-Type": "application/json" }), res.end(JSON.stringify({ error: "Internal server error" }));
|
|
189
46
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
47
|
+
}), await new Promise((resolve, reject) => {
|
|
48
|
+
server.listen(0, "127.0.0.1", () => resolve()), server.on("error", reject);
|
|
49
|
+
}), server.on("connection", (socket) => {
|
|
50
|
+
sockets.add(socket), socket.on("close", () => {
|
|
51
|
+
sockets.delete(socket);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
const address = server.address();
|
|
55
|
+
if (!address || typeof address == "string")
|
|
56
|
+
throw new Error("Failed to get server address");
|
|
57
|
+
const port = address.port;
|
|
58
|
+
return {
|
|
59
|
+
acpUrl,
|
|
60
|
+
port,
|
|
61
|
+
setSessionId(id) {
|
|
62
|
+
sessionId = id;
|
|
63
|
+
for (const conn of connections.values())
|
|
64
|
+
conn.sessionId = id;
|
|
65
|
+
},
|
|
66
|
+
async close() {
|
|
67
|
+
for (const [key, conn] of connections)
|
|
198
68
|
onMessage({
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
69
|
+
type: "connection-closed",
|
|
70
|
+
connectionId: conn.connectionId
|
|
71
|
+
}), connections.delete(key);
|
|
72
|
+
for (const socket of sockets)
|
|
73
|
+
socket.destroy();
|
|
74
|
+
sockets.clear(), server && await new Promise((resolve, reject) => {
|
|
75
|
+
server.close((err) => {
|
|
76
|
+
err ? reject(err) : resolve();
|
|
202
77
|
});
|
|
203
|
-
|
|
204
|
-
res.writeHead(202);
|
|
205
|
-
res.end();
|
|
206
|
-
}
|
|
207
|
-
else {
|
|
208
|
-
// Unknown message type
|
|
209
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
210
|
-
res.end(JSON.stringify({ error: "Invalid message type" }));
|
|
78
|
+
});
|
|
211
79
|
}
|
|
80
|
+
};
|
|
212
81
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
82
|
+
async function handleMessage(connection, message, res, onMessage) {
|
|
83
|
+
if (isJsonRpcRequest(message)) {
|
|
84
|
+
const idKey = String(message.id), responsePromise = new Promise((resolve) => {
|
|
85
|
+
connection.pendingResponses.set(idKey, resolve);
|
|
86
|
+
}), dispatch = {
|
|
87
|
+
type: "request",
|
|
88
|
+
id: message.id,
|
|
89
|
+
method: message.method,
|
|
90
|
+
params: message.params,
|
|
91
|
+
responder: createResponder((result) => {
|
|
92
|
+
const resolver = connection.pendingResponses.get(idKey);
|
|
93
|
+
connection.pendingResponses.delete(idKey), resolver?.({ jsonrpc: "2.0", id: message.id, result });
|
|
94
|
+
}, (error) => {
|
|
95
|
+
const resolver = connection.pendingResponses.get(idKey);
|
|
96
|
+
connection.pendingResponses.delete(idKey), resolver?.({ jsonrpc: "2.0", id: message.id, error });
|
|
97
|
+
})
|
|
98
|
+
};
|
|
99
|
+
onMessage({
|
|
100
|
+
type: "client-message",
|
|
101
|
+
connectionId: connection.connectionId,
|
|
102
|
+
dispatch
|
|
222
103
|
});
|
|
104
|
+
const response = await responsePromise;
|
|
105
|
+
res.writeHead(200, {
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
"Mcp-Session-Id": connection.connectionId
|
|
108
|
+
}), res.end(JSON.stringify(response));
|
|
109
|
+
} else if (isJsonRpcNotification(message)) {
|
|
110
|
+
const dispatch = {
|
|
111
|
+
type: "notification",
|
|
112
|
+
method: message.method,
|
|
113
|
+
params: message.params
|
|
114
|
+
};
|
|
115
|
+
onMessage({
|
|
116
|
+
type: "client-message",
|
|
117
|
+
connectionId: connection.connectionId,
|
|
118
|
+
dispatch
|
|
119
|
+
}), res.writeHead(202), res.end();
|
|
120
|
+
} else
|
|
121
|
+
res.writeHead(400, { "Content-Type": "application/json" }), res.end(JSON.stringify({ error: "Invalid message type" }));
|
|
122
|
+
}
|
|
123
|
+
function readBody(req) {
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
const chunks = [];
|
|
126
|
+
req.on("data", (chunk) => chunks.push(chunk)), req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8"))), req.on("error", reject);
|
|
127
|
+
});
|
|
223
128
|
}
|
|
224
|
-
//# sourceMappingURL=http-listener.js.map
|
package/dist/mcp-bridge/index.js
CHANGED
|
@@ -1,170 +1,123 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Bridge
|
|
3
|
-
*
|
|
4
|
-
* The MCP bridge manages HTTP listeners for `acp:` URLs and coordinates
|
|
5
|
-
* the transformation of MCP server configurations during session creation.
|
|
6
|
-
*
|
|
7
|
-
* Key responsibilities:
|
|
8
|
-
* 1. Transform `acp:$UUID` URLs to `http://localhost:$PORT` during session/new
|
|
9
|
-
* 2. Spawn HTTP listeners for each `acp:` URL
|
|
10
|
-
* 3. Route MCP messages between agents and proxies via `_mcp/*` messages
|
|
11
|
-
* 4. Manage connection lifecycle
|
|
12
|
-
*/
|
|
13
1
|
import { createHttpListener } from "./http-listener.js";
|
|
14
|
-
/**
|
|
15
|
-
* MCP Bridge manager
|
|
16
|
-
*
|
|
17
|
-
* Manages HTTP listeners and connection lifecycle for MCP-over-ACP bridging.
|
|
18
|
-
*/
|
|
19
2
|
export class McpBridge {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
messageQueue;
|
|
4
|
+
// Maps acp:uuid → HttpListener
|
|
5
|
+
listeners = /* @__PURE__ */ new Map();
|
|
6
|
+
// Maps connectionId → ActiveMcpConnection
|
|
7
|
+
connections = /* @__PURE__ */ new Map();
|
|
8
|
+
// Pending sessions waiting for session ID
|
|
9
|
+
pendingSessions = /* @__PURE__ */ new Map();
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.messageQueue = options.messageQueue;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Transform MCP servers in a session/new request
|
|
15
|
+
*
|
|
16
|
+
* For each `acp:` URL:
|
|
17
|
+
* 1. Spawn an HTTP listener on an ephemeral port
|
|
18
|
+
* 2. Replace the URL with `http://localhost:$PORT`
|
|
19
|
+
*
|
|
20
|
+
* Returns the transformed server list and a session key for later correlation.
|
|
21
|
+
*/
|
|
22
|
+
async transformMcpServers(servers, sessionKey) {
|
|
23
|
+
if (!servers || servers.length === 0)
|
|
24
|
+
return { transformedServers: servers, hasAcpServers: !1 };
|
|
25
|
+
const pendingSession = {
|
|
26
|
+
listeners: [],
|
|
27
|
+
urlMap: /* @__PURE__ */ new Map()
|
|
28
|
+
}, transformedServers = [];
|
|
29
|
+
let hasAcpServers = !1;
|
|
30
|
+
for (const server of servers)
|
|
31
|
+
if (server.url.startsWith("acp:")) {
|
|
32
|
+
hasAcpServers = !0;
|
|
33
|
+
const listener = await createHttpListener({
|
|
34
|
+
acpUrl: server.url,
|
|
35
|
+
onMessage: (msg) => this.handleBridgeMessage(msg)
|
|
36
|
+
});
|
|
37
|
+
this.listeners.set(server.url, listener), pendingSession.listeners.push(listener), pendingSession.urlMap.set(server.url, `http://127.0.0.1:${listener.port}`), transformedServers.push({
|
|
38
|
+
...server,
|
|
39
|
+
url: `http://127.0.0.1:${listener.port}`,
|
|
40
|
+
type: "http"
|
|
41
|
+
});
|
|
42
|
+
} else
|
|
43
|
+
transformedServers.push(server);
|
|
44
|
+
return hasAcpServers && this.pendingSessions.set(sessionKey, pendingSession), { transformedServers, hasAcpServers };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Complete session creation after receiving the session ID from the agent
|
|
48
|
+
*
|
|
49
|
+
* This delivers the session ID to all pending listeners so they can
|
|
50
|
+
* correlate connections with the session.
|
|
51
|
+
*/
|
|
52
|
+
completeSession(sessionKey, sessionId) {
|
|
53
|
+
const pending = this.pendingSessions.get(sessionKey);
|
|
54
|
+
if (pending) {
|
|
55
|
+
for (const listener of pending.listeners)
|
|
56
|
+
listener.setSessionId(sessionId);
|
|
57
|
+
this.pendingSessions.delete(sessionKey);
|
|
29
58
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!servers || servers.length === 0) {
|
|
41
|
-
return { transformedServers: servers, hasAcpServers: false };
|
|
42
|
-
}
|
|
43
|
-
const pendingSession = {
|
|
44
|
-
listeners: [],
|
|
45
|
-
urlMap: new Map(),
|
|
46
|
-
};
|
|
47
|
-
const transformedServers = [];
|
|
48
|
-
let hasAcpServers = false;
|
|
49
|
-
for (const server of servers) {
|
|
50
|
-
if (server.url.startsWith("acp:")) {
|
|
51
|
-
hasAcpServers = true;
|
|
52
|
-
// Spawn HTTP listener for this acp: URL
|
|
53
|
-
const listener = await createHttpListener({
|
|
54
|
-
acpUrl: server.url,
|
|
55
|
-
onMessage: (msg) => this.handleBridgeMessage(msg),
|
|
56
|
-
});
|
|
57
|
-
this.listeners.set(server.url, listener);
|
|
58
|
-
pendingSession.listeners.push(listener);
|
|
59
|
-
pendingSession.urlMap.set(server.url, `http://127.0.0.1:${listener.port}`);
|
|
60
|
-
// Transform to HTTP URL
|
|
61
|
-
transformedServers.push({
|
|
62
|
-
...server,
|
|
63
|
-
url: `http://127.0.0.1:${listener.port}`,
|
|
64
|
-
type: "http",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
// Pass through non-acp servers unchanged
|
|
69
|
-
transformedServers.push(server);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (hasAcpServers) {
|
|
73
|
-
this.pendingSessions.set(sessionKey, pendingSession);
|
|
74
|
-
}
|
|
75
|
-
return { transformedServers, hasAcpServers };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Cancel a pending session (e.g., on error)
|
|
62
|
+
*/
|
|
63
|
+
async cancelSession(sessionKey) {
|
|
64
|
+
const pending = this.pendingSessions.get(sessionKey);
|
|
65
|
+
if (pending) {
|
|
66
|
+
for (const listener of pending.listeners)
|
|
67
|
+
await listener.close(), this.listeners.delete(listener.acpUrl);
|
|
68
|
+
this.pendingSessions.delete(sessionKey);
|
|
76
69
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Track the new connection
|
|
112
|
-
this.connections.set(msg.connectionId, {
|
|
113
|
-
connectionId: msg.connectionId,
|
|
114
|
-
acpUrl: msg.acpUrl,
|
|
115
|
-
sessionId: msg.sessionId,
|
|
116
|
-
responder: null,
|
|
117
|
-
});
|
|
118
|
-
// Queue the connection notification for the conductor
|
|
119
|
-
this.messageQueue.push({
|
|
120
|
-
type: "mcp-connection-received",
|
|
121
|
-
acpUrl: msg.acpUrl,
|
|
122
|
-
connectionId: msg.connectionId,
|
|
123
|
-
});
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
case "client-message": {
|
|
127
|
-
// Route MCP message to conductor
|
|
128
|
-
this.messageQueue.push({
|
|
129
|
-
type: "mcp-client-to-server",
|
|
130
|
-
connectionId: msg.connectionId,
|
|
131
|
-
dispatch: msg.dispatch,
|
|
132
|
-
});
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
case "connection-closed": {
|
|
136
|
-
// Clean up connection
|
|
137
|
-
this.connections.delete(msg.connectionId);
|
|
138
|
-
// Queue disconnect notification
|
|
139
|
-
this.messageQueue.push({
|
|
140
|
-
type: "mcp-connection-disconnected",
|
|
141
|
-
connectionId: msg.connectionId,
|
|
142
|
-
});
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Get connection info by connection ID
|
|
149
|
-
*/
|
|
150
|
-
getConnection(connectionId) {
|
|
151
|
-
return this.connections.get(connectionId);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Close all listeners and connections
|
|
155
|
-
*/
|
|
156
|
-
async close() {
|
|
157
|
-
// Close all connections
|
|
158
|
-
this.connections.clear();
|
|
159
|
-
// Close all listeners
|
|
160
|
-
for (const listener of this.listeners.values()) {
|
|
161
|
-
await listener.close();
|
|
162
|
-
}
|
|
163
|
-
this.listeners.clear();
|
|
164
|
-
// Cancel pending sessions
|
|
165
|
-
for (const key of this.pendingSessions.keys()) {
|
|
166
|
-
await this.cancelSession(key);
|
|
167
|
-
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Handle a message from an HTTP listener
|
|
73
|
+
*/
|
|
74
|
+
handleBridgeMessage(msg) {
|
|
75
|
+
switch (msg.type) {
|
|
76
|
+
case "connection-received": {
|
|
77
|
+
this.connections.set(msg.connectionId, {
|
|
78
|
+
connectionId: msg.connectionId,
|
|
79
|
+
acpUrl: msg.acpUrl,
|
|
80
|
+
sessionId: msg.sessionId,
|
|
81
|
+
responder: null
|
|
82
|
+
}), this.messageQueue.push({
|
|
83
|
+
type: "mcp-connection-received",
|
|
84
|
+
acpUrl: msg.acpUrl,
|
|
85
|
+
connectionId: msg.connectionId
|
|
86
|
+
});
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "client-message": {
|
|
90
|
+
this.messageQueue.push({
|
|
91
|
+
type: "mcp-client-to-server",
|
|
92
|
+
connectionId: msg.connectionId,
|
|
93
|
+
dispatch: msg.dispatch
|
|
94
|
+
});
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "connection-closed": {
|
|
98
|
+
this.connections.delete(msg.connectionId), this.messageQueue.push({
|
|
99
|
+
type: "mcp-connection-disconnected",
|
|
100
|
+
connectionId: msg.connectionId
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
168
104
|
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get connection info by connection ID
|
|
108
|
+
*/
|
|
109
|
+
getConnection(connectionId) {
|
|
110
|
+
return this.connections.get(connectionId);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Close all listeners and connections
|
|
114
|
+
*/
|
|
115
|
+
async close() {
|
|
116
|
+
this.connections.clear();
|
|
117
|
+
for (const listener of this.listeners.values())
|
|
118
|
+
await listener.close();
|
|
119
|
+
this.listeners.clear();
|
|
120
|
+
for (const key of this.pendingSessions.keys())
|
|
121
|
+
await this.cancelSession(key);
|
|
122
|
+
}
|
|
169
123
|
}
|
|
170
|
-
//# sourceMappingURL=mcp-bridge.js.map
|
package/dist/mcp-bridge/types.js
CHANGED