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,13 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
3
|
+
const { toolboxMcpToolName } = require('../toolbox/names');
|
|
4
|
+
const {
|
|
5
|
+
defaultToolboxService: toolboxService,
|
|
6
|
+
} = require('../toolbox/service');
|
|
5
7
|
|
|
6
|
-
function
|
|
8
|
+
function firstPartyToolGroups() {
|
|
7
9
|
return [
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
{
|
|
11
|
+
id: 'amalgm.notifications',
|
|
12
|
+
kind: 'first_party',
|
|
13
|
+
tools: require('../notify'),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'amalgm.agents',
|
|
17
|
+
kind: 'first_party',
|
|
18
|
+
tools: require('../agents/tools'),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'amalgm.apps',
|
|
22
|
+
kind: 'first_party',
|
|
23
|
+
tools: require('../apps/tools'),
|
|
24
|
+
},
|
|
11
25
|
];
|
|
12
26
|
}
|
|
13
27
|
|
|
@@ -19,35 +33,27 @@ function normalizeRef(value) {
|
|
|
19
33
|
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
20
34
|
}
|
|
21
35
|
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
function resolveFirstPartyToolAction(toolId, actionName) {
|
|
37
|
+
const normalizedToolId = String(toolId || '').trim();
|
|
38
|
+
const normalizedActionName = String(actionName || '').trim();
|
|
39
|
+
const group = firstPartyToolGroups().find((candidate) => candidate.id === normalizedToolId);
|
|
40
|
+
if (!group) return null;
|
|
41
|
+
const tool = group.tools.find((candidate) => (
|
|
42
|
+
candidate.name === normalizedActionName
|
|
43
|
+
|| candidate.name === normalizedActionName.replace(/\./g, '_')
|
|
44
|
+
));
|
|
45
|
+
if (!tool) return null;
|
|
46
|
+
return {
|
|
47
|
+
kind: 'first_party',
|
|
48
|
+
ref: actionKey(group.id, tool.name),
|
|
49
|
+
toolId: group.id,
|
|
50
|
+
actionName: tool.name,
|
|
51
|
+
tool,
|
|
52
|
+
};
|
|
35
53
|
}
|
|
36
54
|
|
|
37
55
|
function resolveToolboxAction(toolId, actionName) {
|
|
38
|
-
|
|
39
|
-
const normalizedActionName = String(actionName || '').trim();
|
|
40
|
-
const toolbox = readToolbox();
|
|
41
|
-
const action = Object.values(toolbox.toolActions || {}).find((candidate) => {
|
|
42
|
-
if (candidate.toolId !== normalizedToolId) return false;
|
|
43
|
-
return candidate.name === normalizedActionName
|
|
44
|
-
|| candidate.id === normalizedActionName
|
|
45
|
-
|| candidate.id === `${normalizedToolId}.${normalizedActionName}`
|
|
46
|
-
|| candidate.id === `${normalizedToolId}:${normalizedActionName}`;
|
|
47
|
-
});
|
|
48
|
-
if (!action || action.status !== 'enabled') return null;
|
|
49
|
-
const tool = toolbox.tools?.[action.toolId];
|
|
50
|
-
return isRunnableToolboxTool(tool) ? { tool, action } : null;
|
|
56
|
+
return toolboxService.resolveRunnableAction(toolId, actionName);
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
function resolveWorkflowToolAction(toolId, actionName) {
|
|
@@ -62,17 +68,9 @@ function resolveWorkflowToolAction(toolId, actionName) {
|
|
|
62
68
|
description: 'Fetch a URL from a workflow; requires allowlist.network=true at runtime.',
|
|
63
69
|
};
|
|
64
70
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
kind: 'amalgm',
|
|
70
|
-
ref: actionKey('amalgm', tool.name),
|
|
71
|
-
toolId: 'amalgm',
|
|
72
|
-
actionName: tool.name,
|
|
73
|
-
tool,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
71
|
+
const firstParty = resolveFirstPartyToolAction(normalizedToolId, normalizedActionName);
|
|
72
|
+
if (firstParty) return firstParty;
|
|
73
|
+
|
|
76
74
|
const resolved = resolveToolboxAction(normalizedToolId, normalizedActionName);
|
|
77
75
|
if (!resolved) return null;
|
|
78
76
|
return {
|
|
@@ -86,7 +84,6 @@ function resolveWorkflowToolAction(toolId, actionName) {
|
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
function workflowActionSummaries() {
|
|
89
|
-
const toolbox = readToolbox();
|
|
90
87
|
const actions = [{
|
|
91
88
|
ref: 'http.fetch',
|
|
92
89
|
id: 'http.fetch',
|
|
@@ -96,20 +93,20 @@ function workflowActionSummaries() {
|
|
|
96
93
|
description: 'Fetch a URL; requires allowlist.network=true.',
|
|
97
94
|
}];
|
|
98
95
|
|
|
99
|
-
for (const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
96
|
+
for (const group of firstPartyToolGroups()) {
|
|
97
|
+
for (const tool of group.tools) {
|
|
98
|
+
actions.push({
|
|
99
|
+
ref: actionKey(group.id, tool.name),
|
|
100
|
+
id: actionKey(group.id, tool.name),
|
|
101
|
+
toolId: group.id,
|
|
102
|
+
actionName: tool.name,
|
|
103
|
+
kind: 'first_party',
|
|
104
|
+
description: tool.description || '',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
for (const action of
|
|
111
|
-
const tool = toolbox.tools?.[action.toolId];
|
|
112
|
-
if (!isRunnableToolboxTool(tool) || action.status !== 'enabled') continue;
|
|
109
|
+
for (const { tool, action } of toolboxService.listRunnableActions()) {
|
|
113
110
|
actions.push({
|
|
114
111
|
ref: actionKey(tool.id, action.name),
|
|
115
112
|
id: action.id,
|
|
@@ -168,7 +165,7 @@ function validateWorkflowToolActions(ir) {
|
|
|
168
165
|
}
|
|
169
166
|
|
|
170
167
|
module.exports = {
|
|
171
|
-
|
|
168
|
+
resolveFirstPartyToolAction,
|
|
172
169
|
resolveToolboxAction,
|
|
173
170
|
resolveWorkflowToolAction,
|
|
174
171
|
suggestWorkflowToolActions,
|
|
@@ -24,10 +24,10 @@ const WORKFLOW_DESCRIPTION = [
|
|
|
24
24
|
'The script is the workflow. A trigger decides when it runs; cells decide what happens.',
|
|
25
25
|
'Workflow `trigger` refs are `source.event` strings such as `github.push`; `github.*` means any GitHub event; `*.push` means a push from any source; `*.*` means any source and event.',
|
|
26
26
|
'Cells can call `code(name, fn)`, gated local `cli(name, config)`, gated `http(name, config)`, and `tool(name, "tool.action", args)`.',
|
|
27
|
-
'Agents are tools too: use `agent(name, agentIdOrName, { prompt, run_in_background: true })` or `tool(name, "amalgm.talk_to_agent", args)`.',
|
|
27
|
+
'Agents are tools too: use `agent(name, agentIdOrName, { prompt, run_in_background: true })` or `tool(name, "amalgm.agents.talk_to_agent", args)`.',
|
|
28
28
|
'Every function receives one ctx object: `{ automation, trigger, workflow, event, payload, headers, previous, outputs, cells, secrets, stop }`.',
|
|
29
29
|
'Cell outputs are available as `ctx.cells.cellName.output`, `ctx.cells.cellName.field` for object outputs, `ctx.outputs.cellName`, and top-level `ctx.cellName`.',
|
|
30
|
-
'Example: `tool("notify", "amalgm.notify_user", ({ cells }) => ({ message: cells.greet.output.message }))`.',
|
|
30
|
+
'Example: `tool("notify", "amalgm.notifications.notify_user", ({ cells }) => ({ message: cells.greet.output.message }))`.',
|
|
31
31
|
].join('\n\n');
|
|
32
32
|
|
|
33
33
|
function compactAutomation(automation, includeWorkflowText = true) {
|
|
@@ -180,12 +180,12 @@ module.exports = [
|
|
|
180
180
|
{
|
|
181
181
|
name: 'automations_list_actions',
|
|
182
182
|
description:
|
|
183
|
-
'List workflow-callable tool actions for automation scripts. Use these refs in workflow cells like `tool("name", "tool.ref", args)`. This includes built-in `http.fetch`,
|
|
183
|
+
'List workflow-callable tool actions for automation scripts. Use these refs in workflow cells like `tool("name", "tool.ref", args)`. This includes built-in `http.fetch`, first-party Toolbox actions such as `amalgm.notifications.notify_user`, and runnable Toolbox CLI/API actions.',
|
|
184
184
|
inputSchema: {
|
|
185
185
|
type: 'object',
|
|
186
186
|
properties: {
|
|
187
187
|
query: { type: 'string', description: 'Optional case-insensitive filter across ref, id, name, and description.' },
|
|
188
|
-
kind: { type: 'string', enum: ['builtin', '
|
|
188
|
+
kind: { type: 'string', enum: ['builtin', 'first_party', 'toolbox'], description: 'Optional action kind filter.' },
|
|
189
189
|
},
|
|
190
190
|
},
|
|
191
191
|
async handler({ query, kind } = {}) {
|
|
@@ -41,6 +41,24 @@ function asString(value) {
|
|
|
41
41
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function normalizeStringList(values) {
|
|
45
|
+
if (!Array.isArray(values)) return [];
|
|
46
|
+
return [...new Set(values
|
|
47
|
+
.filter((value) => typeof value === 'string')
|
|
48
|
+
.map((value) => value.trim())
|
|
49
|
+
.filter(Boolean))];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sessionToolConfig(metadata) {
|
|
53
|
+
const tools = metadata?.tools && typeof metadata.tools === 'object' && !Array.isArray(metadata.tools)
|
|
54
|
+
? metadata.tools
|
|
55
|
+
: {};
|
|
56
|
+
return {
|
|
57
|
+
mode: tools.mode === 'selected' ? 'selected' : 'all',
|
|
58
|
+
toolIds: normalizeStringList(tools.selectedToolIds),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
function normalizeSubject(subject) {
|
|
45
63
|
const trimmed = asString(subject) || 'Amalgm reply';
|
|
46
64
|
return /^re:/i.test(trimmed) ? trimmed : `Re: ${trimmed}`;
|
|
@@ -124,6 +142,7 @@ async function handleEmailInbound(body, sendJson) {
|
|
|
124
142
|
const defaultAuthMethod = asString(metadata.authMethod) || resolveDefaultAuthMethod(baseHarnessId);
|
|
125
143
|
const cwd = asString(metadata.cwd) || DEFAULT_CWD;
|
|
126
144
|
const computerId = asString(metadata.computerId);
|
|
145
|
+
const toolConfig = sessionToolConfig(metadata);
|
|
127
146
|
|
|
128
147
|
const normalizedChatInput = normalizeChatInput({
|
|
129
148
|
parts: [{ type: 'text', text: prompt }],
|
|
@@ -136,6 +155,8 @@ async function handleEmailInbound(body, sendJson) {
|
|
|
136
155
|
},
|
|
137
156
|
tools: {
|
|
138
157
|
mcpAppIds: Array.isArray(metadata.mcpAppIds) ? metadata.mcpAppIds : [],
|
|
158
|
+
mode: toolConfig.mode,
|
|
159
|
+
toolIds: toolConfig.toolIds,
|
|
139
160
|
},
|
|
140
161
|
execution: {
|
|
141
162
|
cwd,
|
|
@@ -28,7 +28,7 @@ function engineNpmPublishWorkflow(repoPath) {
|
|
|
28
28
|
allowlist: {
|
|
29
29
|
localCompute: true,
|
|
30
30
|
secrets: ["NODE_AUTH_TOKEN"],
|
|
31
|
-
actions: ["amalgm.notify_user"]
|
|
31
|
+
actions: ["amalgm.notifications.notify_user"]
|
|
32
32
|
},
|
|
33
33
|
|
|
34
34
|
limits: {
|
|
@@ -116,7 +116,7 @@ function engineNpmPublishWorkflow(repoPath) {
|
|
|
116
116
|
timeoutMs: 600000
|
|
117
117
|
}),
|
|
118
118
|
|
|
119
|
-
tool("notify_user", "amalgm
|
|
119
|
+
tool("notify_user", "amalgm.notifications.notify_user", {
|
|
120
120
|
subject: "amalgm npm publish complete",
|
|
121
121
|
level: "success",
|
|
122
122
|
message: ({ outputs }) => {
|
|
@@ -153,7 +153,7 @@ function internalWorkflowSeeds() {
|
|
|
153
153
|
allowlist: {
|
|
154
154
|
localCompute: true,
|
|
155
155
|
secrets: ['NODE_AUTH_TOKEN'],
|
|
156
|
-
actions: ['amalgm.notify_user'],
|
|
156
|
+
actions: ['amalgm.notifications.notify_user'],
|
|
157
157
|
},
|
|
158
158
|
limits: {
|
|
159
159
|
maxConcurrentRuns: 1,
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
* agents/ agents CRUD + talk_to_agent
|
|
20
20
|
* browser/ visible Electron browser tools + agent-browser fallback
|
|
21
21
|
* notify/ notify_user (email via proxy)
|
|
22
|
-
* server/
|
|
22
|
+
* server/ HTTP service boundaries + MCP adapter plumbing
|
|
23
|
+
* services/ optional standalone process entrypoints for
|
|
24
|
+
* control-plane, MCP adapter, and worker
|
|
23
25
|
*/
|
|
24
26
|
|
|
25
27
|
async function ensureProxyToken() {
|
|
@@ -365,6 +365,20 @@ function reasoningEffortForSelection(harnessId, modelId, normalizedEffort) {
|
|
|
365
365
|
return normalizeReasoningEffort(settings.effort || settings.reasoningEffort || settings.reasoning) || 'xhigh';
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
function getModelSettingsForSelection(harnessId, modelId) {
|
|
369
|
+
const normalizedModelId = normalizeModelSelection(harnessId, modelId).modelId || modelId;
|
|
370
|
+
const settingsKey = harnessId === 'codex'
|
|
371
|
+
? codexModelSettingsKey(normalizedModelId)
|
|
372
|
+
: normalizedModelId;
|
|
373
|
+
const settings =
|
|
374
|
+
(settingsKey && _modelSettings[settingsKey]) ||
|
|
375
|
+
(modelId && _modelSettings[modelId]) ||
|
|
376
|
+
null;
|
|
377
|
+
return isPlainObject(settings) && Object.keys(settings).length > 0
|
|
378
|
+
? { ...settings }
|
|
379
|
+
: null;
|
|
380
|
+
}
|
|
381
|
+
|
|
368
382
|
function preferredWorkspaceCwd() {
|
|
369
383
|
try {
|
|
370
384
|
const { listWorkspaces } = require('../workspace/store');
|
|
@@ -635,6 +649,7 @@ module.exports = {
|
|
|
635
649
|
writeUserPreferences,
|
|
636
650
|
updateUserPreferences,
|
|
637
651
|
getSelectedModel,
|
|
652
|
+
getModelSettingsForSelection,
|
|
638
653
|
getPreferredAuthMethod,
|
|
639
654
|
resolveModelSelection,
|
|
640
655
|
};
|
|
@@ -8,7 +8,9 @@ const os = require('os');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const { AMALGM_DIR } = require('../config');
|
|
10
10
|
const { appendStateEvent } = require('../state/events');
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
defaultToolboxService: toolboxService,
|
|
13
|
+
} = require('../toolbox/service');
|
|
12
14
|
|
|
13
15
|
const CONNECTIONS_FILE = path.join(AMALGM_DIR, 'mcp-connections.json');
|
|
14
16
|
const CLAUDE_FILE = path.join(os.homedir(), '.claude.json');
|
|
@@ -139,7 +141,7 @@ function mirrorConnectionToToolbox(appId, conn) {
|
|
|
139
141
|
url: conn?.instanceUrl || conn?.config?.url,
|
|
140
142
|
};
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
toolboxService.registerTool({
|
|
143
145
|
id: app?.id || appId,
|
|
144
146
|
name: app?.name || appId,
|
|
145
147
|
type: 'mcp',
|
|
@@ -165,7 +167,7 @@ function mirrorConnectionToToolbox(appId, conn) {
|
|
|
165
167
|
|
|
166
168
|
function removeConnectionFromToolbox(appId) {
|
|
167
169
|
try {
|
|
168
|
-
deleteTool(appId);
|
|
170
|
+
toolboxService.deleteTool(appId);
|
|
169
171
|
} catch {
|
|
170
172
|
// The toolbox mirror is best-effort; MCP connection removal should still
|
|
171
173
|
// succeed even if a future system connector cannot be deleted.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
startAutomationScheduler,
|
|
5
|
+
stopAutomationScheduler,
|
|
6
|
+
} = require('./automations/scheduler');
|
|
7
|
+
|
|
8
|
+
function createRuntimeWorker(scheduler = {
|
|
9
|
+
startAutomationScheduler,
|
|
10
|
+
stopAutomationScheduler,
|
|
11
|
+
}) {
|
|
12
|
+
let running = false;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
get running() {
|
|
16
|
+
return running;
|
|
17
|
+
},
|
|
18
|
+
start() {
|
|
19
|
+
if (running) return false;
|
|
20
|
+
scheduler.startAutomationScheduler();
|
|
21
|
+
running = true;
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
stop() {
|
|
25
|
+
if (!running) return false;
|
|
26
|
+
scheduler.stopAutomationScheduler();
|
|
27
|
+
running = false;
|
|
28
|
+
return true;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const defaultRuntimeWorker = createRuntimeWorker();
|
|
34
|
+
|
|
35
|
+
function startRuntimeWorker() {
|
|
36
|
+
return defaultRuntimeWorker.start();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function stopRuntimeWorker() {
|
|
40
|
+
return defaultRuntimeWorker.stop();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
createRuntimeWorker,
|
|
45
|
+
startRuntimeWorker,
|
|
46
|
+
stopRuntimeWorker,
|
|
47
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createRuntimeHttpServer } = require('./http-service');
|
|
4
|
+
const { createControlPlaneHandler } = require('./control-plane-service');
|
|
5
|
+
const { createMcpHttpHandler } = require('./mcp-http-service');
|
|
6
|
+
|
|
7
|
+
function createCombinedHandler(options = {}) {
|
|
8
|
+
const mcpHandler = options.mcpHandler || createMcpHttpHandler(options);
|
|
9
|
+
const controlPlaneHandler = options.controlPlaneHandler || createControlPlaneHandler(options);
|
|
10
|
+
|
|
11
|
+
return async function handleCombinedRequest(ctx) {
|
|
12
|
+
if (await mcpHandler(ctx)) return true;
|
|
13
|
+
return controlPlaneHandler(ctx);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createCombinedServer(options = {}) {
|
|
18
|
+
return createRuntimeHttpServer(createCombinedHandler(options), {
|
|
19
|
+
serviceName: options.serviceName || 'AmalgmMCP',
|
|
20
|
+
exposeHeaders: options.exposeHeaders,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
createCombinedHandler,
|
|
26
|
+
createCombinedServer,
|
|
27
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createRuntimeHttpServer } = require('./http-service');
|
|
4
|
+
const { createLocalServiceRouter } = require('./local-service-router');
|
|
5
|
+
|
|
6
|
+
function createControlPlaneHandler(options = {}) {
|
|
7
|
+
const serviceRouter = options.serviceRouter || createLocalServiceRouter(options.routeGroups);
|
|
8
|
+
|
|
9
|
+
return async function handleControlPlaneRequest(ctx) {
|
|
10
|
+
return serviceRouter.handle(ctx);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function createControlPlaneServer(options = {}) {
|
|
15
|
+
return createRuntimeHttpServer(createControlPlaneHandler(options), {
|
|
16
|
+
serviceName: options.serviceName || 'AmalgmControlPlane',
|
|
17
|
+
exposeHeaders: options.exposeHeaders,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
createControlPlaneHandler,
|
|
23
|
+
createControlPlaneServer,
|
|
24
|
+
};
|
|
@@ -8,12 +8,57 @@
|
|
|
8
8
|
* circular dependency on the MCP server.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
function group(id, name, description, tools) {
|
|
12
|
+
return {
|
|
13
|
+
id,
|
|
14
|
+
name,
|
|
15
|
+
type: 'mcp',
|
|
16
|
+
owner: 'amalgm',
|
|
17
|
+
origin: 'system',
|
|
18
|
+
status: 'enabled',
|
|
19
|
+
description,
|
|
20
|
+
tools: tools.map((tool) => ({
|
|
21
|
+
...tool,
|
|
22
|
+
toolboxToolId: id,
|
|
23
|
+
toolboxActionId: `${id}.${tool.name}`,
|
|
24
|
+
toolboxActionName: tool.name,
|
|
25
|
+
})),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const CORE_TOOL_GROUPS = [
|
|
30
|
+
group(
|
|
31
|
+
'amalgm.automations',
|
|
32
|
+
'Amalgm Automations',
|
|
33
|
+
'Create, inspect, and run scheduled or event-driven automations.',
|
|
34
|
+
require('../automations/tools'),
|
|
35
|
+
),
|
|
36
|
+
group(
|
|
37
|
+
'amalgm.notifications',
|
|
38
|
+
'Amalgm Notifications',
|
|
39
|
+
'Notify the user about important results, completions, and failures.',
|
|
40
|
+
require('../notify'),
|
|
41
|
+
),
|
|
42
|
+
group(
|
|
43
|
+
'amalgm.agents',
|
|
44
|
+
'Amalgm Agents',
|
|
45
|
+
'Discover and talk to other agents on the platform.',
|
|
46
|
+
require('../agents/tools'),
|
|
47
|
+
),
|
|
48
|
+
group(
|
|
49
|
+
'amalgm.apps',
|
|
50
|
+
'Amalgm Apps',
|
|
51
|
+
'Register, run, and route local-first Amalgm apps.',
|
|
52
|
+
require('../apps/tools'),
|
|
53
|
+
),
|
|
54
|
+
group(
|
|
55
|
+
'amalgm.browser',
|
|
56
|
+
'Amalgm Browser',
|
|
57
|
+
"Use Amalgm's visible browser session for navigation, interaction, and screenshots.",
|
|
58
|
+
require('../browser/tools'),
|
|
59
|
+
),
|
|
17
60
|
];
|
|
18
61
|
|
|
19
|
-
|
|
62
|
+
const CORE_TOOLS = CORE_TOOL_GROUPS.flatMap((toolGroup) => toolGroup.tools);
|
|
63
|
+
|
|
64
|
+
module.exports = { CORE_TOOL_GROUPS, CORE_TOOLS };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function requestPathname(req) {
|
|
4
|
+
return new URL(req.url || '/', 'http://127.0.0.1').pathname;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function requestQuery(req) {
|
|
8
|
+
return Object.fromEntries(
|
|
9
|
+
new URL(req.url || '/', 'http://127.0.0.1').searchParams.entries(),
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function readJsonBody(req) {
|
|
14
|
+
const chunks = [];
|
|
15
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
16
|
+
if (chunks.length === 0) return {};
|
|
17
|
+
return JSON.parse(Buffer.concat(chunks).toString());
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createHttpContext(req, res) {
|
|
21
|
+
return {
|
|
22
|
+
req,
|
|
23
|
+
res,
|
|
24
|
+
method: req.method || 'GET',
|
|
25
|
+
pathname: requestPathname(req),
|
|
26
|
+
getQuery: () => requestQuery(req),
|
|
27
|
+
readJsonBody: () => readJsonBody(req),
|
|
28
|
+
sendJson(statusCode, data) {
|
|
29
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
30
|
+
res.end(JSON.stringify(data));
|
|
31
|
+
},
|
|
32
|
+
notFound() {
|
|
33
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
34
|
+
res.end('Not found');
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
createHttpContext,
|
|
41
|
+
readJsonBody,
|
|
42
|
+
requestPathname,
|
|
43
|
+
requestQuery,
|
|
44
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const { authorizeRuntimeHttp } = require('../../runtime-auth');
|
|
5
|
+
const { createHttpContext } = require('./http-context');
|
|
6
|
+
|
|
7
|
+
function createRuntimeHttpHandler(routeHandler, options = {}) {
|
|
8
|
+
const serviceName = options.serviceName || 'AmalgmMCP';
|
|
9
|
+
const exposeHeaders = options.exposeHeaders === undefined
|
|
10
|
+
? 'Mcp-Session-Id'
|
|
11
|
+
: options.exposeHeaders;
|
|
12
|
+
|
|
13
|
+
return async (req, res) => {
|
|
14
|
+
if (!authorizeRuntimeHttp(req, res, { exposeHeaders })) return;
|
|
15
|
+
const ctx = createHttpContext(req, res);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
if (await routeHandler(ctx)) return;
|
|
19
|
+
ctx.notFound();
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error(`[${serviceName}] Unhandled error:`, err);
|
|
22
|
+
if (!res.headersSent) ctx.sendJson(500, { error: 'Internal server error' });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createRuntimeHttpServer(routeHandler, options = {}) {
|
|
28
|
+
return http.createServer(createRuntimeHttpHandler(routeHandler, options));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
createRuntimeHttpHandler,
|
|
33
|
+
createRuntimeHttpServer,
|
|
34
|
+
};
|