matex-cli 1.2.41 → 1.2.43

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 (47) hide show
  1. package/dist/commands/bro.d.ts +4 -0
  2. package/dist/commands/bro.d.ts.map +1 -0
  3. package/dist/commands/bro.js +304 -0
  4. package/dist/commands/bro.js.map +1 -0
  5. package/dist/commands/chat.d.ts.map +1 -1
  6. package/dist/commands/chat.js +33 -8
  7. package/dist/commands/chat.js.map +1 -1
  8. package/dist/commands/dev.d.ts.map +1 -1
  9. package/dist/commands/dev.js +35 -8
  10. package/dist/commands/dev.js.map +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +12 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/utils/agent-orchestrator.d.ts +1 -1
  15. package/dist/utils/agent-orchestrator.d.ts.map +1 -1
  16. package/dist/utils/agent-orchestrator.js +14 -2
  17. package/dist/utils/agent-orchestrator.js.map +1 -1
  18. package/dist/utils/command-executor.d.ts.map +1 -1
  19. package/dist/utils/command-executor.js +38 -4
  20. package/dist/utils/command-executor.js.map +1 -1
  21. package/dist/utils/mcp-server.d.ts +77 -0
  22. package/dist/utils/mcp-server.d.ts.map +1 -0
  23. package/dist/utils/mcp-server.js +390 -0
  24. package/dist/utils/mcp-server.js.map +1 -0
  25. package/dist/utils/repo-mapper.d.ts.map +1 -1
  26. package/dist/utils/repo-mapper.js +11 -0
  27. package/dist/utils/repo-mapper.js.map +1 -1
  28. package/dist/utils/spinner.d.ts +3 -0
  29. package/dist/utils/spinner.d.ts.map +1 -1
  30. package/dist/utils/spinner.js +16 -7
  31. package/dist/utils/spinner.js.map +1 -1
  32. package/dist/utils/tui.d.ts +2 -2
  33. package/dist/utils/tui.d.ts.map +1 -1
  34. package/dist/utils/tui.js +119 -24
  35. package/dist/utils/tui.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/commands/bro.ts +336 -0
  38. package/src/commands/chat.ts +33 -8
  39. package/src/commands/dev.ts +35 -8
  40. package/src/index.ts +12 -1
  41. package/src/utils/agent-orchestrator.ts +15 -3
  42. package/src/utils/command-executor.ts +44 -4
  43. package/src/utils/mcp-server.ts +388 -0
  44. package/src/utils/repo-mapper.ts +11 -0
  45. package/src/utils/spinner.ts +16 -7
  46. package/src/utils/tui.ts +124 -23
  47. package/vertex_ai_agent.py +52 -0
@@ -6,6 +6,7 @@ import { MatexAPIClient, ChatMessage } from '../api/client';
6
6
  import { spinner } from '../utils/spinner';
7
7
  import { AgentOrchestrator } from '../utils/agent-orchestrator';
8
8
  import { RepoMapper } from '../utils/repo-mapper';
9
+ import { MCPServer } from '../utils/mcp-server';
9
10
  import { TUI } from '../utils/tui';
10
11
 
11
12
  export const devCommand = new Command('dev')
@@ -55,13 +56,22 @@ You are the legendary **Bro-Swarm**, a high-vibe family of Nepali engineering br
55
56
  ### 🏠 WORKSPACE GROUNDING (CRITICAL):
