nexus-mcp-agent 0.1.2

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 (72) hide show
  1. package/.env.example +56 -0
  2. package/README.md +74 -0
  3. package/dist/core/config.d.ts +46 -0
  4. package/dist/core/config.d.ts.map +1 -0
  5. package/dist/core/config.js +68 -0
  6. package/dist/core/config.js.map +1 -0
  7. package/dist/core/logger.d.ts +7 -0
  8. package/dist/core/logger.d.ts.map +1 -0
  9. package/dist/core/logger.js +54 -0
  10. package/dist/core/logger.js.map +1 -0
  11. package/dist/core/registry.d.ts +21 -0
  12. package/dist/core/registry.d.ts.map +1 -0
  13. package/dist/core/registry.js +52 -0
  14. package/dist/core/registry.js.map +1 -0
  15. package/dist/core/server.d.ts +12 -0
  16. package/dist/core/server.d.ts.map +1 -0
  17. package/dist/core/server.js +120 -0
  18. package/dist/core/server.js.map +1 -0
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +38 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/tools/agents/index.d.ts +18 -0
  24. package/dist/tools/agents/index.d.ts.map +1 -0
  25. package/dist/tools/agents/index.js +134 -0
  26. package/dist/tools/agents/index.js.map +1 -0
  27. package/dist/tools/automation/index.d.ts +4 -0
  28. package/dist/tools/automation/index.d.ts.map +1 -0
  29. package/dist/tools/automation/index.js +95 -0
  30. package/dist/tools/automation/index.js.map +1 -0
  31. package/dist/tools/filesystem/index.d.ts +4 -0
  32. package/dist/tools/filesystem/index.d.ts.map +1 -0
  33. package/dist/tools/filesystem/index.js +261 -0
  34. package/dist/tools/filesystem/index.js.map +1 -0
  35. package/dist/tools/git/index.d.ts +4 -0
  36. package/dist/tools/git/index.d.ts.map +1 -0
  37. package/dist/tools/git/index.js +177 -0
  38. package/dist/tools/git/index.js.map +1 -0
  39. package/dist/tools/memory/index.d.ts +4 -0
  40. package/dist/tools/memory/index.d.ts.map +1 -0
  41. package/dist/tools/memory/index.js +178 -0
  42. package/dist/tools/memory/index.js.map +1 -0
  43. package/dist/tools/system/index.d.ts +4 -0
  44. package/dist/tools/system/index.d.ts.map +1 -0
  45. package/dist/tools/system/index.js +121 -0
  46. package/dist/tools/system/index.js.map +1 -0
  47. package/dist/tools/terminal/index.d.ts +4 -0
  48. package/dist/tools/terminal/index.d.ts.map +1 -0
  49. package/dist/tools/terminal/index.js +86 -0
  50. package/dist/tools/terminal/index.js.map +1 -0
  51. package/dist/tools/web/index.d.ts +4 -0
  52. package/dist/tools/web/index.d.ts.map +1 -0
  53. package/dist/tools/web/index.js +193 -0
  54. package/dist/tools/web/index.js.map +1 -0
  55. package/docs/SECURITY.md +31 -0
  56. package/mcp-config.json +9 -0
  57. package/package.json +65 -0
  58. package/scripts/setup.js +102 -0
  59. package/src/core/config.ts +138 -0
  60. package/src/core/logger.ts +61 -0
  61. package/src/core/registry.ts +72 -0
  62. package/src/core/server.ts +144 -0
  63. package/src/index.ts +46 -0
  64. package/src/tools/agents/index.ts +172 -0
  65. package/src/tools/automation/index.ts +127 -0
  66. package/src/tools/filesystem/index.ts +303 -0
  67. package/src/tools/git/index.ts +201 -0
  68. package/src/tools/memory/index.ts +204 -0
  69. package/src/tools/system/index.ts +140 -0
  70. package/src/tools/terminal/index.ts +105 -0
  71. package/src/tools/web/index.ts +224 -0
  72. package/tsconfig.json +30 -0
