@amalgm/tools 0.1.0 → 0.1.1

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 CHANGED
@@ -49,6 +49,22 @@ amalgm-tools connections
49
49
  `amalgm-tools-mcp` serves Toolbox management and enabled action tools over
50
50
  newline-delimited MCP stdio.
51
51
 
52
+ ## HTTP
53
+
54
+ ```js
55
+ import { Toolbox } from '@amalgm/tools';
56
+ import { createToolboxHttpServer } from '@amalgm/tools/http';
57
+
58
+ const toolbox = new Toolbox({ stateDir: './.tools' });
59
+ const server = createToolboxHttpServer({ toolbox });
60
+ await server.listen(8083);
61
+ ```
62
+
63
+ The read adapter serves the Engine-compatible `GET /toolbox`,
64
+ `GET /toolbox/tools?id=...`, and `GET /toolbox/actions?id=...` routes over
65
+ the same live Toolbox used by CLI and MCP consumers. Mutation routes return
66
+ 405 until their legacy record fields can be represented without data loss.
67
+
52
68
  ## Tool types
53
69
 
54
70
  - `cli`: executed directly with `spawn`; a shell is never involved.
package/dist/http.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { Toolbox } from './toolbox.js';
2
+ import type { Catalog, ToolboxOptions } from './types.js';
3
+ /**
4
+ * Toolbox read API.
5
+ *
6
+ * Route contract ported from Engine
7
+ * `runtime/scripts/amalgm-mcp/server/routes/toolbox.js` and
8
+ * `runtime/scripts/amalgm-mcp/toolbox/rest.js`:
9
+ *
10
+ * - `GET /toolbox` returns the whole catalog;
11
+ * - `GET /toolbox/tools?id=...` returns one tool and, by default, its actions;
12
+ * - `GET /toolbox/actions?id=...` returns one action;
13
+ * - domain errors are JSON with status 400; unknown routes are 404.
14
+ *
15
+ * Mutation routes remain deliberately absent until their engine record fields
16
+ * are represented losslessly by the SDK. Returning 405 is honest; translating
17
+ * them approximately would turn a visible parity gap into data loss.
18
+ */
19
+ type Json = Record<string, unknown>;
20
+ interface ToolboxHttpServer {
21
+ listen(port?: number, host?: string): Promise<{
22
+ port: number;
23
+ }>;
24
+ close(): Promise<void>;
25
+ }
26
+ declare function catalogDocument(catalog: Catalog): Json;
27
+ declare function createToolboxHttpServer(options?: ToolboxOptions & {
28
+ toolbox?: Toolbox;
29
+ }): ToolboxHttpServer;
30
+ export { catalogDocument, createToolboxHttpServer };
31
+ export type { ToolboxHttpServer };
package/dist/http.js ADDED
@@ -0,0 +1,115 @@
1
+ import { createServer } from 'node:http';
2
+ import { toolboxIndexDocument } from './artifacts.js';
3
+ import { Toolbox } from './toolbox.js';
4
+ function sourceWithoutType(source) {
5
+ const { type: _type, ...value } = source;
6
+ return value;
7
+ }
8
+ function toolDocument(tool) {
9
+ return {
10
+ ...tool,
11
+ type: tool.source.type,
12
+ source: sourceWithoutType(tool.source),
13
+ };
14
+ }
15
+ function actionDocument(action) {
16
+ return { ...action };
17
+ }
18
+ function catalogDocument(catalog) {
19
+ return toolboxIndexDocument(Object.fromEntries(catalog.tools.map((tool) => [tool.id, toolDocument(tool)])), Object.fromEntries(catalog.actions.map((action) => [action.id, actionDocument(action)])));
20
+ }
21
+ async function readBody(req) {
22
+ const chunks = [];
23
+ for await (const chunk of req)
24
+ chunks.push(Buffer.from(chunk));
25
+ if (chunks.length === 0)
26
+ return {};
27
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
28
+ }
29
+ function createToolboxHttpServer(options = {}) {
30
+ const toolbox = options.toolbox ?? new Toolbox(options);
31
+ const ownsToolbox = options.toolbox === undefined;
32
+ const server = createServer((req, res) => {
33
+ void (async () => {
34
+ const method = req.method ?? 'GET';
35
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
36
+ const send = (status, body) => {
37
+ res.writeHead(status, { 'content-type': 'application/json' });
38
+ res.end(JSON.stringify(body));
39
+ };
40
+ try {
41
+ if (method !== 'GET') {
42
+ if (url.pathname.startsWith('/toolbox')) {
43
+ await readBody(req);
44
+ send(405, { error: 'method not allowed' });
45
+ return;
46
+ }
47
+ send(404, { error: 'not found' });
48
+ return;
49
+ }
50
+ if (url.pathname === '/toolbox') {
51
+ send(200, catalogDocument(toolbox.catalog()));
52
+ return;
53
+ }
54
+ if (url.pathname === '/toolbox/tools') {
55
+ const id = url.searchParams.get('id') || url.searchParams.get('toolId') || '';
56
+ if (!id)
57
+ throw new Error('id is required');
58
+ const found = toolbox.get(id);
59
+ if (!found)
60
+ throw new Error(`Unknown tool: ${id}`);
61
+ const includeActions = url.searchParams.get('include_actions') !== 'false'
62
+ && url.searchParams.get('includeActions') !== 'false';
63
+ send(200, {
64
+ tool: toolDocument(found.tool),
65
+ ...(includeActions
66
+ ? { actions: found.actions.map(actionDocument) }
67
+ : {}),
68
+ });
69
+ return;
70
+ }
71
+ if (url.pathname === '/toolbox/actions') {
72
+ const id = url.searchParams.get('id') || url.searchParams.get('actionId') || '';
73
+ if (!id)
74
+ throw new Error('id is required');
75
+ const action = toolbox.action(id);
76
+ if (!action)
77
+ throw new Error(`Unknown tool action: ${id}`);
78
+ send(200, { action: actionDocument(action) });
79
+ return;
80
+ }
81
+ send(404, { error: 'not found' });
82
+ }
83
+ catch (error) {
84
+ send(400, { error: error instanceof Error ? error.message : 'Failed to read Toolbox' });
85
+ }
86
+ })();
87
+ });
88
+ return {
89
+ listen(port = 0, host = '127.0.0.1') {
90
+ return new Promise((resolve, reject) => {
91
+ server.once('error', reject);
92
+ server.listen(port, host, () => {
93
+ server.off('error', reject);
94
+ const address = server.address();
95
+ if (!address || typeof address === 'string')
96
+ throw new Error('Toolbox HTTP server has no port');
97
+ resolve({ port: address.port });
98
+ });
99
+ });
100
+ },
101
+ close() {
102
+ return new Promise((resolve, reject) => {
103
+ server.close((error) => {
104
+ if (ownsToolbox)
105
+ toolbox.close();
106
+ if (error)
107
+ reject(error);
108
+ else
109
+ resolve();
110
+ });
111
+ });
112
+ },
113
+ };
114
+ }
115
+ export { catalogDocument, createToolboxHttpServer };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { ArtifactFiles } from './artifact-files.js';
2
2
  export { LEGACY_TOOLBOX_BACKUP_FILE_NAME, LEGACY_TOOLBOX_FILE_NAME, TOOL_ARTIFACT_KIND, TOOL_ARTIFACT_SCHEMA_VERSION, TOOLBOX_INDEX_FILE_NAME, artifactDocument, catalogIndexDocument, isToolArtifactDocument, legacyMigrationPlan, safeToolId, toolboxIndexDocument, userArtifactFileName, } from './artifacts.js';
