amalgm 0.0.0 → 0.0.32
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/README.md +41 -1
- package/bin/amalgm.js +8 -2
- package/lib/auth-store.js +223 -0
- package/lib/cli.js +1132 -0
- package/lib/paths.js +30 -0
- package/lib/supervisor.js +476 -0
- package/lib/tunnel-chat.js +328 -0
- package/lib/tunnel-events.js +499 -0
- package/package.json +30 -3
- package/runtime/README.md +4 -0
- package/runtime/lib/chatInput.js +315 -0
- package/runtime/lib/harnesses.js +988 -0
- package/runtime/lib/local/amalgmStore.js +136 -0
- package/runtime/lib/local/credentialResolver.js +425 -0
- package/runtime/lib/mcpApps/registry.js +619 -0
- package/runtime/package.json +5 -0
- package/runtime/scripts/amalgm-mcp/agents/rest.js +144 -0
- package/runtime/scripts/amalgm-mcp/agents/store.js +684 -0
- package/runtime/scripts/amalgm-mcp/agents/talk.js +1162 -0
- package/runtime/scripts/amalgm-mcp/agents/tools.js +221 -0
- package/runtime/scripts/amalgm-mcp/artifacts/advertise.js +132 -0
- package/runtime/scripts/amalgm-mcp/artifacts/rest.js +103 -0
- package/runtime/scripts/amalgm-mcp/artifacts/store.js +157 -0
- package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +402 -0
- package/runtime/scripts/amalgm-mcp/artifacts/tools.js +176 -0
- package/runtime/scripts/amalgm-mcp/browser/page.js +637 -0
- package/runtime/scripts/amalgm-mcp/browser/tools.js +688 -0
- package/runtime/scripts/amalgm-mcp/config.js +140 -0
- package/runtime/scripts/amalgm-mcp/credentials/rest.js +45 -0
- package/runtime/scripts/amalgm-mcp/deps.js +40 -0
- package/runtime/scripts/amalgm-mcp/email/inbound.js +215 -0
- package/runtime/scripts/amalgm-mcp/events/executor.js +179 -0
- package/runtime/scripts/amalgm-mcp/events/ingress.js +113 -0
- package/runtime/scripts/amalgm-mcp/events/matcher.js +125 -0
- package/runtime/scripts/amalgm-mcp/events/rest.js +200 -0
- package/runtime/scripts/amalgm-mcp/events/ring-buffer.js +19 -0
- package/runtime/scripts/amalgm-mcp/events/store.js +113 -0
- package/runtime/scripts/amalgm-mcp/events/tools.js +306 -0
- package/runtime/scripts/amalgm-mcp/events/webhook-url.js +28 -0
- package/runtime/scripts/amalgm-mcp/fs/rest.js +293 -0
- package/runtime/scripts/amalgm-mcp/index.js +100 -0
- package/runtime/scripts/amalgm-mcp/lib/chat-runner.js +167 -0
- package/runtime/scripts/amalgm-mcp/lib/email-md.js +288 -0
- package/runtime/scripts/amalgm-mcp/lib/mcp-resolver.js +63 -0
- package/runtime/scripts/amalgm-mcp/lib/prefs.js +441 -0
- package/runtime/scripts/amalgm-mcp/lib/storage.js +92 -0
- package/runtime/scripts/amalgm-mcp/lib/supabase.js +118 -0
- package/runtime/scripts/amalgm-mcp/lib/tool-result.js +177 -0
- package/runtime/scripts/amalgm-mcp/local/rest.js +87 -0
- package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +234 -0
- package/runtime/scripts/amalgm-mcp/notify/index.js +107 -0
- package/runtime/scripts/amalgm-mcp/server/core-tools.js +20 -0
- package/runtime/scripts/amalgm-mcp/server/http.js +377 -0
- package/runtime/scripts/amalgm-mcp/server/mcp.js +127 -0
- package/runtime/scripts/amalgm-mcp/slack/inbound.js +200 -0
- package/runtime/scripts/amalgm-mcp/state/db.js +194 -0
- package/runtime/scripts/amalgm-mcp/state/events.js +113 -0
- package/runtime/scripts/amalgm-mcp/state/rest.js +64 -0
- package/runtime/scripts/amalgm-mcp/state/snapshot.js +76 -0
- package/runtime/scripts/amalgm-mcp/tasks/executor.js +225 -0
- package/runtime/scripts/amalgm-mcp/tasks/rest.js +110 -0
- package/runtime/scripts/amalgm-mcp/tasks/schedule-normalization.js +85 -0
- package/runtime/scripts/amalgm-mcp/tasks/scheduler.js +105 -0
- package/runtime/scripts/amalgm-mcp/tasks/store.js +154 -0
- package/runtime/scripts/amalgm-mcp/tasks/tools.js +391 -0
- package/runtime/scripts/amalgm-mcp/toolbox/rest.js +75 -0
- package/runtime/scripts/amalgm-mcp/toolbox/runner.js +257 -0
- package/runtime/scripts/amalgm-mcp/toolbox/store.js +933 -0
- package/runtime/scripts/amalgm-mcp/toolbox/tools.js +269 -0
- package/runtime/scripts/amalgm-mcp/user-api-keys/rest.js +105 -0
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +497 -0
- package/runtime/scripts/chat-core/adapters/claude.js +165 -0
- package/runtime/scripts/chat-core/adapters/codex.js +313 -0
- package/runtime/scripts/chat-core/adapters/opencode.js +412 -0
- package/runtime/scripts/chat-core/auth.js +177 -0
- package/runtime/scripts/chat-core/contract.js +328 -0
- package/runtime/scripts/chat-core/credentials/store.js +212 -0
- package/runtime/scripts/chat-core/egress.js +87 -0
- package/runtime/scripts/chat-core/engine.js +253 -0
- package/runtime/scripts/chat-core/event-schema.js +231 -0
- package/runtime/scripts/chat-core/events.js +190 -0
- package/runtime/scripts/chat-core/index.js +11 -0
- package/runtime/scripts/chat-core/input.js +50 -0
- package/runtime/scripts/chat-core/normalizers/claude.js +450 -0
- package/runtime/scripts/chat-core/normalizers/codex.js +380 -0
- package/runtime/scripts/chat-core/normalizers/normalizer_spec.md +259 -0
- package/runtime/scripts/chat-core/normalizers/opencode.js +552 -0
- package/runtime/scripts/chat-core/normalizers/tool_contract.md +123 -0
- package/runtime/scripts/chat-core/normalizers/usage_contract.md +304 -0
- package/runtime/scripts/chat-core/parts.js +253 -0
- package/runtime/scripts/chat-core/recorder.js +65 -0
- package/runtime/scripts/chat-core/runtime.js +86 -0
- package/runtime/scripts/chat-core/server.js +163 -0
- package/runtime/scripts/chat-core/sse.js +196 -0
- package/runtime/scripts/chat-core/stores.js +100 -0
- package/runtime/scripts/chat-core/tool-display.js +149 -0
- package/runtime/scripts/chat-core/tool-shape.js +143 -0
- package/runtime/scripts/chat-core/tooling/mcp-bundle.js +161 -0
- package/runtime/scripts/chat-core/tooling/mcp-relay.js +97 -0
- package/runtime/scripts/chat-core/tooling/native-binaries.js +608 -0
- package/runtime/scripts/chat-core/tooling/system-prompt.js +20 -0
- package/runtime/scripts/chat-core/usage.js +343 -0
- package/runtime/scripts/chat-server/config.js +110 -0
- package/runtime/scripts/chat-server/db.js +529 -0
- package/runtime/scripts/chat-server/index.js +33 -0
- package/runtime/scripts/chat-server/model-catalog.js +327 -0
- package/runtime/scripts/chat-server.js +75 -0
- package/runtime/scripts/credential-adapter.js +131 -0
- package/runtime/scripts/fs-watcher.js +888 -0
- package/runtime/scripts/local-gateway.js +854 -0
- package/runtime/scripts/platform-context.txt +246 -0
- package/runtime/scripts/port-monitor.js +175 -0
- package/runtime/scripts/proxy-token-store.js +162 -0
- package/runtime/scripts/runtime-auth.js +163 -0
- package/runtime/scripts/test-claude-code-models.js +87 -0
- package/runtime/tsconfig.json +15 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP server — routes + lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Route table:
|
|
5
|
+
* GET /healthz → stats snapshot
|
|
6
|
+
* * /mcp → MCP streamable-HTTP (delegated to SDK transport)
|
|
7
|
+
* POST /events → webhook ingress (signature match + agent run)
|
|
8
|
+
* GET /events → recent events (in-memory ring buffer)
|
|
9
|
+
* GET /tasks/list → list tasks
|
|
10
|
+
* POST /tasks/create → create task
|
|
11
|
+
* POST /tasks/update → update task
|
|
12
|
+
* POST /tasks/delete → delete task
|
|
13
|
+
* POST /tasks/run-now → run task immediately
|
|
14
|
+
* GET /event-triggers/list → list triggers
|
|
15
|
+
* POST /event-triggers/{create,update,delete}
|
|
16
|
+
* GET /workspace/projects → list projects
|
|
17
|
+
* POST /workspace/import → copy a local folder into workspaces
|
|
18
|
+
* POST /github/clone → clone repository onto the computer
|
|
19
|
+
* GET /workspace/branches → list git branches
|
|
20
|
+
* POST /workspace/checkout → checkout git branch
|
|
21
|
+
* GET /workspace/diff → git diff summary + patch
|
|
22
|
+
* GET /workspace/worktree → list worktrees
|
|
23
|
+
* POST /workspace/worktree → create worktree
|
|
24
|
+
* DELETE /workspace/worktree → remove worktree
|
|
25
|
+
* GET /fs/list → list files
|
|
26
|
+
* GET /fs/read → read file
|
|
27
|
+
* POST /fs/{write,delete,mkdir,rename}
|
|
28
|
+
* GET /mcp-connections → list sanitized MCP connections
|
|
29
|
+
* POST /mcp-connections → save/update MCP connection
|
|
30
|
+
* DELETE /mcp-connections → remove MCP connection
|
|
31
|
+
* GET /toolbox → list local ToolboxDB tools/actions
|
|
32
|
+
* POST /toolbox/tools → save/update a toolbox tool
|
|
33
|
+
* DELETE /toolbox/tools → remove a toolbox tool
|
|
34
|
+
* POST /toolbox/actions → save/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
|
+
* POST /local/init → init local computer state
|
|
40
|
+
* GET /local/credentials → discover local agent auth
|
|
41
|
+
* POST /local/credentials/resolve → resolve spawn env for agent auth
|
|
42
|
+
* POST /email/inbound → run a reply turn for inbound email
|
|
43
|
+
* POST /slack/inbound → run a reply turn for inbound Slack
|
|
44
|
+
* GET /credentials → list local Amalgm credential profiles
|
|
45
|
+
* POST /credentials → save local Amalgm credential profile
|
|
46
|
+
* DELETE /credentials → remove local Amalgm credential profile
|
|
47
|
+
* POST /user-api-keys → legacy save BYOK credential file
|
|
48
|
+
* DELETE /user-api-keys → legacy remove BYOK credential file
|
|
49
|
+
* GET /artifacts/list → list artifacts
|
|
50
|
+
* GET /artifacts/routes → artifact DNS routes for tunnel adverts
|
|
51
|
+
* POST /artifacts/{register,redeploy,start,stop,connect-dns,disconnect-dns}
|
|
52
|
+
* GET /agents/list → list agents
|
|
53
|
+
* POST /agents/{get,create,update,delete}
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
const http = require('http');
|
|
57
|
+
const { PORT, STORAGE_DIR } = require('../config');
|
|
58
|
+
const { hasSupabase } = require('../lib/supabase');
|
|
59
|
+
const { authorizeRuntimeHttp } = require('../../runtime-auth');
|
|
60
|
+
|
|
61
|
+
const { loadTasks } = require('../tasks/store');
|
|
62
|
+
const { loadAgents } = require('../agents/store');
|
|
63
|
+
const { loadArtifacts } = require('../artifacts/store');
|
|
64
|
+
const { activeAgentConversations } = require('../agents/talk');
|
|
65
|
+
const { startScheduler, stopScheduler } = require('../tasks/scheduler');
|
|
66
|
+
const { createTransport } = require('./mcp');
|
|
67
|
+
const { handleEventsPost } = require('../events/ingress');
|
|
68
|
+
const eventsRing = require('../events/ring-buffer');
|
|
69
|
+
const eventsRest = require('../events/rest');
|
|
70
|
+
const agentsRest = require('../agents/rest');
|
|
71
|
+
const artifactsRest = require('../artifacts/rest');
|
|
72
|
+
const emailInbound = require('../email/inbound');
|
|
73
|
+
const slackInbound = require('../slack/inbound');
|
|
74
|
+
const fsRest = require('../fs/rest');
|
|
75
|
+
const localRest = require('../local/rest');
|
|
76
|
+
const mcpConnectionsRest = require('../mcp-connections/rest');
|
|
77
|
+
const toolboxRest = require('../toolbox/rest');
|
|
78
|
+
const stateRest = require('../state/rest');
|
|
79
|
+
const tasksRest = require('../tasks/rest');
|
|
80
|
+
const userApiKeysRest = require('../user-api-keys/rest');
|
|
81
|
+
const credentialsRest = require('../credentials/rest');
|
|
82
|
+
const workspaceRest = require('../workspace/rest');
|
|
83
|
+
|
|
84
|
+
function createServer() {
|
|
85
|
+
return http.createServer(async (req, res) => {
|
|
86
|
+
if (!authorizeRuntimeHttp(req, res, { exposeHeaders: 'Mcp-Session-Id' })) return;
|
|
87
|
+
|
|
88
|
+
async function readJsonBody() {
|
|
89
|
+
const chunks = [];
|
|
90
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
91
|
+
return JSON.parse(Buffer.concat(chunks).toString());
|
|
92
|
+
}
|
|
93
|
+
function sendJson(statusCode, data) {
|
|
94
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
95
|
+
res.end(JSON.stringify(data));
|
|
96
|
+
}
|
|
97
|
+
function getQuery() {
|
|
98
|
+
return Object.fromEntries(new URL(req.url, 'http://127.0.0.1').searchParams.entries());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// ── MCP ─────────────────────────────────────────────────────────────────
|
|
103
|
+
// Delegate anything under /mcp directly to the SDK transport. A fresh
|
|
104
|
+
// transport + Server pair is created per request: the stateless
|
|
105
|
+
// StreamableHTTPServerTransport is single-shot in this mode, so reusing
|
|
106
|
+
// one across requests would fail on the second call with HTTP 500.
|
|
107
|
+
if (req.url === '/mcp' || req.url === '/mcp/' || req.url.startsWith('/mcp?')) {
|
|
108
|
+
const transport = await createTransport();
|
|
109
|
+
await transport.handleRequest(req, res);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Health ──────────────────────────────────────────────────────────────
|
|
114
|
+
if (req.url === '/healthz') {
|
|
115
|
+
const tasks = loadTasks();
|
|
116
|
+
const agents = loadAgents();
|
|
117
|
+
return sendJson(200, {
|
|
118
|
+
status: 'ok',
|
|
119
|
+
tasks: tasks.tasks.length,
|
|
120
|
+
enabledTasks: tasks.tasks.filter((t) => t.enabled).length,
|
|
121
|
+
artifacts: loadArtifacts().artifacts.length,
|
|
122
|
+
customAgents: agents.agents.length,
|
|
123
|
+
activeAgentConversations: activeAgentConversations.size,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── /state — Local Live Store snapshot/replay/stream ─────────────────
|
|
128
|
+
if (req.url.startsWith('/state/snapshot') && req.method === 'GET') {
|
|
129
|
+
return stateRest.handleSnapshot(getQuery(), sendJson);
|
|
130
|
+
}
|
|
131
|
+
if (req.url.startsWith('/state/events') && req.method === 'GET') {
|
|
132
|
+
return stateRest.handleEvents(getQuery(), sendJson);
|
|
133
|
+
}
|
|
134
|
+
if (req.url.startsWith('/state/stream') && req.method === 'GET') {
|
|
135
|
+
return stateRest.handleStream(req, res, getQuery());
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── /agents (REST, not MCP) ─────────────────────────────────────────────
|
|
139
|
+
if (req.url === '/agents/list' && req.method === 'GET') {
|
|
140
|
+
return agentsRest.handleList(sendJson);
|
|
141
|
+
}
|
|
142
|
+
if (req.url === '/agents/get' && req.method === 'POST') {
|
|
143
|
+
return agentsRest.handleGet(await readJsonBody(), sendJson);
|
|
144
|
+
}
|
|
145
|
+
if (req.url === '/agents/create' && req.method === 'POST') {
|
|
146
|
+
return agentsRest.handleCreate(await readJsonBody(), sendJson);
|
|
147
|
+
}
|
|
148
|
+
if (req.url === '/agents/update' && req.method === 'POST') {
|
|
149
|
+
return agentsRest.handleUpdate(await readJsonBody(), sendJson);
|
|
150
|
+
}
|
|
151
|
+
if (req.url === '/agents/delete' && req.method === 'POST') {
|
|
152
|
+
return agentsRest.handleDelete(await readJsonBody(), sendJson);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── /events — webhook ingress ───────────────────────────────────────────
|
|
156
|
+
if (req.url === '/events' && req.method === 'POST') {
|
|
157
|
+
return handleEventsPost(req, sendJson);
|
|
158
|
+
}
|
|
159
|
+
if (req.url === '/events' && req.method === 'GET') {
|
|
160
|
+
return sendJson(200, { events: eventsRing.recent(50) });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── /tasks REST ─────────────────────────────────────────────────────────
|
|
164
|
+
if (req.url === '/tasks/list' && req.method === 'GET') {
|
|
165
|
+
return tasksRest.handleList(sendJson);
|
|
166
|
+
}
|
|
167
|
+
if (req.url === '/tasks/create' && req.method === 'POST') {
|
|
168
|
+
return tasksRest.handleCreate(await readJsonBody(), sendJson);
|
|
169
|
+
}
|
|
170
|
+
if (req.url === '/tasks/update' && req.method === 'POST') {
|
|
171
|
+
return tasksRest.handleUpdate(await readJsonBody(), sendJson);
|
|
172
|
+
}
|
|
173
|
+
if (req.url === '/tasks/delete' && req.method === 'POST') {
|
|
174
|
+
return tasksRest.handleDelete(await readJsonBody(), sendJson);
|
|
175
|
+
}
|
|
176
|
+
if (req.url === '/tasks/run-now' && req.method === 'POST') {
|
|
177
|
+
return tasksRest.handleRunNow(await readJsonBody(), sendJson);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── /event-triggers REST ────────────────────────────────────────────────
|
|
181
|
+
if (req.url === '/event-triggers/list' && req.method === 'GET') {
|
|
182
|
+
return eventsRest.handleList(req, sendJson);
|
|
183
|
+
}
|
|
184
|
+
if (req.url === '/event-triggers/create' && req.method === 'POST') {
|
|
185
|
+
return eventsRest.handleCreate(await readJsonBody(), sendJson);
|
|
186
|
+
}
|
|
187
|
+
if (req.url === '/event-triggers/update' && req.method === 'POST') {
|
|
188
|
+
return eventsRest.handleUpdate(await readJsonBody(), sendJson);
|
|
189
|
+
}
|
|
190
|
+
if (req.url === '/event-triggers/delete' && req.method === 'POST') {
|
|
191
|
+
return eventsRest.handleDelete(await readJsonBody(), sendJson);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── /workspace REST ─────────────────────────────────────────────────────
|
|
195
|
+
if (req.url === '/workspace/projects' && req.method === 'GET') {
|
|
196
|
+
return workspaceRest.handleProjects(sendJson);
|
|
197
|
+
}
|
|
198
|
+
if (req.url === '/workspace/import' && req.method === 'POST') {
|
|
199
|
+
return workspaceRest.handleImport(await readJsonBody(), sendJson);
|
|
200
|
+
}
|
|
201
|
+
if (req.url === '/github/clone' && req.method === 'POST') {
|
|
202
|
+
return workspaceRest.handleClone(await readJsonBody(), sendJson);
|
|
203
|
+
}
|
|
204
|
+
if (req.url.startsWith('/workspace/branches') && req.method === 'GET') {
|
|
205
|
+
return workspaceRest.handleBranches(getQuery(), sendJson);
|
|
206
|
+
}
|
|
207
|
+
if (req.url === '/workspace/checkout' && req.method === 'POST') {
|
|
208
|
+
return workspaceRest.handleCheckout(await readJsonBody(), sendJson);
|
|
209
|
+
}
|
|
210
|
+
if (req.url.startsWith('/workspace/diff') && req.method === 'GET') {
|
|
211
|
+
return workspaceRest.handleDiff(getQuery(), sendJson);
|
|
212
|
+
}
|
|
213
|
+
if (req.url.startsWith('/workspace/worktree') && req.method === 'GET') {
|
|
214
|
+
return workspaceRest.handleWorktreeList(getQuery(), sendJson);
|
|
215
|
+
}
|
|
216
|
+
if (req.url === '/workspace/worktree' && req.method === 'POST') {
|
|
217
|
+
return workspaceRest.handleWorktreeCreate(await readJsonBody(), sendJson);
|
|
218
|
+
}
|
|
219
|
+
if (req.url === '/workspace/worktree' && req.method === 'DELETE') {
|
|
220
|
+
return workspaceRest.handleWorktreeDelete(await readJsonBody(), sendJson);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── /fs REST ────────────────────────────────────────────────────────────
|
|
224
|
+
if (req.url.startsWith('/fs/list') && req.method === 'GET') {
|
|
225
|
+
return fsRest.handleList(getQuery(), sendJson);
|
|
226
|
+
}
|
|
227
|
+
if (req.url.startsWith('/fs/read') && req.method === 'GET') {
|
|
228
|
+
return fsRest.handleRead(getQuery(), sendJson);
|
|
229
|
+
}
|
|
230
|
+
if (req.url === '/fs/write' && req.method === 'POST') {
|
|
231
|
+
return fsRest.handleWrite(await readJsonBody(), sendJson);
|
|
232
|
+
}
|
|
233
|
+
if (req.url === '/fs/delete' && req.method === 'POST') {
|
|
234
|
+
return fsRest.handleDelete(await readJsonBody(), sendJson);
|
|
235
|
+
}
|
|
236
|
+
if (req.url === '/fs/mkdir' && req.method === 'POST') {
|
|
237
|
+
return fsRest.handleMkdir(await readJsonBody(), sendJson);
|
|
238
|
+
}
|
|
239
|
+
if (req.url === '/fs/rename' && req.method === 'POST') {
|
|
240
|
+
return fsRest.handleRename(await readJsonBody(), sendJson);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── /mcp-connections REST ──────────────────────────────────────────────
|
|
244
|
+
if (req.url === '/mcp-connections' && req.method === 'GET') {
|
|
245
|
+
return mcpConnectionsRest.handleList(sendJson);
|
|
246
|
+
}
|
|
247
|
+
if (req.url === '/mcp-connections' && req.method === 'POST') {
|
|
248
|
+
return mcpConnectionsRest.handleUpsert(await readJsonBody(), sendJson);
|
|
249
|
+
}
|
|
250
|
+
if (req.url === '/mcp-connections' && req.method === 'DELETE') {
|
|
251
|
+
return mcpConnectionsRest.handleDelete(await readJsonBody(), sendJson);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── /toolbox REST ─────────────────────────────────────────────────────
|
|
255
|
+
if (req.url === '/toolbox' && req.method === 'GET') {
|
|
256
|
+
return toolboxRest.handleList(sendJson);
|
|
257
|
+
}
|
|
258
|
+
if (req.url === '/toolbox/tools' && req.method === 'POST') {
|
|
259
|
+
return toolboxRest.handleUpsertTool(await readJsonBody(), sendJson);
|
|
260
|
+
}
|
|
261
|
+
if (req.url === '/toolbox/tools' && req.method === 'DELETE') {
|
|
262
|
+
return toolboxRest.handleDeleteTool(await readJsonBody(), sendJson);
|
|
263
|
+
}
|
|
264
|
+
if (req.url === '/toolbox/actions' && req.method === 'POST') {
|
|
265
|
+
return toolboxRest.handleUpsertAction(await readJsonBody(), sendJson);
|
|
266
|
+
}
|
|
267
|
+
if (req.url === '/toolbox/actions' && req.method === 'DELETE') {
|
|
268
|
+
return toolboxRest.handleDeleteAction(await readJsonBody(), sendJson);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── /local REST ────────────────────────────────────────────────────────
|
|
272
|
+
if (req.url === '/local/init' && req.method === 'POST') {
|
|
273
|
+
return localRest.handleInit(sendJson);
|
|
274
|
+
}
|
|
275
|
+
if (req.url === '/local/credentials' && req.method === 'GET') {
|
|
276
|
+
return localRest.handleCredentials(sendJson);
|
|
277
|
+
}
|
|
278
|
+
if (req.url === '/local/credentials/resolve' && req.method === 'POST') {
|
|
279
|
+
return localRest.handleResolve(await readJsonBody(), sendJson);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── /credentials REST ─────────────────────────────────────────────────
|
|
283
|
+
if (req.url === '/credentials' && req.method === 'GET') {
|
|
284
|
+
return credentialsRest.handleList(sendJson);
|
|
285
|
+
}
|
|
286
|
+
if (req.url === '/credentials' && req.method === 'POST') {
|
|
287
|
+
return credentialsRest.handleSave(await readJsonBody(), sendJson);
|
|
288
|
+
}
|
|
289
|
+
if (req.url === '/credentials' && req.method === 'DELETE') {
|
|
290
|
+
return credentialsRest.handleDelete(await readJsonBody(), sendJson);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── /email REST ────────────────────────────────────────────────────────
|
|
294
|
+
if (req.url === '/email/inbound' && req.method === 'POST') {
|
|
295
|
+
return emailInbound.handleEmailInbound(await readJsonBody(), sendJson);
|
|
296
|
+
}
|
|
297
|
+
if (req.url === '/slack/inbound' && req.method === 'POST') {
|
|
298
|
+
return slackInbound.handleSlackInbound(await readJsonBody(), sendJson);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── /user-api-keys REST ───────────────────────────────────────────────
|
|
302
|
+
if (req.url === '/user-api-keys' && req.method === 'POST') {
|
|
303
|
+
return userApiKeysRest.handleSave(await readJsonBody(), sendJson);
|
|
304
|
+
}
|
|
305
|
+
if (req.url === '/user-api-keys' && req.method === 'DELETE') {
|
|
306
|
+
return userApiKeysRest.handleDelete(sendJson);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── /artifacts REST ────────────────────────────────────────────────────
|
|
310
|
+
if (req.url === '/artifacts/list' && req.method === 'GET') {
|
|
311
|
+
return artifactsRest.handleList(sendJson);
|
|
312
|
+
}
|
|
313
|
+
if (req.url === '/artifacts/routes' && req.method === 'GET') {
|
|
314
|
+
return artifactsRest.handleRoutes(sendJson);
|
|
315
|
+
}
|
|
316
|
+
if (req.url === '/artifacts/register' && req.method === 'POST') {
|
|
317
|
+
return artifactsRest.handleRegister(await readJsonBody(), sendJson);
|
|
318
|
+
}
|
|
319
|
+
if (req.url === '/artifacts/redeploy' && req.method === 'POST') {
|
|
320
|
+
return artifactsRest.handleRedeploy(await readJsonBody(), sendJson);
|
|
321
|
+
}
|
|
322
|
+
if (req.url === '/artifacts/start' && req.method === 'POST') {
|
|
323
|
+
return artifactsRest.handleStart(await readJsonBody(), sendJson);
|
|
324
|
+
}
|
|
325
|
+
if (req.url === '/artifacts/stop' && req.method === 'POST') {
|
|
326
|
+
return artifactsRest.handleStop(await readJsonBody(), sendJson);
|
|
327
|
+
}
|
|
328
|
+
if (req.url === '/artifacts/delete' && req.method === 'POST') {
|
|
329
|
+
return artifactsRest.handleDelete(await readJsonBody(), sendJson);
|
|
330
|
+
}
|
|
331
|
+
if (req.url === '/artifacts/connect-dns' && req.method === 'POST') {
|
|
332
|
+
return artifactsRest.handleConnectDns(await readJsonBody(), sendJson);
|
|
333
|
+
}
|
|
334
|
+
if (req.url === '/artifacts/disconnect-dns' && req.method === 'POST') {
|
|
335
|
+
return artifactsRest.handleDisconnectDns(await readJsonBody(), sendJson);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
339
|
+
res.end('Not found');
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.error('[AmalgmMCP] Unhandled error:', err);
|
|
342
|
+
if (!res.headersSent) sendJson(500, { error: 'Internal server error' });
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function listen() {
|
|
348
|
+
const httpServer = createServer();
|
|
349
|
+
|
|
350
|
+
httpServer.listen(PORT, '127.0.0.1', async () => {
|
|
351
|
+
console.log(`[AmalgmMCP] Listening on 127.0.0.1:${PORT}`);
|
|
352
|
+
console.log(`[AmalgmMCP] Storage: ${STORAGE_DIR}`);
|
|
353
|
+
console.log(
|
|
354
|
+
`[AmalgmMCP] Supabase: ${hasSupabase() ? 'connected' : 'not configured (runs are local-only)'}`,
|
|
355
|
+
);
|
|
356
|
+
// Hydrate user model prefs non-blocking (requires Supabase).
|
|
357
|
+
require('../lib/prefs').hydrateModelPreferences().catch(() => {});
|
|
358
|
+
startScheduler();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Graceful shutdown. Running tasks get aborted via SIGTERM → process exit;
|
|
362
|
+
// we don't try to individually abort them here since chat-server owns its own
|
|
363
|
+
// runtime teardown paths.
|
|
364
|
+
function shutdown() {
|
|
365
|
+
console.log('[AmalgmMCP] Shutting down...');
|
|
366
|
+
stopScheduler();
|
|
367
|
+
httpServer.close(() => process.exit(0));
|
|
368
|
+
setTimeout(() => process.exit(0), 5000);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
process.on('SIGTERM', shutdown);
|
|
372
|
+
process.on('SIGINT', shutdown);
|
|
373
|
+
|
|
374
|
+
return httpServer;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
module.exports = { createServer, listen };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server — built on @modelcontextprotocol/sdk.
|
|
3
|
+
*
|
|
4
|
+
* The SDK handles JSON-RPC framing, protocol-version negotiation, batching,
|
|
5
|
+
* and streamable-HTTP transport. We just register the two tool handlers.
|
|
6
|
+
*
|
|
7
|
+
* Each domain module exports a flat array of
|
|
8
|
+
* { name, description, inputSchema, handler }. We concatenate the built-in
|
|
9
|
+
* Amalgm tools with toolbox management tools. Custom CLI/API actions registered
|
|
10
|
+
* in the ToolboxDB are exposed dynamically through this same MCP transport.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
14
|
+
const {
|
|
15
|
+
StreamableHTTPServerTransport,
|
|
16
|
+
} = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
17
|
+
const {
|
|
18
|
+
ListToolsRequestSchema,
|
|
19
|
+
CallToolRequestSchema,
|
|
20
|
+
} = require('@modelcontextprotocol/sdk/types.js');
|
|
21
|
+
|
|
22
|
+
const { AMALGM_USER_ID } = require('../config');
|
|
23
|
+
const { hasSupabase, supabaseSelect } = require('../lib/supabase');
|
|
24
|
+
const { attachActionDescriptor } = require('../lib/tool-result');
|
|
25
|
+
const { CORE_TOOLS } = require('./core-tools');
|
|
26
|
+
const {
|
|
27
|
+
callToolboxMcpTool,
|
|
28
|
+
listToolboxMcpTools,
|
|
29
|
+
} = require('../toolbox/runner');
|
|
30
|
+
|
|
31
|
+
const TOOLS = [
|
|
32
|
+
...CORE_TOOLS,
|
|
33
|
+
...require('../toolbox/tools'),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Look up the caller's session to tag DB rows with origin metadata. Reads
|
|
38
|
+
* x-amalgm-session-id from the transport's passed-through HTTP headers.
|
|
39
|
+
*/
|
|
40
|
+
async function buildRequestContext(extra) {
|
|
41
|
+
const headers = extra?.requestInfo?.headers || {};
|
|
42
|
+
const h = headers['x-amalgm-session-id'];
|
|
43
|
+
const callerSessionId = Array.isArray(h) ? h[0] : h || null;
|
|
44
|
+
const ctx = { callerSessionId };
|
|
45
|
+
|
|
46
|
+
if (callerSessionId && hasSupabase()) {
|
|
47
|
+
const rows = await supabaseSelect(
|
|
48
|
+
'sessions',
|
|
49
|
+
`id=eq.${callerSessionId}&user_id=eq.${AMALGM_USER_ID}&select=id,title,harness,metadata`,
|
|
50
|
+
);
|
|
51
|
+
const parent = Array.isArray(rows) ? rows[0] : null;
|
|
52
|
+
if (parent) {
|
|
53
|
+
ctx.sessionMetadata = parent.metadata || {};
|
|
54
|
+
ctx.originName = parent.title || undefined;
|
|
55
|
+
ctx.originHarnessId = parent.harness || undefined;
|
|
56
|
+
ctx.originBaseHarnessId = parent.metadata?.baseHarnessId || undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return ctx;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createMcpServer() {
|
|
63
|
+
const server = new Server(
|
|
64
|
+
{ name: 'amalgm', version: '1.0.0' },
|
|
65
|
+
{ capabilities: { tools: {} } },
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
server.setRequestHandler(ListToolsRequestSchema, async (_request, extra) => {
|
|
69
|
+
const ctx = await buildRequestContext(extra);
|
|
70
|
+
return {
|
|
71
|
+
tools: [
|
|
72
|
+
...TOOLS.map(({ name, description, inputSchema }) => ({
|
|
73
|
+
name,
|
|
74
|
+
description,
|
|
75
|
+
inputSchema,
|
|
76
|
+
})),
|
|
77
|
+
...listToolboxMcpTools(ctx),
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
83
|
+
const toolName = request.params?.name;
|
|
84
|
+
const toolArgs = request.params?.arguments || {};
|
|
85
|
+
const tool = TOOLS.find((t) => t.name === toolName);
|
|
86
|
+
if (!tool) {
|
|
87
|
+
const ctx = await buildRequestContext(extra);
|
|
88
|
+
const toolboxResult = await callToolboxMcpTool(toolName, toolArgs, ctx);
|
|
89
|
+
if (toolboxResult) return attachActionDescriptor(toolboxResult, toolName, toolArgs);
|
|
90
|
+
return attachActionDescriptor({
|
|
91
|
+
content: [{ type: 'text', text: `Unknown tool: ${toolName}` }],
|
|
92
|
+
isError: true,
|
|
93
|
+
}, toolName || 'unknown', toolArgs);
|
|
94
|
+
}
|
|
95
|
+
const ctx = await buildRequestContext(extra);
|
|
96
|
+
try {
|
|
97
|
+
const result = await tool.handler(toolArgs, ctx);
|
|
98
|
+
return attachActionDescriptor(result, toolName, toolArgs);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
return attachActionDescriptor({
|
|
101
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
102
|
+
isError: true,
|
|
103
|
+
}, toolName, toolArgs);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return server;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* One stateless streamable-HTTP transport + Server pair. The transport is
|
|
112
|
+
* reusable across requests because we don't use session IDs or bidirectional
|
|
113
|
+
* notifications — every tools/list and tools/call is self-contained.
|
|
114
|
+
*/
|
|
115
|
+
async function createTransport() {
|
|
116
|
+
const server = createMcpServer();
|
|
117
|
+
const transport = new StreamableHTTPServerTransport({
|
|
118
|
+
sessionIdGenerator: undefined,
|
|
119
|
+
// Return plain JSON responses instead of opening an SSE stream. We don't
|
|
120
|
+
// emit progress notifications, so there's nothing to stream.
|
|
121
|
+
enableJsonResponse: true,
|
|
122
|
+
});
|
|
123
|
+
await server.connect(transport);
|
|
124
|
+
return transport;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { createTransport, TOOLS };
|