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