atabey-mcp 0.0.4

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.
Files changed (73) hide show
  1. package/dist/constants.js +64 -0
  2. package/dist/index.js +119 -0
  3. package/dist/tools/control_plane/locking.js +82 -0
  4. package/dist/tools/control_plane/registry.js +34 -0
  5. package/dist/tools/definitions.js +290 -0
  6. package/dist/tools/file_system/batch_surgical_edit.js +59 -0
  7. package/dist/tools/file_system/patch_file.js +29 -0
  8. package/dist/tools/file_system/read_file.js +51 -0
  9. package/dist/tools/file_system/replace_text.js +45 -0
  10. package/dist/tools/file_system/write_file.js +38 -0
  11. package/dist/tools/framework/audit_deps.js +41 -0
  12. package/dist/tools/framework/get_status.js +5 -0
  13. package/dist/tools/framework/orchestrate.js +5 -0
  14. package/dist/tools/framework/run_tests.js +27 -0
  15. package/dist/tools/framework/update_contract_hash.js +5 -0
  16. package/dist/tools/framework/update_memory.js +8 -0
  17. package/dist/tools/index.js +60 -0
  18. package/dist/tools/memory/get_insights.js +34 -0
  19. package/dist/tools/memory/read_memory.js +28 -0
  20. package/dist/tools/messaging/log_action.js +22 -0
  21. package/dist/tools/messaging/send_message.js +94 -0
  22. package/dist/tools/observability/check_ports.js +26 -0
  23. package/dist/tools/observability/get_health.js +19 -0
  24. package/dist/tools/quality/check_lint.js +30 -0
  25. package/dist/tools/search/get_gaps.js +48 -0
  26. package/dist/tools/search/get_map.js +43 -0
  27. package/dist/tools/search/grep_search.js +75 -0
  28. package/dist/tools/search/list_dir.js +28 -0
  29. package/dist/tools/shell/run_command.js +56 -0
  30. package/dist/tools/types.js +1 -0
  31. package/dist/utils/cli.js +59 -0
  32. package/dist/utils/compliance.js +78 -0
  33. package/dist/utils/fs.js +44 -0
  34. package/dist/utils/metrics.js +56 -0
  35. package/dist/utils/security.js +60 -0
  36. package/package.json +26 -0
  37. package/src/constants.ts +78 -0
  38. package/src/declarations.d.ts +17 -0
  39. package/src/index.ts +144 -0
  40. package/src/tools/control_plane/locking.ts +89 -0
  41. package/src/tools/control_plane/registry.ts +38 -0
  42. package/src/tools/definitions.ts +292 -0
  43. package/src/tools/file_system/batch_surgical_edit.ts +79 -0
  44. package/src/tools/file_system/patch_file.ts +39 -0
  45. package/src/tools/file_system/read_file.ts +58 -0
  46. package/src/tools/file_system/replace_text.ts +54 -0
  47. package/src/tools/file_system/write_file.ts +45 -0
  48. package/src/tools/framework/audit_deps.ts +49 -0
  49. package/src/tools/framework/get_status.ts +7 -0
  50. package/src/tools/framework/orchestrate.ts +7 -0
  51. package/src/tools/framework/run_tests.ts +30 -0
  52. package/src/tools/framework/update_contract_hash.ts +7 -0
  53. package/src/tools/framework/update_memory.ts +10 -0
  54. package/src/tools/index.ts +64 -0
  55. package/src/tools/memory/get_insights.ts +41 -0
  56. package/src/tools/memory/read_memory.ts +31 -0
  57. package/src/tools/messaging/log_action.ts +28 -0
  58. package/src/tools/messaging/send_message.ts +97 -0
  59. package/src/tools/observability/check_ports.ts +30 -0
  60. package/src/tools/observability/get_health.ts +24 -0
  61. package/src/tools/quality/check_lint.ts +36 -0
  62. package/src/tools/search/get_gaps.ts +54 -0
  63. package/src/tools/search/get_map.ts +48 -0
  64. package/src/tools/search/grep_search.ts +75 -0
  65. package/src/tools/search/list_dir.ts +34 -0
  66. package/src/tools/shell/run_command.ts +66 -0
  67. package/src/tools/types.ts +89 -0
  68. package/src/utils/cli.ts +53 -0
  69. package/src/utils/compliance.ts +95 -0
  70. package/src/utils/fs.ts +45 -0
  71. package/src/utils/metrics.ts +73 -0
  72. package/src/utils/security.ts +66 -0
  73. package/tsconfig.json +14 -0
