Skip to content

Commit 8469edb

Browse files
author
jovanSAPFIONEER
committed
feat: Phase 6 — Full AI Control (v4.0.0)
- Remove maxParallelAgents hard limit (Infinity default, AI picks concurrency) - Add McpSseServer: HTTP/SSE MCP server (GET /sse, POST /mcp, GET /health, GET /tools) - Add McpSseTransport: HTTP POST transport implementing McpTransport interface - Add McpCombinedBridge + McpBlackboardBridgeAdapter for multi-tool composition - Add ExtendedMcpTools: 10 tools for budget/token/audit AI control - Add ControlMcpTools: 7 tools for config/agent/fsm/orchestrator control - Add bin/mcp-server.ts CLI entry point (network-ai-server binary) - Export getConfig() / setConfig() from package root - 121 new tests in test-phase6.ts (1216 total, all passing) BREAKING: maxParallelAgents default changes from 3 to Infinity; ParallelLimitError no longer thrown at default concurrency.
1 parent 516c94a commit 8469edb

13 files changed

Lines changed: 2416 additions & 14 deletions

CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,43 @@ All notable changes to Network-AI will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [4.0.0] - 2026-02-25
9+
10+
### Added — Phase 6: Full AI Control
11+
- **Pre-work: No hard concurrency limit**`maxParallelAgents` now defaults to `Infinity`; the previous hard cap of 3 is removed; AI agents choose their own parallelism
12+
- **`getConfig(key?)` / `setConfig(key, value)`** — exported from package root; AI can read and mutate live config at runtime via `ControlMcpTools` or directly
13+
- **`McpSseServer`** — production-ready HTTP/SSE MCP server; `GET /sse` (Server-Sent Events stream), `POST /mcp` (JSON-RPC 2.0), `GET /health`, `GET /tools`; CORS-enabled; 4 MB body limit; configurable heartbeat; `broadcast(event, data)` to all SSE clients
14+
- **`McpSseTransport`** — implements `McpTransport` over HTTP POST; supports http and https; optional 30 s timeout; drop-in replacement for `McpInProcessTransport`
15+
- **`McpCombinedBridge`** — aggregates multiple `McpToolProvider` instances and routes `tools/list` (merged) and `tools/call` (by tool name) across all of them
16+
- **`McpBlackboardBridgeAdapter`** — wraps `McpBlackboardBridge` as a `McpToolProvider` for use in `McpCombinedBridge`
17+
- **`McpToolProvider` interface** — any tool set that exposes `getDefinitions()` + `call()`; makes it trivial to plug in new tool groups
18+
- **`ExtendedMcpTools`** — 10 MCP tools for AI budget + token + audit control:
19+
- Budget (5): `budget_status`, `budget_spend`, `budget_reset`, `budget_set_ceiling`, `budget_get_log`
20+
- Token (3): `token_create`, `token_validate`, `token_revoke`
21+
- Audit (2): `audit_query` (with agentId, eventType, outcome, since_iso, limit filters), `audit_tail`
22+
- **`ControlMcpTools`** — 7 MCP tools for AI orchestrator control-plane:
23+
- `config_get` — read any CONFIG key (or all)
24+
- `config_set` — mutate CONFIG at runtime (number, string, boolean, null)
25+
- `agent_list` — list all registered + stopped agents with optional status filter
26+
- `agent_spawn` — write a task to the blackboard so an agent picks it up
27+
- `agent_stop` — mark an agent stopped in the registry and on the blackboard
28+
- `fsm_transition` — drive any FSM to a new state and append history on the blackboard
29+
- `orchestrator_info` — version, live config snapshot, agent counts, blackboard availability
30+
- **`bin/mcp-server.ts`** — full CLI entry point: `network-ai-server`; args: `--port`, `--host`, `--board`, `--ceiling`, `--no-budget`, `--no-token`, `--no-extended`, `--no-control`, `--audit-log`, `--heartbeat`, `--help`; graceful SIGINT/SIGTERM shutdown
31+
- **`network-ai-server` binary** added to `package.json` pointing to `dist/bin/mcp-server.js`
32+
- **121 new tests** in `test-phase6.ts`
33+
34+
### Changed
35+
- `maxParallelAgents` default: `3``Infinity` (no hard limit; AI is in full control)
36+
- `package.json` version: `3.9.0``4.0.0`
37+
38+
### Breaking Changes
39+
- `ParallelLimitError` is no longer thrown when `maxParallelAgents` is `Infinity` (the default). Code that previously caught this error for the default-3 limit will never trigger it. Setting `maxParallelAgents` to a finite number still enforces the limit.
40+
41+
### Notes
42+
- All Phase 6 exports (`McpSseServer`, `McpSseTransport`, `McpCombinedBridge`, `McpBlackboardBridgeAdapter`, `ExtendedMcpTools`, `ControlMcpTools`) available from package root
43+
- Total test count: **1216 passing**
44+
845
## [3.9.0] - 2026-02-25
946

