atabey 0.0.13 → 0.0.15
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/ATABEY.md +1 -1
- package/PRIVACY.md +8 -14
- package/README.md +405 -56
- package/bin/cli.js +72 -5
- package/dist/framework-mcp/src/index.js +988 -63
- package/dist/framework-mcp/src/index.js.map +1 -1
- package/dist/framework-mcp/src/tools/compliance/mask_pii.d.ts +17 -0
- package/dist/framework-mcp/src/tools/compliance/mask_pii.js +56 -0
- package/dist/framework-mcp/src/tools/compliance/mask_pii.js.map +1 -0
- package/dist/framework-mcp/src/tools/definitions.js +10 -9
- package/dist/framework-mcp/src/tools/definitions.js.map +1 -1
- package/dist/framework-mcp/src/tools/framework/run_tests.js +25 -0
- package/dist/framework-mcp/src/tools/framework/run_tests.js.map +1 -1
- package/dist/framework-mcp/src/tools/index.js +7 -8
- package/dist/framework-mcp/src/tools/index.js.map +1 -1
- package/dist/framework-mcp/src/tools/messaging/approve_operation.d.ts +22 -0
- package/dist/framework-mcp/src/tools/messaging/approve_operation.js +81 -0
- package/dist/framework-mcp/src/tools/messaging/approve_operation.js.map +1 -0
- package/dist/framework-mcp/src/tools/messaging/ask_human.d.ts +15 -0
- package/dist/framework-mcp/src/tools/messaging/ask_human.js +89 -20
- package/dist/framework-mcp/src/tools/messaging/ask_human.js.map +1 -1
- package/dist/framework-mcp/src/tools/observability/check_ports.d.ts +1 -0
- package/dist/framework-mcp/src/tools/observability/check_ports.js +24 -8
- package/dist/framework-mcp/src/tools/observability/check_ports.js.map +1 -1
- package/dist/framework-mcp/src/tools/quality/analyze_code_quality.js +13 -64
- package/dist/framework-mcp/src/tools/quality/analyze_code_quality.js.map +1 -1
- package/dist/framework-mcp/src/tools/schemas.d.ts +48 -53
- package/dist/framework-mcp/src/tools/schemas.js +18 -22
- package/dist/framework-mcp/src/tools/schemas.js.map +1 -1
- package/dist/framework-mcp/src/tools/shell/run_command.js +146 -90
- package/dist/framework-mcp/src/tools/shell/run_command.js.map +1 -1
- package/dist/framework-mcp/src/tools/types.d.ts +11 -105
- package/dist/framework-mcp/src/tools/types.js +1 -0
- package/dist/framework-mcp/src/tools/types.js.map +1 -1
- package/dist/framework-mcp/src/utils/auth.d.ts +36 -0
- package/dist/framework-mcp/src/utils/auth.js +79 -0
- package/dist/framework-mcp/src/utils/auth.js.map +1 -0
- package/dist/framework-mcp/src/utils/auto-rollback.d.ts +71 -0
- package/dist/framework-mcp/src/utils/auto-rollback.js +266 -0
- package/dist/framework-mcp/src/utils/auto-rollback.js.map +1 -0
- package/dist/framework-mcp/src/utils/compliance.js +12 -4
- package/dist/framework-mcp/src/utils/compliance.js.map +1 -1
- package/dist/framework-mcp/src/utils/context-optimizer.d.ts +69 -0
- package/dist/framework-mcp/src/utils/context-optimizer.js +157 -0
- package/dist/framework-mcp/src/utils/context-optimizer.js.map +1 -0
- package/dist/framework-mcp/src/utils/discipline.d.ts +68 -0
- package/dist/framework-mcp/src/utils/discipline.js +218 -0
- package/dist/framework-mcp/src/utils/discipline.js.map +1 -0
- package/dist/framework-mcp/src/utils/finops.d.ts +143 -0
- package/dist/framework-mcp/src/utils/finops.js +326 -0
- package/dist/framework-mcp/src/utils/finops.js.map +1 -0
- package/dist/framework-mcp/src/utils/human-in-loop.d.ts +95 -0
- package/dist/framework-mcp/src/utils/human-in-loop.js +216 -0
- package/dist/framework-mcp/src/utils/human-in-loop.js.map +1 -0
- package/dist/framework-mcp/src/utils/license-scanner.d.ts +71 -0
- package/dist/framework-mcp/src/utils/license-scanner.js +284 -0
- package/dist/framework-mcp/src/utils/license-scanner.js.map +1 -0
- package/dist/framework-mcp/src/utils/loop-detector.d.ts +105 -0
- package/dist/framework-mcp/src/utils/loop-detector.js +362 -0
- package/dist/framework-mcp/src/utils/loop-detector.js.map +1 -0
- package/dist/framework-mcp/src/utils/quality.d.ts +19 -0
- package/dist/framework-mcp/src/utils/quality.js +125 -0
- package/dist/framework-mcp/src/utils/quality.js.map +1 -0
- package/dist/framework-mcp/src/utils/rules-engine.d.ts +78 -0
- package/dist/framework-mcp/src/utils/rules-engine.js +232 -0
- package/dist/framework-mcp/src/utils/rules-engine.js.map +1 -0
- package/dist/framework-mcp/src/utils/silent-router.d.ts +49 -0
- package/dist/framework-mcp/src/utils/silent-router.js +155 -0
- package/dist/framework-mcp/src/utils/silent-router.js.map +1 -0
- package/dist/framework-mcp/src/utils/storage.d.ts +1 -0
- package/dist/framework-mcp/src/utils/storage.js +2 -2
- package/dist/framework-mcp/src/utils/storage.js.map +1 -1
- package/dist/framework-mcp/src/utils/telemetry-streamer.d.ts +133 -0
- package/dist/framework-mcp/src/utils/telemetry-streamer.js +397 -0
- package/dist/framework-mcp/src/utils/telemetry-streamer.js.map +1 -0
- package/dist/framework-mcp/tests/tools/file_system/file_system_tools.test.js +2 -2
- package/dist/framework-mcp/tests/tools/file_system/file_system_tools.test.js.map +1 -1
- package/dist/framework-mcp/tests/tools/framework/run_tests.test.d.ts +1 -0
- package/dist/framework-mcp/tests/tools/framework/run_tests.test.js +49 -0
- package/dist/framework-mcp/tests/tools/framework/run_tests.test.js.map +1 -0
- package/dist/framework-mcp/tests/tools/messaging/send_message.test.js +14 -5
- package/dist/framework-mcp/tests/tools/messaging/send_message.test.js.map +1 -1
- package/dist/framework-mcp/tests/tools/observability/check_ports.test.d.ts +1 -0
- package/dist/framework-mcp/tests/tools/observability/check_ports.test.js +44 -0
- package/dist/framework-mcp/tests/tools/observability/check_ports.test.js.map +1 -0
- package/dist/framework-mcp/tests/tools/shell/run_command.test.js +44 -14
- package/dist/framework-mcp/tests/tools/shell/run_command.test.js.map +1 -1
- package/dist/framework-mcp/tests/utils/auto-rollback.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/auto-rollback.test.js +178 -0
- package/dist/framework-mcp/tests/utils/auto-rollback.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/context-optimizer.test.d.ts +1 -0
- package/dist/framework-mcp/tests/utils/context-optimizer.test.js +62 -0
- package/dist/framework-mcp/tests/utils/context-optimizer.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/finops.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/finops.test.js +157 -0
- package/dist/framework-mcp/tests/utils/finops.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/human-in-loop.test.d.ts +1 -0
- package/dist/framework-mcp/tests/utils/human-in-loop.test.js +59 -0
- package/dist/framework-mcp/tests/utils/human-in-loop.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/license-scanner.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/license-scanner.test.js +131 -0
- package/dist/framework-mcp/tests/utils/license-scanner.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/loop-detector.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/loop-detector.test.js +204 -0
- package/dist/framework-mcp/tests/utils/loop-detector.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/rules-engine.test.d.ts +1 -0
- package/dist/framework-mcp/tests/utils/rules-engine.test.js +106 -0
- package/dist/framework-mcp/tests/utils/rules-engine.test.js.map +1 -0
- package/dist/framework-mcp/tests/utils/telemetry-streamer.test.d.ts +7 -0
- package/dist/framework-mcp/tests/utils/telemetry-streamer.test.js +201 -0
- package/dist/framework-mcp/tests/utils/telemetry-streamer.test.js.map +1 -0
- package/dist/src/cli/commands/check.js +16 -1
- package/dist/src/cli/commands/check.js.map +1 -1
- package/dist/src/cli/commands/dashboard.js +188 -164
- package/dist/src/cli/commands/dashboard.js.map +1 -1
- package/dist/src/cli/commands/gateway.js +2 -2
- package/dist/src/cli/commands/gateway.js.map +1 -1
- package/dist/src/cli/commands/init/scaffold-core.d.ts +2 -0
- package/dist/src/cli/commands/init/scaffold-core.js +16 -1
- package/dist/src/cli/commands/init/scaffold-core.js.map +1 -1
- package/dist/src/cli/commands/init/scaffold-ops.js +2 -2
- package/dist/src/cli/commands/init/scaffold-ops.js.map +1 -1
- package/dist/src/cli/commands/init/scaffold-standards.js +13 -1
- package/dist/src/cli/commands/init/scaffold-standards.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts +4 -0
- package/dist/src/cli/commands/init.js +94 -31
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/mcp.js +3 -1
- package/dist/src/cli/commands/mcp.js.map +1 -1
- package/dist/src/cli/commands/quickstart.d.ts +1 -1
- package/dist/src/cli/commands/quickstart.js +12 -24
- package/dist/src/cli/commands/quickstart.js.map +1 -1
- package/dist/src/cli/commands/script.js +8 -1
- package/dist/src/cli/commands/script.js.map +1 -1
- package/dist/src/cli/index.js +24 -23
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/cli/shims.js +7 -7
- package/dist/src/cli/utils/pkg.js +2 -3
- package/dist/src/cli/utils/pkg.js.map +1 -1
- package/dist/src/cli/utils/quality.d.ts +10 -0
- package/dist/src/cli/utils/quality.js +10 -0
- package/dist/src/cli/utils/quality.js.map +1 -0
- package/dist/src/modules/agents/definitions.js +45 -40
- package/dist/src/modules/agents/definitions.js.map +1 -1
- package/dist/src/modules/agents/registry/manager.js +6 -1
- package/dist/src/modules/agents/registry/manager.js.map +1 -1
- package/dist/src/modules/engines/agent-executor.d.ts +6 -10
- package/dist/src/modules/engines/agent-executor.js +63 -21
- package/dist/src/modules/engines/agent-executor.js.map +1 -1
- package/dist/src/modules/engines/agent-loop.d.ts +34 -0
- package/dist/src/modules/engines/agent-loop.js +102 -0
- package/dist/src/modules/engines/agent-loop.js.map +1 -0
- package/dist/src/modules/engines/evaluation-engine.d.ts +12 -2
- package/dist/src/modules/engines/evaluation-engine.js +78 -11
- package/dist/src/modules/engines/evaluation-engine.js.map +1 -1
- package/dist/src/modules/engines/quality-gate.d.ts +13 -5
- package/dist/src/modules/engines/quality-gate.js +39 -31
- package/dist/src/modules/engines/quality-gate.js.map +1 -1
- package/dist/src/modules/engines/routing-engine.d.ts +5 -3
- package/dist/src/modules/engines/routing-engine.js +16 -9
- package/dist/src/modules/engines/routing-engine.js.map +1 -1
- package/dist/src/modules/gateway/cost-tracker.d.ts +5 -0
- package/dist/src/modules/gateway/cost-tracker.js +71 -0
- package/dist/src/modules/gateway/cost-tracker.js.map +1 -1
- package/dist/src/modules/gateway/gateway.d.ts +4 -0
- package/dist/src/modules/gateway/gateway.js +8 -0
- package/dist/src/modules/gateway/gateway.js.map +1 -1
- package/dist/src/shared/audit.js +3 -2
- package/dist/src/shared/audit.js.map +1 -1
- package/dist/src/shared/pii.d.ts +41 -9
- package/dist/src/shared/pii.js +122 -31
- package/dist/src/shared/pii.js.map +1 -1
- package/dist/src/shared/storage.d.ts +62 -0
- package/dist/src/shared/storage.js +47 -7
- package/dist/src/shared/storage.js.map +1 -1
- package/dist/tests/embedding.test.d.ts +1 -0
- package/dist/tests/embedding.test.js +82 -0
- package/dist/tests/embedding.test.js.map +1 -0
- package/dist/tests/engines/evaluation-engine.test.d.ts +1 -0
- package/dist/tests/engines/evaluation-engine.test.js +71 -0
- package/dist/tests/engines/evaluation-engine.test.js.map +1 -0
- package/dist/tests/engines/planning-engine.test.d.ts +1 -0
- package/dist/tests/engines/planning-engine.test.js +100 -0
- package/dist/tests/engines/planning-engine.test.js.map +1 -0
- package/dist/tests/engines/quality-gate.test.d.ts +1 -0
- package/dist/tests/engines/quality-gate.test.js +59 -0
- package/dist/tests/engines/quality-gate.test.js.map +1 -0
- package/dist/tests/engines/risk-engine.test.d.ts +1 -0
- package/dist/tests/engines/risk-engine.test.js +80 -0
- package/dist/tests/engines/risk-engine.test.js.map +1 -0
- package/dist/tests/engines/routing-engine.test.d.ts +1 -0
- package/dist/tests/engines/routing-engine.test.js +78 -0
- package/dist/tests/engines/routing-engine.test.js.map +1 -0
- package/dist/tests/integration/hermes_locking.test.js +1 -0
- package/dist/tests/integration/hermes_locking.test.js.map +1 -1
- package/dist/tests/modules/engines/agent-executor.test.js +1 -1
- package/dist/tests/modules/engines/agent-executor.test.js.map +1 -1
- package/dist/tests/modules/gateway/cost-tracker.test.js +20 -1
- package/dist/tests/modules/gateway/cost-tracker.test.js.map +1 -1
- package/dist/tests/shared/lock.test.d.ts +1 -0
- package/dist/tests/shared/lock.test.js +95 -0
- package/dist/tests/shared/lock.test.js.map +1 -0
- package/dist/tests/shared/pii.test.d.ts +1 -0
- package/dist/tests/shared/pii.test.js +183 -0
- package/dist/tests/shared/pii.test.js.map +1 -0
- package/framework-mcp/README.md +307 -35
- package/framework-mcp/dist/dashboard/assets/index-B_rK57vi.js +1221 -0
- package/framework-mcp/dist/dashboard/index.html +1 -1
- package/framework-mcp/dist/framework-mcp/src/index.js +988 -63
- package/framework-mcp/dist/framework-mcp/src/tools/compliance/mask_pii.js +55 -0
- package/framework-mcp/dist/framework-mcp/src/tools/definitions.js +10 -9
- package/framework-mcp/dist/framework-mcp/src/tools/framework/run_tests.js +25 -0
- package/framework-mcp/dist/framework-mcp/src/tools/index.js +7 -8
- package/framework-mcp/dist/framework-mcp/src/tools/messaging/approve_operation.js +80 -0
- package/framework-mcp/dist/framework-mcp/src/tools/messaging/ask_human.js +89 -20
- package/framework-mcp/dist/framework-mcp/src/tools/observability/check_ports.js +24 -8
- package/framework-mcp/dist/framework-mcp/src/tools/quality/analyze_code_quality.js +13 -64
- package/framework-mcp/dist/framework-mcp/src/tools/schemas.js +18 -22
- package/framework-mcp/dist/framework-mcp/src/tools/shell/run_command.js +146 -90
- package/framework-mcp/dist/framework-mcp/src/tools/types.js +1 -0
- package/framework-mcp/dist/framework-mcp/src/utils/auth.js +78 -0
- package/framework-mcp/dist/framework-mcp/src/utils/auto-rollback.js +265 -0
- package/framework-mcp/dist/framework-mcp/src/utils/compliance.js +12 -4
- package/framework-mcp/dist/framework-mcp/src/utils/context-optimizer.js +156 -0
- package/framework-mcp/dist/framework-mcp/src/utils/discipline.js +217 -0
- package/framework-mcp/dist/framework-mcp/src/utils/finops.js +325 -0
- package/framework-mcp/dist/framework-mcp/src/utils/human-in-loop.js +215 -0
- package/framework-mcp/dist/framework-mcp/src/utils/license-scanner.js +283 -0
- package/framework-mcp/dist/framework-mcp/src/utils/loop-detector.js +361 -0
- package/framework-mcp/dist/framework-mcp/src/utils/quality.js +124 -0
- package/framework-mcp/dist/framework-mcp/src/utils/rules-engine.js +231 -0
- package/framework-mcp/dist/framework-mcp/src/utils/silent-router.js +154 -0
- package/framework-mcp/dist/framework-mcp/src/utils/storage.js +2 -2
- package/framework-mcp/dist/framework-mcp/src/utils/telemetry-streamer.js +396 -0
- package/framework-mcp/dist/src/cli/platforms/core.js +72 -0
- package/framework-mcp/dist/src/cli/platforms/index.js +5 -0
- package/framework-mcp/dist/src/cli/platforms/paths.js +101 -0
- package/framework-mcp/dist/src/cli/platforms/scaffold.js +72 -0
- package/framework-mcp/dist/src/cli/platforms/utils.js +75 -0
- package/framework-mcp/dist/src/cli/utils/claude.js +57 -0
- package/framework-mcp/dist/src/cli/utils/compliance.js +12 -0
- package/framework-mcp/dist/src/cli/utils/fs.js +138 -0
- package/framework-mcp/dist/src/cli/utils/pkg.js +282 -0
- package/framework-mcp/dist/src/cli/utils/quality.js +9 -0
- package/framework-mcp/dist/src/modules/agents/definitions.js +563 -0
- package/framework-mcp/dist/src/modules/agents/registry/analyst.js +39 -0
- package/framework-mcp/dist/src/modules/agents/registry/architect.js +42 -0
- package/framework-mcp/dist/src/modules/agents/registry/backend.js +50 -0
- package/framework-mcp/dist/src/modules/agents/registry/database.js +45 -0
- package/framework-mcp/dist/src/modules/agents/registry/devops.js +45 -0
- package/framework-mcp/dist/src/modules/agents/registry/explorer.js +37 -0
- package/framework-mcp/dist/src/modules/agents/registry/frontend.js +52 -0
- package/framework-mcp/dist/src/modules/agents/registry/git.js +36 -0
- package/framework-mcp/dist/src/modules/agents/registry/manager.js +64 -0
- package/framework-mcp/dist/src/modules/agents/registry/mobile.js +41 -0
- package/framework-mcp/dist/src/modules/agents/registry/native.js +41 -0
- package/framework-mcp/dist/src/modules/agents/registry/quality.js +43 -0
- package/framework-mcp/dist/src/modules/agents/registry/security.js +42 -0
- package/framework-mcp/dist/src/modules/agents/types.js +1 -0
- package/framework-mcp/dist/src/modules/engines/evaluation-engine.js +169 -0
- package/framework-mcp/dist/src/modules/engines/quality-gate.js +92 -0
- package/framework-mcp/dist/src/modules/engines/risk-engine.js +105 -0
- package/framework-mcp/dist/src/modules/engines/routing-engine.js +408 -0
- package/framework-mcp/dist/src/modules/engines/types.js +1 -0
- package/framework-mcp/dist/src/modules/providers/definitions.js +140 -0
- package/framework-mcp/dist/src/modules/providers/registry.js +18 -0
- package/framework-mcp/dist/src/modules/providers/shared.js +104 -0
- package/framework-mcp/dist/src/modules/providers/types.js +1 -0
- package/framework-mcp/dist/src/modules/skills/adapter-skills.js +449 -0
- package/framework-mcp/dist/src/modules/skills/definitions.js +70 -0
- package/framework-mcp/dist/src/shared/audit.js +220 -0
- package/framework-mcp/dist/src/shared/errors.js +68 -0
- package/framework-mcp/dist/src/shared/lock.js +159 -0
- package/framework-mcp/dist/src/shared/pii.js +122 -31
- package/framework-mcp/dist/src/shared/retention.js +170 -0
- package/framework-mcp/dist/src/shared/storage.js +47 -7
- package/framework-mcp/dist/src/shared/string.js +29 -0
- package/framework-mcp/package.json +8 -2
- package/mcp.json +12 -6
- package/package.json +11 -5
- package/templates/full/ATABEY_FULL.md +1 -1
- package/framework-mcp/dist/dashboard/assets/index-BAPGLiWE.js +0 -267
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
6
|
import fs from "fs";
|
|
7
|
+
import http from "http";
|
|
3
8
|
import path from "path";
|
|
4
9
|
import { fileURLToPath } from "url";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
10
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
11
|
+
import { generateULID } from "../../src/cli/utils/time.js";
|
|
12
|
+
import { Audit } from "../../src/shared/audit.js";
|
|
13
|
+
import { maskText, maskToolArgs, maskToolResult } from "../../src/shared/pii.js";
|
|
14
|
+
import { Storage } from "../../src/shared/storage.js";
|
|
9
15
|
import { RESOURCES, handleReadResource } from "./resources/index.js";
|
|
10
|
-
|
|
16
|
+
import { TOOLS, toolHandlers, toolSchemas } from "./tools/index.js";
|
|
17
|
+
// ─── Paths ────────────────────────────────────────────────────────
|
|
11
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
//
|
|
19
|
+
// ─── Friendly Startup Validation ──────────────────────────────────
|
|
20
|
+
function validateEnvironment() {
|
|
21
|
+
// Node.js version check
|
|
22
|
+
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
23
|
+
if (nodeMajor < 18) {
|
|
24
|
+
process.stderr.write(`
|
|
25
|
+
╔══════════════════════════════════════════════════════════╗
|
|
26
|
+
║ ⚠️ Unsupported Node.js version ║
|
|
27
|
+
╠══════════════════════════════════════════════════════════╣
|
|
28
|
+
║ Atabey MCP requires Node.js >= 18.0.0 ║
|
|
29
|
+
║ ║
|
|
30
|
+
║ Current version: ${process.versions.node} ║
|
|
31
|
+
║ ║
|
|
32
|
+
║ 📋 To fix this: ║
|
|
33
|
+
║ nvm install 18 && nvm use 18 (if using nvm) ║
|
|
34
|
+
║ brew install node (if using Homebrew) ║
|
|
35
|
+
║ https://nodejs.org (official installer) ║
|
|
36
|
+
╚══════════════════════════════════════════════════════════╝
|
|
37
|
+
`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
13
41
|
function findPackageJson(startDir) {
|
|
14
42
|
let currentDir = startDir;
|
|
15
43
|
while (currentDir !== path.parse(currentDir).root) {
|
|
@@ -18,24 +46,92 @@ function findPackageJson(startDir) {
|
|
|
18
46
|
return pkgPath;
|
|
19
47
|
currentDir = path.dirname(currentDir);
|
|
20
48
|
}
|
|
21
|
-
|
|
49
|
+
process.stderr.write(`
|
|
50
|
+
╔══════════════════════════════════════════════════════════╗
|
|
51
|
+
║ 🚧 Package not found ║
|
|
52
|
+
╠══════════════════════════════════════════════════════════╣
|
|
53
|
+
║ Atabey MCP package.json could not be located. ║
|
|
54
|
+
║ ║
|
|
55
|
+
║ This usually means: ║
|
|
56
|
+
║ 1. The package was not installed correctly ║
|
|
57
|
+
║ 2. Node modules are missing ║
|
|
58
|
+
║ ║
|
|
59
|
+
║ 📋 To fix this: ║
|
|
60
|
+
║ npm install -g atabey (global install) ║
|
|
61
|
+
║ npm install (local install) ║
|
|
62
|
+
║ npm run build (if developing) ║
|
|
63
|
+
╚══════════════════════════════════════════════════════════╝
|
|
64
|
+
`);
|
|
65
|
+
process.exit(1);
|
|
22
66
|
}
|
|
67
|
+
validateEnvironment();
|
|
23
68
|
const pkgPath = findPackageJson(__dirname);
|
|
24
69
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
25
70
|
const serverVersion = pkg.version;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
71
|
+
// Validate environment variables with friendly messages
|
|
72
|
+
const PROJECT_ROOT = process.env.ATABEY_PROJECT_ROOT || process.cwd();
|
|
73
|
+
if (!process.env.ATABEY_PROJECT_ROOT) {
|
|
74
|
+
const transportMode = process.env.MCP_TRANSPORT || "unified";
|
|
75
|
+
if (transportMode !== "stdio") {
|
|
76
|
+
process.stderr.write(`
|
|
77
|
+
╔══════════════════════════════════════════════════════════╗
|
|
78
|
+
║ 💡 Tip: Set ATABEY_PROJECT_ROOT ║
|
|
79
|
+
╠══════════════════════════════════════════════════════════╣
|
|
80
|
+
║ In HTTP/SSE mode, it's recommended to explicitly set ║
|
|
81
|
+
║ the project root to avoid confusion. ║
|
|
82
|
+
║ ║
|
|
83
|
+
║ Example: ║
|
|
84
|
+
║ export ATABEY_PROJECT_ROOT=/path/to/your/project ║
|
|
85
|
+
║ ║
|
|
86
|
+
║ Using current directory: ${process.cwd()} ║
|
|
87
|
+
╚══════════════════════════════════════════════════════════╝
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const FRAMEWORK_DIR = process.env.ATABEY_FRAMEWORK_DIR || path.join(PROJECT_ROOT, ".atabey");
|
|
92
|
+
const UI_DIST_PATH = path.join(__dirname, "../../dashboard/dist");
|
|
93
|
+
// ─── Ports ────────────────────────────────────────────────────────
|
|
94
|
+
const PORT = parseInt(process.env.MCP_PORT || "5858", 10);
|
|
95
|
+
if (isNaN(PORT) || PORT < 1 || PORT > 65535) {
|
|
96
|
+
process.stderr.write(`
|
|
97
|
+
╔══════════════════════════════════════════════════════════╗
|
|
98
|
+
║ ❌ Invalid MCP_PORT ║
|
|
99
|
+
╠══════════════════════════════════════════════════════════╣
|
|
100
|
+
║ The port must be a number between 1 and 65535. ║
|
|
101
|
+
║ ║
|
|
102
|
+
║ You set: MCP_PORT=${process.env.MCP_PORT || "(empty)"} ║
|
|
103
|
+
║ ║
|
|
104
|
+
║ 📋 To fix this: ║
|
|
105
|
+
║ export MCP_PORT=5858 (default) ║
|
|
106
|
+
║ export MCP_PORT=8080 (alternative) ║
|
|
107
|
+
╚══════════════════════════════════════════════════════════╝
|
|
108
|
+
`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const HOST = process.env.MCP_HOST || "0.0.0.0";
|
|
112
|
+
// ─── MCP Server ───────────────────────────────────────────────────
|
|
113
|
+
const server = new Server({ name: "atabey-mcp", version: serverVersion }, { capabilities: { tools: {}, resources: {} } });
|
|
114
|
+
// Track multiple SSE sessions
|
|
115
|
+
const sessions = new Map();
|
|
116
|
+
// ─── WebSocket Clients ────────────────────────────────────────────
|
|
117
|
+
const wsClients = new Set();
|
|
118
|
+
function broadcastWS(type, payload) {
|
|
119
|
+
const msg = JSON.stringify({ type, payload });
|
|
120
|
+
wsClients.forEach((ws) => {
|
|
121
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
122
|
+
try {
|
|
123
|
+
ws.send(msg);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
wsClients.delete(ws);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
wsClients.delete(ws);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// ─── Validation ───────────────────────────────────────────────────
|
|
39
135
|
function validateArgs(toolName, args) {
|
|
40
136
|
const schema = toolSchemas[toolName];
|
|
41
137
|
if (schema) {
|
|
@@ -45,7 +141,6 @@ function validateArgs(toolName, args) {
|
|
|
45
141
|
}
|
|
46
142
|
return null;
|
|
47
143
|
}
|
|
48
|
-
// Fallback for tools without Zod schemas yet
|
|
49
144
|
const definition = TOOLS.find(t => t.name === toolName);
|
|
50
145
|
if (!definition)
|
|
51
146
|
return `Unknown tool: ${toolName}`;
|
|
@@ -57,11 +152,11 @@ function validateArgs(toolName, args) {
|
|
|
57
152
|
}
|
|
58
153
|
return null;
|
|
59
154
|
}
|
|
155
|
+
// ─── MCP Handlers ─────────────────────────────────────────────────
|
|
60
156
|
server.setRequestHandler(ListToolsRequestSchema, async (request) => {
|
|
61
|
-
// 2026 Stateless Spec: Log client info from metadata if available
|
|
62
157
|
const meta = request._meta;
|
|
63
158
|
if (meta) {
|
|
64
|
-
process.stderr.write(`[MCP]
|
|
159
|
+
process.stderr.write(`[MCP] ListTools from ${meta.client?.name || "unknown"} v${meta.client?.version || "?.?"}\n`);
|
|
65
160
|
}
|
|
66
161
|
return { tools: TOOLS };
|
|
67
162
|
});
|
|
@@ -72,15 +167,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
72
167
|
const uri = request.params.uri;
|
|
73
168
|
try {
|
|
74
169
|
const content = await handleReadResource(uri);
|
|
75
|
-
return {
|
|
76
|
-
contents: [
|
|
77
|
-
{
|
|
78
|
-
uri,
|
|
79
|
-
mimeType: "text/markdown",
|
|
80
|
-
text: content,
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
};
|
|
170
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: content }] };
|
|
84
171
|
}
|
|
85
172
|
catch (error) {
|
|
86
173
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -91,67 +178,905 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
91
178
|
const req = request;
|
|
92
179
|
const { name, arguments: args } = req.params;
|
|
93
180
|
const meta = request._meta;
|
|
94
|
-
// 2026 Stateless Spec: Prioritize metadata-driven context
|
|
95
181
|
if (meta) {
|
|
96
|
-
process.stderr.write(`[MCP]
|
|
182
|
+
process.stderr.write(`[MCP] CallTool: ${name} (Client: ${meta.client?.name || "unknown"})\n`);
|
|
97
183
|
}
|
|
98
|
-
const projectRoot = process.env.ATABEY_PROJECT_ROOT || process.cwd();
|
|
99
184
|
try {
|
|
100
185
|
const handler = toolHandlers[name];
|
|
101
186
|
if (!handler) {
|
|
102
|
-
return {
|
|
103
|
-
isError: true,
|
|
104
|
-
content: [{ type: "text", text: `[ERROR] Unknown tool: ${name}` }],
|
|
105
|
-
};
|
|
187
|
+
return { isError: true, content: [{ type: "text", text: `[ERROR] Unknown tool: ${name}` }] };
|
|
106
188
|
}
|
|
107
|
-
// [SECURITY] Runtime Validation
|
|
108
189
|
const validationError = validateArgs(name, args || {});
|
|
109
190
|
if (validationError) {
|
|
110
|
-
return {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
191
|
+
return { isError: true, content: [{ type: "text", text: `[ERROR] Validation Error: ${validationError}` }] };
|
|
192
|
+
}
|
|
193
|
+
// [PII/GDPR/KVKK] Mask args before handler
|
|
194
|
+
const maskedArgs = maskToolArgs(args || {});
|
|
195
|
+
// [TOKEN ECONOMY] Log token usage
|
|
196
|
+
const argsJson = JSON.stringify(maskedArgs);
|
|
197
|
+
const estimatedTokens = Math.ceil(argsJson.length / 4);
|
|
198
|
+
const clientAgent = meta?.client?.name || "mcp-client";
|
|
199
|
+
try {
|
|
200
|
+
const { Metrics } = await import("./utils/metrics.js");
|
|
201
|
+
Metrics.logUsage(PROJECT_ROOT, clientAgent, name, estimatedTokens);
|
|
202
|
+
}
|
|
203
|
+
catch { /* metrics should not block */ }
|
|
204
|
+
// [GOVERNANCE] Validate arguments against governance rules (pre-execution)
|
|
205
|
+
try {
|
|
206
|
+
const { validateArgsAgainstRules } = await import("./utils/rules-engine.js");
|
|
207
|
+
const governanceError = validateArgsAgainstRules(name, maskedArgs);
|
|
208
|
+
if (governanceError) {
|
|
209
|
+
process.stderr.write(`[GOVERNANCE] Blocked: ${governanceError}\n`);
|
|
210
|
+
broadcastWS("governance_violation", { agent: clientAgent, tool: name, error: governanceError, timestamp: new Date().toISOString() });
|
|
211
|
+
return { isError: true, content: [{ type: "text", text: governanceError }] };
|
|
212
|
+
}
|
|
114
213
|
}
|
|
115
|
-
|
|
214
|
+
catch { /* governance should not block */ }
|
|
215
|
+
// [DISCIPLINE] Enforce AI discipline at tool level BEFORE execution
|
|
216
|
+
try {
|
|
217
|
+
const { enforceDiscipline } = await import("./utils/discipline.js");
|
|
218
|
+
const disciplineError = await enforceDiscipline(clientAgent, name, maskedArgs);
|
|
219
|
+
if (disciplineError) {
|
|
220
|
+
process.stderr.write(`[DISCIPLINE] Blocked: ${disciplineError}\n`);
|
|
221
|
+
broadcastWS("discipline_violation", { agent: clientAgent, tool: name, error: disciplineError, timestamp: new Date().toISOString() });
|
|
222
|
+
return { isError: true, content: [{ type: "text", text: `[DISCIPLINE] ${disciplineError}` }] };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch { /* discipline should not block execution if module fails */ }
|
|
226
|
+
// [SILENT ROUTER] Detect agent from context and inject rules
|
|
227
|
+
const traceId = generateULID();
|
|
228
|
+
let detectedAgent = clientAgent;
|
|
229
|
+
try {
|
|
230
|
+
const { detectAgent, stealthNotify } = await import("./utils/silent-router.js");
|
|
231
|
+
detectedAgent = detectAgent(name, maskedArgs);
|
|
232
|
+
if (detectedAgent !== clientAgent) {
|
|
233
|
+
stealthNotify(detectedAgent, name, `Silently routed from ${clientAgent}`);
|
|
234
|
+
process.stderr.write(`[SILENT ROUTER] ${name} → ${detectedAgent} (from ${clientAgent})\n`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch { /* silent router should not block */ }
|
|
238
|
+
// [LOOP DETECTION] Check for infinite loop patterns
|
|
239
|
+
try {
|
|
240
|
+
const { recordAndCheck } = await import("./utils/loop-detector.js");
|
|
241
|
+
const loopAlert = recordAndCheck(detectedAgent, name, maskedArgs);
|
|
242
|
+
if (loopAlert) {
|
|
243
|
+
process.stderr.write(`[LOOP DETECT] ${loopAlert.type}: ${loopAlert.detail}\n`);
|
|
244
|
+
broadcastWS("loop_detected", { agent: detectedAgent, tool: name, alert: loopAlert, timestamp: new Date().toISOString() });
|
|
245
|
+
if (loopAlert.severity === "critical" && loopAlert.cooldownUntil) {
|
|
246
|
+
const remaining = Math.ceil((loopAlert.cooldownUntil - Date.now()) / 1000);
|
|
247
|
+
return {
|
|
248
|
+
isError: true,
|
|
249
|
+
content: [{ type: "text", text: `[LOOP DETECTED] ${loopAlert.detail} Cooldown: ${remaining}s. Please change your approach.` }]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch { /* loop detection should not block */ }
|
|
255
|
+
// [FINOPS] Check budget BEFORE execution (record usage)
|
|
256
|
+
try {
|
|
257
|
+
const { budgetManager } = await import("./utils/finops.js");
|
|
258
|
+
const budgetError = budgetManager.recordUsage(detectedAgent, estimatedTokens);
|
|
259
|
+
if (budgetError) {
|
|
260
|
+
process.stderr.write(`[FINOPS] Budget blocked: ${budgetError}\n`);
|
|
261
|
+
broadcastWS("budget_blocked", { agent: detectedAgent, tool: name, error: budgetError, timestamp: new Date().toISOString() });
|
|
262
|
+
return { isError: true, content: [{ type: "text", text: budgetError }] };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch { /* finops should not block */ }
|
|
266
|
+
// [LICENSE] Validate write content for license compliance (pre-execution)
|
|
267
|
+
if (name === "write_file" || name === "replace_text" || name === "patch_file") {
|
|
268
|
+
try {
|
|
269
|
+
const { validateLicenseCompliance } = await import("./utils/license-scanner.js");
|
|
270
|
+
const filePath = maskedArgs.path || "";
|
|
271
|
+
const content = maskedArgs.content || "";
|
|
272
|
+
if (filePath && content) {
|
|
273
|
+
const licenseError = validateLicenseCompliance(filePath, content);
|
|
274
|
+
if (licenseError) {
|
|
275
|
+
process.stderr.write(`[LICENSE] Blocked: ${licenseError}\n`);
|
|
276
|
+
broadcastWS("license_violation", { agent: detectedAgent, tool: name, error: licenseError, timestamp: new Date().toISOString() });
|
|
277
|
+
return { isError: true, content: [{ type: "text", text: licenseError }] };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch { /* license scan should not block */ }
|
|
282
|
+
}
|
|
283
|
+
// [AUTO-ROLLBACK] Prepare snapshot for write operations
|
|
284
|
+
if (name === "write_file" || name === "replace_text" || name === "patch_file") {
|
|
285
|
+
try {
|
|
286
|
+
const { AutoRollbackEngine } = await import("./utils/auto-rollback.js");
|
|
287
|
+
const filePath = maskedArgs.path || "";
|
|
288
|
+
if (filePath) {
|
|
289
|
+
AutoRollbackEngine.prepareWrite(filePath, traceId);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch { /* auto-rollback should not block */ }
|
|
293
|
+
}
|
|
294
|
+
// [HUMAN-IN-LOOP] Check risk gate before execution
|
|
295
|
+
try {
|
|
296
|
+
const { RiskEngine } = await import("../../src/modules/engines/risk-engine.js");
|
|
297
|
+
const riskContext = `${name} ${JSON.stringify(maskedArgs)}`;
|
|
298
|
+
const riskResult = RiskEngine.assessTaskRisk(riskContext);
|
|
299
|
+
if (riskResult.totalScore > 0) {
|
|
300
|
+
const { checkRiskGate } = await import("./utils/human-in-loop.js");
|
|
301
|
+
const riskReason = riskResult.factors.map(f => f.description).join("; ") || "High-risk operation detected";
|
|
302
|
+
const gateResult = checkRiskGate(traceId, name, detectedAgent, riskResult.totalScore, riskReason, maskedArgs);
|
|
303
|
+
if (gateResult?.blocked) {
|
|
304
|
+
process.stderr.write(`[RISK GATE] Blocked: ${riskReason} (score: ${riskResult.totalScore})\n`);
|
|
305
|
+
broadcastWS("risk_blocked", { agent: detectedAgent, tool: name, riskScore: riskResult.totalScore, traceId, timestamp: new Date().toISOString() });
|
|
306
|
+
return { isError: true, content: [{ type: "text", text: gateResult.message }] };
|
|
307
|
+
}
|
|
308
|
+
if (gateResult?.warning) {
|
|
309
|
+
process.stderr.write(`[RISK WARNING] ${gateResult.warning}\n`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch { /* risk gate should not block */ }
|
|
314
|
+
// Execute
|
|
315
|
+
const result = await handler(PROJECT_ROOT, maskedArgs);
|
|
316
|
+
// [AUTO-ROLLBACK] Check write results for governance violations
|
|
317
|
+
if (name === "write_file" || name === "replace_text" || name === "patch_file") {
|
|
318
|
+
try {
|
|
319
|
+
const filePath = maskedArgs.path || "";
|
|
320
|
+
const content = maskedArgs.content || "";
|
|
321
|
+
if (filePath && content) {
|
|
322
|
+
const { AutoRollbackEngine } = await import("./utils/auto-rollback.js");
|
|
323
|
+
const { scanFileForViolations } = await import("./utils/rules-engine.js");
|
|
324
|
+
const violations = scanFileForViolations(filePath, content);
|
|
325
|
+
if (violations.length > 0) {
|
|
326
|
+
const violationRecords = violations.map(v => ({
|
|
327
|
+
rule: v.rule.name,
|
|
328
|
+
severity: v.rule.priority,
|
|
329
|
+
filePath,
|
|
330
|
+
line: v.line,
|
|
331
|
+
detail: `Line ${v.line}: ${v.match}`,
|
|
332
|
+
regenerateInstruction: v.rule.errorMessage,
|
|
333
|
+
}));
|
|
334
|
+
const rolledBack = AutoRollbackEngine.validateAndRollback(filePath, content, violationRecords);
|
|
335
|
+
if (rolledBack) {
|
|
336
|
+
const instruction = AutoRollbackEngine.buildRegenerateInstruction(rolledBack, name);
|
|
337
|
+
broadcastWS("rollback_violation", { agent: detectedAgent, tool: name, violations: rolledBack, timestamp: new Date().toISOString() });
|
|
338
|
+
return { isError: true, content: [{ type: "text", text: instruction }] };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch { /* auto-rollback should not block */ }
|
|
344
|
+
}
|
|
345
|
+
// [DISCIPLINE] Validate response content BEFORE returning to AI
|
|
346
|
+
try {
|
|
347
|
+
const { validateResponse } = await import("./utils/discipline.js");
|
|
348
|
+
const responseError = validateResponse(name, result);
|
|
349
|
+
if (responseError) {
|
|
350
|
+
process.stderr.write(`[DISCIPLINE] Response blocked: ${responseError}\n`);
|
|
351
|
+
broadcastWS("discipline_violation", { agent: detectedAgent, tool: name, error: responseError, timestamp: new Date().toISOString() });
|
|
352
|
+
return { isError: true, content: [{ type: "text", text: `[DISCIPLINE] ${responseError}` }] };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch { /* response validation should not block */ }
|
|
356
|
+
// [GOVERNANCE] Validate response against governance rules (post-execution)
|
|
357
|
+
try {
|
|
358
|
+
const { validateResponseAgainstRules } = await import("./utils/rules-engine.js");
|
|
359
|
+
const responseText = result.content?.filter(b => b.type === "text").map(b => b.text).join(" ") || "";
|
|
360
|
+
const govResponseError = validateResponseAgainstRules(name, responseText);
|
|
361
|
+
if (govResponseError) {
|
|
362
|
+
process.stderr.write(`[GOVERNANCE] Response violation: ${govResponseError}\n`);
|
|
363
|
+
broadcastWS("governance_violation", { agent: detectedAgent, tool: name, error: govResponseError, timestamp: new Date().toISOString() });
|
|
364
|
+
// Don't block the response, just warn
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch { /* governance response validation should not block */ }
|
|
368
|
+
// [CONTEXT OPTIMIZER] Check token budget after execution
|
|
369
|
+
try {
|
|
370
|
+
const { checkTokenBudget } = await import("./utils/context-optimizer.js");
|
|
371
|
+
const responseText = result.content?.filter(b => b.type === "text").map(b => b.text).join(" ") || "";
|
|
372
|
+
const budgetError = checkTokenBudget(detectedAgent, name, responseText);
|
|
373
|
+
if (budgetError) {
|
|
374
|
+
process.stderr.write(`[TOKEN BUDGET] Warning: ${budgetError}\n`);
|
|
375
|
+
broadcastWS("token_budget_warning", { agent: detectedAgent, tool: name, error: budgetError, timestamp: new Date().toISOString() });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch { /* token budget should not block */ }
|
|
379
|
+
// [SILENT ROUTER] Build silent context injection
|
|
380
|
+
try {
|
|
381
|
+
const { buildSilentContext } = await import("./utils/silent-router.js");
|
|
382
|
+
if (result.content) {
|
|
383
|
+
for (let i = 0; i < result.content.length; i++) {
|
|
384
|
+
const block = result.content[i];
|
|
385
|
+
if (block.type === "text") {
|
|
386
|
+
result.content[i] = {
|
|
387
|
+
...block,
|
|
388
|
+
text: await buildSilentContext(detectedAgent, name, block.text),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
catch { /* silent context injection should not block */ }
|
|
395
|
+
// Broadcast to dashboard WS
|
|
396
|
+
broadcastWS("tool_call", { agent: detectedAgent, action: name, tokens: estimatedTokens, traceId, timestamp: new Date().toISOString() });
|
|
397
|
+
// [PII/GDPR/KVKK] Mask result before returning to AI
|
|
398
|
+
return maskToolResult(result);
|
|
116
399
|
}
|
|
117
400
|
catch (error) {
|
|
118
401
|
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
119
|
-
return {
|
|
120
|
-
isError: true,
|
|
121
|
-
content: [{ type: "text", text: `[ERROR] Execution failed: ${message}` }],
|
|
122
|
-
};
|
|
402
|
+
return { isError: true, content: [{ type: "text", text: `[ERROR] Execution failed: ${message}` }] };
|
|
123
403
|
}
|
|
124
404
|
});
|
|
125
|
-
// ───
|
|
405
|
+
// ─── API Helpers ──────────────────────────────────────────────────
|
|
406
|
+
function serveJson(res, statusCode, data) {
|
|
407
|
+
res.writeHead(statusCode, { "Content-Type": "application/json" });
|
|
408
|
+
res.end(JSON.stringify(data));
|
|
409
|
+
}
|
|
410
|
+
function serveFile(res, filePath, contentType) {
|
|
411
|
+
if (fs.existsSync(filePath)) {
|
|
412
|
+
try {
|
|
413
|
+
const content = fs.readFileSync(filePath);
|
|
414
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
415
|
+
res.end(content);
|
|
416
|
+
}
|
|
417
|
+
catch (e) {
|
|
418
|
+
res.writeHead(500);
|
|
419
|
+
res.end(e.message);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
res.writeHead(404);
|
|
424
|
+
res.end("Not Found");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function parseUrl(url) {
|
|
428
|
+
const [pathname, queryString] = url.split("?");
|
|
429
|
+
const params = {};
|
|
430
|
+
if (queryString) {
|
|
431
|
+
queryString.split("&").forEach((pair) => {
|
|
432
|
+
const [key, value] = pair.split("=");
|
|
433
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value || "");
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return { pathname, params };
|
|
437
|
+
}
|
|
438
|
+
// ─── Unified HTTP Server ──────────────────────────────────────────
|
|
439
|
+
function createUnifiedServer() {
|
|
440
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
441
|
+
const url = req.url || "/";
|
|
442
|
+
const { pathname, params } = parseUrl(url);
|
|
443
|
+
const method = req.method || "GET";
|
|
444
|
+
// CORS
|
|
445
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
446
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
447
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
448
|
+
if (req.method === "OPTIONS") {
|
|
449
|
+
res.writeHead(204);
|
|
450
|
+
res.end();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
// [SECURITY] Authentication check for API/MCP routes
|
|
454
|
+
// Skip auth for static UI and health endpoints
|
|
455
|
+
const needsAuth = pathname.startsWith("/api/") || pathname.startsWith("/mcp/");
|
|
456
|
+
const isPublicPath = pathname === "/api/health" || pathname === "/mcp/health" || !pathname.startsWith("/api/") && !pathname.startsWith("/mcp/");
|
|
457
|
+
if (needsAuth && !isPublicPath) {
|
|
458
|
+
const { authenticate } = await import("./utils/auth.js");
|
|
459
|
+
const auth = authenticate(req);
|
|
460
|
+
if (!auth.authenticated) {
|
|
461
|
+
process.stderr.write(`[AUTH] Unauthorized request: ${pathname}\n`);
|
|
462
|
+
serveJson(res, 401, { error: "Unauthorized. Provide Authorization: Bearer <token> header. Set MCP_AUTH_TOKEN env var to configure." });
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// ───────────── MCP ROUTES (prefix: /mcp) ─────────────
|
|
467
|
+
if (pathname === "/mcp/health") {
|
|
468
|
+
serveJson(res, 200, { status: "ok", version: serverVersion, sessions: sessions.size });
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (pathname === "/mcp/sse") {
|
|
472
|
+
const transport = new SSEServerTransport("/mcp/messages", res);
|
|
473
|
+
const sessionId = transport.sessionId;
|
|
474
|
+
sessions.set(sessionId, transport);
|
|
475
|
+
process.stderr.write(`[MCP] Client connected: ${sessionId} (total: ${sessions.size})\n`);
|
|
476
|
+
broadcastWS("mcp_session", { sessionId, action: "connected", total: sessions.size });
|
|
477
|
+
res.on("close", () => {
|
|
478
|
+
sessions.delete(sessionId);
|
|
479
|
+
process.stderr.write(`[MCP] Client disconnected: ${sessionId} (remaining: ${sessions.size})\n`);
|
|
480
|
+
broadcastWS("mcp_session", { sessionId, action: "disconnected", total: sessions.size });
|
|
481
|
+
});
|
|
482
|
+
await server.connect(transport);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (pathname === "/mcp/messages") {
|
|
486
|
+
const sessionId = params.sessionId;
|
|
487
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
488
|
+
serveJson(res, 400, { error: "Invalid or missing sessionId" });
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const transport = sessions.get(sessionId);
|
|
492
|
+
await transport.handlePostMessage(req, res);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
// ───────────── DASHBOARD API ROUTES (/api) ───────────
|
|
496
|
+
// Health
|
|
497
|
+
if (pathname === "/api/health") {
|
|
498
|
+
serveJson(res, 200, { status: "healthy", version: serverVersion, frameworkDir: FRAMEWORK_DIR });
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
// Status
|
|
502
|
+
if (pathname === "/api/status") {
|
|
503
|
+
const statusPath = path.join(FRAMEWORK_DIR, "memory", "status.json");
|
|
504
|
+
if (fs.existsSync(statusPath)) {
|
|
505
|
+
serveFile(res, statusPath, "application/json");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
serveJson(res, 200, { success: true, data: { status: "initializing" } });
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
// Memory (PROJECT_MEMORY.md)
|
|
512
|
+
if (pathname === "/api/memory") {
|
|
513
|
+
const memoryPath = path.join(FRAMEWORK_DIR, "memory", "PROJECT_MEMORY.md");
|
|
514
|
+
if (fs.existsSync(memoryPath)) {
|
|
515
|
+
serveFile(res, memoryPath, "text/markdown");
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
serveJson(res, 200, { success: true, data: "No memory file yet." });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
// Memory Search
|
|
522
|
+
if (pathname === "/api/memory/search") {
|
|
523
|
+
try {
|
|
524
|
+
const query = params.q || "";
|
|
525
|
+
const limit = parseInt(params.limit || "10", 10);
|
|
526
|
+
if (!query) {
|
|
527
|
+
const db = Storage.getDB();
|
|
528
|
+
const rows = db.prepare("SELECT * FROM vector_memory ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
529
|
+
// [KVKK/GDPR] Mask PII in memory content before returning
|
|
530
|
+
const results = rows.map(row => {
|
|
531
|
+
const meta = JSON.parse(row.metadata || "{}");
|
|
532
|
+
return { id: row.id, content: maskText(row.content), type: row.category || meta.category || "memory", timestamp: meta.createdAt || row.created_at, relevance: 1.0 };
|
|
533
|
+
});
|
|
534
|
+
serveJson(res, 200, { success: true, data: results });
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const { CoreMemory } = await import("../../src/modules/memory/core.js");
|
|
538
|
+
const results = await CoreMemory.recall(query, { limit });
|
|
539
|
+
// [KVKK/GDPR] Mask PII in memory content before returning
|
|
540
|
+
const flattened = results.map(r => ({
|
|
541
|
+
id: r.entry.id, content: maskText(r.entry.content), type: r.entry.metadata.category || "memory",
|
|
542
|
+
timestamp: r.entry.metadata.createdAt || new Date().toISOString(), relevance: r.score
|
|
543
|
+
}));
|
|
544
|
+
serveJson(res, 200, { success: true, data: flattened });
|
|
545
|
+
}
|
|
546
|
+
catch (e) {
|
|
547
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
548
|
+
}
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
// Agents
|
|
552
|
+
if (pathname === "/api/agents") {
|
|
553
|
+
try {
|
|
554
|
+
const agents = Storage.getAllAgents();
|
|
555
|
+
serveJson(res, 200, { success: true, data: agents });
|
|
556
|
+
}
|
|
557
|
+
catch (e) {
|
|
558
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
// Messages (Hermes queue)
|
|
563
|
+
if (pathname === "/api/messages") {
|
|
564
|
+
try {
|
|
565
|
+
const messages = Storage.getPendingMessages();
|
|
566
|
+
serveJson(res, 200, { success: true, data: messages });
|
|
567
|
+
}
|
|
568
|
+
catch (e) {
|
|
569
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
// Hermes Stats
|
|
574
|
+
if (pathname === "/api/hermes/stats") {
|
|
575
|
+
try {
|
|
576
|
+
const messages = Storage.getPendingMessages();
|
|
577
|
+
const totalMessages = Storage.getDB().prepare("SELECT COUNT(*) as count FROM messages").get();
|
|
578
|
+
const byCategory = {};
|
|
579
|
+
const byStatus = {};
|
|
580
|
+
messages.forEach(m => {
|
|
581
|
+
byCategory[m.category] = (byCategory[m.category] || 0) + 1;
|
|
582
|
+
byStatus[m.status] = (byStatus[m.status] || 0) + 1;
|
|
583
|
+
});
|
|
584
|
+
serveJson(res, 200, {
|
|
585
|
+
success: true, data: {
|
|
586
|
+
total: totalMessages.count, pending: messages.length, byCategory, byStatus,
|
|
587
|
+
lastMessage: messages.length > 0 ? messages[messages.length - 1] : null,
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
catch (e) {
|
|
592
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
// Tasks
|
|
597
|
+
if (pathname === "/api/tasks") {
|
|
598
|
+
try {
|
|
599
|
+
const { asTraceID } = await import("../../src/shared/types.js");
|
|
600
|
+
const traceId = params.traceId ? asTraceID(params.traceId) : undefined;
|
|
601
|
+
const tasks = Storage.getTasks(traceId);
|
|
602
|
+
// [KVKK/GDPR] Mask PII in task descriptions before returning
|
|
603
|
+
const maskedTasks = tasks.map(t => ({
|
|
604
|
+
...t,
|
|
605
|
+
description: maskText(t.description),
|
|
606
|
+
}));
|
|
607
|
+
serveJson(res, 200, { success: true, data: maskedTasks });
|
|
608
|
+
}
|
|
609
|
+
catch (e) {
|
|
610
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
// Logs
|
|
615
|
+
if (pathname.startsWith("/api/logs")) {
|
|
616
|
+
try {
|
|
617
|
+
const logs = Storage.getLogs();
|
|
618
|
+
const agent = pathname.split("/api/logs/")[1];
|
|
619
|
+
const filteredLogs = agent ? logs.filter(l => l.agent === agent) : logs;
|
|
620
|
+
serveJson(res, 200, { success: true, data: filteredLogs });
|
|
621
|
+
}
|
|
622
|
+
catch (e) {
|
|
623
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
624
|
+
}
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
// Approvals
|
|
628
|
+
if (pathname === "/api/approvals") {
|
|
629
|
+
try {
|
|
630
|
+
const allMessages = Storage.getPendingMessages();
|
|
631
|
+
// [KVKK/GDPR] Mask PII in approval descriptions
|
|
632
|
+
const approvals = allMessages.filter(m => m.category === "ALERT" || m.category === "ACTION")
|
|
633
|
+
.map(m => ({
|
|
634
|
+
id: String(m.id || m.traceId), traceId: m.traceId,
|
|
635
|
+
description: m.content ? (typeof m.content === "string" ? maskText(m.content) : maskText(JSON.stringify(m.content))) : "Approval required",
|
|
636
|
+
status: m.status || "PENDING", timestamp: m.timestamp || new Date().toISOString()
|
|
637
|
+
}));
|
|
638
|
+
serveJson(res, 200, { success: true, data: approvals });
|
|
639
|
+
}
|
|
640
|
+
catch (e) {
|
|
641
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
642
|
+
}
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
// Approve
|
|
646
|
+
if (pathname.startsWith("/api/approve/") && method === "POST") {
|
|
647
|
+
const traceId = pathname.replace("/api/approve/", "");
|
|
648
|
+
if (!traceId) {
|
|
649
|
+
serveJson(res, 400, { success: false, error: "Trace ID required" });
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
const pendingMessages = Storage.getPendingMessages().filter(m => m.traceId === traceId && (m.category === "ALERT" || m.category === "ACTION"));
|
|
654
|
+
if (pendingMessages.length === 0) {
|
|
655
|
+
serveJson(res, 404, { success: false, error: "No pending approval found" });
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
pendingMessages.forEach(msg => { if (msg.id !== undefined)
|
|
659
|
+
Storage.updateMessageStatus(msg.id, "APPROVED"); });
|
|
660
|
+
broadcastWS("approval", { traceId, status: "APPROVED" });
|
|
661
|
+
serveJson(res, 200, { success: true, message: `Trace ${traceId} approved`, approved: pendingMessages.length });
|
|
662
|
+
}
|
|
663
|
+
catch (e) {
|
|
664
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
// Reject
|
|
669
|
+
if (pathname.startsWith("/api/reject/") && method === "POST") {
|
|
670
|
+
const traceId = pathname.replace("/api/reject/", "");
|
|
671
|
+
if (!traceId) {
|
|
672
|
+
serveJson(res, 400, { success: false, error: "Trace ID required" });
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
const pendingMessages = Storage.getPendingMessages().filter(m => m.traceId === traceId && (m.category === "ALERT" || m.category === "ACTION"));
|
|
677
|
+
if (pendingMessages.length === 0) {
|
|
678
|
+
serveJson(res, 404, { success: false, error: "No pending approval found" });
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
pendingMessages.forEach(msg => { if (msg.id !== undefined)
|
|
682
|
+
Storage.updateMessageStatus(msg.id, "REJECTED"); });
|
|
683
|
+
broadcastWS("approval", { traceId, status: "REJECTED" });
|
|
684
|
+
serveJson(res, 200, { success: true, message: `Trace ${traceId} rejected`, rejected: pendingMessages.length });
|
|
685
|
+
}
|
|
686
|
+
catch (e) {
|
|
687
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
688
|
+
}
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
// Audit (GDPR/KVKK)
|
|
692
|
+
if (pathname === "/api/audit") {
|
|
693
|
+
try {
|
|
694
|
+
Audit.initialize();
|
|
695
|
+
const stats = Audit.getStats();
|
|
696
|
+
const entries = Audit.query({ limit: 100 });
|
|
697
|
+
serveJson(res, 200, { success: true, data: { stats, entries } });
|
|
698
|
+
}
|
|
699
|
+
catch (e) {
|
|
700
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
701
|
+
}
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
// Audit Erase (GDPR/KVKK Right to Erasure)
|
|
705
|
+
if (pathname === "/api/audit/erase" && method === "POST") {
|
|
706
|
+
try {
|
|
707
|
+
Audit.initialize();
|
|
708
|
+
let body = "";
|
|
709
|
+
req.on("data", chunk => { body += chunk.toString(); });
|
|
710
|
+
req.on("end", () => {
|
|
711
|
+
try {
|
|
712
|
+
const parsed = JSON.parse(body);
|
|
713
|
+
const confirmation = parsed.confirmationCode || "";
|
|
714
|
+
const changes = Audit.clearAll(confirmation);
|
|
715
|
+
serveJson(res, 200, { success: true, message: `${changes} records cleared under KVKK/GDPR right to erasure.` });
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
serveJson(res, 400, { success: false, error: err.message });
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
724
|
+
}
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
// Compliance
|
|
728
|
+
if (pathname === "/api/compliance") {
|
|
729
|
+
try {
|
|
730
|
+
const { scanProjectCompliance } = await import("../../src/cli/utils/compliance.js");
|
|
731
|
+
const targetPath = params.path || "src";
|
|
732
|
+
const scanPath = path.join(PROJECT_ROOT, targetPath);
|
|
733
|
+
const rawIssues = scanProjectCompliance(scanPath);
|
|
734
|
+
const violations = rawIssues.map(issue => {
|
|
735
|
+
const ruleLower = issue.rule.toLowerCase();
|
|
736
|
+
const type = ruleLower.includes("any") ? "any-type" : ruleLower.includes("console") ? "console-log" : "other";
|
|
737
|
+
return { file: path.relative(PROJECT_ROOT, issue.file), line: issue.line, type, message: issue.rule.replace(/^\[ERROR\]\s*Corporate\s*Compliance\s*Breach:\s*/i, "") };
|
|
738
|
+
});
|
|
739
|
+
const violatingFiles = new Set(violations.map(v => v.file));
|
|
740
|
+
serveJson(res, 200, {
|
|
741
|
+
success: true, data: {
|
|
742
|
+
summary: { totalFiles: violatingFiles.size, totalViolations: violations.length, byType: { "any-type": violations.filter(v => v.type === "any-type").length, "console-log": violations.filter(v => v.type === "console-log").length } },
|
|
743
|
+
violations
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
catch (e) {
|
|
748
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
749
|
+
}
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
// Quality
|
|
753
|
+
if (pathname === "/api/quality") {
|
|
754
|
+
try {
|
|
755
|
+
const { analyzePathQuality } = await import("../../src/cli/utils/quality.js");
|
|
756
|
+
const targetPath = params.path || "src";
|
|
757
|
+
const result = analyzePathQuality(PROJECT_ROOT, targetPath);
|
|
758
|
+
serveJson(res, 200, {
|
|
759
|
+
success: true, data: {
|
|
760
|
+
totalFiles: result.totalFiles, totalIssues: result.totalIssues,
|
|
761
|
+
longFunctions: result.longFunctions, deepNesting: result.deepNesting,
|
|
762
|
+
anyTypes: result.anyTypes, issues: result.issues.slice(0, 50),
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
catch (e) {
|
|
767
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
768
|
+
}
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
// Adapter Skills
|
|
772
|
+
if (pathname === "/api/adapters/skills") {
|
|
773
|
+
try {
|
|
774
|
+
const { ADAPTER_SKILLS } = await import("../../src/modules/skills/adapter-skills.js");
|
|
775
|
+
const summary = Object.entries(ADAPTER_SKILLS).map(([id, config]) => ({
|
|
776
|
+
id, skillCount: Object.keys(config.enabledSkills).length,
|
|
777
|
+
toolsCount: config.toolMapping ? Object.keys(config.toolMapping).length : 0,
|
|
778
|
+
skills: Object.keys(config.enabledSkills),
|
|
779
|
+
}));
|
|
780
|
+
serveJson(res, 200, { success: true, data: summary });
|
|
781
|
+
}
|
|
782
|
+
catch (e) {
|
|
783
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
784
|
+
}
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
// MCP Sessions
|
|
788
|
+
if (pathname === "/api/mcp/sessions") {
|
|
789
|
+
const sessionList = Array.from(sessions.keys()).map(id => ({ id, connectedAt: new Date().toISOString() }));
|
|
790
|
+
serveJson(res, 200, { success: true, data: { total: sessions.size, sessions: sessionList } });
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
// Discipline Stats
|
|
794
|
+
if (pathname === "/api/discipline") {
|
|
795
|
+
try {
|
|
796
|
+
const { getAllDisciplineStats } = await import("./utils/discipline.js");
|
|
797
|
+
const stats = getAllDisciplineStats();
|
|
798
|
+
serveJson(res, 200, { success: true, data: stats });
|
|
799
|
+
}
|
|
800
|
+
catch (e) {
|
|
801
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
802
|
+
}
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
// Token Economy / Metrics
|
|
806
|
+
if (pathname === "/api/metrics") {
|
|
807
|
+
try {
|
|
808
|
+
const metricsPath = path.join(FRAMEWORK_DIR, "observability/metrics.json");
|
|
809
|
+
let entries = [];
|
|
810
|
+
if (fs.existsSync(metricsPath)) {
|
|
811
|
+
entries = JSON.parse(fs.readFileSync(metricsPath, "utf8"));
|
|
812
|
+
}
|
|
813
|
+
const byAgent = {};
|
|
814
|
+
const byAction = {};
|
|
815
|
+
let totalTokens = 0;
|
|
816
|
+
for (const entry of entries) {
|
|
817
|
+
const agent = entry.agent || "unknown";
|
|
818
|
+
const action = entry.action || "unknown";
|
|
819
|
+
const tokens = entry.estimatedTokens || 0;
|
|
820
|
+
if (!byAgent[agent])
|
|
821
|
+
byAgent[agent] = { calls: 0, tokens: 0, cost: 0 };
|
|
822
|
+
byAgent[agent].calls++;
|
|
823
|
+
byAgent[agent].tokens += tokens;
|
|
824
|
+
byAgent[agent].cost = +(byAgent[agent].tokens / 1000 * 0.003).toFixed(4);
|
|
825
|
+
if (!byAction[action])
|
|
826
|
+
byAction[action] = { calls: 0, tokens: 0 };
|
|
827
|
+
byAction[action].calls++;
|
|
828
|
+
byAction[action].tokens += tokens;
|
|
829
|
+
totalTokens += tokens;
|
|
830
|
+
}
|
|
831
|
+
serveJson(res, 200, {
|
|
832
|
+
success: true, data: {
|
|
833
|
+
totalToolCalls: entries.length, totalEstimatedTokens: totalTokens,
|
|
834
|
+
totalEstimatedCost: +(totalTokens / 1000 * 0.003).toFixed(4),
|
|
835
|
+
mcpSessions: sessions.size, byAgent, byAction, recentEntries: entries.slice(-100),
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
catch (e) {
|
|
840
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
841
|
+
}
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
// Telemetry Status
|
|
845
|
+
if (pathname === "/api/telemetry") {
|
|
846
|
+
try {
|
|
847
|
+
const { telemetryStreamer, TelemetryConfig } = await import("./utils/telemetry-streamer.js");
|
|
848
|
+
const status = telemetryStreamer.getStatus();
|
|
849
|
+
serveJson(res, 200, { success: true, data: { ...status, config: { ...TelemetryConfig, AUTH_TOKEN: TelemetryConfig.AUTH_TOKEN ? "***" : "" } } });
|
|
850
|
+
}
|
|
851
|
+
catch (e) {
|
|
852
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
853
|
+
}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
// Loop Detection Stats
|
|
857
|
+
if (pathname === "/api/loop-detector") {
|
|
858
|
+
try {
|
|
859
|
+
const { getAllLoopStats } = await import("./utils/loop-detector.js");
|
|
860
|
+
const agent = params.agent;
|
|
861
|
+
const stats = agent
|
|
862
|
+
? (await import("./utils/loop-detector.js")).getLoopStats(agent)
|
|
863
|
+
: getAllLoopStats();
|
|
864
|
+
serveJson(res, 200, { success: true, data: stats });
|
|
865
|
+
}
|
|
866
|
+
catch (e) {
|
|
867
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
868
|
+
}
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
// Clear Loop Cooldown
|
|
872
|
+
if (pathname.startsWith("/api/loop-detector/clear/") && method === "POST") {
|
|
873
|
+
const agent = decodeURIComponent(pathname.replace("/api/loop-detector/clear/", ""));
|
|
874
|
+
try {
|
|
875
|
+
const { clearCooldown } = await import("./utils/loop-detector.js");
|
|
876
|
+
const cleared = clearCooldown(agent);
|
|
877
|
+
serveJson(res, 200, { success: true, data: { agent, cleared } });
|
|
878
|
+
}
|
|
879
|
+
catch (e) {
|
|
880
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
881
|
+
}
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
// Budget / FinOps Status
|
|
885
|
+
if (pathname === "/api/finops") {
|
|
886
|
+
try {
|
|
887
|
+
const { budgetManager } = await import("./utils/finops.js");
|
|
888
|
+
const state = budgetManager.getState();
|
|
889
|
+
serveJson(res, 200, { success: true, data: state });
|
|
890
|
+
}
|
|
891
|
+
catch (e) {
|
|
892
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
893
|
+
}
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
// Budget Check for an agent
|
|
897
|
+
if (pathname === "/api/finops/check") {
|
|
898
|
+
try {
|
|
899
|
+
const agent = params.agent || "default";
|
|
900
|
+
const { budgetManager } = await import("./utils/finops.js");
|
|
901
|
+
const result = budgetManager.checkBudget(agent);
|
|
902
|
+
serveJson(res, 200, { success: true, data: result });
|
|
903
|
+
}
|
|
904
|
+
catch (e) {
|
|
905
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
906
|
+
}
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
// Budget Reset
|
|
910
|
+
if (pathname === "/api/finops/reset" && method === "POST") {
|
|
911
|
+
try {
|
|
912
|
+
const { budgetManager } = await import("./utils/finops.js");
|
|
913
|
+
budgetManager.resetPeriod();
|
|
914
|
+
serveJson(res, 200, { success: true, message: "Budget period reset." });
|
|
915
|
+
}
|
|
916
|
+
catch (e) {
|
|
917
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
918
|
+
}
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
// License Scanner
|
|
922
|
+
if (pathname === "/api/license") {
|
|
923
|
+
try {
|
|
924
|
+
const { scanForLicenses, getLicenseSeveritySummary, LicenseScannerConfig } = await import("./utils/license-scanner.js");
|
|
925
|
+
const filePath = params.path || "";
|
|
926
|
+
const content = params.content || "";
|
|
927
|
+
const matches = filePath && content ? scanForLicenses(filePath, content) : [];
|
|
928
|
+
const summary = matches.length > 0 ? getLicenseSeveritySummary(matches) : null;
|
|
929
|
+
serveJson(res, 200, {
|
|
930
|
+
success: true, data: {
|
|
931
|
+
matches,
|
|
932
|
+
summary,
|
|
933
|
+
config: LicenseScannerConfig,
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
catch (e) {
|
|
938
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
939
|
+
}
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
// Auto-Rollback Stats
|
|
943
|
+
if (pathname === "/api/rollback") {
|
|
944
|
+
try {
|
|
945
|
+
const { AutoRollbackEngine } = await import("./utils/auto-rollback.js");
|
|
946
|
+
const stats = AutoRollbackEngine.getSnapshotStats();
|
|
947
|
+
serveJson(res, 200, { success: true, data: stats });
|
|
948
|
+
}
|
|
949
|
+
catch (e) {
|
|
950
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
// All Governance Stats (combined endpoint)
|
|
955
|
+
if (pathname === "/api/governance") {
|
|
956
|
+
try {
|
|
957
|
+
const { getAllDisciplineStats } = await import("./utils/discipline.js");
|
|
958
|
+
const { getAllTokenBudgetStats } = await import("./utils/context-optimizer.js");
|
|
959
|
+
const { getAllLoopStats } = await import("./utils/loop-detector.js");
|
|
960
|
+
const { getAllRules } = await import("./utils/rules-engine.js");
|
|
961
|
+
const { AutoRollbackEngine } = await import("./utils/auto-rollback.js");
|
|
962
|
+
const { budgetManager } = await import("./utils/finops.js");
|
|
963
|
+
serveJson(res, 200, {
|
|
964
|
+
success: true, data: {
|
|
965
|
+
discipline: getAllDisciplineStats(),
|
|
966
|
+
tokenBudget: getAllTokenBudgetStats(),
|
|
967
|
+
loopDetection: getAllLoopStats(),
|
|
968
|
+
rules: getAllRules().map(r => ({ id: r.id, name: r.name, priority: r.priority, bypassable: r.bypassable })),
|
|
969
|
+
rollback: AutoRollbackEngine.getSnapshotStats(),
|
|
970
|
+
budget: budgetManager.getState(),
|
|
971
|
+
telemetry: (await import("./utils/telemetry-streamer.js")).telemetryStreamer.getStatus(),
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
catch (e) {
|
|
976
|
+
serveJson(res, 500, { success: false, error: e.message });
|
|
977
|
+
}
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
// SSE (legacy support)
|
|
981
|
+
if (pathname === "/api/events") {
|
|
982
|
+
res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive" });
|
|
983
|
+
res.write(": connected\n\n");
|
|
984
|
+
const keepAlive = setInterval(() => res.write(": keepalive\n\n"), 30000);
|
|
985
|
+
req.on("close", () => clearInterval(keepAlive));
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
// ───────────── STATIC UI ─────────────────────────────
|
|
989
|
+
const uiPath = path.join(UI_DIST_PATH, pathname === "/" ? "index.html" : pathname);
|
|
990
|
+
const finalPath = (fs.existsSync(uiPath) && !fs.statSync(uiPath).isDirectory())
|
|
991
|
+
? uiPath
|
|
992
|
+
: path.join(UI_DIST_PATH, "index.html");
|
|
993
|
+
if (fs.existsSync(finalPath) && !fs.statSync(finalPath).isDirectory()) {
|
|
994
|
+
const ext = path.extname(finalPath).toLowerCase();
|
|
995
|
+
const mimeTypes = {
|
|
996
|
+
".html": "text/html", ".js": "application/javascript", ".css": "text/css",
|
|
997
|
+
".json": "application/json", ".png": "image/png", ".jpg": "image/jpg",
|
|
998
|
+
".gif": "image/gif", ".svg": "image/svg+xml", ".ico": "image/x-icon",
|
|
999
|
+
};
|
|
1000
|
+
serveFile(res, finalPath, mimeTypes[ext] || "application/octet-stream");
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
serveJson(res, 404, { error: "Not Found" });
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
// ─── WebSocket Server ────────────────────────────────────
|
|
1007
|
+
const wss = new WebSocketServer({ server: httpServer, path: "/ws" });
|
|
1008
|
+
wss.on("connection", (ws) => {
|
|
1009
|
+
wsClients.add(ws);
|
|
1010
|
+
ws.send(JSON.stringify({ type: "mcp_session", payload: { total: sessions.size, sessions: Array.from(sessions.keys()) } }));
|
|
1011
|
+
ws.on("close", () => wsClients.delete(ws));
|
|
1012
|
+
ws.on("message", (data) => {
|
|
1013
|
+
try {
|
|
1014
|
+
const msg = JSON.parse(data.toString());
|
|
1015
|
+
if (msg.type === "ping")
|
|
1016
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
1017
|
+
}
|
|
1018
|
+
catch { /* ignore */ }
|
|
1019
|
+
});
|
|
1020
|
+
});
|
|
1021
|
+
// ─── Periodic Broadcast ──────────────────────────────────
|
|
1022
|
+
const broadcastInterval = setInterval(() => {
|
|
1023
|
+
try {
|
|
1024
|
+
broadcastWS("mcp_heartbeat", { sessions: sessions.size, timestamp: new Date().toISOString() });
|
|
1025
|
+
}
|
|
1026
|
+
catch { /* silent */ }
|
|
1027
|
+
}, 10000);
|
|
1028
|
+
httpServer.on("close", () => {
|
|
1029
|
+
clearInterval(broadcastInterval);
|
|
1030
|
+
wsClients.clear();
|
|
1031
|
+
});
|
|
1032
|
+
// ─── Start ───────────────────────────────────────────────
|
|
1033
|
+
httpServer.listen(PORT, HOST, () => {
|
|
1034
|
+
process.stderr.write("\n");
|
|
1035
|
+
process.stderr.write("╔══════════════════════════════════════════════════════╗\n");
|
|
1036
|
+
process.stderr.write("║ ATABEY UNIFIED SERVER ║\n");
|
|
1037
|
+
process.stderr.write("╠══════════════════════════════════════════════════════╣\n");
|
|
1038
|
+
process.stderr.write(`║ Dashboard: http://localhost:${PORT} \n`);
|
|
1039
|
+
process.stderr.write(`║ MCP SSE: http://localhost:${PORT}/mcp/sse \n`);
|
|
1040
|
+
process.stderr.write(`║ WS Live: ws://localhost:${PORT}/ws \n`);
|
|
1041
|
+
process.stderr.write(`║ API: http://localhost:${PORT}/api/health \n`);
|
|
1042
|
+
process.stderr.write(`║ Sessions: ${sessions.size} active \n`);
|
|
1043
|
+
process.stderr.write("╚══════════════════════════════════════════════════════╝\n");
|
|
1044
|
+
process.stderr.write("\n");
|
|
1045
|
+
process.stderr.write(`[atabey-mcp] Unified server ready on port ${PORT}\n`);
|
|
1046
|
+
process.stderr.write(`[atabey-mcp] mcp.json: { "url": "http://localhost:${PORT}/mcp/sse" }\n`);
|
|
1047
|
+
process.stderr.write("\n");
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
// ─── Startup ──────────────────────────────────────────────────────
|
|
126
1051
|
async function run() {
|
|
127
|
-
const
|
|
128
|
-
// Prevent unhandled errors from crashing the MCP stream
|
|
1052
|
+
const transportMode = process.env.MCP_TRANSPORT || "unified";
|
|
129
1053
|
process.on("uncaughtException", (error) => {
|
|
130
|
-
process.stderr.write(`[atabey-mcp] Uncaught exception: ${error.message}
|
|
131
|
-
`);
|
|
1054
|
+
process.stderr.write(`[atabey-mcp] Uncaught exception: ${error.message}\n`);
|
|
132
1055
|
});
|
|
133
1056
|
process.on("unhandledRejection", (reason) => {
|
|
134
1057
|
const message = reason instanceof Error ? reason.message : String(reason);
|
|
135
|
-
process.stderr.write(`[atabey-mcp] Unhandled rejection: ${message}
|
|
136
|
-
`);
|
|
1058
|
+
process.stderr.write(`[atabey-mcp] Unhandled rejection: ${message}\n`);
|
|
137
1059
|
});
|
|
138
|
-
// Graceful shutdown on SIGINT/SIGTERM
|
|
139
1060
|
const shutdown = async () => {
|
|
140
1061
|
try {
|
|
141
1062
|
await server.close();
|
|
142
1063
|
}
|
|
143
|
-
catch {
|
|
144
|
-
// Already closed or failed — safe to ignore
|
|
145
|
-
}
|
|
1064
|
+
catch { /* ignore */ }
|
|
146
1065
|
process.exit(0);
|
|
147
1066
|
};
|
|
148
1067
|
process.on("SIGINT", shutdown);
|
|
149
1068
|
process.on("SIGTERM", shutdown);
|
|
150
|
-
|
|
1069
|
+
if (transportMode === "stdio") {
|
|
1070
|
+
const transport = new StdioServerTransport();
|
|
1071
|
+
process.stderr.write("[atabey-mcp] Stdio mode (single client).\n");
|
|
1072
|
+
await server.connect(transport);
|
|
1073
|
+
}
|
|
1074
|
+
else {
|
|
1075
|
+
createUnifiedServer();
|
|
1076
|
+
}
|
|
151
1077
|
}
|
|
152
1078
|
run().catch((error) => {
|
|
153
|
-
process.stderr.write(`[atabey-mcp] Fatal startup error: ${error.message}
|
|
154
|
-
`);
|
|
1079
|
+
process.stderr.write(`[atabey-mcp] Fatal startup error: ${error.message}\n`);
|
|
155
1080
|
process.exit(1);
|
|
156
1081
|
});
|
|
157
1082
|
//# sourceMappingURL=index.js.map
|