@@ -0,0 +1,64 @@
1
+ import path from "path";
2
+ /**
3
+ * Agent Atabey — Single Source of Truth for framework constants.
4
+ * Import from here instead of hardcoding paths, phases, or directory names.
5
+ */
6
+ // ─── Framework identity ───────────────────────────────────────────────────
7
+ export const FRAMEWORK = {
8
+ NAME: "Agent Atabey",
9
+ CORE_DIR: ".atabey",
10
+ // This is the hub for unified adapter layouts (e.g. .agents/gemini, .agents/claude)
11
+ UNIFIED_HUB_DIR: ".agents",
12
+ // This is the default directory to scaffold new apps into
13
+ APPS_DIR: "apps",
14
+ // This is where all skills are stored
15
+ SKILLS_DIR: "skills",
16
+ };
17
+ export const FRAMEWORK_SUBDIRS = {
18
+ AGENTS: "agents",
19
+ SKILLS: "skills",
20
+ KNOWLEDGE: "knowledge",
21
+ MESSAGES: "messages",
22
+ MEMORY: "memory",
23
+ MEMORY_GRAPH: "memory-graph",
24
+ LOGS: "logs",
25
+ CONFIG: "config",
26
+ };
27
+ export const ROOT_CONFIG_FILES = {
28
+ MCP: "mcp.json",
29
+ NATIVE_MODULES: "native-modules.json",
30
+ TSCONFIG: "tsconfig.json",
31
+ ESLINT: "eslint.config.js",
32
+ };
33
+ export const MCP = {
34
+ // Environment variable used by MCP to identify project root
35
+ PROJECT_ROOT_ENV: "ATABEY_PROJECT_ROOT",
36
+ // Environment variable for test mode
37
+ TEST_DIR_ENV: "ATABEY_TEST_DIR",
38
+ };
39
+ export const MEMORY_FILES = {
40
+ STATE: "state.json",
41
+ SHARED_FACTS: "shared_facts.json",
42
+ };
43
+ export const NATIVE_AGENT_PATHS = {
44
+ gemini: ".gemini/agents",
45
+ claude: ".claude/agents",
46
+ cursor: ".cursor/rules",
47
+ codex: ".agents/instructions",
48
+ grok: ".grok",
49
+ "antigravity-cli": ".antigravity/agents",
50
+ };
51
+ // ─── Backward-compatible aliases ──────────────────────────────────────────
52
+ export const CORE_FRAMEWORK_DIR = FRAMEWORK.CORE_DIR;
53
+ export const UNIFIED_HUB_DIR = FRAMEWORK.UNIFIED_HUB_DIR;
54
+ export const SKILLS_HUB_PATH = pathJoin(UNIFIED_HUB_DIR, FRAMEWORK_SUBDIRS.SKILLS);
55
+ // ─── Path Helpers ─────────────────────────────────────────────────────────
56
+ function pathJoin(...args) {
57
+ return path.join(...args);
58
+ }
59
+ function corePath(subdir, filename) {
60
+ return pathJoin(FRAMEWORK.CORE_DIR, subdir, filename);
61
+ }
62
+ export function knowledgePath(filename) {
63
+ return corePath(FRAMEWORK_SUBDIRS.KNOWLEDGE, filename);
64
+ }
package/dist/index.js ADDED
@@ -0,0 +1,119 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
7
+ import { TOOLS, toolHandlers } from "./tools/index.js";
8
+ // ─── Server Setup ─────────────────────────────────────────────────
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ // Robustly find package.json by walking up from __dirname
11
+ function findPackageJson(startDir) {
12
+ let currentDir = startDir;
13
+ while (currentDir !== path.parse(currentDir).root) {
14
+ const pkgPath = path.join(currentDir, "package.json");
15
+ if (fs.existsSync(pkgPath))
16
+ return pkgPath;
17
+ currentDir = path.dirname(currentDir);
18
+ }
19
+ throw new Error("Could not find package.json for atabey-mcp");
20
+ }
21
+ const pkgPath = findPackageJson(__dirname);
22
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
23
+ const serverVersion = pkg.version;
24
+ const server = new Server({
25
+ name: "atabey-mcp",
26
+ version: serverVersion,
27
+ }, {
28
+ capabilities: {
29
+ tools: {},
30
+ },
31
+ });
32
+ // Basic Schema Validator for Required Fields
33
+ function validateArgs(toolName, args) {
34
+ const definition = TOOLS.find(t => t.name === toolName);
35
+ if (!definition)
36
+ return `Unknown tool: ${toolName}`;
37
+ const required = definition.inputSchema.required || [];
38
+ for (const field of required) {
39
+ if (args[field] === undefined || args[field] === null || args[field] === "") {
40
+ return `Missing required argument: '${field}' for tool '${toolName}'`;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ server.setRequestHandler(ListToolsRequestSchema, async (request) => {
46
+ // 2026 Stateless Spec: Log client info from metadata if available
47
+ const meta = request._meta;
48
+ if (meta) {
49
+ process.stderr.write(`[MCP] Stateless ListTools from ${meta.client?.name || "unknown"} v${meta.client?.version || "?.?"}\n`);
50
+ }
51
+ return { tools: TOOLS };
52
+ });
53
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
54
+ const req = request;
55
+ const { name, arguments: args } = req.params;
56
+ const meta = request._meta;
57
+ // 2026 Stateless Spec: Prioritize metadata-driven context
58
+ if (meta) {
59
+ process.stderr.write(`[MCP] Stateless CallTool: ${name} (Client: ${meta.client?.name || "unknown"})\n`);
60
+ }
61
+ const projectRoot = process.env.ATABEY_PROJECT_ROOT || process.cwd();
62
+ try {
63
+ const handler = toolHandlers[name];
64
+ if (!handler) {
65
+ return {
66
+ isError: true,
67
+ content: [{ type: "text", text: `❌ Unknown tool: ${name}` }],
68
+ };
69
+ }
70
+ // 🛡️ Runtime Validation
71
+ const validationError = validateArgs(name, args || {});
72
+ if (validationError) {
73
+ return {
74
+ isError: true,
75
+ content: [{ type: "text", text: `❌ Validation Error: ${validationError}` }],
76
+ };
77
+ }
78
+ return await handler(projectRoot, args || {});
79
+ }
80
+ catch (error) {
81
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
82
+ return {
83
+ isError: true,
84
+ content: [{ type: "text", text: `❌ Execution failed: ${message}` }],
85
+ };
86
+ }
87
+ });
88
+ // ─── Graceful Startup & Shutdown ──────────────────────────────────
89
+ async function run() {
90
+ const transport = new StdioServerTransport();
91
+ // Prevent unhandled errors from crashing the MCP stream
92
+ process.on("uncaughtException", (error) => {
93
+ process.stderr.write(`[atabey-mcp] Uncaught exception: ${error.message}
94
+ `);
95
+ });
96
+ process.on("unhandledRejection", (reason) => {
97
+ const message = reason instanceof Error ? reason.message : String(reason);
98
+ process.stderr.write(`[atabey-mcp] Unhandled rejection: ${message}
99
+ `);
100
+ });
101
+ // Graceful shutdown on SIGINT/SIGTERM
102
+ const shutdown = async () => {
103
+ try {
104
+ await server.close();
105
+ }
106
+ catch {
107
+ // Already closed or failed — safe to ignore
108
+ }
109
+ process.exit(0);
110
+ };
111
+ process.on("SIGINT", shutdown);
112
+ process.on("SIGTERM", shutdown);
113
+ await server.connect(transport);
114
+ }
115
+ run().catch((error) => {
116
+ process.stderr.write(`[atabey-mcp] Fatal startup error: ${error.message}
117
+ `);
118
+ process.exit(1);
119
+ });
@@ -0,0 +1,82 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { resolveFrameworkDir } from "../../utils/security.js";
4
+ /**
5
+ * Handles acquiring a stateful lock on a resource.
6
+ */
7
+ export async function handleAcquireLock(projectRoot, args) {
8
+ const { resource, agent, ttl = 60 } = args;
9
+ const frameworkDir = resolveFrameworkDir(projectRoot);
10
+ const lockDir = path.join(projectRoot, frameworkDir, "locks");
11
+ const lockPath = path.join(lockDir, `${resource}.lock`);
12
+ try {
13
+ if (!fs.existsSync(lockDir))
14
+ fs.mkdirSync(lockDir, { recursive: true });
15
+ // Check for stale lock first
16
+ if (fs.existsSync(lockPath)) {
17
+ const stat = fs.statSync(lockPath);
18
+ const now = new Date().getTime();
19
+ const age = (now - stat.mtimeMs) / 1000;
20
+ if (age < ttl) {
21
+ return {
22
+ isError: true,
23
+ content: [{ type: "text", text: `Resource '${resource}' is currently locked by another agent.` }]
24
+ };
25
+ }
26
+ // Lock expired, safe to override by renaming first (POSIX atomic operation)
27
+ const tempLockPath = `${lockPath}.${Math.random().toString(36).substring(2)}.old`;
28
+ try {
29
+ fs.renameSync(lockPath, tempLockPath);
30
+ fs.unlinkSync(tempLockPath);
31
+ }
32
+ catch {
33
+ // If rename failed, it means another agent already evicting/deleting it.
34
+ // Do not delete anything else; proceed and let writeFileSync (wx flag) fail if a new lock exists.
35
+ }
36
+ }
37
+ // Use 'wx' flag for atomic file creation
38
+ const lockData = JSON.stringify({ agent, timestamp: new Date().toISOString() });
39
+ fs.writeFileSync(lockPath, lockData, { flag: "wx" });
40
+ return {
41
+ content: [{ type: "text", text: `✅ Lock acquired for resource '${resource}' by ${agent}.` }]
42
+ };
43
+ }
44
+ catch (e) {
45
+ const error = e;
46
+ if (error.code === "EEXIST") {
47
+ return {
48
+ isError: true,
49
+ content: [{ type: "text", text: `Resource '${resource}' was just acquired by another agent.` }]
50
+ };
51
+ }
52
+ return {
53
+ isError: true,
54
+ content: [{ type: "text", text: `Failed to acquire lock: ${String(e)}` }]
55
+ };
56
+ }
57
+ }
58
+ /**
59
+ * Handles releasing a lock.
60
+ */
61
+ export async function handleReleaseLock(projectRoot, args) {
62
+ const { resource, agent } = args;
63
+ const frameworkDir = resolveFrameworkDir(projectRoot);
64
+ const lockPath = path.join(projectRoot, frameworkDir, "locks", `${resource}.lock`);
65
+ try {
66
+ if (!fs.existsSync(lockPath)) {
67
+ return { content: [{ type: "text", text: `ℹ️ No lock found for resource '${resource}'.` }] };
68
+ }
69
+ const lockData = JSON.parse(fs.readFileSync(lockPath, "utf8"));
70
+ if (lockData.agent !== agent) {
71
+ return {
72
+ isError: true,
73
+ content: [{ type: "text", text: `❌ Denied: You do not own the lock for '${resource}'. Owned by ${lockData.agent}.` }]
74
+ };
75
+ }
76
+ fs.unlinkSync(lockPath);
77
+ return { content: [{ type: "text", text: `✅ Lock released for resource '${resource}' by ${agent}.` }] };
78
+ }
79
+ catch (e) {
80
+ return { isError: true, content: [{ type: "text", text: `Failed to release lock: ${String(e)}` }] };
81
+ }
82
+ }
@@ -0,0 +1,34 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { resolveFrameworkDir } from "../../utils/security.js";
4
+ /**
5
+ * Handles agent registration with the Control Plane.
6
+ * This can be used to validate permissions and active status.
7
+ */
8
+ export async function handleRegisterAgent(projectRoot, args) {
9
+ const { agent, role, capability = 5 } = args;
10
+ const frameworkDir = resolveFrameworkDir(projectRoot);
11
+ const registryDir = path.join(projectRoot, frameworkDir, "registry");
12
+ const agentFile = path.join(registryDir, `${agent.replace("@", "")}_active.json`);
13
+ try {
14
+ if (!fs.existsSync(registryDir))
15
+ fs.mkdirSync(registryDir, { recursive: true });
16
+ const agentData = {
17
+ agent,
18
+ role,
19
+ capability,
20
+ last_seen: new Date().toISOString(),
21
+ status: "ACTIVE"
22
+ };
23
+ fs.writeFileSync(agentFile, JSON.stringify(agentData, null, 2));
24
+ return {
25
+ content: [{ type: "text", text: `🎖️ Agent ${agent} (${role}) registered successfully in the Atabey Control Plane.` }]
26
+ };
27
+ }
28
+ catch (e) {
29
+ return {
30
+ isError: true,
31
+ content: [{ type: "text", text: `Failed to register agent: ${String(e)}` }]
32
+ };
33
+ }
34
+ }
@@ -0,0 +1,290 @@
1
+ export const TOOLS = [
2
+ {
3
+ name: "read_file",
4
+ description: "Read the content of a file within the project. Supports optional line range reading to prevent stream overload.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {
8
+ path: { type: "string", description: "Path to the file relative to project root" },
9
+ startLine: { type: "number", description: "Optional starting line number (1-indexed)" },
10
+ endLine: { type: "number", description: "Optional ending line number (inclusive)" },
11
+ },
12
+ required: ["path"],
13
+ },
14
+ },
15
+ {
16
+ name: "list_dir",
17
+ description: "List the contents of a directory. Essential for codebase exploration.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ path: { type: "string", description: "Path to the directory relative to project root (default: '.')" },
22
+ },
23
+ },
24
+ },
25
+ {
26
+ name: "grep_search",
27
+ description: "Perform a recursive regex search across the codebase to find functions, variables, or patterns.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ pattern: { type: "string", description: "The regex pattern to search for" },
32
+ includePattern: { type: "string", description: "Optional: Filter files by extension (e.g., '.ts')" },
33
+ excludePattern: { type: "string", description: "Optional: Directory pattern to exclude (default: 'node_modules')" },
34
+ },
35
+ required: ["pattern"],
36
+ },
37
+ },
38
+ {
39
+ name: "write_file",
40
+ description: "Write content to a file. Creates directories if missing.",
41
+ inputSchema: {
42
+ type: "object",
43
+ properties: {
44
+ path: { type: "string", description: "Path to the file relative to project root" },
45
+ content: { type: "string", description: "Complete content of the file" },
46
+ },
47
+ required: ["path", "content"],
48
+ },
49
+ },
50
+ {
51
+ name: "replace_text",
52
+ description: "Surgically replace a string in a file with another string.",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ path: { type: "string", description: "Path to the file" },
57
+ oldText: { type: "string", description: "The exact text to find" },
58
+ newText: { type: "string", description: "The text to replace it with" },
59
+ allowMultiple: { type: "boolean", description: "Allow multiple replacements if oldText is not unique." }
60
+ },
61
+ required: ["path", "oldText", "newText"],
62
+ },
63
+ },
64
+ {
65
+ name: "batch_surgical_edit",
66
+ description: "Perform multiple surgical text replacements across multiple files in a single batch request. More efficient for cross-cutting changes.",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ edits: {
71
+ type: "array",
72
+ items: {
73
+ type: "object",
74
+ properties: {
75
+ path: { type: "string", description: "Path to the file" },
76
+ oldText: { type: "string", description: "The exact text to find" },
77
+ newText: { type: "string", description: "The text to replace it with" },
78
+ allowMultiple: { type: "boolean", description: "Allow multiple replacements in this file." }
79
+ },
80
+ required: ["path", "oldText", "newText"]
81
+ }
82
+ }
83
+ },
84
+ required: ["edits"],
85
+ },
86
+ },
87
+ {
88
+ name: "patch_file",
89
+ description: "Safely update a file by replacing a specific line range with new content.",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ path: { type: "string", description: "Path to the file" },
94
+ startLine: { type: "number", description: "Starting line number (1-indexed)" },
95
+ endLine: { type: "number", description: "Ending line number (inclusive)" },
96
+ newContent: { type: "string", description: "The new lines to insert" },
97
+ },
98
+ required: ["path", "startLine", "endLine", "newContent"],
99
+ },
100
+ },
101
+ {
102
+ name: "get_project_map",
103
+ description: "Generate a tree-view map of the project structure to visualize the layout. Useful for onboarding and architectural analysis.",
104
+ inputSchema: {
105
+ type: "object",
106
+ properties: {
107
+ maxDepth: { type: "number", description: "Maximum directory depth to scan (default: 3)" },
108
+ includeFiles: { type: "boolean", description: "Whether to include files in the map (default: true)" }
109
+ }
110
+ }
111
+ },
112
+ {
113
+ name: "get_project_gaps",
114
+ description: "Scans the codebase for TODOs, FIXMEs, and empty function bodies. Helps identify what is left and where the agent might have skipped logic.",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: {
118
+ path: { type: "string", description: "Path to the source directory (default: 'src')" },
119
+ },
120
+ },
121
+ },
122
+ {
123
+ name: "audit_dependencies",
124
+ description: "Audits package.json for unused, redundant, or duplicate-like packages to ensure a clean dependency tree.",
125
+ inputSchema: { type: "object", properties: {} },
126
+ },
127
+ {
128
+ name: "run_tests",
129
+ description: "Execute project test suites (Vitest, Playwright, etc.) and capture detailed pass/fail reports for analysis.",
130
+ inputSchema: {
131
+ type: "object",
132
+ properties: {
133
+ command: { type: "string", description: "The test command to run (default: project's standard test command)" }
134
+ }
135
+ }
136
+ },
137
+ {
138
+ name: "get_system_health",
139
+ description: "Retrieve real-time system metrics like CPU load, RAM usage, and uptime.",
140
+ inputSchema: { type: "object", properties: {} }
141
+ },
142
+ {
143
+ name: "check_active_ports",
144
+ description: "Identify which network ports are currently active and listening. Helps verify if the dev server or API is running.",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: {
148
+ filter: { type: "string", description: "Optional: Filter results by port number or service name (e.g., ':3000')" }
149
+ }
150
+ }
151
+ },
152
+ {
153
+ name: "get_framework_status",
154
+ description: "Get the current project phase, active traces, and agent states.",
155
+ inputSchema: { type: "object", properties: {} },
156
+ },
157
+ {
158
+ name: "read_project_memory",
159
+ description: "Read the full project central memory (PROJECT_MEMORY.md). Use this at the start of a session to gain context.",
160
+ inputSchema: { type: "object", properties: {} },
161
+ },
162
+ {
163
+ name: "get_memory_insights",
164
+ description: "Retrieve a summarized version of the project memory (active phase, trace, and last 5 actions) to save tokens.",
165
+ inputSchema: { type: "object", properties: {} },
166
+ },
167
+ {
168
+ name: "update_project_memory",
169
+ description: "Update a specific section in PROJECT_MEMORY.md.",
170
+ inputSchema: {
171
+ type: "object",
172
+ properties: {
173
+ section: { type: "string", description: "Section name (e.g., HISTORY, ACTIVE TASKS)" },
174
+ content: { type: "string", description: "Markdown content to append or set" },
175
+ },
176
+ required: ["section", "content"],
177
+ },
178
+ },
179
+ {
180
+ name: "orchestrate_loop",
181
+ description: "Process the pending Hermes messages and trigger dynamic state transitions.",
182
+ inputSchema: { type: "object", properties: {} },
183
+ },
184
+ {
185
+ name: "send_agent_message",
186
+ description: "Send a Hermes protocol message to another agent for collaboration or sub-tasking.",
187
+ inputSchema: {
188
+ type: "object",
189
+ properties: {
190
+ from: { type: "string", description: "Sender agent (e.g., @manager, @backend)." },
191
+ to: { type: "string", description: "Target agent (e.g., @backend, @quality)." },
192
+ category: { type: "string", enum: ["ACTION", "DELEGATION", "SUBTASK", "REPLY", "ALERT"], description: "Message type." },
193
+ content: { type: "string", description: "Task description or message content." },
194
+ traceId: { type: "string", description: "Active Trace ID." },
195
+ parentId: { type: "string", description: "Optional parent task trace ID for hierarchical tracking." },
196
+ priority: { type: "string", enum: ["HIGH", "NORMAL", "LOW"], description: "Optional priority level (HIGH, NORMAL, LOW). Defaults to HIGH for ALERT/ACTION." },
197
+ requiresApproval: { type: "boolean", description: "If true, this message will require manager approval before being processed." },
198
+ },
199
+ required: ["from", "to", "category", "content", "traceId"],
200
+ },
201
+ },
202
+ {
203
+ name: "acquire_lock",
204
+ description: "Acquire a stateful lock on a shared resource (like PROJECT_MEMORY.md) to prevent concurrent write conflicts.",
205
+ inputSchema: {
206
+ type: "object",
207
+ properties: {
208
+ resource: { type: "string", description: "The resource name or path to lock (e.g., 'memory')." },
209
+ agent: { type: "string", description: "The agent requesting the lock (e.g., '@backend')." },
210
+ ttl: { type: "number", description: "Time-to-live in seconds before the lock auto-expires (default: 60)." }
211
+ },
212
+ required: ["resource", "agent"]
213
+ }
214
+ },
215
+ {
216
+ name: "release_lock",
217
+ description: "Release a previously acquired lock on a resource.",
218
+ inputSchema: {
219
+ type: "object",
220
+ properties: {
221
+ resource: { type: "string", description: "The resource name or path." },
222
+ agent: { type: "string", description: "The agent releasing the lock." }
223
+ },
224
+ required: ["resource", "agent"]
225
+ }
226
+ },
227
+ {
228
+ name: "register_agent",
229
+ description: "Register an agent instance with the Control Plane and validate its permissions.",
230
+ inputSchema: {
231
+ type: "object",
232
+ properties: {
233
+ agent: { type: "string", description: "The agent name (e.g., '@backend')." },
234
+ role: { type: "string", description: "The role of the agent." },
235
+ capability: { type: "number", description: "The capability score (1-10)." }
236
+ },
237
+ required: ["agent", "role"]
238
+ }
239
+ },
240
+ {
241
+ name: "log_agent_action",
242
+ description: "Log an agent action to the framework logs.",
243
+ inputSchema: {
244
+ type: "object",
245
+ properties: {
246
+ agent: { type: "string", description: "The agent name (e.g., @manager, @backend)" },
247
+ action: { type: "string", description: "Action type or name" },
248
+ traceId: { type: "string", description: "The active Trace ID" },
249
+ status: { type: "string", enum: ["SUCCESS", "FAILURE"], description: "The status of the action" },
250
+ summary: { type: "string", description: "Brief description of the action taken" },
251
+ findings: { type: "string", description: "Optional comma-separated findings or details" }
252
+ },
253
+ required: ["agent", "action", "traceId", "status", "summary"]
254
+ }
255
+ },
256
+ {
257
+ name: "update_contract_hash",
258
+ description: "Re-generate and synchronize the backend contract SHA-256 hash.",
259
+ inputSchema: { type: "object", properties: {} }
260
+ },
261
+ {
262
+ name: "run_shell_command",
263
+ description: "Execute a shell command. For security, only a limited set of commands are allowed.",
264
+ inputSchema: {
265
+ type: "object",
266
+ properties: {
267
+ command: { type: "string", description: "The shell command to execute." },
268
+ },
269
+ required: ["command"],
270
+ },
271
+ },
272
+ {
273
+ name: "check_lint",
274
+ description: "Run the project's linter (e.g., ESLint) to check for code quality and style issues.",
275
+ inputSchema: { type: "object", properties: {} },
276
+ },
277
+ {
278
+ name: "view_file",
279
+ description: "Alias for read_file. Reads the content of a file within the project.",
280
+ inputSchema: {
281
+ type: "object",
282
+ properties: {
283
+ path: { type: "string", description: "Path to the file relative to project root" },
284
+ startLine: { type: "number", description: "Optional starting line number (1-indexed)" },
285
+ endLine: { type: "number", description: "Optional ending line number (inclusive)" },
286
+ },
287
+ required: ["path"],
288
+ },
289
+ }
290
+ ];
@@ -0,0 +1,59 @@
1
+ import fs from "fs";
2
+ import { safePath } from "../../utils/security.js";
3
+ import { writeTextFileAtomic } from "../../utils/fs.js";
4
+ import { Metrics } from "../../utils/metrics.js";
5
+ import { verifyCorporateCompliance } from "../../utils/compliance.js";
6
+ /**
7
+ * Performs multiple surgical text replacements across multiple files in a single batch.
8
+ */
9
+ export function handleBatchSurgicalEdit(projectRoot, args) {
10
+ const edits = args.edits;
11
+ if (!Array.isArray(edits) || edits.length === 0) {
12
+ const err = "No edits provided in the batch request.";
13
+ Metrics.logError(projectRoot, "@mcp", "batch_surgical_edit", err);
14
+ throw new Error(err);
15
+ }
16
+ const results = [];
17
+ let totalTokens = 0;
18
+ for (const edit of edits) {
19
+ const filePath = safePath(projectRoot, edit.path);
20
+ if (!fs.existsSync(filePath)) {
21
+ const err = `File not found: ${edit.path}`;
22
+ Metrics.logError(projectRoot, "@mcp", `batch_surgical_edit:${edit.path}`, err);
23
+ throw new Error(err);
24
+ }
25
+ const content = fs.readFileSync(filePath, "utf8");
26
+ const { oldText, newText, allowMultiple = false } = edit;
27
+ if (!content.includes(oldText)) {
28
+ const err = `Text not found in file ${edit.path}`;
29
+ Metrics.logError(projectRoot, "@mcp", `batch_surgical_edit:${edit.path}`, err);
30
+ throw new Error(err);
31
+ }
32
+ // Surgical precision guard
33
+ if (!allowMultiple) {
34
+ const firstIndex = content.indexOf(oldText);
35
+ const lastIndex = content.lastIndexOf(oldText);
36
+ if (firstIndex !== lastIndex) {
37
+ const err = `Ambiguous replacement in ${edit.path}`;
38
+ Metrics.logError(projectRoot, "@mcp", `batch_surgical_edit:${edit.path}`, err);
39
+ throw new Error(err);
40
+ }
41
+ }
42
+ const newContent = allowMultiple
43
+ ? content.replace(new RegExp(oldText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newText)
44
+ : content.replace(oldText, newText);
45
+ // ENFORCE CORPORATE COMPLIANCE
46
+ verifyCorporateCompliance(newContent, edit.path);
47
+ writeTextFileAtomic(filePath, newContent);
48
+ const tokens = Metrics.estimateTokens(newText);
49
+ totalTokens += tokens;
50
+ results.push(`✅ Edited ${edit.path}`);
51
+ }
52
+ Metrics.logUsage(projectRoot, "@mcp", `batch_surgical_edit: ${edits.length} files`, totalTokens);
53
+ return {
54
+ content: [{
55
+ type: "text",
56
+ text: `Successfully performed ${edits.length} edits:\n${results.join("\n")}`
57
+ }]
58
+ };
59
+ }