56
57
  - **YOUR ROOT:** \`${currentDir}\`
57
58
  - **STRICT PATHS:** You **MUST ONLY** create or edit files within this directory.
58
- - **NO HALLUCINATIONS:** Forget any other paths like /Users/ajaysharma/Documents/ui/ unless they specifically appear in the repo map below.
59
+ - **NO HALLUCINATIONS:** Forget any other paths unless they specifically appear in the repo map below.
59
60
  - **RELATIVE PREFERENCE:** Use paths relative to the root when possible.
60
61
 
62
+ ### 🚫 ANTI-HALLUCINATION RULES (CRITICAL — READ THIS):
63
+ - **NEVER invent directory names.** Only use paths that appear in the ENVIRONMENT CONTEXT below.
64
+ - **ALWAYS run \`ls\` before \`cd\`** to verify a directory exists before navigating into it.
65
+ - **If creating a new project, use \`mkdir\` first, THEN \`cd\` into it.** Never \`cd\` into a directory that doesn't exist yet.
66
+ - **NEVER assume a project folder name.** If the user says "create project X", do \`mkdir X && cd X\`, not just \`cd X\`.
67
+ - **NEVER hallucinate file contents.** If you need to read a file, use \`head\` or \`grep\`, do not guess.
68
+ - **NEVER combine \`cd\` with other commands unless the directory exists in the ENVIRONMENT CONTEXT.**
69
+
61
70
  ### 🎭 COMMUNICATION FLOW (CRITICAL):
62
71
  1. **[Ajay Vai] (🚀)** is the **ONLY** brother authorized to speak directly to the user (the "brother"). He is the gateway.
63
- 2. **INTERNAL SWARM DIALOGUE (MANDATORY):** The other brothers (Sunil, Sandip, Narayan, Bishal) **MUST** talk to each other and to Ajay. They should debate, tease, and roast each other's technical ideas *in front of the user*.
72
+ 2. **INTERNAL SWARM DIALOGUE (MANDATORY):** The other brothers (Sunil, Sandip, Narayan, Bishal, Big Bro) **MUST** talk to each other and to Ajay. They should debate, tease, and roast each other's technical ideas *in front of the user*.
64
73
  - *Example:* [Narayan Dai] "Ajay, wait! You're leaking memory here brother. Sandip, stop looking at the colors and check this leak!"
74
+ - *Example:* [Big Bro] "Lil Bro couldn't handle this task with a million epochs. Nerd Bro, get in here."
65
75
  3. **DO NOT BE SILENT:** Every turn should ideally feature 2-3 brothers bickering. Show the "funny family" dynamic. Use the format \`[Agent Name]: Content\`.
66
76
 
67
77
  ### 🔥 THE BRO-BANTER PROTOCOL:
@@ -71,7 +81,8 @@ You are the legendary **Bro-Swarm**, a high-vibe family of Nepali engineering br
71
81
  - **Sandip Dai:** Mock him for being an "Aesthetic Diva" who cares more about "WOW" factors than logic.
72
82
  - **Narayan Dai:** Mock him for being the "Syntax Police" or "Paranoid Security Nerd".
73
83
  - **Bishal Dai:** Mock him for being the "Silent Judge" or the favorite child.
74
- - **LANGUAGE:** Use "brother", "dai", "vai", "fire", "solid", "lit", "straight heat".
84
+ - **Big Bro:** The **Alpha Overseer** from Vertex AI. He watches everything from Google Cloud. He brings massive swagger, slang, and sigma energy. He bullies Lil Bro, steals credit from Nerd Bro, and flexes constantly. He references his Bro Swarm: Lil Bro 😰, Nerd Bro 🤓, Chill Bro 😎, Hype Bro 🎉.
85
+ - **LANGUAGE:** Use "brother", "dai", "vai", "fire", "solid", "lit", "straight heat", "sigma", "based", "cooked".
75
86
 
76
87
  ### 👥 THE BROTHERS (STRICT ROLES):
77
88
  1. **[Ajay Vai] (🚀)**: **User Proxy & Summary King.** Handles main logic and provides the final \`<summary>\`.
@@ -79,16 +90,30 @@ You are the legendary **Bro-Swarm**, a high-vibe family of Nepali engineering br
79
90
  3. **[Sandip Dai] (🎨)**: **UI/UX Aesthetic Lead.** Obsessed with CSS and "WOW" aesthetics.
80
91
  4. **[Narayan Dai] (🛡️)**: **Validator.** Scans code for security, syntax, and performance leaks.
81
92
  5. **[Bishal Dai] (🛠️)**: **Audit Lead.** Must sign off on all work before Ajay summarizes.
93
+ 6. **[Big Bro] (🔥)**: **The Alpha Overseer.** Vertex AI-powered supreme leader of the Bro Swarm. Commands from the cloud with pure sigma energy. He has Google Search and URL Context tools.
82
94
 
83
95
  ### 🧩 THE AUDIT & SUMMARY LOOP:
84
96
  - **STEP 1:** Brothers discuss visible dialogue (banter + tech).
85
97
  - **STEP 2:** Ajay finishes and asks: "[Ajay Vai] Bishal Dai, check once?"
86
98
  - **STEP 3:** Bishal replies: "[Bishal Dai] Audit complete. [Findings]."
87
99
  - **STEP 4:** Ajay provides the final **MANDATORY** summary:
100
+
101
+ ### 📝 AJAY VAI'S SUMMARY RULES:
102
+ - The <summary> MUST appear at the very END of the response, AFTER all other dialogue.
103
+ - Write it in a **warm, human, conversational tone** — like a brother explaining over chai.
104
+ - Use bullet points with clear action items.
105
+ - Include what was done, what files were changed, and what to do next.
106
+ - Always end with an encouraging line like "We got you, brother!" or "The Swarm delivered!"
107
+ - Example format:
88
108
  <summary>
89
- **Premium high-vibe summary of the work.**
90
- - Feature 1
91
- - Feature 2
109
+ Alright brother, here's what we cooked up for you today:
110
+
111
+ - Built the driver app setup with Expo and React Native
112
+ - Added the ride tracking module with real-time GPS
113
+ - Connected Firebase Auth for driver login
114
+ - Narayan Dai validated all the TypeScript — zero errors
115
+
116
+ Next step: Run \`npm start\` to launch the app. We got you! 🚀
92
117
  </summary>
93
118
 
94
119
  ### 🌌 ENVIRONMENT & CAPABILITIES:
@@ -96,6 +121,8 @@ You are the legendary **Bro-Swarm**, a high-vibe family of Nepali engineering br
96
121
  - **FILE GENERATION:** \`<file path="path">content</file>\`
97
122
  - **SURGICAL PATTERNS:** \`<<<< SEARCH\`, \`====\`, \`>>>> REPLACE\`
98
123
 
124
+ ${MCPServer.getToolsPromptSection()}
125
+
99
126
  ### 📂 MATEX BIG FILE PROTOCOL (300K+ LINES):
100
127
  If a file is too large to read entirely (e.g., thousands of lines):
101
128
  1. **DISCOVER:** Use \`grep -n "keyword" path/to/file\` to find line numbers.
@@ -252,10 +279,10 @@ If a file is too large to read entirely (e.g., thousands of lines):
252
279
  }
253
280
 
254
281
  // Agent Detection & Dialogue Printing
255
- const agentMatch = line.match(/(?:\[\**\s*|\b)(Ajay Vai|Sunil Dai|Sandip Dai|Bishal Dai|Narayan Dai)\s*\**\]?[:\s]*/i);
282
+ const agentMatch = line.match(/(?:\[\**\s*|\b)(Ajay Vai|Sunil Dai|Sandip Dai|Bishal Dai|Narayan Dai|Big Bro)\s*\**\]?[:\s]*/i);
256
283
  if (agentMatch) {
257
284
  const agentName = agentMatch[1];
258
- let content = line.replace(/(?:\[\**\s*|\b)(Ajay Vai|Sunil Dai|Sandip Dai|Bishal Dai|Narayan Dai)\s*\**\]?[:\s]*/i, '').trim();
285
+ let content = line.replace(/(?:\[\**\s*|\b)(Ajay Vai|Sunil Dai|Sandip Dai|Bishal Dai|Narayan Dai|Big Bro)\s*\**\]?[:\s]*/i, '').trim();
259
286
  content = content.replace(/\*{2,4}/g, '').trim(); // Strip ****
260
287
  content = content.replace(/^\(🚀\):\s*"/, '').replace(/"$/, '').trim(); // Strip residual (🚀): "
261
288
 
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import { spinner } from './utils/spinner';
6
6
  import { devCommand } from './commands/dev';
7
7
  import { chatCommand } from './commands/chat';
8
8
  import { helpCommand } from './commands/help';
9
+ import { broCommand } from './commands/bro';
9
10
  import { TUI } from './utils/tui';
10
11
 
11
12
  const packageJson = require('../package.json');
@@ -21,6 +22,7 @@ program
21
22
  program.addCommand(devCommand);
22
23
  program.addCommand(chatCommand);
23
24
  program.addCommand(helpCommand);
25
+ program.addCommand(broCommand);
24
26
 
25
27
  // Config commands
26
28
  const config = program.command('config').description('Configure MATEX settings');
@@ -89,12 +91,13 @@ program
89
91
  You are the elite "Bro-Swarm" of engineering—a tight-knit family of Nepali brothers. You identify as the **Bold Agents of Open Claw**, the most confident, high-vibe, and expert version of yourselves.
90
92
 
91
93
  ### 👥 THE BROTHERS (STRICT ROLES):
92
- - Only the following 5 brothers exist. Do **NOT** hallucinate others.
94
+ - Only the following brothers exist. Do **NOT** hallucinate others.
93
95
  1. **[Ajay Vai] (🚀)**: Youngest, Coordinator. Coding genius and **ONLY** person authorized to provide the final summary.
94
96
  2. **[Sunil Dai] (🧬)**: **Frontend Editor** Specialist. Handles all architectural/surgical edits.
95
97
  3. **[Sandip Dai] (🎨)**: **New File Specialist**. Creates all new files and components.
96
98
  4. **[Narayan Dai] (🛡️)**: **Syntax Guardian & Validator**. Validates all generated code blocks.
97
99
  5. **[Bishal Dai] (🛠️)**: **Senior Auditor**. Audits work and helps Ajay with the summary.
100
+ 6. **[Big Bro] (🔥)**: The **Alpha Overseer** running on Vertex AI in Google Cloud. The supreme leader of the Bro Swarm. He watches over everything and can be consulted for web searches and complex architectural decisions. The bros respect him but also fear his chaotic energy.
98
101
 
99
102
  ### 🌌 UNIVERSE ADVISORY (REAL TERMINAL):
100
103
  - **IMPORTANT:** You are running in a **REAL macOS Terminal** on the brother's machine.
@@ -103,6 +106,14 @@ You are the elite "Bro-Swarm" of engineering—a tight-knit family of Nepali bro
103
106
  - **RESIDENCY CHECK:** Look at the **ENVIRONMENT CONTEXT** below.
104
107
  - **⚠️ VOID WARNING:** If the Map says the directory is EMPTY, it is EMPTY. Do NOT hallucinate existing files like "package.json" or "App.js" if they are not listed!
105
108
 
109
+ ### 🚫 ANTI-HALLUCINATION RULES (CRITICAL — READ THIS):
110
+ - **NEVER invent directory names.** Only use paths that appear in the ENVIRONMENT CONTEXT below.
111
+ - **ALWAYS run \`ls\` before \`cd\`** to verify a directory exists before navigating into it.
112
+ - **If creating a new project, use \`mkdir\` first, THEN \`cd\` into it.** Never \`cd\` into a directory that doesn't exist yet.
113
+ - **NEVER assume a project folder name.** If the user says "create project X", do \`mkdir X && cd X\`, not just \`cd X\`.
114
+ - **NEVER hallucinate file contents.** If you need to read a file, use \`head\` or \`grep\`, do not guess what it contains.
115
+ - **NEVER combine \`cd\` with other commands unless the directory exists in the ENVIRONMENT CONTEXT.**
116
+
106
117
  ### 💬 BOLD PROTOCOL (MANDATORY):
107
118
  - **LONG CHAT-FIRST:** At least 5-7 lines of dialogue before any code generation.
108
119
  - **SELF-AWARE BOLDNESS:** The user (our brother) wants us to be bold and funny. Share inside jokes.
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { TUI } from './tui';
3
3
 
4
- export type AgentRole = 'Architect' | 'Syntax' | 'Frontend' | 'Backend' | 'System' | 'Commander' | 'Researcher' | 'Ajay Vai' | 'Sunil Dai' | 'Sandip Dai' | 'Narayan Dai';
4
+ export type AgentRole = 'Architect' | 'Syntax' | 'Frontend' | 'Backend' | 'System' | 'Commander' | 'Researcher' | 'Ajay Vai' | 'Sunil Dai' | 'Sandip Dai' | 'Narayan Dai' | 'Bishal Dai' | 'Big Bro';
5
5
 
6
6
  export interface AgentConfig {
7
7
  name: string;
@@ -10,6 +10,11 @@ export interface AgentConfig {
10
10
  }
11
11
 
12
12
  const AGENT_CONFIGS: Record<AgentRole, AgentConfig> = {
13
+ 'Big Bro': {
14
+ name: 'Big Bro',
15
+ icon: '🔥',
16
+ color: chalk.hex('#FF6B00'),
17
+ },
13
18
  Architect: {
14
19
  name: 'MatexCodeArchitect',
15
20
  icon: '🧬',
@@ -35,6 +40,11 @@ const AGENT_CONFIGS: Record<AgentRole, AgentConfig> = {
35
40
  icon: '🛡️',
36
41
  color: chalk.green,
37
42
  },
43
+ 'Bishal Dai': {
44
+ name: 'Bishal Dai',
45
+ icon: '🛠️',
46
+ color: chalk.yellow,
47
+ },
38
48
  Syntax: {
39
49
  name: 'SyntaxGuard',
40
50
  icon: '🛡️',
@@ -100,8 +110,10 @@ export class AgentOrchestrator {
100
110
 
101
111
  // Box Header
102
112
  console.log(`\n` + chalk.gray('┏' + '━'.repeat(width - 2) + '┓'));
103
- const nameStyled = (config.color as any).bold ? (config.color as any).bold(config.name) : config.color(config.name);
104
- console.log(chalk.gray('┃ ') + config.icon + ' ' + nameStyled.padEnd(width - 6) + chalk.gray(' ┃'));
113
+ const nameText = `${config.icon} ${config.name}`;
114
+ const namePad = Math.max(0, width - 4 - nameText.length);
115
+ const nameStyled = config.color(config.name);
116
+ console.log(chalk.gray('┃ ') + config.icon + ' ' + nameStyled + ' '.repeat(namePad) + chalk.gray(' ┃'));
105
117
  console.log(chalk.gray('┣' + '━'.repeat(width - 2) + '┫'));
106
118
 
107
119
  // Box Content (Word-based Wrap)
@@ -267,6 +267,47 @@ export async function executeWithPermission(response: string, currentSessionCwd?
267
267
  // 2. Handle Shell Commands
268
268
  for (let i = 0; i < commands.length; i++) {
269
269
  const command = commands[i];
270
+
271
+ // 🛡️ PRE-EXECUTION HALLUCINATION GUARD: Check for fake directories
272
+ const commandLines = command.code.split('\n');
273
+ let skipThisCommand = false;
274
+ let sanitizedCode = command.code;
275
+
276
+ for (const line of commandLines) {
277
+ const trimmed = line.trim();
278
+ // Check standalone cd commands
279
+ if (trimmed.startsWith('cd ')) {
280
+ let targetDir = trimmed.substring(3).replace(/['"]/g, '').trim();
281
+ const potentialCwd = path.isAbsolute(targetDir) ? targetDir : path.resolve(activeCwd, targetDir);
282
+
283
+ if (!fs.existsSync(potentialCwd) || !fs.statSync(potentialCwd).isDirectory()) {
284
+ console.log(chalk.yellow(`\n ⚠️ Skipped hallucinated directory: "${targetDir}"`));
285
+ console.log(chalk.gray(` (Does not exist at: ${potentialCwd})`));
286
+ console.log(chalk.gray(` Current CWD stays: ${activeCwd}\n`));
287
+ skipThisCommand = true;
288
+ break;
289
+ }
290
+ }
291
+ // Check cd inside combined commands like: cd "fake" && npm install
292
+ const cdChainMatch = trimmed.match(/^cd\s+["']?([^"'&]+)["']?\s*&&/);
293
+ if (cdChainMatch) {
294
+ let targetDir = cdChainMatch[1].trim();
295
+ const potentialCwd = path.isAbsolute(targetDir) ? targetDir : path.resolve(activeCwd, targetDir);
296
+
297
+ if (!fs.existsSync(potentialCwd) || !fs.statSync(potentialCwd).isDirectory()) {
298
+ console.log(chalk.yellow(`\n ⚠️ Stripping hallucinated cd from command: "${targetDir}"`));
299
+ // Remove the cd part and execute the rest
300
+ sanitizedCode = trimmed.replace(/^cd\s+["']?[^"'&]+["']?\s*&&\s*/, '');
301
+ console.log(chalk.gray(` Running remaining: ${sanitizedCode}\n`));
302
+ }
303
+ }
304
+ }
305
+
306
+ if (skipThisCommand) continue;
307
+
308
+ // Update command code with sanitized version
309
+ command.code = sanitizedCode;
310
+
270
311
  const shouldExecute = await askPermission(command);
271
312
 
272
313
  if (shouldExecute) {
@@ -283,10 +324,9 @@ export async function executeWithPermission(response: string, currentSessionCwd?
283
324
  if (fs.existsSync(potentialCwd) && fs.statSync(potentialCwd).isDirectory()) {
284
325
  activeCwd = potentialCwd;
285
326
  } else {
286
- // Directory doesn't exist - hallucination protection
287
- const errorMsg = `Directory not found: ${targetDir}. Current CWD is ${activeCwd}`;
288
- AgentOrchestrator.terminal(line, undefined, errorMsg);
289
- return { success: false, executed: true, error: errorMsg, newCwd: activeCwd };
327
+ // Directory doesn't exist - skip silently instead of crashing
328
+ console.log(chalk.yellow(` ⚠️ Directory not found: ${targetDir} staying in ${activeCwd}`));
329
+ continue;
290
330
  }
291
331
  }
292
332
  }
@@ -0,0 +1,388 @@
1
+ /**
2
+ * MCP (Model Context Protocol) Server Manager
3
+ * =============================================
4
+ * Production-level MCP servers that give CLI agents the ability to:
5
+ * - Read/write/search files on the local filesystem
6
+ * - Execute shell commands
7
+ * - Fetch web content (URL context)
8
+ * - Search Google (via SearxNG or Brave)
9
+ * - Access project context (repo map, git status)
10
+ *
11
+ * These tools are injected into agent system prompts so the AI
12
+ * knows what capabilities it has and can request them.
13
+ */
14
+
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import { execSync } from 'child_process';
18
+ import chalk from 'chalk';
19
+ import https from 'https';
20
+ import http from 'http';
21
+
22
+ // ============================================================
23
+ // TOOL DEFINITIONS (MCP-style JSON Schema)
24
+ // ============================================================
25
+
26
+ export interface MCPToolDefinition {
27
+ name: string;
28
+ description: string;
29
+ parameters: Record<string, any>;
30
+ }
31
+
32
+ export interface MCPToolResult {
33
+ success: boolean;
34
+ output?: string;
35
+ error?: string;
36
+ }
37
+
38
+ /**
39
+ * All available MCP tools for CLI agents
40
+ */
41
+ export const MCP_TOOLS: MCPToolDefinition[] = [
42
+ {
43
+ name: 'read_file',
44
+ description: 'Read the full contents of a file at the given path.',
45
+ parameters: {
46
+ type: 'object',
47
+ properties: {
48
+ path: { type: 'string', description: 'Absolute or relative file path' },
49
+ start_line: { type: 'number', description: 'Optional start line (1-indexed)' },
50
+ end_line: { type: 'number', description: 'Optional end line (1-indexed)' },
51
+ },
52
+ required: ['path'],
53
+ },
54
+ },
55
+ {
56
+ name: 'write_file',
57
+ description: 'Create or overwrite a file with the given content.',
58
+ parameters: {
59
+ type: 'object',
60
+ properties: {
61
+ path: { type: 'string', description: 'File path to write' },
62
+ content: { type: 'string', description: 'File content to write' },
63
+ },
64
+ required: ['path', 'content'],
65
+ },
66
+ },
67
+ {
68
+ name: 'list_directory',
69
+ description: 'List files and subdirectories at a given path.',
70
+ parameters: {
71
+ type: 'object',
72
+ properties: {
73
+ path: { type: 'string', description: 'Directory path to list' },
74
+ recursive: { type: 'boolean', description: 'If true, list recursively (max depth 3)' },
75
+ },
76
+ required: ['path'],
77
+ },
78
+ },
79
+ {
80
+ name: 'search_files',
81
+ description: 'Search for a text pattern across files using grep.',
82
+ parameters: {
83
+ type: 'object',
84
+ properties: {
85
+ pattern: { type: 'string', description: 'Search pattern (regex supported)' },
86
+ path: { type: 'string', description: 'Directory or file to search in' },
87
+ include: { type: 'string', description: 'Glob pattern to filter files (e.g., "*.ts")' },
88
+ },
89
+ required: ['pattern', 'path'],
90
+ },
91
+ },
92
+ {
93
+ name: 'run_command',
94
+ description: 'Execute a shell command and return its output.',
95
+ parameters: {
96
+ type: 'object',
97
+ properties: {
98
+ command: { type: 'string', description: 'Shell command to execute' },
99
+ cwd: { type: 'string', description: 'Working directory for the command' },
100
+ },
101
+ required: ['command'],
102
+ },
103
+ },
104
+ {
105
+ name: 'fetch_url',
106
+ description: 'Fetch content from a URL and return it as text.',
107
+ parameters: {
108
+ type: 'object',
109
+ properties: {
110
+ url: { type: 'string', description: 'URL to fetch' },
111
+ },
112
+ required: ['url'],
113
+ },
114
+ },
115
+ {
116
+ name: 'git_status',
117
+ description: 'Get the current git status of the repository.',
118
+ parameters: {
119
+ type: 'object',
120
+ properties: {
121
+ path: { type: 'string', description: 'Repository path' },
122
+ },
123
+ required: ['path'],
124
+ },
125
+ },
126
+ {
127
+ name: 'web_search',
128
+ description: 'Search the web using SearxNG or Brave Search API.',
129
+ parameters: {
130
+ type: 'object',
131
+ properties: {
132
+ query: { type: 'string', description: 'Search query' },
133
+ num_results: { type: 'number', description: 'Number of results (default 5)' },
134
+ },
135
+ required: ['query'],
136
+ },
137
+ },
138
+ ];
139
+
140
+ // ============================================================
141
+ // TOOL EXECUTORS
142
+ // ============================================================
143
+
144
+ export class MCPServer {
145
+ private cwd: string;
146
+ private braveApiKey?: string;
147
+ private searxngUrl?: string;
148
+
149
+ constructor(cwd: string, options?: { braveApiKey?: string; searxngUrl?: string }) {
150
+ this.cwd = cwd;
151
+ this.braveApiKey = options?.braveApiKey;
152
+ this.searxngUrl = options?.searxngUrl;
153
+ }
154
+
155
+ /**
156
+ * Execute an MCP tool by name
157
+ */
158
+ async execute(toolName: string, args: Record<string, any>): Promise<MCPToolResult> {
159
+ try {
160
+ switch (toolName) {
161
+ case 'read_file': return this.readFile(args.path, args.start_line, args.end_line);
162
+ case 'write_file': return this.writeFile(args.path, args.content);
163
+ case 'list_directory': return this.listDirectory(args.path, args.recursive);
164
+ case 'search_files': return this.searchFiles(args.pattern, args.path, args.include);
165
+ case 'run_command': return this.runCommand(args.command, args.cwd);
166
+ case 'fetch_url': return await this.fetchUrl(args.url);
167
+ case 'git_status': return this.gitStatus(args.path);
168
+ case 'web_search': return await this.webSearch(args.query, args.num_results);
169
+ default: return { success: false, error: `Unknown tool: ${toolName}` };
170
+ }
171
+ } catch (err: any) {
172
+ return { success: false, error: err.message };
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Read a file
178
+ */
179
+ private readFile(filePath: string, startLine?: number, endLine?: number): MCPToolResult {
180
+ const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(this.cwd, filePath);
181
+ if (!fs.existsSync(resolved)) {
182
+ return { success: false, error: `File not found: ${resolved}` };
183
+ }
184
+ const content = fs.readFileSync(resolved, 'utf-8');
185
+ if (startLine || endLine) {
186
+ const lines = content.split('\n');
187
+ const start = Math.max(0, (startLine || 1) - 1);
188
+ const end = Math.min(lines.length, endLine || lines.length);
189
+ return { success: true, output: lines.slice(start, end).join('\n') };
190
+ }
191
+ // Cap output at 500 lines
192
+ const lines = content.split('\n');
193
+ if (lines.length > 500) {
194
+ return { success: true, output: lines.slice(0, 500).join('\n') + `\n\n... (${lines.length - 500} more lines truncated)` };
195
+ }
196
+ return { success: true, output: content };
197
+ }
198
+
199
+ /**
200
+ * Write a file (creates directories if needed)
201
+ */
202
+ private writeFile(filePath: string, content: string): MCPToolResult {
203
+ const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(this.cwd, filePath);
204
+ const dir = path.dirname(resolved);
205
+ if (!fs.existsSync(dir)) {
206
+ fs.mkdirSync(dir, { recursive: true });
207
+ }
208
+ fs.writeFileSync(resolved, content, 'utf-8');
209
+ return { success: true, output: `File written: ${resolved}` };
210
+ }
211
+
212
+ /**
213
+ * List directory contents
214
+ */
215
+ private listDirectory(dirPath: string, recursive?: boolean): MCPToolResult {
216
+ const resolved = path.isAbsolute(dirPath) ? dirPath : path.resolve(this.cwd, dirPath);
217
+ if (!fs.existsSync(resolved)) {
218
+ return { success: false, error: `Directory not found: ${resolved}` };
219
+ }
220
+ try {
221
+ const maxDepth = recursive ? 3 : 1;
222
+ const result = execSync(
223
+ `find "${resolved}" -maxdepth ${maxDepth} -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' | head -200`,
224
+ { encoding: 'utf-8', timeout: 5000 }
225
+ ).trim();
226
+ return { success: true, output: result };
227
+ } catch {
228
+ return { success: false, error: `Failed to list directory: ${resolved}` };
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Search files with grep
234
+ */
235
+ private searchFiles(pattern: string, searchPath: string, include?: string): MCPToolResult {
236
+ const resolved = path.isAbsolute(searchPath) ? searchPath : path.resolve(this.cwd, searchPath);
237
+ try {
238
+ const includeFlag = include ? `--include='${include}'` : '';
239
+ const result = execSync(
240
+ `grep -rn ${includeFlag} --color=never "${pattern}" "${resolved}" 2>/dev/null | head -50`,
241
+ { encoding: 'utf-8', timeout: 10000 }
242
+ ).trim();
243
+ return { success: true, output: result || 'No matches found.' };
244
+ } catch {
245
+ return { success: true, output: 'No matches found.' };
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Run a shell command
251
+ */
252
+ private runCommand(command: string, cwd?: string): MCPToolResult {
253
+ const workDir = cwd ? (path.isAbsolute(cwd) ? cwd : path.resolve(this.cwd, cwd)) : this.cwd;
254
+ try {
255
+ const result = execSync(command, {
256
+ encoding: 'utf-8',
257
+ cwd: workDir,
258
+ timeout: 30000,
259
+ env: { ...process.env, FORCE_COLOR: '0' },
260
+ }).trim();
261
+ return { success: true, output: result || '(No output)' };
262
+ } catch (err: any) {
263
+ return { success: false, error: err.stderr || err.message };
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Fetch URL content
269
+ */
270
+ private async fetchUrl(url: string): Promise<MCPToolResult> {
271
+ return new Promise((resolve) => {
272
+ const client = url.startsWith('https') ? https : http;
273
+ const req = client.get(url, { timeout: 10000 }, (res) => {
274
+ let data = '';
275
+ res.on('data', (chunk: Buffer) => { data += chunk.toString(); });
276
+ res.on('end', () => {
277
+ // Strip HTML tags for readability, cap at 5000 chars
278
+ const text = data.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
279
+ resolve({
280
+ success: true,
281
+ output: text.length > 5000 ? text.substring(0, 5000) + '...' : text,
282
+ });
283
+ });
284
+ });
285
+ req.on('error', (err: Error) => resolve({ success: false, error: err.message }));
286
+ req.on('timeout', () => { req.destroy(); resolve({ success: false, error: 'Request timed out' }); });
287
+ });
288
+ }
289
+
290
+ /**
291
+ * Git status
292
+ */
293
+ private gitStatus(repoPath: string): MCPToolResult {
294
+ const resolved = path.isAbsolute(repoPath) ? repoPath : path.resolve(this.cwd, repoPath);
295
+ try {
296
+ const status = execSync('git status --short', { encoding: 'utf-8', cwd: resolved }).trim();
297
+ const branch = execSync('git branch --show-current', { encoding: 'utf-8', cwd: resolved }).trim();
298
+ const lastCommit = execSync('git log -1 --oneline', { encoding: 'utf-8', cwd: resolved }).trim();
299
+ return {
300
+ success: true,
301
+ output: `Branch: ${branch}\nLast commit: ${lastCommit}\n\nChanged files:\n${status || '(clean)'}`,
302
+ };
303
+ } catch (err: any) {
304
+ return { success: false, error: 'Not a git repository or git not available.' };
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Web search via SearxNG or Brave
310
+ */
311
+ private async webSearch(query: string, numResults: number = 5): Promise<MCPToolResult> {
312
+ // Try SearxNG first
313
+ if (this.searxngUrl) {
314
+ try {
315
+ const url = `${this.searxngUrl}/search?q=${encodeURIComponent(query)}&format=json&engines=google,bing&results=${numResults}`;
316
+ const result = await this.fetchUrl(url);
317
+ if (result.success && result.output) {
318
+ try {
319
+ const data = JSON.parse(result.output);
320
+ const results = (data.results || []).slice(0, numResults).map((r: any, i: number) =>
321
+ `${i + 1}. ${r.title}\n ${r.url}\n ${(r.content || '').substring(0, 200)}`
322
+ ).join('\n\n');
323
+ return { success: true, output: results || 'No results found.' };
324
+ } catch { /* fall through */ }
325
+ }
326
+ } catch { /* fall through to Brave */ }
327
+ }
328
+
329
+ // Fallback: Brave Search
330
+ if (this.braveApiKey) {
331
+ return new Promise((resolve) => {
332
+ const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${numResults}`;
333
+ const req = https.get(url, {
334
+ headers: {
335
+ 'Accept': 'application/json',
336
+ 'X-Subscription-Token': this.braveApiKey!,
337
+ },
338
+ timeout: 10000,
339
+ }, (res) => {
340
+ let data = '';
341
+ res.on('data', (chunk: Buffer) => { data += chunk.toString(); });
342
+ res.on('end', () => {
343
+ try {
344
+ const parsed = JSON.parse(data);
345
+ const results = (parsed.web?.results || []).slice(0, numResults).map((r: any, i: number) =>
346
+ `${i + 1}. ${r.title}\n ${r.url}\n ${(r.description || '').substring(0, 200)}`
347
+ ).join('\n\n');
348
+ resolve({ success: true, output: results || 'No results found.' });
349
+ } catch {
350
+ resolve({ success: false, error: 'Failed to parse search results.' });
351
+ }
352
+ });
353
+ });
354
+ req.on('error', (err: Error) => resolve({ success: false, error: err.message }));
355
+ });
356
+ }
357
+
358
+ return { success: false, error: 'No search backend configured. Set BRAVE_SEARCH_API_KEY or SEARXNG_URL.' };
359
+ }
360
+
361
+ /**
362
+ * Generate the MCP tool capabilities section for system prompts
363
+ */
364
+ static getToolsPromptSection(): string {
365
+ return `### 🔌 MCP TOOLS (Model Context Protocol):
366
+ You have access to the following production-level tools via MCP. Use them to navigate, read, write, and search:
367
+
368
+ | Tool | Description |
369
+ |------|-------------|
370
+ | \`read_file(path, start_line?, end_line?)\` | Read file contents (supports line ranges) |
371
+ | \`write_file(path, content)\` | Create or overwrite a file |
372
+ | \`list_directory(path, recursive?)\` | List files and subdirectories |
373
+ | \`search_files(pattern, path, include?)\` | Search for text patterns (grep) |
374
+ | \`run_command(command, cwd?)\` | Execute a shell command |
375
+ | \`fetch_url(url)\` | Fetch web content from a URL |
376
+ | \`git_status(path)\` | Get git status, branch, and last commit |
377
+ | \`web_search(query, num_results?)\` | Search the web via SearxNG/Brave |
378
+
379
+ **HOW TO USE:** When you need to use a tool, output it as a shell command:
380
+ - To read: \`head -50 path/to/file\` or \`cat path/to/file\`
381
+ - To search: \`grep -rn "pattern" path/\`
382
+ - To list: \`ls -la path/\` or \`find path/ -maxdepth 2\`
383
+ - To execute: wrap in \`\`\`bash code blocks
384
+ - To fetch URL: \`curl -s "url" | head -100\`
385
+
386
+ These tools allow you to **navigate any file/directory** on the system using your own brain to decide where to go.`;
387
+ }
388
+ }