@@ -0,0 +1,140 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod/v3';
3
+ import { ToolRegistry } from '../../core/registry.js';
4
+ import { createContextLogger } from '../../core/logger.js';
5
+ import { nexusConfig } from '../../core/config.js';
6
+
7
+ const log = createContextLogger('tools/system');
8
+
9
+ const SENSITIVE_KEYS = ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'BRAVE_SEARCH_API_KEY', 'NEXUS_CONFIG'];
10
+
11
+ async function systemInfoFull(): Promise<any> {
12
+ const os = await import('os');
13
+ return {
14
+ nexus: {
15
+ name: 'NEXUS',
16
+ version: '0.1.0',
17
+ uptime: process.uptime(),
18
+ memory: process.memoryUsage(),
19
+ },
20
+ system: {
21
+ platform: process.platform,
22
+ arch: process.arch,
23
+ nodeVersion: process.version,
24
+ hostname: os.hostname(),
25
+ uptime: os.uptime(),
26
+ cpus: os.cpus().length,
27
+ totalMemory: os.totalmem(),
28
+ freeMemory: os.freemem(),
29
+ },
30
+ config: {
31
+ llmProvider: nexusConfig.llm.provider,
32
+ llmModel: nexusConfig.llm.model,
33
+ searchProvider: nexusConfig.search.provider,
34
+ vectorDb: nexusConfig.vector.type,
35
+ allowedDirs: nexusConfig.security.allowedDirs,
36
+ },
37
+ };
38
+ }
39
+
40
+ const ConfigGetSchema = z.object({
41
+ key: z.string().optional(),
42
+ });
43
+
44
+ const ConfigSetSchema = z.object({
45
+ key: z.string(),
46
+ value: z.any(),
47
+ });
48
+
49
+ const EnvReadSchema = z.object({
50
+ key: z.string().optional(),
51
+ });
52
+
53
+ type ConfigGetParams = z.infer<typeof ConfigGetSchema>;
54
+ type ConfigSetParams = z.infer<typeof ConfigSetSchema>;
55
+ type EnvReadParams = z.infer<typeof EnvReadSchema>;
56
+
57
+ async function configGet(params: ConfigGetParams): Promise<any> {
58
+ if (!params.key) return { success: true, config: nexusConfig };
59
+ const keys = params.key.split('.');
60
+ let val: any = nexusConfig;
61
+ for (const k of keys) {
62
+ if (val && typeof val === 'object' && k in val) val = val[k];
63
+ else return { success: false, error: `Key not found: ${params.key}` };
64
+ }
65
+ return { success: true, key: params.key, value: val };
66
+ }
67
+
68
+ async function configSet(params: ConfigSetParams): Promise<any> {
69
+ const { key, value } = params;
70
+ const keys = key.split('.');
71
+ let obj: any = nexusConfig;
72
+ for (let i = 0; i < keys.length - 1; i++) {
73
+ if (!obj[keys[i]] || typeof obj[keys[i]] !== 'object') {
74
+ return { success: false, error: `Invalid key path: ${key}` };
75
+ }
76
+ obj = obj[keys[i]];
77
+ }
78
+ obj[keys[keys.length - 1]] = value;
79
+ log.info(`Config updated: ${key} = ${JSON.stringify(value)}`);
80
+ return { success: true, key, value };
81
+ }
82
+
83
+ async function envRead(params: EnvReadParams): Promise<any> {
84
+ if (!params.key) {
85
+ const env: Record<string, string | undefined> = {};
86
+ for (const [k, v] of Object.entries(process.env)) {
87
+ if (!SENSITIVE_KEYS.includes(k)) {
88
+ env[k] = v;
89
+ }
90
+ }
91
+ return { success: true, env, filteredKeys: SENSITIVE_KEYS };
92
+ }
93
+ if (SENSITIVE_KEYS.includes(params.key)) {
94
+ return { success: false, error: `Access denied: ${params.key} is a sensitive key` };
95
+ }
96
+ return { success: true, key: params.key, value: process.env[params.key] };
97
+ }
98
+
99
+ async function ping(): Promise<any> {
100
+ return { success: true, pong: true, timestamp: new Date().toISOString(), uptime: process.uptime() };
101
+ }
102
+
103
+ export function registerSystemTools(server: McpServer, registry: ToolRegistry): void {
104
+ server.tool('system_info_full', 'Get detailed system information', {}, async () => ({
105
+ content: [{ type: 'text', text: JSON.stringify(await systemInfoFull(), null, 2) }],
106
+ }));
107
+
108
+ server.registerTool('config_get', {
109
+ description: 'Get configuration value',
110
+ inputSchema: ConfigGetSchema,
111
+ }, async (params) => ({
112
+ content: [{ type: 'text', text: JSON.stringify(await configGet(params), null, 2) }],
113
+ }));
114
+
115
+ server.registerTool('config_set', {
116
+ description: 'Set configuration value',
117
+ inputSchema: ConfigSetSchema,
118
+ }, async (params) => ({
119
+ content: [{ type: 'text', text: JSON.stringify(await configSet(params), null, 2) }],
120
+ }));
121
+
122
+ server.registerTool('env_read', {
123
+ description: 'Read environment variable (sensitive keys filtered)',
124
+ inputSchema: EnvReadSchema,
125
+ }, async (params) => ({
126
+ content: [{ type: 'text', text: JSON.stringify(await envRead(params), null, 2) }],
127
+ }));
128
+
129
+ server.tool('ping', 'Health check', {}, async () => ({
130
+ content: [{ type: 'text', text: JSON.stringify(await ping(), null, 2) }],
131
+ }));
132
+
133
+ registry.register({ name: 'system_info_full', category: 'system', description: 'System info', handler: systemInfoFull });
134
+ registry.register({ name: 'config_get', category: 'system', description: 'Get config', handler: configGet });
135
+ registry.register({ name: 'config_set', category: 'system', description: 'Set config', handler: configSet });
136
+ registry.register({ name: 'env_read', category: 'system', description: 'Read env var', handler: envRead });
137
+ registry.register({ name: 'ping', category: 'system', description: 'Health check', handler: ping });
138
+
139
+ log.info('Registered 5 system tools');
140
+ }
@@ -0,0 +1,105 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod/v3';
3
+ import { exec as execCb } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import { ToolRegistry } from '../../core/registry.js';
6
+ import { createContextLogger, logAudit } from '../../core/logger.js';
7
+ import { nexusConfig } from '../../core/config.js';
8
+
9
+ const exec = promisify(execCb);
10
+ const log = createContextLogger('tools/terminal');
11
+
12
+ function isCommandBlocked(command: string): boolean {
13
+ return nexusConfig.security.blockedCommands.some(blocked =>
14
+ command.toLowerCase().includes(blocked.toLowerCase())
15
+ );
16
+ }
17
+
18
+ const ShellExecSchema = z.object({
19
+ command: z.string().describe('Command to execute'),
20
+ cwd: z.string().optional().describe('Working directory'),
21
+ timeout: z.number().optional().default(30000).describe('Timeout in ms'),
22
+ env: z.record(z.string()).optional().describe('Environment variables'),
23
+ });
24
+
25
+ type ShellExecParams = z.infer<typeof ShellExecSchema>;
26
+
27
+ async function shellExec(params: ShellExecParams): Promise<any> {
28
+ const { command, cwd, timeout = 30000, env = {} } = params;
29
+
30
+ if (isCommandBlocked(command)) {
31
+ logAudit('shell_exec_blocked', { command });
32
+ return { success: false, error: `Command blocked by security policy: ${command}` };
33
+ }
34
+
35
+ logAudit('shell_exec', { command, cwd });
36
+ log.info(`Executing: ${command}`);
37
+
38
+ try {
39
+ const { stdout, stderr } = await exec(command, {
40
+ cwd,
41
+ timeout,
42
+ env: { ...process.env as any, ...env },
43
+ maxBuffer: 10 * 1024 * 1024,
44
+ });
45
+
46
+ return { success: true, stdout, stderr, exitCode: 0 };
47
+ } catch (error: any) {
48
+ return {
49
+ success: false,
50
+ stdout: error.stdout || '',
51
+ stderr: error.stderr || error.message,
52
+ exitCode: error.code || 1,
53
+ };
54
+ }
55
+ }
56
+
57
+ async function processList(): Promise<any> {
58
+ try {
59
+ const isWindows = process.platform === 'win32';
60
+ const cmd = isWindows ? 'tasklist' : 'ps aux';
61
+ const { stdout } = await exec(cmd);
62
+
63
+ return { success: true, processes: stdout };
64
+ } catch (error: any) {
65
+ return { success: false, error: error.message };
66
+ }
67
+ }
68
+
69
+ async function systemInfo(): Promise<any> {
70
+ const os = await import('os');
71
+ return {
72
+ platform: process.platform,
73
+ arch: process.arch,
74
+ nodeVersion: process.version,
75
+ uptime: os.uptime(),
76
+ cpus: os.cpus().length,
77
+ totalMemory: os.totalmem(),
78
+ freeMemory: os.freemem(),
79
+ hostname: os.hostname(),
80
+ userInfo: os.userInfo(),
81
+ };
82
+ }
83
+
84
+ export function registerTerminalTools(server: McpServer, registry: ToolRegistry): void {
85
+ server.registerTool('shell_exec', {
86
+ description: 'Execute shell command',
87
+ inputSchema: ShellExecSchema,
88
+ }, async (params) => ({
89
+ content: [{ type: 'text', text: JSON.stringify(await shellExec(params), null, 2) }],
90
+ }));
91
+
92
+ server.tool('process_list', 'List running processes', {}, async () => ({
93
+ content: [{ type: 'text', text: JSON.stringify(await processList(), null, 2) }],
94
+ }));
95
+
96
+ server.tool('system_info', 'Get system information', {}, async () => ({
97
+ content: [{ type: 'text', text: JSON.stringify(await systemInfo(), null, 2) }],
98
+ }));
99
+
100
+ registry.register({ name: 'shell_exec', category: 'terminal', description: 'Execute shell command', handler: shellExec });
101
+ registry.register({ name: 'process_list', category: 'terminal', description: 'List processes', handler: processList });
102
+ registry.register({ name: 'system_info', category: 'terminal', description: 'System info', handler: systemInfo });
103
+
104
+ log.info('Registered 3 terminal tools');
105
+ }
@@ -0,0 +1,224 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod/v3';
3
+ import axios from 'axios';
4
+ import * as cheerio from 'cheerio';
5
+ import { ToolRegistry } from '../../core/registry.js';
6
+ import { createContextLogger } from '../../core/logger.js';
7
+ import { nexusConfig } from '../../core/config.js';
8
+
9
+ const log = createContextLogger('tools/web');
10
+
11
+ const WebSearchSchema = z.object({
12
+ query: z.string().describe('Search query'),
13
+ maxResults: z.number().optional().default(10).describe('Maximum number of results'),
14
+ });
15
+
16
+ const WebBrowseSchema = z.object({
17
+ url: z.string().describe('URL to browse'),
18
+ actions: z.string().optional().describe('JavaScript code to execute on the page'),
19
+ waitFor: z.number().optional().default(2000).describe('Time to wait after page load (ms)'),
20
+ });
21
+
22
+ const WebScrapeSchema = z.object({
23
+ url: z.string().describe('URL to scrape'),
24
+ selector: z.string().optional().describe('CSS selector to extract specific content'),
25
+ });
26
+
27
+ const ApiCallSchema = z.object({
28
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).describe('HTTP method'),
29
+ url: z.string().describe('API endpoint URL'),
30
+ headers: z.record(z.string()).optional().describe('HTTP headers'),
31
+ body: z.any().optional().describe('Request body'),
32
+ });
33
+
34
+ type WebSearchParams = z.infer<typeof WebSearchSchema>;
35
+ type WebBrowseParams = z.infer<typeof WebBrowseSchema>;
36
+ type WebScrapeParams = z.infer<typeof WebScrapeSchema>;
37
+ type ApiCallParams = z.infer<typeof ApiCallSchema>;
38
+
39
+ async function webSearch(params: WebSearchParams): Promise<any> {
40
+ const { query, maxResults = 10 } = params;
41
+ log.info(`Web search: "${query}"`);
42
+
43
+ if (nexusConfig.search.braveApiKey) {
44
+ try {
45
+ const response = await axios.get('https://api.search.brave.com/res/v1/web/search', {
46
+ params: { q: query, count: maxResults },
47
+ headers: { 'X-Subscription-Token': nexusConfig.search.braveApiKey },
48
+ timeout: 15000,
49
+ });
50
+ return {
51
+ success: true,
52
+ provider: 'brave',
53
+ results: response.data.web?.results?.map((r: any) => ({
54
+ title: r.title,
55
+ url: r.url,
56
+ snippet: r.description,
57
+ })) || [],
58
+ };
59
+ } catch (error) {
60
+ log.warn('Brave search failed, falling back to DuckDuckGo');
61
+ }
62
+ }
63
+
64
+ try {
65
+ const response = await axios.get('https://html.duckduckgo.com/html/', {
66
+ params: { q: query },
67
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
68
+ timeout: 15000,
69
+ });
70
+
71
+ const $ = cheerio.load(response.data);
72
+ const results: any[] = [];
73
+ $('.result').slice(0, maxResults).each((_, el) => {
74
+ const title = $(el).find('.result__title a').text().trim();
75
+ const url = $(el).find('.result__url').text().trim();
76
+ const snippet = $(el).find('.result__snippet').text().trim();
77
+ if (title && url) {
78
+ results.push({ title, url: url.startsWith('http') ? url : `https://${url}`, snippet });
79
+ }
80
+ });
81
+
82
+ return { success: true, provider: 'duckduckgo', results };
83
+ } catch (error: any) {
84
+ return { success: false, error: error.message };
85
+ }
86
+ }
87
+
88
+ async function webBrowse(params: WebBrowseParams): Promise<any> {
89
+ const { url, actions, waitFor = 2000 } = params;
90
+ log.info(`Browsing: ${url}`);
91
+
92
+ try {
93
+ const { chromium } = await import('playwright');
94
+ const browser = await chromium.launch({ headless: nexusConfig.browser.headless });
95
+ const page = await browser.newPage();
96
+
97
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: nexusConfig.browser.timeout });
98
+
99
+ if (waitFor > 0) await page.waitForTimeout(waitFor);
100
+
101
+ if (actions) {
102
+ try {
103
+ const actionFn = new Function('page', actions);
104
+ await actionFn(page);
105
+ } catch (err: any) {
106
+ log.warn(`Action execution failed: ${err.message}`);
107
+ }
108
+ }
109
+
110
+ const title = await page.title();
111
+ const content = await page.content();
112
+ const text = await page.evaluate(`document.body?.innerText || ''`) as string;
113
+ const snapshot = await (page as any).accessibility.snapshot();
114
+
115
+ await browser.close();
116
+
117
+ return {
118
+ success: true,
119
+ url,
120
+ title,
121
+ text: text.slice(0, 50000),
122
+ accessibilityTree: snapshot,
123
+ };
124
+ } catch (error: any) {
125
+ return { success: false, error: error.message };
126
+ }
127
+ }
128
+
129
+ async function webScrape(params: WebScrapeParams): Promise<any> {
130
+ const { url, selector } = params;
131
+ log.info(`Scraping: ${url}`);
132
+
133
+ try {
134
+ const response = await axios.get(url, {
135
+ headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' },
136
+ timeout: 15000,
137
+ });
138
+
139
+ const $ = cheerio.load(response.data);
140
+ $('script, style, nav, footer, header').remove();
141
+
142
+ let content = '';
143
+ if (selector) {
144
+ content = $(selector).text();
145
+ } else {
146
+ content = $('body').text();
147
+ }
148
+
149
+ content = content.replace(/\s+/g, ' ').trim();
150
+
151
+ return {
152
+ success: true,
153
+ url,
154
+ content: content.slice(0, 100000),
155
+ };
156
+ } catch (error: any) {
157
+ return { success: false, error: error.message };
158
+ }
159
+ }
160
+
161
+ async function apiCall(params: ApiCallParams): Promise<any> {
162
+ const { method, url, headers, body } = params;
163
+ log.info(`API call: ${method} ${url}`);
164
+
165
+ try {
166
+ const response = await axios({
167
+ method: method as any,
168
+ url,
169
+ headers,
170
+ data: body,
171
+ timeout: 30000,
172
+ });
173
+
174
+ return {
175
+ success: true,
176
+ status: response.status,
177
+ headers: response.headers,
178
+ data: response.data,
179
+ };
180
+ } catch (error: any) {
181
+ return {
182
+ success: false,
183
+ status: error.response?.status,
184
+ error: error.message,
185
+ };
186
+ }
187
+ }
188
+
189
+ export function registerWebTools(server: McpServer, registry: ToolRegistry): void {
190
+ server.registerTool('web_search', {
191
+ description: 'Search the web for information',
192
+ inputSchema: WebSearchSchema,
193
+ }, async (params) => ({
194
+ content: [{ type: 'text', text: JSON.stringify(await webSearch(params), null, 2) }],
195
+ }));
196
+
197
+ server.registerTool('web_browse', {
198
+ description: 'Browse a webpage with Playwright and extract content',
199
+ inputSchema: WebBrowseSchema,
200
+ }, async (params) => ({
201
+ content: [{ type: 'text', text: JSON.stringify(await webBrowse(params), null, 2) }],
202
+ }));
203
+
204
+ server.registerTool('web_scrape', {
205
+ description: 'Scrape content from a webpage',
206
+ inputSchema: WebScrapeSchema,
207
+ }, async (params) => ({
208
+ content: [{ type: 'text', text: JSON.stringify(await webScrape(params), null, 2) }],
209
+ }));
210
+
211
+ server.registerTool('api_call', {
212
+ description: 'Make HTTP API calls (REST/GraphQL)',
213
+ inputSchema: ApiCallSchema,
214
+ }, async (params) => ({
215
+ content: [{ type: 'text', text: JSON.stringify(await apiCall(params), null, 2) }],
216
+ }));
217
+
218
+ registry.register({ name: 'web_search', category: 'web', description: 'Search the web', handler: webSearch });
219
+ registry.register({ name: 'web_browse', category: 'web', description: 'Browse webpage', handler: webBrowse });
220
+ registry.register({ name: 'web_scrape', category: 'web', description: 'Scrape webpage', handler: webScrape });
221
+ registry.register({ name: 'api_call', category: 'web', description: 'HTTP API call', handler: apiCall });
222
+
223
+ log.info('Registered 4 web tools');
224
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "node",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "noImplicitAny": true,
11
+ "strictNullChecks": true,
12
+ "strictFunctionTypes": true,
13
+ "noImplicitThis": true,
14
+ "alwaysStrict": true,
15
+ "esModuleInterop": true,
16
+ "skipLibCheck": true,
17
+ "forceConsistentCasingInFileNames": true,
18
+ "resolveJsonModule": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+ "sourceMap": true,
22
+ "allowSyntheticDefaultImports": true,
23
+ "isolatedModules": true,
24
+ "noUnusedLocals": false,
25
+ "noUnusedParameters": false,
26
+ "noFallthroughCasesInSwitch": true
27
+ },
28
+ "include": ["src/**/*"],
29
+ "exclude": ["node_modules", "dist", "tests"]
30
+ }