atabey-mcp 0.0.7 → 0.0.8
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/{src/constants.ts → dist/constants.js} +3 -17
- package/{src/index.ts → dist/index.js} +31 -59
- package/{src/resources/index.ts → dist/resources/index.js} +4 -11
- package/dist/src/cli/adapters/core.js +12 -19
- package/dist/src/shared/constants.js +1 -0
- package/{src/tools/control_plane/locking.ts → dist/tools/control_plane/locking.js} +11 -18
- package/{src/tools/control_plane/registry.ts → dist/tools/control_plane/registry.js} +5 -9
- package/{src/tools/definitions.ts → dist/tools/definitions.js} +2 -4
- package/{src/tools/file_system/batch_surgical_edit.ts → dist/tools/file_system/batch_surgical_edit.js} +9 -32
- package/{src/tools/file_system/patch_file.ts → dist/tools/file_system/patch_file.js} +2 -14
- package/{src/tools/file_system/read_file.ts → dist/tools/file_system/read_file.js} +6 -13
- package/{src/tools/file_system/replace_text.ts → dist/tools/file_system/replace_text.js} +6 -17
- package/{src/tools/file_system/write_file.ts → dist/tools/file_system/write_file.js} +5 -14
- package/{src/tools/framework/audit_deps.ts → dist/tools/framework/audit_deps.js} +8 -16
- package/{src/tools/framework/get_status.ts → dist/tools/framework/get_status.js} +1 -3
- package/{src/tools/framework/orchestrate.ts → dist/tools/framework/orchestrate.js} +1 -3
- package/{src/tools/framework/run_tests.ts → dist/tools/framework/run_tests.js} +8 -11
- package/{src/tools/framework/submit_plan.ts → dist/tools/framework/submit_plan.js} +1 -14
- package/{src/tools/framework/update_contract_hash.ts → dist/tools/framework/update_contract_hash.js} +1 -3
- package/{src/tools/framework/update_memory.ts → dist/tools/framework/update_memory.js} +1 -3
- package/{src/tools/index.ts → dist/tools/index.js} +3 -7
- package/{src/tools/memory/get_insights.ts → dist/tools/memory/get_insights.js} +3 -10
- package/{src/tools/memory/read_memory.ts → dist/tools/memory/read_memory.js} +3 -6
- package/{src/tools/messaging/log_action.ts → dist/tools/messaging/log_action.js} +1 -7
- package/{src/tools/messaging/send_message.ts → dist/tools/messaging/send_message.js} +14 -17
- package/{src/tools/observability/check_ports.ts → dist/tools/observability/check_ports.js} +6 -10
- package/{src/tools/observability/get_health.ts → dist/tools/observability/get_health.js} +1 -6
- package/{src/tools/quality/check_lint.ts → dist/tools/quality/check_lint.js} +1 -7
- package/{src/tools/search/get_gaps.ts → dist/tools/search/get_gaps.js} +12 -18
- package/{src/tools/search/get_map.ts → dist/tools/search/get_map.js} +14 -19
- package/{src/tools/search/grep_search.ts → dist/tools/search/grep_search.js} +23 -23
- package/{src/tools/search/list_dir.ts → dist/tools/search/list_dir.js} +4 -10
- package/{src/tools/shell/run_command.ts → dist/tools/shell/run_command.js} +1 -11
- package/dist/tools/types.js +1 -0
- package/{src/utils/cli.ts → dist/utils/cli.js} +25 -19
- package/{src/utils/compliance.ts → dist/utils/compliance.js} +34 -74
- package/{src/utils/fs.ts → dist/utils/fs.js} +9 -10
- package/{src/utils/metrics.ts → dist/utils/metrics.js} +11 -28
- package/{src/utils/permissions.ts → dist/utils/permissions.js} +11 -28
- package/{src/utils/security.ts → dist/utils/security.js} +9 -15
- package/package.json +7 -1
- package/src/declarations.d.ts +0 -19
- package/src/tools/types.ts +0 -89
- package/tsconfig.json +0 -13
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* Agent Atabey — Single Source of Truth for framework constants.
|
|
5
4
|
* Import from here instead of hardcoding paths, phases, or directory names.
|
|
6
5
|
*/
|
|
7
|
-
|
|
8
6
|
// ─── Framework identity ───────────────────────────────────────────────────
|
|
9
|
-
|
|
10
7
|
export const FRAMEWORK = {
|
|
11
8
|
NAME: "Agent Atabey",
|
|
12
9
|
CORE_DIR: ".atabey",
|
|
@@ -17,7 +14,6 @@ export const FRAMEWORK = {
|
|
|
17
14
|
// This is where all skills are stored
|
|
18
15
|
SKILLS_DIR: "skills",
|
|
19
16
|
};
|
|
20
|
-
|
|
21
17
|
export const FRAMEWORK_SUBDIRS = {
|
|
22
18
|
AGENTS: "agents",
|
|
23
19
|
SKILLS: "skills",
|
|
@@ -28,26 +24,22 @@ export const FRAMEWORK_SUBDIRS = {
|
|
|
28
24
|
LOGS: "logs",
|
|
29
25
|
CONFIG: "config",
|
|
30
26
|
};
|
|
31
|
-
|
|
32
27
|
export const ROOT_CONFIG_FILES = {
|
|
33
28
|
MCP: "mcp.json",
|
|
34
29
|
NATIVE_MODULES: "native-modules.json",
|
|
35
30
|
TSCONFIG: "tsconfig.json",
|
|
36
31
|
ESLINT: "eslint.config.js",
|
|
37
32
|
};
|
|
38
|
-
|
|
39
33
|
export const MCP = {
|
|
40
34
|
// Environment variable used by MCP to identify project root
|
|
41
35
|
PROJECT_ROOT_ENV: "ATABEY_PROJECT_ROOT",
|
|
42
36
|
// Environment variable for test mode
|
|
43
37
|
TEST_DIR_ENV: "ATABEY_TEST_DIR",
|
|
44
38
|
};
|
|
45
|
-
|
|
46
39
|
export const MEMORY_FILES = {
|
|
47
40
|
STATE: "state.json",
|
|
48
41
|
SHARED_FACTS: "shared_facts.json",
|
|
49
42
|
};
|
|
50
|
-
|
|
51
43
|
export const NATIVE_AGENT_PATHS = {
|
|
52
44
|
gemini: ".gemini/agents",
|
|
53
45
|
claude: ".claude/agents",
|
|
@@ -56,23 +48,17 @@ export const NATIVE_AGENT_PATHS = {
|
|
|
56
48
|
grok: ".grok",
|
|
57
49
|
"antigravity-cli": ".agents/agents",
|
|
58
50
|
};
|
|
59
|
-
|
|
60
51
|
// ─── Backward-compatible aliases ──────────────────────────────────────────
|
|
61
|
-
|
|
62
52
|
export const CORE_FRAMEWORK_DIR = FRAMEWORK.CORE_DIR;
|
|
63
53
|
export const UNIFIED_HUB_DIR = FRAMEWORK.UNIFIED_HUB_DIR;
|
|
64
54
|
export const SKILLS_HUB_PATH = pathJoin(UNIFIED_HUB_DIR, FRAMEWORK_SUBDIRS.SKILLS);
|
|
65
|
-
|
|
66
55
|
// ─── Path Helpers ─────────────────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
function pathJoin(...args: string[]): string {
|
|
56
|
+
function pathJoin(...args) {
|
|
69
57
|
return path.join(...args);
|
|
70
58
|
}
|
|
71
|
-
|
|
72
|
-
function corePath(subdir: string, filename: string): string {
|
|
59
|
+
function corePath(subdir, filename) {
|
|
73
60
|
return pathJoin(FRAMEWORK.CORE_DIR, subdir, filename);
|
|
74
61
|
}
|
|
75
|
-
|
|
76
|
-
export function knowledgePath(filename: string): string {
|
|
62
|
+
export function knowledgePath(filename) {
|
|
77
63
|
return corePath(FRAMEWORK_SUBDIRS.KNOWLEDGE, filename);
|
|
78
64
|
}
|
|
@@ -4,55 +4,39 @@ import path from "path";
|
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
-
import {
|
|
8
|
-
CallToolRequestSchema,
|
|
9
|
-
ListToolsRequestSchema,
|
|
10
|
-
ListResourcesRequestSchema,
|
|
11
|
-
ReadResourceRequestSchema,
|
|
12
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
15
8
|
import { TOOLS, toolHandlers } from "./tools/index.js";
|
|
16
9
|
import { RESOURCES, handleReadResource } from "./resources/index.js";
|
|
17
|
-
|
|
18
|
-
|
|
19
10
|
// ─── Server Setup ─────────────────────────────────────────────────
|
|
20
|
-
|
|
21
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
|
|
23
12
|
// Robustly find package.json by walking up from __dirname
|
|
24
|
-
function findPackageJson(startDir
|
|
13
|
+
function findPackageJson(startDir) {
|
|
25
14
|
let currentDir = startDir;
|
|
26
15
|
while (currentDir !== path.parse(currentDir).root) {
|
|
27
16
|
const pkgPath = path.join(currentDir, "package.json");
|
|
28
|
-
if (fs.existsSync(pkgPath))
|
|
17
|
+
if (fs.existsSync(pkgPath))
|
|
18
|
+
return pkgPath;
|
|
29
19
|
currentDir = path.dirname(currentDir);
|
|
30
20
|
}
|
|
31
21
|
throw new Error("Could not find package.json for atabey-mcp");
|
|
32
22
|
}
|
|
33
|
-
|
|
34
23
|
const pkgPath = findPackageJson(__dirname);
|
|
35
24
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
36
25
|
const serverVersion = pkg.version;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
const server = new Server({
|
|
27
|
+
name: "atabey-mcp",
|
|
28
|
+
version: serverVersion,
|
|
29
|
+
}, {
|
|
30
|
+
capabilities: {
|
|
31
|
+
tools: {},
|
|
32
|
+
resources: {},
|
|
42
33
|
},
|
|
43
|
-
|
|
44
|
-
capabilities: {
|
|
45
|
-
tools: {},
|
|
46
|
-
resources: {},
|
|
47
|
-
},
|
|
48
|
-
}
|
|
49
|
-
);
|
|
50
|
-
|
|
34
|
+
});
|
|
51
35
|
// Basic Schema Validator for Required Fields
|
|
52
|
-
function validateArgs(toolName
|
|
36
|
+
function validateArgs(toolName, args) {
|
|
53
37
|
const definition = TOOLS.find(t => t.name === toolName);
|
|
54
|
-
if (!definition)
|
|
55
|
-
|
|
38
|
+
if (!definition)
|
|
39
|
+
return `Unknown tool: ${toolName}`;
|
|
56
40
|
const required = definition.inputSchema.required || [];
|
|
57
41
|
for (const field of required) {
|
|
58
42
|
if (args[field] === undefined || args[field] === null || args[field] === "") {
|
|
@@ -61,22 +45,19 @@ function validateArgs(toolName: string, args: Record<string, unknown>): string |
|
|
|
61
45
|
}
|
|
62
46
|
return null;
|
|
63
47
|
}
|
|
64
|
-
|
|
65
48
|
server.setRequestHandler(ListToolsRequestSchema, async (request) => {
|
|
66
49
|
// 2026 Stateless Spec: Log client info from metadata if available
|
|
67
|
-
const meta =
|
|
50
|
+
const meta = request._meta;
|
|
68
51
|
if (meta) {
|
|
69
52
|
process.stderr.write(`[MCP] Stateless ListTools from ${meta.client?.name || "unknown"} v${meta.client?.version || "?.?"}\n`);
|
|
70
53
|
}
|
|
71
54
|
return { tools: TOOLS };
|
|
72
55
|
});
|
|
73
|
-
|
|
74
56
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
75
57
|
return { resources: RESOURCES };
|
|
76
58
|
});
|
|
77
|
-
|
|
78
59
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
79
|
-
const uri =
|
|
60
|
+
const uri = request.params.uri;
|
|
80
61
|
try {
|
|
81
62
|
const content = await handleReadResource(uri);
|
|
82
63
|
return {
|
|
@@ -88,84 +69,75 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
88
69
|
},
|
|
89
70
|
],
|
|
90
71
|
};
|
|
91
|
-
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
92
74
|
const message = error instanceof Error ? error.message : String(error);
|
|
93
75
|
throw new Error(`Failed to read resource: ${message}`, { cause: error });
|
|
94
76
|
}
|
|
95
77
|
});
|
|
96
|
-
|
|
97
78
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
98
|
-
const req = request
|
|
79
|
+
const req = request;
|
|
99
80
|
const { name, arguments: args } = req.params;
|
|
100
|
-
const meta =
|
|
101
|
-
|
|
81
|
+
const meta = request._meta;
|
|
102
82
|
// 2026 Stateless Spec: Prioritize metadata-driven context
|
|
103
83
|
if (meta) {
|
|
104
84
|
process.stderr.write(`[MCP] Stateless CallTool: ${name} (Client: ${meta.client?.name || "unknown"})\n`);
|
|
105
85
|
}
|
|
106
|
-
|
|
107
86
|
const projectRoot = process.env.ATABEY_PROJECT_ROOT || process.cwd();
|
|
108
|
-
|
|
109
87
|
try {
|
|
110
88
|
const handler = toolHandlers[name];
|
|
111
89
|
if (!handler) {
|
|
112
90
|
return {
|
|
113
91
|
isError: true,
|
|
114
|
-
content: [{ type: "text"
|
|
92
|
+
content: [{ type: "text", text: `[ERROR] Unknown tool: ${name}` }],
|
|
115
93
|
};
|
|
116
94
|
}
|
|
117
|
-
|
|
118
95
|
// [SECURITY] Runtime Validation
|
|
119
96
|
const validationError = validateArgs(name, args || {});
|
|
120
97
|
if (validationError) {
|
|
121
98
|
return {
|
|
122
99
|
isError: true,
|
|
123
|
-
content: [{ type: "text"
|
|
100
|
+
content: [{ type: "text", text: `[ERROR] Validation Error: ${validationError}` }],
|
|
124
101
|
};
|
|
125
102
|
}
|
|
126
|
-
|
|
127
103
|
return await handler(projectRoot, args || {});
|
|
128
|
-
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
129
106
|
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
130
107
|
return {
|
|
131
108
|
isError: true,
|
|
132
|
-
content: [{ type: "text"
|
|
109
|
+
content: [{ type: "text", text: `[ERROR] Execution failed: ${message}` }],
|
|
133
110
|
};
|
|
134
111
|
}
|
|
135
112
|
});
|
|
136
|
-
|
|
137
113
|
// ─── Graceful Startup & Shutdown ──────────────────────────────────
|
|
138
|
-
|
|
139
114
|
async function run() {
|
|
140
115
|
const transport = new StdioServerTransport();
|
|
141
|
-
|
|
142
116
|
// Prevent unhandled errors from crashing the MCP stream
|
|
143
|
-
process.on("uncaughtException", (error
|
|
117
|
+
process.on("uncaughtException", (error) => {
|
|
144
118
|
process.stderr.write(`[atabey-mcp] Uncaught exception: ${error.message}
|
|
145
119
|
`);
|
|
146
120
|
});
|
|
147
|
-
process.on("unhandledRejection", (reason
|
|
121
|
+
process.on("unhandledRejection", (reason) => {
|
|
148
122
|
const message = reason instanceof Error ? reason.message : String(reason);
|
|
149
123
|
process.stderr.write(`[atabey-mcp] Unhandled rejection: ${message}
|
|
150
124
|
`);
|
|
151
125
|
});
|
|
152
|
-
|
|
153
126
|
// Graceful shutdown on SIGINT/SIGTERM
|
|
154
127
|
const shutdown = async () => {
|
|
155
128
|
try {
|
|
156
129
|
await server.close();
|
|
157
|
-
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
158
132
|
// Already closed or failed — safe to ignore
|
|
159
133
|
}
|
|
160
134
|
process.exit(0);
|
|
161
135
|
};
|
|
162
136
|
process.on("SIGINT", shutdown);
|
|
163
137
|
process.on("SIGTERM", shutdown);
|
|
164
|
-
|
|
165
138
|
await server.connect(transport);
|
|
166
139
|
}
|
|
167
|
-
|
|
168
|
-
run().catch((error: Error) => {
|
|
140
|
+
run().catch((error) => {
|
|
169
141
|
process.stderr.write(`[atabey-mcp] Fatal startup error: ${error.message}
|
|
170
142
|
`);
|
|
171
143
|
process.exit(1);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Storage
|
|
2
|
-
|
|
1
|
+
import { Storage } from "../../../src/shared/storage.js";
|
|
3
2
|
/**
|
|
4
3
|
* [DATA] MCP Resource Definitions
|
|
5
4
|
*/
|
|
@@ -23,43 +22,37 @@ export const RESOURCES = [
|
|
|
23
22
|
mimeType: "text/markdown"
|
|
24
23
|
}
|
|
25
24
|
];
|
|
26
|
-
|
|
27
|
-
export async function handleReadResource(uri: string): Promise<string> {
|
|
25
|
+
export async function handleReadResource(uri) {
|
|
28
26
|
if (uri === "atabey://army/status") {
|
|
29
27
|
const agents = Storage.getAllAgents();
|
|
30
28
|
let md = "# [AI] Agent Army Status\n\n| Agent | State | Active Task | Last Updated |\n| :--- | :--- | :--- | :--- |\n";
|
|
31
|
-
agents.forEach((a
|
|
29
|
+
agents.forEach((a) => {
|
|
32
30
|
md += `| @${a.name} | ${a.state} | ${a.task} | ${a.last_updated} |\n`;
|
|
33
31
|
});
|
|
34
32
|
return md;
|
|
35
33
|
}
|
|
36
|
-
|
|
37
34
|
if (uri === "atabey://plan/active") {
|
|
38
35
|
const tasks = Storage.getTasks();
|
|
39
36
|
let md = "# 📋 Active Execution Plan\n\n| ID | Task | Agent | Status | Dependencies |\n| :--- | :--- | :--- | :--- | :--- |\n";
|
|
40
|
-
tasks.forEach((t
|
|
37
|
+
tasks.forEach((t) => {
|
|
41
38
|
const deps = t.dependencies.join(", ") || "-";
|
|
42
39
|
md += `| ${t.id} | ${t.description} | ${t.agent} | ${t.status} | ${deps} |\n`;
|
|
43
40
|
});
|
|
44
41
|
return md;
|
|
45
42
|
}
|
|
46
|
-
|
|
47
43
|
if (uri === "atabey://memory/project") {
|
|
48
44
|
const fs = await import("fs");
|
|
49
45
|
const path = await import("path");
|
|
50
46
|
const { getFrameworkDir } = await import("../../../src/cli/utils/memory.js");
|
|
51
|
-
|
|
52
47
|
const projectRoot = process.env.ATABEY_PROJECT_ROOT || process.cwd();
|
|
53
48
|
const fwDir = getFrameworkDir();
|
|
54
49
|
const p = path.isAbsolute(fwDir)
|
|
55
50
|
? path.join(fwDir, "memory", "PROJECT_MEMORY.md")
|
|
56
51
|
: path.join(projectRoot, fwDir, "memory", "PROJECT_MEMORY.md");
|
|
57
|
-
|
|
58
52
|
if (fs.existsSync(p)) {
|
|
59
53
|
return fs.readFileSync(p, "utf8");
|
|
60
54
|
}
|
|
61
55
|
return "Project memory not found. Run 'atabey init' first.";
|
|
62
56
|
}
|
|
63
|
-
|
|
64
57
|
throw new Error(`Unknown resource URI: ${uri}`);
|
|
65
58
|
}
|
|
@@ -2,6 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { writeJsonFile } from "../utils/fs.js";
|
|
4
4
|
import { getPackageRoot } from "../utils/pkg.js";
|
|
5
|
+
import { MCP } from "../../shared/constants.js";
|
|
5
6
|
import { ADAPTER_CONFIGS, POST_INIT_HANDLERS } from "../../modules/adapters/definitions.js";
|
|
6
7
|
export const ADAPTERS = ADAPTER_CONFIGS;
|
|
7
8
|
export const SHIM_FILES = Object.keys(ADAPTERS).map((id) => ADAPTERS[id].shimFile);
|
|
@@ -25,7 +26,7 @@ export function buildMcpServerEntry(projectRoot) {
|
|
|
25
26
|
let relativePath;
|
|
26
27
|
if (isLocalFrameworkDev) {
|
|
27
28
|
// In local framework dev, always use the build path directly relative to project root
|
|
28
|
-
let mcpServerPath = path.join(packageRoot,
|
|
29
|
+
let mcpServerPath = path.join(packageRoot, MCP.SERVER_DIST_PATH);
|
|
29
30
|
if (!fs.existsSync(mcpServerPath)) {
|
|
30
31
|
mcpServerPath = path.join(packageRoot, "../atabey-mcp/dist/index.js");
|
|
31
32
|
}
|
|
@@ -39,39 +40,31 @@ export function buildMcpServerEntry(projectRoot) {
|
|
|
39
40
|
}
|
|
40
41
|
else {
|
|
41
42
|
// If we are initializing in a user's project:
|
|
42
|
-
// We
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
if (fs.existsSync(
|
|
48
|
-
relativePath = path.
|
|
49
|
-
}
|
|
50
|
-
else if (fs.existsSync(localAtabeyPath)) {
|
|
51
|
-
relativePath = path.relative(projectRoot, localAtabeyPath);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
// Default to the standard relative local path in node_modules/atabey
|
|
55
|
-
// This is clean and portable across machines/developers.
|
|
56
|
-
relativePath = "node_modules/atabey/framework-mcp/dist/index.js";
|
|
43
|
+
// We target the atabey-mcp package which is a dependency of atabey.
|
|
44
|
+
// This ensures a stable path across different npm/pnpm/yarn setups.
|
|
45
|
+
relativePath = "node_modules/atabey-mcp/dist/index.js";
|
|
46
|
+
// Fallback check if it actually exists in a different location during init
|
|
47
|
+
const localAtabeyPath = path.join(projectRoot, "node_modules/atabey", MCP.SERVER_DIST_PATH);
|
|
48
|
+
if (!fs.existsSync(path.join(projectRoot, relativePath)) && fs.existsSync(localAtabeyPath)) {
|
|
49
|
+
relativePath = path.join("node_modules/atabey", MCP.SERVER_DIST_PATH);
|
|
57
50
|
}
|
|
58
51
|
}
|
|
59
52
|
return {
|
|
60
53
|
command: "node",
|
|
61
54
|
args: [relativePath],
|
|
62
55
|
env: {
|
|
63
|
-
|
|
56
|
+
[MCP.PROJECT_ROOT_ENV]: projectRoot,
|
|
64
57
|
},
|
|
65
58
|
};
|
|
66
59
|
}
|
|
67
60
|
export function runAdapterPostInit(adapter, projectRoot) {
|
|
68
61
|
const mcpEntry = buildMcpServerEntry(projectRoot);
|
|
69
|
-
const mcpBlock = { mcpServers: {
|
|
62
|
+
const mcpBlock = { mcpServers: { [MCP.SERVER_NAME]: mcpEntry } };
|
|
70
63
|
const postInitFn = POST_INIT_HANDLERS[adapter.id];
|
|
71
64
|
if (postInitFn) {
|
|
72
65
|
postInitFn(projectRoot, mcpBlock);
|
|
73
66
|
}
|
|
74
|
-
const rootMcpPath = path.join(projectRoot,
|
|
67
|
+
const rootMcpPath = path.join(projectRoot, MCP.ROOT_CONFIG_FILE);
|
|
75
68
|
if (!fs.existsSync(rootMcpPath)) {
|
|
76
69
|
writeJsonFile(rootMcpPath, mcpBlock);
|
|
77
70
|
}
|
|
@@ -143,6 +143,7 @@ export const MCP = {
|
|
|
143
143
|
ROOT_CONFIG_FILE: "mcp.json",
|
|
144
144
|
PROJECT_ROOT_ENV: "ATABEY_PROJECT_ROOT",
|
|
145
145
|
TEST_DIR_ENV: "ATABEY_TEST_DIR",
|
|
146
|
+
SERVER_DIST_PATH: "framework-mcp/dist/index.js",
|
|
146
147
|
};
|
|
147
148
|
export const ROOT_CONFIG_FILES = {
|
|
148
149
|
MCP: MCP.ROOT_CONFIG_FILE,
|
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { ToolResult, AcquireLockArgs, ReleaseLockArgs } from "../types.js";
|
|
4
3
|
import { resolveFrameworkDir } from "../../utils/security.js";
|
|
5
|
-
|
|
6
4
|
/**
|
|
7
5
|
* Handles acquiring a stateful lock on a resource.
|
|
8
6
|
*/
|
|
9
|
-
export async function handleAcquireLock(projectRoot
|
|
7
|
+
export async function handleAcquireLock(projectRoot, args) {
|
|
10
8
|
const { resource, agent, ttl = 60 } = args;
|
|
11
9
|
const frameworkDir = resolveFrameworkDir(projectRoot);
|
|
12
10
|
const lockDir = path.join(projectRoot, frameworkDir, "locks");
|
|
13
11
|
const lockPath = path.join(lockDir, `${resource}.lock`);
|
|
14
|
-
|
|
15
12
|
try {
|
|
16
|
-
if (!fs.existsSync(lockDir))
|
|
17
|
-
|
|
13
|
+
if (!fs.existsSync(lockDir))
|
|
14
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
18
15
|
// Check for stale lock first
|
|
19
16
|
if (fs.existsSync(lockPath)) {
|
|
20
17
|
const stat = fs.statSync(lockPath);
|
|
21
18
|
const now = new Date().getTime();
|
|
22
19
|
const age = (now - stat.mtimeMs) / 1000;
|
|
23
|
-
|
|
24
20
|
if (age < ttl) {
|
|
25
21
|
return {
|
|
26
22
|
isError: true,
|
|
@@ -32,21 +28,21 @@ export async function handleAcquireLock(projectRoot: string, args: AcquireLockAr
|
|
|
32
28
|
try {
|
|
33
29
|
fs.renameSync(lockPath, tempLockPath);
|
|
34
30
|
fs.unlinkSync(tempLockPath);
|
|
35
|
-
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
36
33
|
// If rename failed, it means another agent already evicting/deleting it.
|
|
37
34
|
// Do not delete anything else; proceed and let writeFileSync (wx flag) fail if a new lock exists.
|
|
38
35
|
}
|
|
39
36
|
}
|
|
40
|
-
|
|
41
37
|
// Use 'wx' flag for atomic file creation
|
|
42
38
|
const lockData = JSON.stringify({ agent, timestamp: new Date().toISOString() });
|
|
43
39
|
fs.writeFileSync(lockPath, lockData, { flag: "wx" });
|
|
44
|
-
|
|
45
40
|
return {
|
|
46
41
|
content: [{ type: "text", text: `[OK] Lock acquired for resource '${resource}' by ${agent}.` }]
|
|
47
42
|
};
|
|
48
|
-
}
|
|
49
|
-
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
const error = e;
|
|
50
46
|
if (error.code === "EEXIST") {
|
|
51
47
|
return {
|
|
52
48
|
isError: true,
|
|
@@ -59,20 +55,17 @@ export async function handleAcquireLock(projectRoot: string, args: AcquireLockAr
|
|
|
59
55
|
};
|
|
60
56
|
}
|
|
61
57
|
}
|
|
62
|
-
|
|
63
58
|
/**
|
|
64
59
|
* Handles releasing a lock.
|
|
65
60
|
*/
|
|
66
|
-
export async function handleReleaseLock(projectRoot
|
|
61
|
+
export async function handleReleaseLock(projectRoot, args) {
|
|
67
62
|
const { resource, agent } = args;
|
|
68
63
|
const frameworkDir = resolveFrameworkDir(projectRoot);
|
|
69
64
|
const lockPath = path.join(projectRoot, frameworkDir, "locks", `${resource}.lock`);
|
|
70
|
-
|
|
71
65
|
try {
|
|
72
66
|
if (!fs.existsSync(lockPath)) {
|
|
73
67
|
return { content: [{ type: "text", text: `ℹ️ No lock found for resource '${resource}'.` }] };
|
|
74
68
|
}
|
|
75
|
-
|
|
76
69
|
const lockData = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
77
70
|
if (lockData.agent !== agent) {
|
|
78
71
|
return {
|
|
@@ -80,10 +73,10 @@ export async function handleReleaseLock(projectRoot: string, args: ReleaseLockAr
|
|
|
80
73
|
content: [{ type: "text", text: `[ERROR] Denied: You do not own the lock for '${resource}'. Owned by ${lockData.agent}.` }]
|
|
81
74
|
};
|
|
82
75
|
}
|
|
83
|
-
|
|
84
76
|
fs.unlinkSync(lockPath);
|
|
85
77
|
return { content: [{ type: "text", text: `[OK] Lock released for resource '${resource}' by ${agent}.` }] };
|
|
86
|
-
}
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
87
80
|
return { isError: true, content: [{ type: "text", text: `Failed to release lock: ${String(e)}` }] };
|
|
88
81
|
}
|
|
89
82
|
}
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { ToolResult, RegisterAgentArgs } from "../types.js";
|
|
4
3
|
import { resolveFrameworkDir } from "../../utils/security.js";
|
|
5
|
-
|
|
6
4
|
/**
|
|
7
5
|
* Handles agent registration with the Control Plane.
|
|
8
6
|
* This can be used to validate permissions and active status.
|
|
9
7
|
*/
|
|
10
|
-
export async function handleRegisterAgent(projectRoot
|
|
8
|
+
export async function handleRegisterAgent(projectRoot, args) {
|
|
11
9
|
const { agent, role, capability = 5, specialties } = args;
|
|
12
10
|
const frameworkDir = resolveFrameworkDir(projectRoot);
|
|
13
11
|
const registryDir = path.join(projectRoot, frameworkDir, "registry");
|
|
14
12
|
const agentFile = path.join(registryDir, `${agent.replace("@", "")}_active.json`);
|
|
15
|
-
|
|
16
13
|
try {
|
|
17
|
-
if (!fs.existsSync(registryDir))
|
|
18
|
-
|
|
14
|
+
if (!fs.existsSync(registryDir))
|
|
15
|
+
fs.mkdirSync(registryDir, { recursive: true });
|
|
19
16
|
const agentData = {
|
|
20
17
|
agent,
|
|
21
18
|
role,
|
|
@@ -24,13 +21,12 @@ export async function handleRegisterAgent(projectRoot: string, args: RegisterAge
|
|
|
24
21
|
last_seen: new Date().toISOString(),
|
|
25
22
|
status: "ACTIVE"
|
|
26
23
|
};
|
|
27
|
-
|
|
28
24
|
fs.writeFileSync(agentFile, JSON.stringify(agentData, null, 2));
|
|
29
|
-
|
|
30
25
|
return {
|
|
31
26
|
content: [{ type: "text", text: `[ATABEY] Agent ${agent} (${role}) registered successfully in the Atabey Control Plane.` }]
|
|
32
27
|
};
|
|
33
|
-
}
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
34
30
|
return {
|
|
35
31
|
isError: true,
|
|
36
32
|
content: [{ type: "text", text: `Failed to register agent: ${String(e)}` }]
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const TOOLS: ToolDefinition[] = [
|
|
1
|
+
export const TOOLS = [
|
|
4
2
|
{
|
|
5
3
|
name: "read_file",
|
|
6
4
|
description: "Read the content of a file within the project. Supports optional line range reading to prevent stream overload.",
|
|
@@ -321,4 +319,4 @@ export const TOOLS: ToolDefinition[] = [
|
|
|
321
319
|
required: ["path"],
|
|
322
320
|
},
|
|
323
321
|
}
|
|
324
|
-
];
|
|
322
|
+
];
|
|
@@ -1,53 +1,37 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import { safePath } from "../../utils/security.js";
|
|
3
3
|
import { writeTextFileAtomic } from "../../utils/fs.js";
|
|
4
|
-
import { BatchSurgicalEditArgs, ToolResult } from "../types.js";
|
|
5
4
|
import { Metrics } from "../../utils/metrics.js";
|
|
6
5
|
import { verifyCorporateCompliance, verifyRiskAndAwaitApproval } from "../../utils/compliance.js";
|
|
7
6
|
import { verifyWritePermission } from "../../utils/permissions.js";
|
|
8
|
-
|
|
9
|
-
interface SurgicalEdit {
|
|
10
|
-
path: string;
|
|
11
|
-
oldText: string;
|
|
12
|
-
newText: string;
|
|
13
|
-
allowMultiple?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
7
|
/**
|
|
17
8
|
* Performs multiple surgical text replacements across multiple files in a single batch.
|
|
18
9
|
*/
|
|
19
|
-
export async function handleBatchSurgicalEdit(projectRoot
|
|
20
|
-
const edits = args.edits
|
|
10
|
+
export async function handleBatchSurgicalEdit(projectRoot, args) {
|
|
11
|
+
const edits = args.edits;
|
|
21
12
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
22
13
|
const err = "No edits provided in the batch request.";
|
|
23
14
|
Metrics.logError(projectRoot, "@mcp", "batch_surgical_edit", err);
|
|
24
15
|
throw new Error(err);
|
|
25
16
|
}
|
|
26
|
-
|
|
27
|
-
const results: string[] = [];
|
|
17
|
+
const results = [];
|
|
28
18
|
let totalTokens = 0;
|
|
29
|
-
|
|
30
19
|
for (const edit of edits) {
|
|
31
20
|
const filePath = safePath(projectRoot, edit.path);
|
|
32
|
-
|
|
33
21
|
// ENFORCE PERMISSION MATRIX
|
|
34
22
|
verifyWritePermission(projectRoot, edit.path);
|
|
35
|
-
|
|
36
23
|
if (!fs.existsSync(filePath)) {
|
|
37
24
|
const err = `File not found: ${edit.path}`;
|
|
38
25
|
Metrics.logError(projectRoot, "@mcp", `batch_surgical_edit:${edit.path}`, err);
|
|
39
26
|
throw new Error(err);
|
|
40
27
|
}
|
|
41
|
-
|
|
42
28
|
const content = fs.readFileSync(filePath, "utf8");
|
|
43
29
|
const { oldText, newText, allowMultiple = false } = edit;
|
|
44
|
-
|
|
45
30
|
if (!content.includes(oldText)) {
|
|
46
31
|
const err = `Text not found in file ${edit.path}`;
|
|
47
32
|
Metrics.logError(projectRoot, "@mcp", `batch_surgical_edit:${edit.path}`, err);
|
|
48
33
|
throw new Error(err);
|
|
49
34
|
}
|
|
50
|
-
|
|
51
35
|
// Surgical precision guard
|
|
52
36
|
if (!allowMultiple) {
|
|
53
37
|
const firstIndex = content.indexOf(oldText);
|
|
@@ -58,30 +42,23 @@ export async function handleBatchSurgicalEdit(projectRoot: string, args: BatchSu
|
|
|
58
42
|
throw new Error(err);
|
|
59
43
|
}
|
|
60
44
|
}
|
|
61
|
-
|
|
62
|
-
const newContent = allowMultiple
|
|
45
|
+
const newContent = allowMultiple
|
|
63
46
|
? content.replace(new RegExp(oldText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newText)
|
|
64
47
|
: content.replace(oldText, newText);
|
|
65
|
-
|
|
66
48
|
// ENFORCE CORPORATE COMPLIANCE
|
|
67
49
|
verifyCorporateCompliance(newContent, edit.path);
|
|
68
|
-
|
|
69
50
|
// ENFORCE RISK & HUMAN APPROVAL GATEWAY
|
|
70
51
|
await verifyRiskAndAwaitApproval(projectRoot, newContent, edit.path);
|
|
71
|
-
|
|
72
52
|
writeTextFileAtomic(filePath, newContent);
|
|
73
|
-
|
|
74
53
|
const tokens = Metrics.estimateTokens(newText);
|
|
75
54
|
totalTokens += tokens;
|
|
76
55
|
results.push(`[OK] Edited ${edit.path}`);
|
|
77
56
|
}
|
|
78
|
-
|
|
79
57
|
Metrics.logUsage(projectRoot, "@mcp", `batch_surgical_edit: ${edits.length} files`, totalTokens);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}]
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `Successfully performed ${edits.length} edits:\n${results.join("\n")}`
|
|
62
|
+
}]
|
|
86
63
|
};
|
|
87
64
|
}
|