1047
### Added -- Phase 5 Part 7: MCP Networking

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
[![CI](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/ci.yml/badge.svg)](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/ci.yml)
66
[![CodeQL](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/codeql.yml/badge.svg)](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/codeql.yml)
7-
[![Release](https://img.shields.io/badge/release-v3.9.0-blue.svg)](https://github.com/jovanSAPFIONEER/Network-AI/releases)
7+
[![Release](https://img.shields.io/badge/release-v4.0.0-blue.svg)](https://github.com/jovanSAPFIONEER/Network-AI/releases)
88
[![npm](https://img.shields.io/npm/dw/network-ai.svg?label=npm%20downloads)](https://www.npmjs.com/package/network-ai)
99
[![ClawHub](https://img.shields.io/badge/ClawHub-network--ai-orange.svg)](https://clawhub.ai/skills/network-ai)
1010
[![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org)
@@ -13,7 +13,7 @@
1313
[![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
1414
[![Socket](https://socket.dev/api/badge/npm/package/network-ai)](https://socket.dev/npm/package/network-ai/overview)
1515
[![AgentSkills](https://img.shields.io/badge/AgentSkills-compatible-orange.svg)](https://agentskills.io)
16-
[![Tests](https://img.shields.io/badge/tests-1095%20passing-brightgreen.svg)](#testing)
16+
[![Tests](https://img.shields.io/badge/tests-1216%20passing-brightgreen.svg)](#testing)
1717
[![Adapters](https://img.shields.io/badge/frameworks-12%20supported-blueviolet.svg)](#adapter-system)
1818
[![RSS Feed](https://img.shields.io/badge/RSS-releases-orange?logo=rss)](https://github.com/jovanSAPFIONEER/Network-AI/releases.atom)
1919

SECURITY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
| Version | Supported |
66
|---------|-----------|
7-
| 3.9.x | Yes (latest) |
7+
| 4.0.x | Yes (latest) |
8+
| 3.9.x | Security fixes only |
89
| 3.8.x | Security fixes only |
910
| 3.7.x | Security fixes only |
1011
| 3.6.x | Security fixes only |

bin/mcp-server.ts

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Network-AI MCP Server — Phase 6 Part 3
4+
*
5+
* Starts an HTTP/SSE server that exposes the full Network-AI tool suite to
6+
* any MCP-compatible AI agent (Claude Desktop, Cursor, Cline, etc.).
7+
*
8+
* Usage:
9+
* npx ts-node bin/mcp-server.ts [options]
10+
* npx network-ai-server [options] (after clawhub publish)
11+
*
12+
* Options:
13+
* --port <n> TCP port to listen on (default: 3001)
14+
* --host <h> Hostname to bind to (default: 0.0.0.0)
15+
* --board <name> Named blackboard to expose (default: main)
16+
* --ceiling <n> Initial FederatedBudget token ceiling (default: 1_000_000)
17+
* --no-budget Disable budget tools
18+
* --no-token Disable token tools
19+
* --no-extended Disable all extended tools (budget + token + audit)
20+
* --no-control Disable control-plane tools
21+
* --audit-log <path> Path to audit log file (default: ./data/audit_log.jsonl)
22+
* --heartbeat <ms> SSE heartbeat interval in ms (default: 15000)
23+
* --help Print this help text
24+
*
25+
* Connect any MCP client to:
26+
* http://localhost:3001 (SSE stream — GET /sse)
27+
* http://localhost:3001/mcp (JSON-RPC POST)
28+
* http://localhost:3001/health (health check)
29+
* http://localhost:3001/tools (list all tools)
30+
*
31+
* @module bin/mcp-server
32+
* @version 4.0.0
33+
*/
34+
35+
import {
36+
createSwarmOrchestrator,
37+
FederatedBudget,
38+
McpBlackboardBridge,
39+
getConfig,
40+
setConfig,
41+
} from '../index';
42+
import { SecureTokenManager } from '../security';
43+
import {
44+
McpSseServer,
45+
McpCombinedBridge,
46+
McpBlackboardBridgeAdapter,
47+
} from '../lib/mcp-transport-sse';
48+
import { ExtendedMcpTools } from '../lib/mcp-tools-extended';
49+
import { ControlMcpTools } from '../lib/mcp-tools-control';
50+
51+
// ============================================================================
52+
// ARGUMENT PARSING
53+
// ============================================================================
54+
55+
interface ServerArgs {
56+
port: number;
57+
host: string;
58+
board: string;
59+
ceiling: number;
60+
noBudget: boolean;
61+
noToken: boolean;
62+
noExtended: boolean;
63+
noControl: boolean;
64+
auditLog: string;
65+
heartbeat: number;
66+
help: boolean;
67+
}
68+
69+
function parseArgs(argv: string[]): ServerArgs {
70+
const args: ServerArgs = {
71+
port: 3001,
72+
host: '0.0.0.0',
73+
board: 'main',
74+
ceiling: 1_000_000,
75+
noBudget: false,
76+
noToken: false,
77+
noExtended: false,
78+
noControl: false,
79+
auditLog: './data/audit_log.jsonl',
80+
heartbeat: 15000,
81+
help: false,
82+
};
83+
84+
for (let i = 0; i < argv.length; i++) {
85+
const arg = argv[i];
86+
const next = argv[i + 1];
87+
switch (arg) {
88+
case '--port': args.port = parseInt(next ?? '3001', 10); i++; break;
89+
case '--host': args.host = next ?? '0.0.0.0'; i++; break;
90+
case '--board': args.board = next ?? 'main'; i++; break;
91+
case '--ceiling': args.ceiling = parseFloat(next ?? '1000000'); i++; break;
92+
case '--audit-log': args.auditLog = next ?? './data/audit_log.jsonl'; i++; break;
93+
case '--heartbeat': args.heartbeat = parseInt(next ?? '15000', 10); i++; break;
94+
case '--no-budget': args.noBudget = true; break;
95+
case '--no-token': args.noToken = true; break;
96+
case '--no-extended': args.noExtended = true; break;
97+
case '--no-control': args.noControl = true; break;
98+
case '--help': case '-h': args.help = true; break;
99+
}
100+
}
101+
return args;
102+
}
103+
104+
function printHelp(): void {
105+
console.log(`
106+
network-ai-server — Network-AI MCP Server v4.0.0
107+
108+
Usage: npx ts-node bin/mcp-server.ts [options]
109+
110+
Options:
111+
--port <n> TCP port (default: 3001)
112+
--host <h> Bind host (default: 0.0.0.0)
113+
--board <name> Named blackboard to expose (default: main)
114+
--ceiling <n> Budget token ceiling (default: 1000000)
115+
--audit-log <path> Audit log path (default: ./data/audit_log.jsonl)
116+
--heartbeat <ms> SSE heartbeat interval (default: 15000)
117+
--no-budget Disable budget tools
118+
--no-token Disable token tools
119+
--no-extended Disable extended tools (budget + token + audit)
120+
--no-control Disable control-plane tools
121+
--help Show this help
122+
123+
Connect to:
124+
GET http://localhost:3001/sse SSE stream
125+
POST http://localhost:3001/mcp JSON-RPC 2.0 tool calls
126+
GET http://localhost:3001/health Health check
127+
GET http://localhost:3001/tools All available tools
128+
`);
129+
}
130+
131+
// ============================================================================
132+
// MAIN
133+
// ============================================================================
134+
135+
async function main(): Promise<void> {
136+
const args = parseArgs(process.argv.slice(2));
137+
138+
if (args.help) {
139+
printHelp();
140+
process.exit(0);
141+
}
142+
143+
console.log(`\n[network-ai-server] Starting MCP Server v4.0.0`);
144+
console.log(`[network-ai-server] Board: ${args.board} | Port: ${args.port}`);
145+
146+
// --------------------------------------------------------------------------
147+
// 1. Create orchestrator + blackboard
148+
// --------------------------------------------------------------------------
149+
const orchestrator = createSwarmOrchestrator();
150+
const blackboard = orchestrator.getBlackboard(args.board);
151+
152+
// --------------------------------------------------------------------------
153+
// 2. Create MCP bridge for blackboard tools (5 tools)
154+
// --------------------------------------------------------------------------
155+
const blackboardBridge = new McpBlackboardBridge(blackboard, { name: args.board });
156+
const blackboardAdapter = new McpBlackboardBridgeAdapter(blackboardBridge);
157+
158+
// --------------------------------------------------------------------------
159+
// 3. Create extended tools (budget + token + audit) — optional
160+
// --------------------------------------------------------------------------
161+
let extendedTools: ExtendedMcpTools | null = null;
162+
if (!args.noExtended) {
163+
const budget = !args.noBudget
164+
? new FederatedBudget({ ceiling: args.ceiling })
165+
: undefined;
166+
167+
const tokenManager = !args.noToken
168+
? new SecureTokenManager()
169+
: undefined;
170+
171+
extendedTools = new ExtendedMcpTools({
172+
budget,
173+
tokenManager,
174+
auditLogPath: args.auditLog,
175+
});
176+
177+
const toolsEnabled: string[] = [];
178+
if (budget) toolsEnabled.push('budget (5 tools)');
179+
if (tokenManager) toolsEnabled.push('token (3 tools)');
180+
toolsEnabled.push('audit (2 tools)');
181+
console.log(`[network-ai-server] Extended tools: ${toolsEnabled.join(', ')}`);
182+
}
183+
184+
// --------------------------------------------------------------------------
185+
// 4. Create control-plane tools — optional
186+
// --------------------------------------------------------------------------
187+
let controlTools: ControlMcpTools | null = null;
188+
if (!args.noControl) {
189+
// Get the live CONFIG reference via the exported accessor
190+
const liveConfig = getConfig() as Record<string, unknown>;
191+
192+
// Create a proxy config object that both reads from and writes to CONFIG
193+
const configProxy = new Proxy(liveConfig, {
194+
get(_t, key: string) {
195+
return getConfig(key);
196+
},
197+
set(_t, key: string, value: unknown) {
198+
setConfig(key, value);
199+
return true;
200+
},
201+
ownKeys() {
202+
return Object.keys(getConfig() as object);
203+
},
204+
getOwnPropertyDescriptor(_t, key: string) {
205+
return { value: getConfig(key), writable: true, enumerable: true, configurable: true };
206+
},
207+
}) as unknown as import('../lib/mcp-tools-control').IConfig;
208+
209+
controlTools = new ControlMcpTools({
210+
config: configProxy,
211+
blackboard: blackboard as unknown as import('../lib/mcp-tools-control').IControlBlackboard,
212+
systemToken: 'system-orchestrator-token',
213+
});
214+
215+
console.log(`[network-ai-server] Control tools: config (2), agent (3), fsm (1), info (1)`);
216+
}
217+
218+
// --------------------------------------------------------------------------
219+
// 5. Assemble combined bridge
220+
// --------------------------------------------------------------------------
221+
const combined = new McpCombinedBridge('network-ai');
222+
combined.register(blackboardAdapter);
223+
if (extendedTools) combined.register(extendedTools);
224+
if (controlTools) combined.register(controlTools);
225+
226+
const totalTools = combined.allDefinitions().length;
227+
console.log(`[network-ai-server] Total tools exposed: ${totalTools}`);
228+
229+
// --------------------------------------------------------------------------
230+
// 6. Start SSE server
231+
// --------------------------------------------------------------------------
232+
const server = new McpSseServer(combined, {
233+
port: args.port,
234+
host: args.host,
235+
heartbeatMs: args.heartbeat,
236+
});
237+
238+
await server.listen();
239+
240+
const localUrl = `http://localhost:${args.port}`;
241+
console.log(`\n[network-ai-server] ✓ Listening on ${localUrl}`);
242+
console.log(`[network-ai-server] SSE stream : ${localUrl}/sse`);
243+
console.log(`[network-ai-server] Tool calls : ${localUrl}/mcp (POST)`);
244+
console.log(`[network-ai-server] Health : ${localUrl}/health`);
245+
console.log(`[network-ai-server] All tools : ${localUrl}/tools\n`);
246+
console.log(`[network-ai-server] Connect any MCP client to: ${localUrl}`);
247+
console.log(`[network-ai-server] Press Ctrl+C to stop.\n`);
248+
249+
// --------------------------------------------------------------------------
250+
// 7. Graceful shutdown
251+
// --------------------------------------------------------------------------
252+
const shutdown = async (signal: string): Promise<void> => {
253+
console.log(`\n[network-ai-server] Received ${signal} — shutting down...`);
254+
await server.close();
255+
console.log('[network-ai-server] Server stopped. Goodbye.\n');
256+
process.exit(0);
257+
};
258+
259+
process.on('SIGINT', () => void shutdown('SIGINT'));
260+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
261+
}
262+
263+
main().catch(err => {
264+
console.error('\n[network-ai-server] Fatal error:', err instanceof Error ? err.message : String(err));
265+
process.exit(1);
266+
});

0 commit comments

Comments
 (0)