amalgm 0.1.72 → 0.1.73
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 -2
- package/runtime/scripts/amalgm-mcp/agents/rest.js +29 -22
- package/runtime/scripts/amalgm-mcp/agents/store.js +90 -24
- package/runtime/scripts/amalgm-mcp/agents/talk.js +66 -37
- package/runtime/scripts/amalgm-mcp/agents/tools.js +0 -4
- package/runtime/scripts/amalgm-mcp/automations/runner.js +4 -5
- package/runtime/scripts/amalgm-mcp/automations/tool-actions.js +54 -57
- package/runtime/scripts/amalgm-mcp/automations/tools.js +4 -4
- package/runtime/scripts/amalgm-mcp/email/inbound.js +21 -0
- package/runtime/scripts/amalgm-mcp/events/internal-workflows.js +3 -3
- package/runtime/scripts/amalgm-mcp/index.js +3 -1
- package/runtime/scripts/amalgm-mcp/lib/prefs.js +15 -0
- package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +5 -3
- package/runtime/scripts/amalgm-mcp/runtime-worker.js +47 -0
- package/runtime/scripts/amalgm-mcp/server/combined-service.js +27 -0
- package/runtime/scripts/amalgm-mcp/server/control-plane-service.js +24 -0
- package/runtime/scripts/amalgm-mcp/server/core-tools.js +52 -7
- package/runtime/scripts/amalgm-mcp/server/http-context.js +44 -0
- package/runtime/scripts/amalgm-mcp/server/http-service.js +34 -0
- package/runtime/scripts/amalgm-mcp/server/http.js +31 -465
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +57 -0
- package/runtime/scripts/amalgm-mcp/server/mcp-adapter.js +22 -0
- package/runtime/scripts/amalgm-mcp/server/mcp-http-service.js +24 -0
- package/runtime/scripts/amalgm-mcp/server/mcp-request-context.js +32 -0
- package/runtime/scripts/amalgm-mcp/server/mcp.js +14 -73
- package/runtime/scripts/amalgm-mcp/server/routes/agents.js +29 -0
- package/runtime/scripts/amalgm-mcp/server/routes/apps.js +59 -0
- package/runtime/scripts/amalgm-mcp/server/routes/automations.js +45 -0
- package/runtime/scripts/amalgm-mcp/server/routes/browser.js +49 -0
- package/runtime/scripts/amalgm-mcp/server/routes/credentials.js +30 -0
- package/runtime/scripts/amalgm-mcp/server/routes/events.js +18 -0
- package/runtime/scripts/amalgm-mcp/server/routes/files.js +33 -0
- package/runtime/scripts/amalgm-mcp/server/routes/health.js +24 -0
- package/runtime/scripts/amalgm-mcp/server/routes/inbound.js +18 -0
- package/runtime/scripts/amalgm-mcp/server/routes/local.js +21 -0
- package/runtime/scripts/amalgm-mcp/server/routes/preferences.js +18 -0
- package/runtime/scripts/amalgm-mcp/server/routes/state.js +23 -0
- package/runtime/scripts/amalgm-mcp/server/routes/toolbox.js +59 -0
- package/runtime/scripts/amalgm-mcp/server/routes/workspace.js +57 -0
- package/runtime/scripts/amalgm-mcp/server/service-lifecycle.js +54 -0
- package/runtime/scripts/amalgm-mcp/services/control-plane.js +19 -0
- package/runtime/scripts/amalgm-mcp/services/mcp-adapter.js +12 -0
- package/runtime/scripts/amalgm-mcp/services/runtime-worker.js +18 -0
- package/runtime/scripts/amalgm-mcp/slack/inbound.js +21 -0
- package/runtime/scripts/amalgm-mcp/state/snapshot.js +3 -3
- package/runtime/scripts/amalgm-mcp/tests/agents-source-of-truth.test.js +82 -0
- package/runtime/scripts/amalgm-mcp/tests/automations-store-runner.test.js +5 -5
- package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +37 -0
- package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +158 -0
- package/runtime/scripts/amalgm-mcp/tests/server-routing.test.js +81 -0
- package/runtime/scripts/amalgm-mcp/tests/server-services.test.js +156 -0
- package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +81 -0
- package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +250 -0
- package/runtime/scripts/amalgm-mcp/toolbox/mcp-surface.js +106 -0
- package/runtime/scripts/amalgm-mcp/toolbox/names.js +41 -0
- package/runtime/scripts/amalgm-mcp/toolbox/rest.js +107 -53
- package/runtime/scripts/amalgm-mcp/toolbox/runner.js +9 -75
- package/runtime/scripts/amalgm-mcp/toolbox/selection.js +22 -0
- package/runtime/scripts/amalgm-mcp/toolbox/service.js +227 -0
- package/runtime/scripts/amalgm-mcp/toolbox/store.js +9 -78
- package/runtime/scripts/amalgm-mcp/toolbox/system-catalog.js +112 -0
- package/runtime/scripts/amalgm-mcp/toolbox/tools.js +11 -74
- package/runtime/scripts/amalgm-mcp/workflows/compiler.js +1 -1
- package/runtime/scripts/amalgm-mcp/workflows/runner.js +4 -5
|
@@ -1,479 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP server
|
|
2
|
+
* HTTP server lifecycle and auth boundary.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* POST /events → webhook ingress (signature match + workflow run)
|
|
8
|
-
* GET /events → recent events (in-memory ring buffer)
|
|
9
|
-
* GET /automations/list → list automations/triggers/workflows
|
|
10
|
-
* POST /automations/{get,create,update,delete,run-now,validate}
|
|
11
|
-
* POST /automations/triggers/{update,delete}
|
|
12
|
-
* GET /workspace/projects → list projects
|
|
13
|
-
* POST /workspace/import → copy a local folder into workspaces
|
|
14
|
-
* POST /github/clone → clone repository onto the computer
|
|
15
|
-
* GET /workspace/branches → list git branches
|
|
16
|
-
* POST /workspace/checkout → checkout git branch
|
|
17
|
-
* GET /workspace/diff → git diff summary + patch
|
|
18
|
-
* GET /workspace/worktree → list worktrees
|
|
19
|
-
* POST /workspace/worktree → create worktree
|
|
20
|
-
* DELETE /workspace/worktree → remove worktree
|
|
21
|
-
* GET /fs/list → list files
|
|
22
|
-
* GET /fs/read → read file
|
|
23
|
-
* POST /fs/{write,delete,mkdir,rename}
|
|
24
|
-
* GET /mcp-connections → list sanitized MCP connections
|
|
25
|
-
* POST /mcp-connections → save/update MCP connection
|
|
26
|
-
* DELETE /mcp-connections → remove MCP connection
|
|
27
|
-
* GET /toolbox → list local ToolboxDB tools/actions
|
|
28
|
-
* GET /toolbox/tools?id=... → get one toolbox tool
|
|
29
|
-
* POST /toolbox/tools → save/update a toolbox tool
|
|
30
|
-
* PATCH /toolbox/tools → partially update a toolbox tool
|
|
31
|
-
* DELETE /toolbox/tools → remove a toolbox tool
|
|
32
|
-
* GET /toolbox/actions?id=... → get one toolbox action
|
|
33
|
-
* POST /toolbox/actions → save/update a toolbox action
|
|
34
|
-
* PATCH /toolbox/actions → partially update a toolbox action
|
|
35
|
-
* DELETE /toolbox/actions → remove a toolbox action
|
|
36
|
-
* GET /state/snapshot → current Local Live Store snapshot
|
|
37
|
-
* GET /state/events → replay Local Live Store events
|
|
38
|
-
* GET /state/stream → SSE Local Live Store stream
|
|
39
|
-
* GET /browser/profiles → list durable Amalgm browser profiles
|
|
40
|
-
* POST /browser/profiles → save/update browser profile metadata
|
|
41
|
-
* GET /browser/auth-bundles → list encrypted browser auth bundles
|
|
42
|
-
* POST /browser/auth-bundles/export → save cookies/storage into encrypted bundle
|
|
43
|
-
* POST /browser/auth-bundles/import → hydrate cookies/storage from encrypted bundle
|
|
44
|
-
* DELETE /browser/auth-bundles → remove encrypted auth bundle metadata/blob
|
|
45
|
-
* GET /browser/login-sessions → list or token-read temporary browser login sessions
|
|
46
|
-
* POST /browser/login-sessions → create a temporary browser auth link
|
|
47
|
-
* POST /browser/login-sessions/complete → export auth bundle and mark login complete
|
|
48
|
-
* POST /browser/login-sessions/cancel → cancel a temporary browser auth link
|
|
49
|
-
* GET /user-preferences → current local user preferences
|
|
50
|
-
* PUT /user-preferences → update local user preferences
|
|
51
|
-
* POST /local/init → init local computer state
|
|
52
|
-
* GET /local/credentials → discover local agent auth
|
|
53
|
-
* POST /local/credentials/resolve → resolve spawn env for agent auth
|
|
54
|
-
* POST /email/inbound → run a reply turn for inbound email
|
|
55
|
-
* POST /slack/inbound → run a reply turn for inbound Slack
|
|
56
|
-
* GET /credentials → list local Amalgm credential profiles
|
|
57
|
-
* POST /credentials → save local Amalgm credential profile
|
|
58
|
-
* DELETE /credentials → remove local Amalgm credential profile
|
|
59
|
-
* POST /user-api-keys → legacy save BYOK credential file
|
|
60
|
-
* DELETE /user-api-keys → legacy remove BYOK credential file
|
|
61
|
-
* GET /apps/list → list apps
|
|
62
|
-
* GET /apps/routes → app DNS routes for tunnel adverts
|
|
63
|
-
* POST /apps/{register,redeploy,start,stop,connect-dns,disconnect-dns}
|
|
64
|
-
* Legacy /artifacts/* aliases remain for older clients during the rename.
|
|
65
|
-
* GET /agents/list → list agents
|
|
66
|
-
* POST /agents/{get,create,update,delete}
|
|
4
|
+
* This file should stay small: MCP transport handling lives in mcp-adapter.js,
|
|
5
|
+
* local product APIs live under server/routes/*, and background work starts via
|
|
6
|
+
* runtime-worker.js.
|
|
67
7
|
*/
|
|
68
8
|
|
|
69
|
-
const http = require('http');
|
|
70
9
|
const { PORT, STORAGE_DIR } = require('../config');
|
|
71
10
|
const { hasSupabase } = require('../lib/supabase');
|
|
72
|
-
const {
|
|
11
|
+
const { startRuntimeWorker, stopRuntimeWorker } = require('../runtime-worker');
|
|
12
|
+
const { createCombinedServer } = require('./combined-service');
|
|
13
|
+
const { startHttpServer } = require('./service-lifecycle');
|
|
73
14
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const { activeAgentConversations } = require('../agents/talk');
|
|
77
|
-
const { listAutomations } = require('../automations/store');
|
|
78
|
-
const { startAutomationScheduler, stopAutomationScheduler } = require('../automations/scheduler');
|
|
79
|
-
const { createTransport } = require('./mcp');
|
|
80
|
-
const { handleEventsPost } = require('../events/ingress');
|
|
81
|
-
const eventsRing = require('../events/ring-buffer');
|
|
82
|
-
const automationsRest = require('../automations/rest');
|
|
83
|
-
const agentsRest = require('../agents/rest');
|
|
84
|
-
const appsRest = require('../apps/rest');
|
|
85
|
-
const emailInbound = require('../email/inbound');
|
|
86
|
-
const slackInbound = require('../slack/inbound');
|
|
87
|
-
const fsRest = require('../fs/rest');
|
|
88
|
-
const localRest = require('../local/rest');
|
|
89
|
-
const mcpConnectionsRest = require('../mcp-connections/rest');
|
|
90
|
-
const toolboxRest = require('../toolbox/rest');
|
|
91
|
-
const stateRest = require('../state/rest');
|
|
92
|
-
const browserRest = require('../browser/rest');
|
|
93
|
-
const prefsStore = require('../lib/prefs');
|
|
94
|
-
const userApiKeysRest = require('../user-api-keys/rest');
|
|
95
|
-
const credentialsRest = require('../credentials/rest');
|
|
96
|
-
const workspaceRest = require('../workspace/rest');
|
|
97
|
-
|
|
98
|
-
function createServer() {
|
|
99
|
-
return http.createServer(async (req, res) => {
|
|
100
|
-
if (!authorizeRuntimeHttp(req, res, { exposeHeaders: 'Mcp-Session-Id' })) return;
|
|
101
|
-
|
|
102
|
-
async function readJsonBody() {
|
|
103
|
-
const chunks = [];
|
|
104
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
105
|
-
return JSON.parse(Buffer.concat(chunks).toString());
|
|
106
|
-
}
|
|
107
|
-
function sendJson(statusCode, data) {
|
|
108
|
-
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
109
|
-
res.end(JSON.stringify(data));
|
|
110
|
-
}
|
|
111
|
-
function getQuery() {
|
|
112
|
-
return Object.fromEntries(new URL(req.url, 'http://127.0.0.1').searchParams.entries());
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// ── MCP ─────────────────────────────────────────────────────────────────
|
|
117
|
-
// Delegate anything under /mcp directly to the SDK transport. A fresh
|
|
118
|
-
// transport + Server pair is created per request: the stateless
|
|
119
|
-
// StreamableHTTPServerTransport is single-shot in this mode, so reusing
|
|
120
|
-
// one across requests would fail on the second call with HTTP 500.
|
|
121
|
-
if (req.url === '/mcp' || req.url === '/mcp/' || req.url.startsWith('/mcp?')) {
|
|
122
|
-
const transport = await createTransport();
|
|
123
|
-
await transport.handleRequest(req, res);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ── Health ──────────────────────────────────────────────────────────────
|
|
128
|
-
if (req.url === '/healthz') {
|
|
129
|
-
const agents = loadAgents();
|
|
130
|
-
const automations = listAutomations();
|
|
131
|
-
return sendJson(200, {
|
|
132
|
-
status: 'ok',
|
|
133
|
-
automations: automations.length,
|
|
134
|
-
enabledAutomations: automations.filter((automation) => automation.enabled !== false).length,
|
|
135
|
-
apps: loadApps().apps.length,
|
|
136
|
-
customAgents: agents.agents.length,
|
|
137
|
-
activeAgentConversations: activeAgentConversations.size,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ── /state — Local Live Store snapshot/replay/stream ─────────────────
|
|
142
|
-
if (req.url.startsWith('/state/snapshot') && req.method === 'GET') {
|
|
143
|
-
return stateRest.handleSnapshot(getQuery(), sendJson);
|
|
144
|
-
}
|
|
145
|
-
if (req.url.startsWith('/state/events') && req.method === 'GET') {
|
|
146
|
-
return stateRest.handleEvents(getQuery(), sendJson);
|
|
147
|
-
}
|
|
148
|
-
if (req.url.startsWith('/state/stream') && req.method === 'GET') {
|
|
149
|
-
return stateRest.handleStream(req, res, getQuery());
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ── /browser REST ───────────────────────────────────────────────────
|
|
153
|
-
if (req.url === '/browser/profiles' && req.method === 'GET') {
|
|
154
|
-
return browserRest.handleProfilesList(sendJson);
|
|
155
|
-
}
|
|
156
|
-
if (req.url === '/browser/profiles' && req.method === 'POST') {
|
|
157
|
-
return browserRest.handleProfilesUpsert(await readJsonBody(), sendJson);
|
|
158
|
-
}
|
|
159
|
-
if (req.url === '/browser/auth-bundles' && req.method === 'GET') {
|
|
160
|
-
return browserRest.handleAuthBundlesList(sendJson);
|
|
161
|
-
}
|
|
162
|
-
if (req.url === '/browser/auth-bundles/export' && req.method === 'POST') {
|
|
163
|
-
return browserRest.handleAuthBundleExport(await readJsonBody(), sendJson);
|
|
164
|
-
}
|
|
165
|
-
if (req.url === '/browser/auth-bundles/import' && req.method === 'POST') {
|
|
166
|
-
return browserRest.handleAuthBundleImport(await readJsonBody(), sendJson);
|
|
167
|
-
}
|
|
168
|
-
if (req.url === '/browser/auth-bundles' && req.method === 'DELETE') {
|
|
169
|
-
return browserRest.handleAuthBundleDelete(await readJsonBody(), sendJson);
|
|
170
|
-
}
|
|
171
|
-
if (req.url.startsWith('/browser/login-sessions') && req.method === 'GET') {
|
|
172
|
-
return browserRest.handleLoginSessionsList(getQuery(), sendJson);
|
|
173
|
-
}
|
|
174
|
-
if (req.url === '/browser/login-sessions' && req.method === 'POST') {
|
|
175
|
-
return browserRest.handleLoginSessionCreate(await readJsonBody(), sendJson);
|
|
176
|
-
}
|
|
177
|
-
if (req.url === '/browser/login-sessions/complete' && req.method === 'POST') {
|
|
178
|
-
return browserRest.handleLoginSessionComplete(await readJsonBody(), sendJson);
|
|
179
|
-
}
|
|
180
|
-
if (req.url === '/browser/login-sessions/cancel' && req.method === 'POST') {
|
|
181
|
-
return browserRest.handleLoginSessionCancel(await readJsonBody(), sendJson);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ── /user-preferences REST ───────────────────────────────────────────
|
|
185
|
-
if (req.url === '/user-preferences' && req.method === 'GET') {
|
|
186
|
-
return sendJson(200, prefsStore.readUserPreferences());
|
|
187
|
-
}
|
|
188
|
-
if (req.url === '/user-preferences' && req.method === 'PUT') {
|
|
189
|
-
const preferences = prefsStore.updateUserPreferences(await readJsonBody());
|
|
190
|
-
return sendJson(200, { success: true, preferences });
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ── /agents (REST, not MCP) ─────────────────────────────────────────────
|
|
194
|
-
if (req.url === '/agents/list' && req.method === 'GET') {
|
|
195
|
-
return agentsRest.handleList(sendJson);
|
|
196
|
-
}
|
|
197
|
-
if (req.url === '/agents/get' && req.method === 'POST') {
|
|
198
|
-
return agentsRest.handleGet(await readJsonBody(), sendJson);
|
|
199
|
-
}
|
|
200
|
-
if (req.url === '/agents/create' && req.method === 'POST') {
|
|
201
|
-
return agentsRest.handleCreate(await readJsonBody(), sendJson);
|
|
202
|
-
}
|
|
203
|
-
if (req.url === '/agents/update' && req.method === 'POST') {
|
|
204
|
-
return agentsRest.handleUpdate(await readJsonBody(), sendJson);
|
|
205
|
-
}
|
|
206
|
-
if (req.url === '/agents/delete' && req.method === 'POST') {
|
|
207
|
-
return agentsRest.handleDelete(await readJsonBody(), sendJson);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ── /events — webhook ingress ───────────────────────────────────────────
|
|
211
|
-
if (req.url === '/events' && req.method === 'POST') {
|
|
212
|
-
return handleEventsPost(req, sendJson);
|
|
213
|
-
}
|
|
214
|
-
if (req.url === '/events' && req.method === 'GET') {
|
|
215
|
-
return sendJson(200, { events: eventsRing.recent(50) });
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ── /automations REST ─────────────────────────────────────────────────
|
|
219
|
-
if (req.url === '/automations/list' && req.method === 'GET') {
|
|
220
|
-
return automationsRest.handleList(req, sendJson);
|
|
221
|
-
}
|
|
222
|
-
if (req.url === '/automations/get' && req.method === 'POST') {
|
|
223
|
-
return automationsRest.handleGet(await readJsonBody(), sendJson);
|
|
224
|
-
}
|
|
225
|
-
if (req.url === '/automations/create' && req.method === 'POST') {
|
|
226
|
-
return automationsRest.handleCreate(await readJsonBody(), sendJson);
|
|
227
|
-
}
|
|
228
|
-
if (req.url === '/automations/update' && req.method === 'POST') {
|
|
229
|
-
return automationsRest.handleUpdate(await readJsonBody(), sendJson);
|
|
230
|
-
}
|
|
231
|
-
if (req.url === '/automations/delete' && req.method === 'POST') {
|
|
232
|
-
return automationsRest.handleDelete(await readJsonBody(), sendJson);
|
|
233
|
-
}
|
|
234
|
-
if (req.url === '/automations/run-now' && req.method === 'POST') {
|
|
235
|
-
return automationsRest.handleRunNow(await readJsonBody(), sendJson);
|
|
236
|
-
}
|
|
237
|
-
if (req.url === '/automations/validate' && req.method === 'POST') {
|
|
238
|
-
return automationsRest.handleValidate(await readJsonBody(), sendJson);
|
|
239
|
-
}
|
|
240
|
-
if (req.url === '/automations/triggers/update' && req.method === 'POST') {
|
|
241
|
-
return automationsRest.handleTriggerUpdate(await readJsonBody(), sendJson);
|
|
242
|
-
}
|
|
243
|
-
if (req.url === '/automations/triggers/delete' && req.method === 'POST') {
|
|
244
|
-
return automationsRest.handleTriggerDelete(await readJsonBody(), sendJson);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ── /workspace REST ─────────────────────────────────────────────────────
|
|
248
|
-
if (req.url === '/workspace/projects' && req.method === 'GET') {
|
|
249
|
-
return workspaceRest.handleProjects(sendJson);
|
|
250
|
-
}
|
|
251
|
-
if (req.url === '/workspace/projects' && req.method === 'POST') {
|
|
252
|
-
return workspaceRest.handleProjectRegister(await readJsonBody(), sendJson);
|
|
253
|
-
}
|
|
254
|
-
if (req.url === '/workspace/projects' && req.method === 'PATCH') {
|
|
255
|
-
return workspaceRest.handleProjectUpdate(await readJsonBody(), sendJson);
|
|
256
|
-
}
|
|
257
|
-
if (req.url === '/workspace/projects' && req.method === 'DELETE') {
|
|
258
|
-
return workspaceRest.handleProjectDelete(await readJsonBody(), sendJson);
|
|
259
|
-
}
|
|
260
|
-
if (req.url === '/workspace/import' && req.method === 'POST') {
|
|
261
|
-
return workspaceRest.handleImport(await readJsonBody(), sendJson);
|
|
262
|
-
}
|
|
263
|
-
if (req.url === '/github/clone' && req.method === 'POST') {
|
|
264
|
-
return workspaceRest.handleClone(await readJsonBody(), sendJson);
|
|
265
|
-
}
|
|
266
|
-
if (req.url.startsWith('/workspace/branches') && req.method === 'GET') {
|
|
267
|
-
return workspaceRest.handleBranches(getQuery(), sendJson);
|
|
268
|
-
}
|
|
269
|
-
if (req.url === '/workspace/checkout' && req.method === 'POST') {
|
|
270
|
-
return workspaceRest.handleCheckout(await readJsonBody(), sendJson);
|
|
271
|
-
}
|
|
272
|
-
if (req.url.startsWith('/workspace/diff') && req.method === 'GET') {
|
|
273
|
-
return workspaceRest.handleDiff(getQuery(), sendJson);
|
|
274
|
-
}
|
|
275
|
-
if (req.url.startsWith('/workspace/worktree') && req.method === 'GET') {
|
|
276
|
-
return workspaceRest.handleWorktreeList(getQuery(), sendJson);
|
|
277
|
-
}
|
|
278
|
-
if (req.url === '/workspace/worktree' && req.method === 'POST') {
|
|
279
|
-
return workspaceRest.handleWorktreeCreate(await readJsonBody(), sendJson);
|
|
280
|
-
}
|
|
281
|
-
if (req.url === '/workspace/worktree' && req.method === 'DELETE') {
|
|
282
|
-
return workspaceRest.handleWorktreeDelete(await readJsonBody(), sendJson);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ── /fs REST ────────────────────────────────────────────────────────────
|
|
286
|
-
if (req.url.startsWith('/fs/list') && req.method === 'GET') {
|
|
287
|
-
return fsRest.handleList(getQuery(), sendJson);
|
|
288
|
-
}
|
|
289
|
-
if (req.url.startsWith('/fs/read') && req.method === 'GET') {
|
|
290
|
-
return fsRest.handleRead(getQuery(), sendJson);
|
|
291
|
-
}
|
|
292
|
-
if (req.url === '/fs/write' && req.method === 'POST') {
|
|
293
|
-
return fsRest.handleWrite(await readJsonBody(), sendJson);
|
|
294
|
-
}
|
|
295
|
-
if (req.url === '/fs/delete' && req.method === 'POST') {
|
|
296
|
-
return fsRest.handleDelete(await readJsonBody(), sendJson);
|
|
297
|
-
}
|
|
298
|
-
if (req.url === '/fs/mkdir' && req.method === 'POST') {
|
|
299
|
-
return fsRest.handleMkdir(await readJsonBody(), sendJson);
|
|
300
|
-
}
|
|
301
|
-
if (req.url === '/fs/rename' && req.method === 'POST') {
|
|
302
|
-
return fsRest.handleRename(await readJsonBody(), sendJson);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// ── /mcp-connections REST ──────────────────────────────────────────────
|
|
306
|
-
if (req.url === '/mcp-connections' && req.method === 'GET') {
|
|
307
|
-
return mcpConnectionsRest.handleList(sendJson);
|
|
308
|
-
}
|
|
309
|
-
if (req.url === '/mcp-connections' && req.method === 'POST') {
|
|
310
|
-
return mcpConnectionsRest.handleUpsert(await readJsonBody(), sendJson);
|
|
311
|
-
}
|
|
312
|
-
if (req.url === '/mcp-connections' && req.method === 'DELETE') {
|
|
313
|
-
return mcpConnectionsRest.handleDelete(await readJsonBody(), sendJson);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// ── /toolbox REST ─────────────────────────────────────────────────────
|
|
317
|
-
if (req.url === '/toolbox' && req.method === 'GET') {
|
|
318
|
-
return toolboxRest.handleList(sendJson);
|
|
319
|
-
}
|
|
320
|
-
if ((req.url === '/toolbox/tools' || req.url.startsWith('/toolbox/tools?')) && req.method === 'GET') {
|
|
321
|
-
return toolboxRest.handleGetTool(getQuery(), sendJson);
|
|
322
|
-
}
|
|
323
|
-
if (req.url === '/toolbox/tools' && req.method === 'POST') {
|
|
324
|
-
return toolboxRest.handleUpsertTool(await readJsonBody(), sendJson);
|
|
325
|
-
}
|
|
326
|
-
if (req.url === '/toolbox/tools' && req.method === 'PATCH') {
|
|
327
|
-
return toolboxRest.handleUpdateTool(await readJsonBody(), sendJson);
|
|
328
|
-
}
|
|
329
|
-
if (req.url === '/toolbox/tools' && req.method === 'DELETE') {
|
|
330
|
-
return toolboxRest.handleDeleteTool(await readJsonBody(), sendJson);
|
|
331
|
-
}
|
|
332
|
-
if ((req.url === '/toolbox/actions' || req.url.startsWith('/toolbox/actions?')) && req.method === 'GET') {
|
|
333
|
-
return toolboxRest.handleGetAction(getQuery(), sendJson);
|
|
334
|
-
}
|
|
335
|
-
if (req.url === '/toolbox/actions' && req.method === 'POST') {
|
|
336
|
-
return toolboxRest.handleUpsertAction(await readJsonBody(), sendJson);
|
|
337
|
-
}
|
|
338
|
-
if (req.url === '/toolbox/actions' && req.method === 'PATCH') {
|
|
339
|
-
return toolboxRest.handleUpdateAction(await readJsonBody(), sendJson);
|
|
340
|
-
}
|
|
341
|
-
if (req.url === '/toolbox/actions' && req.method === 'DELETE') {
|
|
342
|
-
return toolboxRest.handleDeleteAction(await readJsonBody(), sendJson);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// ── /local REST ────────────────────────────────────────────────────────
|
|
346
|
-
if (req.url === '/local/init' && req.method === 'POST') {
|
|
347
|
-
return localRest.handleInit(sendJson);
|
|
348
|
-
}
|
|
349
|
-
if (req.url === '/local/credentials' && req.method === 'GET') {
|
|
350
|
-
return localRest.handleCredentials(sendJson);
|
|
351
|
-
}
|
|
352
|
-
if (req.url === '/local/credentials/resolve' && req.method === 'POST') {
|
|
353
|
-
return localRest.handleResolve(await readJsonBody(), sendJson);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// ── /credentials REST ─────────────────────────────────────────────────
|
|
357
|
-
if (req.url === '/credentials' && req.method === 'GET') {
|
|
358
|
-
return credentialsRest.handleList(sendJson);
|
|
359
|
-
}
|
|
360
|
-
if (req.url === '/credentials' && req.method === 'POST') {
|
|
361
|
-
return credentialsRest.handleSave(await readJsonBody(), sendJson);
|
|
362
|
-
}
|
|
363
|
-
if (req.url === '/credentials' && req.method === 'DELETE') {
|
|
364
|
-
return credentialsRest.handleDelete(await readJsonBody(), sendJson);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// ── /email REST ────────────────────────────────────────────────────────
|
|
368
|
-
if (req.url === '/email/inbound' && req.method === 'POST') {
|
|
369
|
-
return emailInbound.handleEmailInbound(await readJsonBody(), sendJson);
|
|
370
|
-
}
|
|
371
|
-
if (req.url === '/slack/inbound' && req.method === 'POST') {
|
|
372
|
-
return slackInbound.handleSlackInbound(await readJsonBody(), sendJson);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ── /user-api-keys REST ───────────────────────────────────────────────
|
|
376
|
-
if (req.url === '/user-api-keys' && req.method === 'POST') {
|
|
377
|
-
return userApiKeysRest.handleSave(await readJsonBody(), sendJson);
|
|
378
|
-
}
|
|
379
|
-
if (req.url === '/user-api-keys' && req.method === 'DELETE') {
|
|
380
|
-
return userApiKeysRest.handleDelete(sendJson);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// ── /apps REST ────────────────────────────────────────────────────
|
|
384
|
-
if (req.url === '/apps/list' && req.method === 'GET') {
|
|
385
|
-
return appsRest.handleList(sendJson);
|
|
386
|
-
}
|
|
387
|
-
if (req.url === '/apps/routes' && req.method === 'GET') {
|
|
388
|
-
return appsRest.handleRoutes(sendJson);
|
|
389
|
-
}
|
|
390
|
-
if (req.url === '/apps/register' && req.method === 'POST') {
|
|
391
|
-
return appsRest.handleRegister(await readJsonBody(), sendJson);
|
|
392
|
-
}
|
|
393
|
-
if (req.url === '/apps/redeploy' && req.method === 'POST') {
|
|
394
|
-
return appsRest.handleRedeploy(await readJsonBody(), sendJson);
|
|
395
|
-
}
|
|
396
|
-
if (req.url === '/apps/start' && req.method === 'POST') {
|
|
397
|
-
return appsRest.handleStart(await readJsonBody(), sendJson);
|
|
398
|
-
}
|
|
399
|
-
if (req.url === '/apps/stop' && req.method === 'POST') {
|
|
400
|
-
return appsRest.handleStop(await readJsonBody(), sendJson);
|
|
401
|
-
}
|
|
402
|
-
if (req.url === '/apps/delete' && req.method === 'POST') {
|
|
403
|
-
return appsRest.handleDelete(await readJsonBody(), sendJson);
|
|
404
|
-
}
|
|
405
|
-
if (req.url === '/apps/connect-dns' && req.method === 'POST') {
|
|
406
|
-
return appsRest.handleConnectDns(await readJsonBody(), sendJson);
|
|
407
|
-
}
|
|
408
|
-
if (req.url === '/apps/disconnect-dns' && req.method === 'POST') {
|
|
409
|
-
return appsRest.handleDisconnectDns(await readJsonBody(), sendJson);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// ── legacy /artifacts REST aliases ────────────────────────────────
|
|
413
|
-
if (req.url === '/artifacts/list' && req.method === 'GET') {
|
|
414
|
-
return appsRest.handleList(sendJson, { legacy: true });
|
|
415
|
-
}
|
|
416
|
-
if (req.url === '/artifacts/routes' && req.method === 'GET') {
|
|
417
|
-
return appsRest.handleRoutes(sendJson, { legacy: true });
|
|
418
|
-
}
|
|
419
|
-
if (req.url === '/artifacts/register' && req.method === 'POST') {
|
|
420
|
-
return appsRest.handleRegister(await readJsonBody(), sendJson, { legacy: true });
|
|
421
|
-
}
|
|
422
|
-
if (req.url === '/artifacts/redeploy' && req.method === 'POST') {
|
|
423
|
-
return appsRest.handleRedeploy(await readJsonBody(), sendJson, { legacy: true });
|
|
424
|
-
}
|
|
425
|
-
if (req.url === '/artifacts/start' && req.method === 'POST') {
|
|
426
|
-
return appsRest.handleStart(await readJsonBody(), sendJson, { legacy: true });
|
|
427
|
-
}
|
|
428
|
-
if (req.url === '/artifacts/stop' && req.method === 'POST') {
|
|
429
|
-
return appsRest.handleStop(await readJsonBody(), sendJson, { legacy: true });
|
|
430
|
-
}
|
|
431
|
-
if (req.url === '/artifacts/delete' && req.method === 'POST') {
|
|
432
|
-
return appsRest.handleDelete(await readJsonBody(), sendJson, { legacy: true });
|
|
433
|
-
}
|
|
434
|
-
if (req.url === '/artifacts/connect-dns' && req.method === 'POST') {
|
|
435
|
-
return appsRest.handleConnectDns(await readJsonBody(), sendJson, { legacy: true });
|
|
436
|
-
}
|
|
437
|
-
if (req.url === '/artifacts/disconnect-dns' && req.method === 'POST') {
|
|
438
|
-
return appsRest.handleDisconnectDns(await readJsonBody(), sendJson, { legacy: true });
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
442
|
-
res.end('Not found');
|
|
443
|
-
} catch (err) {
|
|
444
|
-
console.error('[AmalgmMCP] Unhandled error:', err);
|
|
445
|
-
if (!res.headersSent) sendJson(500, { error: 'Internal server error' });
|
|
446
|
-
}
|
|
447
|
-
});
|
|
15
|
+
function createServer(options = {}) {
|
|
16
|
+
return createCombinedServer(options);
|
|
448
17
|
}
|
|
449
18
|
|
|
450
|
-
async function listen() {
|
|
451
|
-
const httpServer = createServer();
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
19
|
+
async function listen(options = {}) {
|
|
20
|
+
const httpServer = createServer(options);
|
|
21
|
+
const runtimeWorker = options.runtimeWorker || {
|
|
22
|
+
start: startRuntimeWorker,
|
|
23
|
+
stop: stopRuntimeWorker,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
startHttpServer(httpServer, {
|
|
27
|
+
port: options.port || PORT,
|
|
28
|
+
serviceName: 'AmalgmMCP',
|
|
29
|
+
onListening() {
|
|
30
|
+
console.log(`[AmalgmMCP] Storage: ${STORAGE_DIR}`);
|
|
31
|
+
console.log(
|
|
32
|
+
`[AmalgmMCP] Supabase: ${hasSupabase() ? 'connected' : 'not configured (runs are local-only)'}`,
|
|
33
|
+
);
|
|
34
|
+
// Hydrate user model prefs non-blocking from local SQLite.
|
|
35
|
+
require('../lib/prefs').hydrateModelPreferences().catch(() => {});
|
|
36
|
+
runtimeWorker.start();
|
|
37
|
+
},
|
|
38
|
+
onShutdown() {
|
|
39
|
+
runtimeWorker.stop();
|
|
40
|
+
},
|
|
462
41
|
});
|
|
463
42
|
|
|
464
|
-
// Graceful shutdown. Running tasks get aborted via SIGTERM → process exit;
|
|
465
|
-
// we don't try to individually abort them here since chat-server owns its own
|
|
466
|
-
// runtime teardown paths.
|
|
467
|
-
function shutdown() {
|
|
468
|
-
console.log('[AmalgmMCP] Shutting down...');
|
|
469
|
-
stopAutomationScheduler();
|
|
470
|
-
httpServer.close(() => process.exit(0));
|
|
471
|
-
setTimeout(() => process.exit(0), 5000);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
process.on('SIGTERM', shutdown);
|
|
475
|
-
process.on('SIGINT', shutdown);
|
|
476
|
-
|
|
477
43
|
return httpServer;
|
|
478
44
|
}
|
|
479
45
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local product/control-plane API dispatcher.
|
|
5
|
+
*
|
|
6
|
+
* These route groups are not the MCP surface. They are the local HTTP APIs the
|
|
7
|
+
* app uses to manage resources: agents, apps, automations, toolbox records,
|
|
8
|
+
* browser auth, workspace/files, credentials, state, and inbound event helpers.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { handleHealthRoutes } = require('./routes/health');
|
|
12
|
+
const { handleStateRoutes } = require('./routes/state');
|
|
13
|
+
const { handleBrowserRoutes } = require('./routes/browser');
|
|
14
|
+
const { handlePreferenceRoutes } = require('./routes/preferences');
|
|
15
|
+
const { handleAgentRoutes } = require('./routes/agents');
|
|
16
|
+
const { handleEventRoutes } = require('./routes/events');
|
|
17
|
+
const { handleAutomationRoutes } = require('./routes/automations');
|
|
18
|
+
const { handleWorkspaceRoutes } = require('./routes/workspace');
|
|
19
|
+
const { handleFileRoutes } = require('./routes/files');
|
|
20
|
+
const { handleToolboxRoutes } = require('./routes/toolbox');
|
|
21
|
+
const { handleLocalRoutes } = require('./routes/local');
|
|
22
|
+
const { handleCredentialRoutes } = require('./routes/credentials');
|
|
23
|
+
const { handleInboundRoutes } = require('./routes/inbound');
|
|
24
|
+
const { handleAppRoutes } = require('./routes/apps');
|
|
25
|
+
|
|
26
|
+
const DEFAULT_ROUTE_GROUPS = [
|
|
27
|
+
handleHealthRoutes,
|
|
28
|
+
handleStateRoutes,
|
|
29
|
+
handleBrowserRoutes,
|
|
30
|
+
handlePreferenceRoutes,
|
|
31
|
+
handleAgentRoutes,
|
|
32
|
+
handleEventRoutes,
|
|
33
|
+
handleAutomationRoutes,
|
|
34
|
+
handleWorkspaceRoutes,
|
|
35
|
+
handleFileRoutes,
|
|
36
|
+
handleToolboxRoutes,
|
|
37
|
+
handleLocalRoutes,
|
|
38
|
+
handleCredentialRoutes,
|
|
39
|
+
handleInboundRoutes,
|
|
40
|
+
handleAppRoutes,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function createLocalServiceRouter(routeGroups = DEFAULT_ROUTE_GROUPS) {
|
|
44
|
+
return {
|
|
45
|
+
async handle(ctx) {
|
|
46
|
+
for (const routeGroup of routeGroups) {
|
|
47
|
+
if (await routeGroup(ctx)) return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
DEFAULT_ROUTE_GROUPS,
|
|
56
|
+
createLocalServiceRouter,
|
|
57
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createTransport } = require('./mcp');
|
|
4
|
+
|
|
5
|
+
function isMcpRequest(pathname) {
|
|
6
|
+
return pathname === '/mcp' || pathname === '/mcp/';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function handleMcpRequest(ctx) {
|
|
10
|
+
if (!isMcpRequest(ctx.pathname)) return false;
|
|
11
|
+
|
|
12
|
+
// The SDK transport is single-shot in stateless streamable-HTTP mode.
|
|
13
|
+
// Reusing one across requests fails on the second call with HTTP 500.
|
|
14
|
+
const transport = await createTransport();
|
|
15
|
+
await transport.handleRequest(ctx.req, ctx.res);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
handleMcpRequest,
|
|
21
|
+
isMcpRequest,
|
|
22
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createRuntimeHttpServer } = require('./http-service');
|
|
4
|
+
const { handleMcpRequest } = require('./mcp-adapter');
|
|
5
|
+
|
|
6
|
+
function createMcpHttpHandler(options = {}) {
|
|
7
|
+
const mcpRequestHandler = options.mcpRequestHandler || handleMcpRequest;
|
|
8
|
+
|
|
9
|
+
return async function handleMcpHttpRequest(ctx) {
|
|
10
|
+
return mcpRequestHandler(ctx);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createMcpHttpServer(options = {}) {
|
|
15
|
+
return createRuntimeHttpServer(createMcpHttpHandler(options), {
|
|
16
|
+
serviceName: options.serviceName || 'AmalgmMCPAdapter',
|
|
17
|
+
exposeHeaders: options.exposeHeaders,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
createMcpHttpHandler,
|
|
23
|
+
createMcpHttpServer,
|
|
24
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { AMALGM_USER_ID } = require('../config');
|
|
4
|
+
const { hasSupabase, supabaseSelect } = require('../lib/supabase');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Look up the caller's session to tag DB rows with origin metadata. Reads
|
|
8
|
+
* x-amalgm-session-id from the transport's passed-through HTTP headers.
|
|
9
|
+
*/
|
|
10
|
+
async function buildMcpRequestContext(extra) {
|
|
11
|
+
const headers = extra?.requestInfo?.headers || {};
|
|
12
|
+
const h = headers['x-amalgm-session-id'];
|
|
13
|
+
const callerSessionId = Array.isArray(h) ? h[0] : h || null;
|
|
14
|
+
const ctx = { callerSessionId };
|
|
15
|
+
|
|
16
|
+
if (callerSessionId && hasSupabase()) {
|
|
17
|
+
const rows = await supabaseSelect(
|
|
18
|
+
'sessions',
|
|
19
|
+
`id=eq.${callerSessionId}&user_id=eq.${AMALGM_USER_ID}&select=id,title,harness,metadata`,
|
|
20
|
+
);
|
|
21
|
+
const parent = Array.isArray(rows) ? rows[0] : null;
|
|
22
|
+
if (parent) {
|
|
23
|
+
ctx.sessionMetadata = parent.metadata || {};
|
|
24
|
+
ctx.originName = parent.title || undefined;
|
|
25
|
+
ctx.originHarnessId = parent.harness || undefined;
|
|
26
|
+
ctx.originBaseHarnessId = parent.metadata?.baseHarnessId || undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return ctx;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { buildMcpRequestContext };
|