@fnet/cli 0.133.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fbin/index.js +14 -1
- package/dist/fnet/index.0jcm9pn5.js +1 -0
- package/dist/fnet/index.2084z2ed.js +2 -0
- package/dist/fnet/index.33f1ggpr.js +1 -0
- package/dist/fnet/index.3exge2js.js +1 -0
- package/dist/fnet/index.3kfx538h.js +1 -0
- package/dist/fnet/index.490y87nc.js +1 -0
- package/dist/fnet/index.4g9yezkq.js +1 -0
- package/dist/fnet/index.4jkat7r4.js +1 -0
- package/dist/fnet/index.7crx8ky1.js +1 -0
- package/dist/fnet/index.7vw06nrn.js +1 -0
- package/dist/fnet/index.9567fa9x.js +1 -0
- package/dist/fnet/index.gh75wt1m.js +1 -0
- package/dist/fnet/index.hzsfswvp.js +1 -0
- package/dist/fnet/index.jgpc3grc.js +1 -0
- package/dist/fnet/index.js +19 -1
- package/dist/fnet/index.p0zb7e1b.js +1 -0
- package/dist/fnet/index.r19p3bpa.js +2 -0
- package/dist/fnet/index.r82rtnmz.js +1 -0
- package/dist/fnet/index.s662t98v.js +1 -0
- package/dist/fnet/index.w74dpnpn.js +1 -0
- package/dist/fnet/index.xeaw5xa9.js +1 -0
- package/dist/fnet/index.zm4kesg6.js +1 -0
- package/dist/fnode/index.05n3mvs9.js +2 -0
- package/dist/fnode/index.2hc9tbyx.js +1 -0
- package/dist/fnode/index.2pnjg6dc.js +1 -0
- package/dist/fnode/index.9qgtmxq3.js +2 -0
- package/dist/fnode/index.b1q7y05p.js +1 -0
- package/dist/fnode/index.bhapgrs7.js +1 -0
- package/dist/fnode/index.cvxrf34y.js +2 -0
- package/dist/fnode/index.dp9wyahp.js +1 -0
- package/dist/fnode/index.eqxmcpdc.js +1 -0
- package/dist/fnode/index.f2tb0x3t.js +1 -0
- package/dist/fnode/index.fbzv6wwf.js +1 -0
- package/dist/fnode/index.gazd9raq.js +1 -0
- package/dist/fnode/index.hv4s25f0.js +3 -0
- package/dist/fnode/index.j5z7dtsx.js +1 -0
- package/dist/fnode/index.js +9 -1
- package/dist/fnode/index.kb4e4bxf.js +1 -0
- package/dist/fnode/index.qn0schqp.js +1 -0
- package/dist/fnode/index.rht29phd.js +1 -0
- package/dist/fnode/index.rzsfmek6.js +3 -0
- package/dist/fnode/index.s0nk6cv8.js +4 -0
- package/dist/fnode/index.s66v6wt4.js +1 -0
- package/dist/fnode/index.sv7v0y60.js +1 -0
- package/dist/fnode/index.tgkhgnrp.js +1 -0
- package/dist/fnode/index.vq706f75.js +1 -0
- package/dist/fnode/index.y8pvdcny.js +1 -0
- package/dist/fnode/index.z4vz93ww.js +1 -0
- package/dist/frun/index.js +1 -1
- package/dist/fservice/index.js +18 -1
- package/dist/fservice/index.q01yvaz0.js +2 -0
- package/package.json +21 -18
- package/readme.md +62 -15
- package/template/fnet/core/object.js +10 -10
- package/template/fnet/node/package.json.njk +6 -3
- package/template/fnet/node/src/cli/index.js.njk +5 -315
- package/template/fnet/node/src/cli/index.js.v1.njk +318 -0
- package/template/fnet/node/src/cli/v2/core/args-parser.njk +10 -0
- package/template/fnet/node/src/cli/v2/core/imports.njk +31 -0
- package/template/fnet/node/src/cli/v2/core/run-wrapper.njk +25 -0
- package/template/fnet/node/src/cli/v2/index.js.njk +184 -0
- package/template/fnet/node/src/cli/v2/modes/default/extend.njk +11 -0
- package/template/fnet/node/src/cli/v2/modes/default/standard.njk +20 -0
- package/template/fnet/node/src/cli/v2/modes/http/builtin.njk +66 -0
- package/template/fnet/node/src/cli/v2/modes/http/imports.njk +9 -0
- package/template/fnet/node/src/cli/v2/modes/http/index.njk +12 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/imports.njk +33 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/index.njk +30 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/server-setup.njk +15 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/tool-handlers.njk +46 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/transport-http.njk +222 -0
- package/template/fnet/node/src/cli/v2/modes/mcp/transport-stdio.njk +9 -0
- package/template/fnet/node/src/cli/v2/modes/pipeline/imports.njk +9 -0
- package/template/fnet/node/src/cli/v2/modes/pipeline/index.njk +113 -0
- package/template/fnet/node/src/cli/v2/modes/webhook/imports.njk +9 -0
- package/template/fnet/node/src/cli/v2/modes/webhook/index.njk +127 -0
- package/template/fnet/node/src/cli/v2/modes/websocket/imports.njk +15 -0
- package/template/fnet/node/src/cli/v2/modes/websocket/index.njk +104 -0
- package/template/fnet/node/src/default/blocks/assign.js.njk +10 -48
- package/template/fnet/node/src/default/blocks/call.js.njk +110 -156
- package/template/fnet/node/src/default/blocks/for.js.njk +58 -74
- package/template/fnet/node/src/default/blocks/form.js.njk +21 -59
- package/template/fnet/node/src/default/blocks/http.js.njk +135 -0
- package/template/fnet/node/src/default/blocks/modules.js.njk +10 -52
- package/template/fnet/node/src/default/blocks/new.js.njk +40 -85
- package/template/fnet/node/src/default/blocks/next.js.njk +10 -32
- package/template/fnet/node/src/default/blocks/output.js.njk +10 -48
- package/template/fnet/node/src/default/blocks/parallel.js.njk +64 -0
- package/template/fnet/node/src/default/blocks/pipeline.js.njk +109 -0
- package/template/fnet/node/src/default/blocks/raise.js.njk +9 -25
- package/template/fnet/node/src/default/blocks/retry.js.njk +70 -0
- package/template/fnet/node/src/default/blocks/return.js.njk +13 -25
- package/template/fnet/node/src/default/blocks/schedule.js.njk +66 -0
- package/template/fnet/node/src/default/blocks/signal.js.njk +10 -32
- package/template/fnet/node/src/default/blocks/steps.js.njk +32 -48
- package/template/fnet/node/src/default/blocks/switch.js.njk +37 -67
- package/template/fnet/node/src/default/blocks/tryexcept.js.njk +58 -52
- package/template/fnet/node/src/default/blocks/wait.js.njk +10 -30
- package/template/fnet/node/src/default/input.args.js.njk +5 -2
- package/template/fnet/node/src/default/macros/block-body-header.js.njk +1 -1
- package/template/fnet/node/src/default/macros/block-header.js.njk +0 -3
- package/template/fnet/node/src/default/macros/block-next.js.njk +1 -1
- package/template/fnet/node/src/default/macros/block-run-footer.js.njk +1 -1
- package/template/fnet/node/src/default/macros/block-run-header.js.njk +14 -2
- package/template/fnet/node/src/default/macros/page.js.njk +1 -1
- package/template/fnet/node/src/default/types/block.js.njk +133 -0
- package/template/fnet/node/src/default/workflow.js.njk +8 -6
- package/template/fnode/node/package.json.njk +5 -4
- package/template/fnode/node/src/cli/index.js.njk +3 -397
- package/template/fnode/node/src/cli/index.js.v1.njk +464 -0
- package/template/fnode/node/src/cli/v2/core/args-parser.njk +10 -0
- package/template/fnode/node/src/cli/v2/core/imports.njk +29 -0
- package/template/fnode/node/src/cli/v2/core/run-wrapper.njk +25 -0
- package/template/fnode/node/src/cli/v2/index.js.njk +184 -0
- package/template/fnode/node/src/cli/v2/modes/default/extend.njk +11 -0
- package/template/fnode/node/src/cli/v2/modes/default/standard.njk +19 -0
- package/template/fnode/node/src/cli/v2/modes/http/builtin.njk +61 -0
- package/template/fnode/node/src/cli/v2/modes/http/imports.njk +9 -0
- package/template/fnode/node/src/cli/v2/modes/http/index.njk +12 -0
- package/template/fnode/node/src/cli/v2/modes/mcp/imports.njk +33 -0
- package/template/fnode/node/src/cli/v2/modes/mcp/index.njk +30 -0
- package/template/fnode/node/src/cli/v2/modes/mcp/server-setup.njk +15 -0
- package/template/fnode/node/src/cli/v2/modes/mcp/tool-handlers.njk +41 -0
- package/template/fnode/node/src/cli/v2/modes/mcp/transport-http.njk +212 -0
- package/template/fnode/node/src/cli/v2/modes/mcp/transport-stdio.njk +9 -0
- package/template/fnode/node/src/cli/v2/modes/pipeline/imports.njk +9 -0
- package/template/fnode/node/src/cli/v2/modes/pipeline/index.njk +103 -0
- package/template/fnode/node/src/cli/v2/modes/webhook/imports.njk +9 -0
- package/template/fnode/node/src/cli/v2/modes/webhook/index.njk +122 -0
- package/template/fnode/node/src/cli/v2/modes/websocket/imports.njk +15 -0
- package/template/fnode/node/src/cli/v2/modes/websocket/index.njk +99 -0
- package/template/fnode/node/src/default/input.args.js.njk +6 -2
- package/dist/fnet/index.-SGbq2cI.js +0 -1
- package/dist/fnet/index.B5XE4ChJ.js +0 -1
- package/dist/fnet/index.Bft2w7m3.js +0 -1
- package/dist/fnet/index.BuYxdKtK.js +0 -1
- package/dist/fnet/index.C0YpfQ5j.js +0 -1
- package/dist/fnet/index.C2S9JYhS.js +0 -1
- package/dist/fnet/index.C7saWH6d.js +0 -1
- package/dist/fnet/index.CDct_kkF.js +0 -1
- package/dist/fnet/index.CMC8mlye.js +0 -1
- package/dist/fnet/index.CmMM-Ek9.js +0 -1
- package/dist/fnet/index.CuMyez3E.js +0 -1
- package/dist/fnet/index.CzAV0S36.js +0 -1
- package/dist/fnet/index.D2N9YZmA.js +0 -1
- package/dist/fnet/index.D3p7pncT.js +0 -1
- package/dist/fnet/index.DG8TqL-1.js +0 -1
- package/dist/fnet/index.DI3yyTtl.js +0 -1
- package/dist/fnet/index.DOYkqsYT.js +0 -1
- package/dist/fnet/index.DWpw12No.js +0 -1
- package/dist/fnet/index.DrwlOzAe.js +0 -1
- package/dist/fnet/index.Q-CYRcna.js +0 -1
- package/dist/fnet/index.W6RYgypK.js +0 -1
- package/dist/fnet/index.xd8c7XMr.js +0 -1
- package/dist/fnode/index.-SGbq2cI.js +0 -1
- package/dist/fnode/index.B5XE4ChJ.js +0 -1
- package/dist/fnode/index.B8gal9up.js +0 -1
- package/dist/fnode/index.BU1440aO.js +0 -1
- package/dist/fnode/index.BcGYSPne.js +0 -1
- package/dist/fnode/index.Bft2w7m3.js +0 -1
- package/dist/fnode/index.BuYxdKtK.js +0 -1
- package/dist/fnode/index.C2S9JYhS.js +0 -1
- package/dist/fnode/index.C7saWH6d.js +0 -1
- package/dist/fnode/index.CDct_kkF.js +0 -1
- package/dist/fnode/index.CMC8mlye.js +0 -1
- package/dist/fnode/index.CmMM-Ek9.js +0 -1
- package/dist/fnode/index.CuMyez3E.js +0 -1
- package/dist/fnode/index.CzAV0S36.js +0 -1
- package/dist/fnode/index.D2N9YZmA.js +0 -1
- package/dist/fnode/index.D3p7pncT.js +0 -1
- package/dist/fnode/index.DG8TqL-1.js +0 -1
- package/dist/fnode/index.DI3yyTtl.js +0 -1
- package/dist/fnode/index.DWpw12No.js +0 -1
- package/dist/fnode/index.DrwlOzAe.js +0 -1
- package/dist/fnode/index.N_a5FdgA.js +0 -1
- package/dist/fnode/index.Q-CYRcna.js +0 -1
- package/dist/fnode/index.UNoFg95r.js +0 -1
- package/dist/fnode/index.W6RYgypK.js +0 -1
- package/dist/fnode/index.xd8c7XMr.js +0 -1
- package/template/fnet/core/print.js +0 -1
- package/template/fnet/node/src/default/macros/workflow-header.js.njk +0 -7
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
HTTP MODE - MAIN
|
|
3
|
+
============================================================================
|
|
4
|
+
HTTP REST API mode using Node.js built-in http module
|
|
5
|
+
Zero dependencies, simple and fast
|
|
6
|
+
============================================================================ #}
|
|
7
|
+
|
|
8
|
+
if (cliMode === 'http') {
|
|
9
|
+
// HTTP mode code
|
|
10
|
+
{% include "./builtin.njk" %}
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
MCP MODE - IMPORTS
|
|
3
|
+
============================================================================
|
|
4
|
+
MCP SDK dependencies for Model Context Protocol support
|
|
5
|
+
Supports both ESM and CJS formats
|
|
6
|
+
============================================================================ #}
|
|
7
|
+
|
|
8
|
+
{% if atom.doc.features.project.format==='esm' %}
|
|
9
|
+
{# ESM Format #}
|
|
10
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
11
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
14
|
+
import express from "express";
|
|
15
|
+
{% if atom.doc.features.cli.mcp.ws===true %}
|
|
16
|
+
import expressWs from "express-ws";
|
|
17
|
+
{% endif %}
|
|
18
|
+
{# crypto imported in shared section #}
|
|
19
|
+
|
|
20
|
+
{% elif atom.doc.features.project.format==='cjs' %}
|
|
21
|
+
{# CJS Format #}
|
|
22
|
+
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
23
|
+
const { ListToolsRequestSchema, CallToolRequestSchema } = require("@modelcontextprotocol/sdk/types.js");
|
|
24
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
25
|
+
const { StreamableHTTPServerTransport } = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
26
|
+
const express = require("express");
|
|
27
|
+
{% if atom.doc.features.cli.mcp.ws===true %}
|
|
28
|
+
const expressWs = require("express-ws");
|
|
29
|
+
{% endif %}
|
|
30
|
+
{# crypto imported in shared section #}
|
|
31
|
+
|
|
32
|
+
{% endif %}
|
|
33
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
MCP MODE - MAIN
|
|
3
|
+
============================================================================
|
|
4
|
+
Model Context Protocol mode orchestrator
|
|
5
|
+
Supports multiple transports: STDIO, HTTP (future: WebSocket, SSE, gRPC)
|
|
6
|
+
============================================================================ #}
|
|
7
|
+
|
|
8
|
+
if (cliMode === 'mcp') {
|
|
9
|
+
// MCP mode code
|
|
10
|
+
{% include "./server-setup.njk" %}
|
|
11
|
+
{% include "./tool-handlers.njk" %}
|
|
12
|
+
|
|
13
|
+
// Get transport type from arguments
|
|
14
|
+
const transportType = args['mcp-transport'] || args.mcp_transport || 'stdio';
|
|
15
|
+
let transport;
|
|
16
|
+
|
|
17
|
+
if (transportType === 'stdio') {
|
|
18
|
+
{% include "./transport-stdio.njk" %}
|
|
19
|
+
} else if (transportType === 'http') {
|
|
20
|
+
{% include "./transport-http.njk" %}
|
|
21
|
+
} else {
|
|
22
|
+
console.error(`Unknown MCP transport type: ${transportType}`);
|
|
23
|
+
console.error(`Supported types: stdio, http`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await server.connect(transport);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
MCP MODE - SERVER SETUP
|
|
3
|
+
============================================================================
|
|
4
|
+
Initialize MCP server with capabilities
|
|
5
|
+
============================================================================ #}
|
|
6
|
+
|
|
7
|
+
const server = new Server({
|
|
8
|
+
name: "{{atom.doc.features.cli.mcp.name or atom.doc.name}}",
|
|
9
|
+
version: "{{atom.doc.version or '0.0.1'}}"
|
|
10
|
+
}, {
|
|
11
|
+
capabilities: {
|
|
12
|
+
tools: {}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
MCP MODE - TOOL HANDLERS
|
|
3
|
+
============================================================================
|
|
4
|
+
Register MCP tool handlers for listing and executing tools
|
|
5
|
+
============================================================================ #}
|
|
6
|
+
|
|
7
|
+
// Define available tools
|
|
8
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
9
|
+
return {
|
|
10
|
+
tools: [{
|
|
11
|
+
name: "{{atom.doc.features.cli.mcp.tool.name or atom.doc.name}}",
|
|
12
|
+
description: `{{atom.doc.features.cli.mcp.tool.description or atom.doc.description}}`,
|
|
13
|
+
inputSchema: inputSchema
|
|
14
|
+
}]
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Handle tool execution
|
|
19
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
20
|
+
if (request.params.name === "{{atom.doc.features.cli.mcp.tool.name or atom.doc.name}}") {
|
|
21
|
+
try {
|
|
22
|
+
{% if atom.doc.features.cli.extend===true %}
|
|
23
|
+
const result = await runExtended(request.params.arguments, { Engine });
|
|
24
|
+
{% else %}
|
|
25
|
+
const engine = new Engine();
|
|
26
|
+
const result = await engine.run(request.params.arguments);
|
|
27
|
+
{% endif %}
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: JSON.stringify(result)
|
|
32
|
+
}]
|
|
33
|
+
};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `Error: ${error.message}`
|
|
39
|
+
}],
|
|
40
|
+
isError: true
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw new Error("Tool not found");
|
|
45
|
+
});
|
|
46
|
+
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
MCP MODE - HTTP TRANSPORT
|
|
3
|
+
============================================================================
|
|
4
|
+
Streamable HTTP transport for MCP protocol with session management
|
|
5
|
+
Official transport as of MCP 2025-03-26
|
|
6
|
+
============================================================================ #}
|
|
7
|
+
|
|
8
|
+
// Use Streamable HTTP transport (official transport as of MCP 2025-03-26)
|
|
9
|
+
const app = express();
|
|
10
|
+
{% if atom.doc.features.cli.mcp.ws===true %}
|
|
11
|
+
// Enable WebSocket support
|
|
12
|
+
expressWs(app);
|
|
13
|
+
{% endif %}
|
|
14
|
+
app.use(express.json());
|
|
15
|
+
|
|
16
|
+
const port = args['cli-port'] || args.cli_port || 3000;
|
|
17
|
+
const host = args['cli-host'] || args.cli_host || '0.0.0.0';
|
|
18
|
+
const basePath = '{{atom.doc.features.cli.mcp.path or 'mcp'}}';
|
|
19
|
+
const mcpEndpoint = `/${basePath}`; // MCP protocol endpoint
|
|
20
|
+
const verbose = args['cli-verbose'] || args.cli_verbose || false;
|
|
21
|
+
|
|
22
|
+
// Optional endpoints (enabled via config)
|
|
23
|
+
const httpEnabled = {{atom.doc.features.cli.mcp.http or false}};
|
|
24
|
+
const wsEnabled = {{atom.doc.features.cli.mcp.ws or false}};
|
|
25
|
+
const httpEndpoint = `/${basePath}/input`; // HTTP input endpoint
|
|
26
|
+
const wsEndpoint = `/${basePath}/ws`; // WebSocket endpoint
|
|
27
|
+
|
|
28
|
+
// Verbose logging helper
|
|
29
|
+
const log = (...args) => {
|
|
30
|
+
if (verbose) {
|
|
31
|
+
console.log('[MCP]', new Date().toISOString(), ...args);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Map to store transports by session ID
|
|
36
|
+
const transports = {};
|
|
37
|
+
|
|
38
|
+
// Handle POST requests for client-to-server communication
|
|
39
|
+
app.post(mcpEndpoint, async (req, res) => {
|
|
40
|
+
// Check for existing session ID
|
|
41
|
+
const sessionId = req.get('Mcp-Session-Id');
|
|
42
|
+
|
|
43
|
+
log('POST request received');
|
|
44
|
+
log('Session ID:', sessionId || 'none');
|
|
45
|
+
log('Request body:', JSON.stringify(req.body, null, 2));
|
|
46
|
+
|
|
47
|
+
let transport;
|
|
48
|
+
|
|
49
|
+
if (sessionId && transports[sessionId]) {
|
|
50
|
+
// Reuse existing transport
|
|
51
|
+
log('Reusing existing transport for session:', sessionId);
|
|
52
|
+
transport = transports[sessionId];
|
|
53
|
+
} else if (!sessionId && req.body && (req.body.method === 'initialize' ||
|
|
54
|
+
(Array.isArray(req.body) && req.body.some(item => item.method === 'initialize')))) {
|
|
55
|
+
// New initialization request
|
|
56
|
+
log('Creating new transport for initialization');
|
|
57
|
+
transport = new StreamableHTTPServerTransport({
|
|
58
|
+
sessionIdGenerator: () => {
|
|
59
|
+
// Generate cryptographically secure session ID
|
|
60
|
+
return crypto.randomBytes(16).toString('hex');
|
|
61
|
+
},
|
|
62
|
+
onsessioninitialized: (sessionId) => {
|
|
63
|
+
// Store the transport by session ID
|
|
64
|
+
log('Session initialized:', sessionId);
|
|
65
|
+
transports[sessionId] = transport;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Clean up transport when closed
|
|
70
|
+
transport.onclose = () => {
|
|
71
|
+
if (transport.sessionId) {
|
|
72
|
+
log('Transport closed, cleaning up session:', transport.sessionId);
|
|
73
|
+
delete transports[transport.sessionId];
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Connect to the MCP server
|
|
78
|
+
await server.connect(transport);
|
|
79
|
+
log('Server connected to transport');
|
|
80
|
+
} else {
|
|
81
|
+
// Invalid request
|
|
82
|
+
log('Invalid request - no valid session ID');
|
|
83
|
+
return res.status(400).json({
|
|
84
|
+
jsonrpc: '2.0',
|
|
85
|
+
error: {
|
|
86
|
+
code: -32000,
|
|
87
|
+
message: 'Bad Request: No valid session ID provided'
|
|
88
|
+
},
|
|
89
|
+
id: null
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle the request
|
|
94
|
+
log('Handling request with transport');
|
|
95
|
+
await transport.handleRequest(req, res, req.body);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Reusable handler for GET and DELETE requests
|
|
99
|
+
const handleSessionRequest = async (req, res) => {
|
|
100
|
+
const sessionId = req.get('Mcp-Session-Id');
|
|
101
|
+
log(`${req.method} request received for session:`, sessionId || 'none');
|
|
102
|
+
|
|
103
|
+
if (!sessionId || !transports[sessionId]) {
|
|
104
|
+
log('Invalid or missing session ID');
|
|
105
|
+
return res.status(400).send('Invalid or missing session ID');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const transport = transports[sessionId];
|
|
109
|
+
log('Handling request with existing transport');
|
|
110
|
+
await transport.handleRequest(req, res);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Handle GET requests for server-to-client notifications via SSE
|
|
114
|
+
app.get(mcpEndpoint, handleSessionRequest);
|
|
115
|
+
|
|
116
|
+
// Handle DELETE requests for session termination
|
|
117
|
+
app.delete(mcpEndpoint, handleSessionRequest);
|
|
118
|
+
|
|
119
|
+
{% if atom.doc.features.cli.mcp.http===true %}
|
|
120
|
+
// HTTP input endpoint (enabled via config: cli.mcp.http: true)
|
|
121
|
+
app.post(httpEndpoint, async (req, res) => {
|
|
122
|
+
log('HTTP input request received');
|
|
123
|
+
log('Request body:', JSON.stringify(req.body, null, 2));
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
// Call the Engine with the request body
|
|
127
|
+
{% if atom.doc.features.cli.extend===true %}
|
|
128
|
+
const result = await runExtended(req.body, { Engine });
|
|
129
|
+
{% else %}
|
|
130
|
+
const engine = new Engine();
|
|
131
|
+
const result = await engine.run(req.body);
|
|
132
|
+
{% endif %}
|
|
133
|
+
|
|
134
|
+
log('HTTP input processed successfully');
|
|
135
|
+
log('Result:', JSON.stringify(result, null, 2));
|
|
136
|
+
|
|
137
|
+
// Return the result as JSON
|
|
138
|
+
res.json(result);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
log('HTTP input processing error:', error.message);
|
|
141
|
+
|
|
142
|
+
// Return error response
|
|
143
|
+
res.status(500).json({
|
|
144
|
+
error: error.message,
|
|
145
|
+
stack: verbose ? error.stack : undefined
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
{% endif %}
|
|
150
|
+
|
|
151
|
+
{% if atom.doc.features.cli.mcp.ws===true %}
|
|
152
|
+
// WebSocket endpoint (enabled via config: cli.mcp.ws: true)
|
|
153
|
+
// Map to store WebSocket clients
|
|
154
|
+
const wsClients = new Set();
|
|
155
|
+
|
|
156
|
+
app.ws(wsEndpoint, (ws, req) => {
|
|
157
|
+
log('WebSocket connection established');
|
|
158
|
+
wsClients.add(ws);
|
|
159
|
+
|
|
160
|
+
// Handle incoming messages
|
|
161
|
+
ws.on('message', async (message) => {
|
|
162
|
+
log('WebSocket message received:', message);
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Parse JSON message
|
|
166
|
+
const data = JSON.parse(message);
|
|
167
|
+
|
|
168
|
+
// Call the Engine
|
|
169
|
+
{% if atom.doc.features.cli.extend===true %}
|
|
170
|
+
const result = await runExtended(data, { Engine });
|
|
171
|
+
{% else %}
|
|
172
|
+
const engine = new Engine();
|
|
173
|
+
const result = await engine.run(data);
|
|
174
|
+
{% endif %}
|
|
175
|
+
|
|
176
|
+
log('WebSocket message processed successfully');
|
|
177
|
+
log('Result:', JSON.stringify(result, null, 2));
|
|
178
|
+
|
|
179
|
+
// Send result back to client
|
|
180
|
+
ws.send(JSON.stringify(result));
|
|
181
|
+
} catch (error) {
|
|
182
|
+
log('WebSocket message processing error:', error.message);
|
|
183
|
+
|
|
184
|
+
// Send error response
|
|
185
|
+
ws.send(JSON.stringify({
|
|
186
|
+
error: error.message,
|
|
187
|
+
stack: verbose ? error.stack : undefined
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Handle connection close
|
|
193
|
+
ws.on('close', () => {
|
|
194
|
+
log('WebSocket connection closed');
|
|
195
|
+
wsClients.delete(ws);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Handle errors
|
|
199
|
+
ws.on('error', (error) => {
|
|
200
|
+
log('WebSocket error:', error.message);
|
|
201
|
+
wsClients.delete(ws);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
{% endif %}
|
|
205
|
+
|
|
206
|
+
app.listen(port, host, () => {
|
|
207
|
+
console.log(`MCP server started with Streamable HTTP transport`);
|
|
208
|
+
console.log(`MCP endpoint: http://${host}:${port}${mcpEndpoint}`);
|
|
209
|
+
{% if atom.doc.features.cli.mcp.http===true %}
|
|
210
|
+
console.log(`HTTP endpoint: http://${host}:${port}${httpEndpoint}`);
|
|
211
|
+
{% endif %}
|
|
212
|
+
{% if atom.doc.features.cli.mcp.ws===true %}
|
|
213
|
+
console.log(`WebSocket endpoint: ws://${host}:${port}${wsEndpoint}`);
|
|
214
|
+
{% endif %}
|
|
215
|
+
console.log(`Listening on ${host}:${port}`);
|
|
216
|
+
if (verbose) {
|
|
217
|
+
console.log(`Verbose logging enabled`);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return;
|
|
222
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
MCP MODE - STDIO TRANSPORT
|
|
3
|
+
============================================================================
|
|
4
|
+
Standard input/output transport for MCP protocol
|
|
5
|
+
============================================================================ #}
|
|
6
|
+
|
|
7
|
+
// Use stdio transport
|
|
8
|
+
transport = new StdioServerTransport();
|
|
9
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
PIPELINE MODE - IMPORTS
|
|
3
|
+
============================================================================
|
|
4
|
+
No external dependencies needed - uses native Node.js streams
|
|
5
|
+
stdin/stdout are available globally
|
|
6
|
+
============================================================================ #}
|
|
7
|
+
|
|
8
|
+
{# No imports needed for pipeline mode - stdin/stdout are native #}
|
|
9
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
PIPELINE MODE - MAIN
|
|
3
|
+
============================================================================
|
|
4
|
+
Unix pipeline integration via stdin/stdout
|
|
5
|
+
Supports line-by-line processing and full input mode
|
|
6
|
+
============================================================================ #}
|
|
7
|
+
|
|
8
|
+
if (cliMode === 'pipeline') {
|
|
9
|
+
// Pipeline mode code
|
|
10
|
+
const pipelineMode = args['pipeline-mode'] || args.pipeline_mode || 'lines';
|
|
11
|
+
const pipelineFormat = args['pipeline-format'] || args.pipeline_format || 'json';
|
|
12
|
+
|
|
13
|
+
if (pipelineMode === 'lines') {
|
|
14
|
+
// Line-by-line processing (default)
|
|
15
|
+
{% if atom.doc.features.project.format==='esm' %}
|
|
16
|
+
const readline = await import('readline');
|
|
17
|
+
{% elif atom.doc.features.project.format==='cjs' %}
|
|
18
|
+
const readline = require('readline');
|
|
19
|
+
{% endif %}
|
|
20
|
+
const rl = readline.createInterface({
|
|
21
|
+
input: process.stdin,
|
|
22
|
+
output: process.stdout,
|
|
23
|
+
terminal: false
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
rl.on('line', async (line) => {
|
|
27
|
+
try {
|
|
28
|
+
// Parse input based on format
|
|
29
|
+
let input;
|
|
30
|
+
if (pipelineFormat === 'json') {
|
|
31
|
+
input = JSON.parse(line);
|
|
32
|
+
} else {
|
|
33
|
+
input = { data: line };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Validate input
|
|
37
|
+
// TODO: Add schema validation if needed
|
|
38
|
+
|
|
39
|
+
// Process with Engine
|
|
40
|
+
{% if atom.doc.features.cli.extend===true %}
|
|
41
|
+
const result = await runExtended(input, { Engine });
|
|
42
|
+
{% else %}
|
|
43
|
+
const engine = new Engine();
|
|
44
|
+
const result = await engine.run(input);
|
|
45
|
+
{% endif %}
|
|
46
|
+
|
|
47
|
+
// Output result
|
|
48
|
+
if (pipelineFormat === 'json') {
|
|
49
|
+
console.log(JSON.stringify(result));
|
|
50
|
+
} else {
|
|
51
|
+
console.log(result);
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`Error processing line: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
rl.on('close', () => {
|
|
59
|
+
process.exit(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
} else if (pipelineMode === 'full') {
|
|
63
|
+
// Full input processing
|
|
64
|
+
let inputData = '';
|
|
65
|
+
|
|
66
|
+
process.stdin.on('data', (chunk) => {
|
|
67
|
+
inputData += chunk;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
process.stdin.on('end', async () => {
|
|
71
|
+
try {
|
|
72
|
+
// Parse input based on format
|
|
73
|
+
let input;
|
|
74
|
+
if (pipelineFormat === 'json') {
|
|
75
|
+
input = JSON.parse(inputData);
|
|
76
|
+
} else {
|
|
77
|
+
input = { data: inputData };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Validate input
|
|
81
|
+
// TODO: Add schema validation if needed
|
|
82
|
+
|
|
83
|
+
// Process with Engine
|
|
84
|
+
{% if atom.doc.features.cli.extend===true %}
|
|
85
|
+
const result = await runExtended(input, { Engine });
|
|
86
|
+
{% else %}
|
|
87
|
+
const engine = new Engine();
|
|
88
|
+
const result = await engine.run(input);
|
|
89
|
+
{% endif %}
|
|
90
|
+
|
|
91
|
+
// Output result
|
|
92
|
+
if (pipelineFormat === 'json') {
|
|
93
|
+
console.log(JSON.stringify(result, null, 2));
|
|
94
|
+
} else {
|
|
95
|
+
console.log(result);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
process.exit(0);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`Error processing input: ${error.message}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
} else {
|
|
106
|
+
console.error(`Unknown pipeline mode: ${pipelineMode}`);
|
|
107
|
+
console.error(`Supported modes: lines, full`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
WEBHOOK MODE - IMPORTS
|
|
3
|
+
============================================================================
|
|
4
|
+
No mode-specific imports needed
|
|
5
|
+
http and crypto modules are imported in shared imports section
|
|
6
|
+
============================================================================ #}
|
|
7
|
+
|
|
8
|
+
{# http and crypto modules imported in shared section #}
|
|
9
|
+
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
WEBHOOK MODE - IMPLEMENTATION
|
|
3
|
+
============================================================================
|
|
4
|
+
Event-driven HTTP webhook server with HMAC signature verification
|
|
5
|
+
============================================================================ #}
|
|
6
|
+
|
|
7
|
+
if (cliMode === 'webhook') {
|
|
8
|
+
const port = args['cli-port'] || args.cli_port || 3000;
|
|
9
|
+
const webhookPath = args['webhook-path'] || args.webhook_path || '/webhook';
|
|
10
|
+
const webhookSecret = args['webhook-secret'] || args.webhook_secret || process.env.WEBHOOK_SECRET;
|
|
11
|
+
const signatureHeader = args['webhook-signature-header'] || args.webhook_signature_header || 'x-hub-signature-256';
|
|
12
|
+
|
|
13
|
+
const server = http.createServer(async (req, res) => {
|
|
14
|
+
// CORS headers
|
|
15
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
16
|
+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
17
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, ' + signatureHeader);
|
|
18
|
+
|
|
19
|
+
// Handle OPTIONS preflight
|
|
20
|
+
if (req.method === 'OPTIONS') {
|
|
21
|
+
res.writeHead(200);
|
|
22
|
+
res.end();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Only accept POST requests
|
|
27
|
+
if (req.method !== 'POST') {
|
|
28
|
+
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
29
|
+
res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Only accept webhook path
|
|
34
|
+
if (req.url !== webhookPath) {
|
|
35
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
36
|
+
res.end(JSON.stringify({ error: 'Not found. Use ' + webhookPath }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Read request body
|
|
41
|
+
let body = '';
|
|
42
|
+
req.on('data', chunk => {
|
|
43
|
+
body += chunk.toString();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
req.on('end', async () => {
|
|
47
|
+
try {
|
|
48
|
+
// Verify signature if secret is provided
|
|
49
|
+
if (webhookSecret) {
|
|
50
|
+
const signature = req.headers[signatureHeader.toLowerCase()];
|
|
51
|
+
|
|
52
|
+
if (!signature) {
|
|
53
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
54
|
+
res.end(JSON.stringify({ error: 'Missing signature header: ' + signatureHeader }));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Calculate expected signature (HMAC-SHA256)
|
|
59
|
+
const hmac = crypto.createHmac('sha256', webhookSecret);
|
|
60
|
+
hmac.update(body);
|
|
61
|
+
const expectedSignature = 'sha256=' + hmac.digest('hex');
|
|
62
|
+
|
|
63
|
+
// Compare signatures (timing-safe)
|
|
64
|
+
const signatureBuffer = Buffer.from(signature);
|
|
65
|
+
const expectedBuffer = Buffer.from(expectedSignature);
|
|
66
|
+
|
|
67
|
+
if (signatureBuffer.length !== expectedBuffer.length ||
|
|
68
|
+
!crypto.timingSafeEqual(signatureBuffer, expectedBuffer)) {
|
|
69
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
70
|
+
res.end(JSON.stringify({ error: 'Invalid signature' }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Parse JSON payload
|
|
76
|
+
let payload;
|
|
77
|
+
try {
|
|
78
|
+
payload = JSON.parse(body);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
81
|
+
res.end(JSON.stringify({ error: 'Invalid JSON payload' }));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Process webhook with Engine
|
|
86
|
+
{% if atom.doc.features.cli.extend===true %}
|
|
87
|
+
const result = await runExtended(payload, { Engine });
|
|
88
|
+
{% else %}
|
|
89
|
+
const engine = new Engine();
|
|
90
|
+
const result = await engine.run(payload);
|
|
91
|
+
{% endif %}
|
|
92
|
+
|
|
93
|
+
// Send response
|
|
94
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
95
|
+
res.end(JSON.stringify(result));
|
|
96
|
+
|
|
97
|
+
} catch (error) {
|
|
98
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
99
|
+
res.end(JSON.stringify({
|
|
100
|
+
error: error.message,
|
|
101
|
+
type: 'processing_error'
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
req.on('error', (error) => {
|
|
107
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
108
|
+
res.end(JSON.stringify({
|
|
109
|
+
error: error.message,
|
|
110
|
+
type: 'request_error'
|
|
111
|
+
}));
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
server.listen(port, () => {
|
|
116
|
+
console.log(`Webhook server started on port ${port}`);
|
|
117
|
+
console.log(`Webhook endpoint: ${webhookPath}`);
|
|
118
|
+
if (webhookSecret) {
|
|
119
|
+
console.log(`Signature verification: enabled (header: ${signatureHeader})`);
|
|
120
|
+
} else {
|
|
121
|
+
console.log(`Signature verification: disabled (no secret provided)`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{# ============================================================================
|
|
2
|
+
WEBSOCKET MODE - IMPORTS
|
|
3
|
+
============================================================================
|
|
4
|
+
WebSocket server dependencies using 'ws' library
|
|
5
|
+
Supports both ESM and CJS formats
|
|
6
|
+
============================================================================ #}
|
|
7
|
+
|
|
8
|
+
{% if atom.doc.features.project.format==='esm' %}
|
|
9
|
+
// Using ws library for WebSocket mode
|
|
10
|
+
import { WebSocketServer } from 'ws';
|
|
11
|
+
{% elif atom.doc.features.project.format==='cjs' %}
|
|
12
|
+
// Using ws library for WebSocket mode
|
|
13
|
+
const { WebSocketServer } = require('ws');
|
|
14
|
+
{% endif %}
|
|
15
|
+
|