3
3
  export type { ArtifactFileWrite, LegacyMigrationPlan, ToolArtifactDocument, ToolboxIndexDocument, } from './artifacts.js';
4
4
  export { defineTool, normalizeDefinition } from './definition.js';
5
+ export { catalogDocument, createToolboxHttpServer } from './http.js';
6
+ export type { ToolboxHttpServer } from './http.js';
5
7
  export { actionId, actionName, id, mcpName } from './ids.js';
6
8
  export { actionTools, callMcpTool, createMcpTools, findMcpTool, managementTools, } from './mcp.js';
7
9
  export { createMcpServer } from './mcp-server.js';
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { ArtifactFiles } from './artifact-files.js';
2
2
  export { LEGACY_TOOLBOX_BACKUP_FILE_NAME, LEGACY_TOOLBOX_FILE_NAME, TOOL_ARTIFACT_KIND, TOOL_ARTIFACT_SCHEMA_VERSION, TOOLBOX_INDEX_FILE_NAME, artifactDocument, catalogIndexDocument, isToolArtifactDocument, legacyMigrationPlan, safeToolId, toolboxIndexDocument, userArtifactFileName, } from './artifacts.js';
3
3
  export { defineTool, normalizeDefinition } from './definition.js';
4
+ export { catalogDocument, createToolboxHttpServer } from './http.js';
4
5
  export { actionId, actionName, id, mcpName } from './ids.js';
5
6
  export { actionTools, callMcpTool, createMcpTools, findMcpTool, managementTools, } from './mcp.js';
6
7
  export { createMcpServer } from './mcp-server.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amalgm/tools",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Local-first tool definitions, Toolbox registry, and agent execution surfaces.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -22,6 +22,10 @@
22
22
  "./mcp": {
23
23
  "types": "./dist/mcp.d.ts",
24
24
  "default": "./dist/mcp.js"
25
+ },
26
+ "./http": {
27
+ "types": "./dist/http.d.ts",
28
+ "default": "./dist/http.js"
25
29
  }
26
30
  },
27
31
  "bin": {