kadins-personal-mcp 1.0.0 → 1.0.1
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/README.md +40 -6
- package/dist/{chunk-T6KZEVGN.js → chunk-IFFK3HCL.js} +4 -1
- package/dist/index.js +8 -22
- package/dist/toolLoader.js +1 -1
- package/package.json +12 -3
- package/tools/fileEdit.ts +71 -0
- package/tools/fileGlob.ts +45 -0
- package/tools/listFiles.ts +63 -0
- package/tools/readManyFiles.ts +57 -0
- package/tools/shellCommand.ts +57 -0
- package/tools/textSearch.ts +61 -0
- package/tools/webFetch.ts +50 -0
- package/tools/writeFile.ts +36 -0
- package/PUBLISHING.md +0 -43
- package/gitignore +0 -7
- package/src/index.ts +0 -63
- package/src/simple.test.ts +0 -9
- package/src/toolLoader.ts +0 -59
- package/tools/calculator.ts +0 -40
- package/tools/currentTime.ts +0 -11
- package/tools/directoryCreate.ts +0 -43
- package/tools/directoryList.ts +0 -70
- package/tools/echo.ts +0 -12
- package/tools/fileCopy.ts +0 -55
- package/tools/fileDelete.ts +0 -48
- package/tools/fileList.ts +0 -88
- package/tools/fileMove.ts +0 -62
- package/tools/filePermissions.ts +0 -50
- package/tools/fileSearch.ts +0 -88
- package/tools/fileWrite.ts +0 -50
- package/tools/readFile.ts +0 -39
- package/tools/runCommand.ts +0 -38
- package/tsconfig.json +0 -12
package/README.md
CHANGED
|
@@ -42,32 +42,60 @@ npm run dev
|
|
|
42
42
|
- `file_permissions`: Change file permissions
|
|
43
43
|
- `directory_create`: Create a new directory
|
|
44
44
|
- `directory_list`: List directory contents with details
|
|
45
|
+
- `directory_tree`: Generate a tree diagram of a directory structure
|
|
46
|
+
- `file_edit`: Edit file content by replacing specific text
|
|
47
|
+
- `file_glob`: Find files using glob patterns
|
|
48
|
+
- `list_files`: List files and subdirectories in a directory
|
|
49
|
+
- `write_file`: Create or overwrite a file with specified content
|
|
45
50
|
|
|
46
51
|
### System Tools
|
|
47
52
|
- `run_command`: Execute shell commands on the system
|
|
53
|
+
- `shell_command`: Execute shell commands with more options
|
|
48
54
|
- `read_file`: Read the contents of a file
|
|
49
|
-
- `
|
|
50
|
-
- `
|
|
51
|
-
|
|
55
|
+
- `read_many_files`: Read content from multiple files
|
|
56
|
+
- `text_search`: Search for text patterns within files using grep
|
|
57
|
+
|
|
58
|
+
### Network Tools
|
|
59
|
+
- `web_fetch`: Fetch content from a specified URL
|
|
52
60
|
|
|
53
61
|
## Configuration
|
|
54
62
|
|
|
55
63
|
The server can be configured to work with various MCP-compatible clients like Cursor, Claude Desktop, and Windsurf.
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
### For Local Installation (Recommended for Development)
|
|
66
|
+
|
|
67
|
+
Since you've installed the package globally with `npm install -g .`, the MCP client can directly execute the command. Add the following entry to your MCP configuration file:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"kadins-personal-mcp": {
|
|
73
|
+
"command": "kadins-personal-mcp",
|
|
74
|
+
"args": []
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### For Direct Path Installation
|
|
81
|
+
|
|
82
|
+
If the above doesn't work, you can also configure it to run directly with node:
|
|
58
83
|
|
|
59
84
|
```json
|
|
60
85
|
{
|
|
61
86
|
"mcpServers": {
|
|
62
87
|
"kadins-personal-mcp": {
|
|
63
88
|
"command": "node",
|
|
64
|
-
"args": ["/
|
|
89
|
+
"args": ["/Users/kadin/.nvm/versions/node/v22.21.1/lib/node_modules/kadins-personal-mcp/dist/index.js"]
|
|
65
90
|
}
|
|
66
91
|
}
|
|
67
92
|
}
|
|
68
93
|
```
|
|
69
94
|
|
|
70
|
-
Replace
|
|
95
|
+
Replace the path with the actual path to the installed package. You can find the correct path by running:
|
|
96
|
+
```bash
|
|
97
|
+
npm list -g --depth=0 kadins-personal-mcp
|
|
98
|
+
```
|
|
71
99
|
|
|
72
100
|
For Claude Desktop, the configuration file is typically located at:
|
|
73
101
|
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS
|
|
@@ -96,6 +124,12 @@ To run tests:
|
|
|
96
124
|
npm run test
|
|
97
125
|
```
|
|
98
126
|
|
|
127
|
+
## System Prompt
|
|
128
|
+
|
|
129
|
+
For AI models to properly understand and use these tools, refer to the SYSTEM_PROMPT.md file which contains a comprehensive system prompt explaining all available tools and their usage.
|
|
130
|
+
|
|
131
|
+
For integration with existing AI systems, see INTEGRATED_SYSTEM_PROMPT.md which combines the Kadins Personal MCP tools information with a standard AI system prompt template.
|
|
132
|
+
|
|
99
133
|
## License
|
|
100
134
|
|
|
101
135
|
MIT
|
|
@@ -6,11 +6,14 @@ var __commonJS = (cb, mod) => function __require() {
|
|
|
6
6
|
// src/toolLoader.ts
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
9
10
|
function isValidTool(module) {
|
|
10
11
|
return typeof module.name === "string" && typeof module.description === "string" && typeof module.parameters === "object" && typeof module.handler === "function";
|
|
11
12
|
}
|
|
12
13
|
async function loadTools(server) {
|
|
13
|
-
const
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
const toolsDir = path.join(__dirname, "..", "tools");
|
|
14
17
|
if (!fs.existsSync(toolsDir)) {
|
|
15
18
|
console.error(`Tools directory does not exist: ${toolsDir}`);
|
|
16
19
|
return;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
__commonJS,
|
|
4
4
|
loadTools
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-IFFK3HCL.js";
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
8
|
var require_package = __commonJS({
|
|
@@ -17,6 +17,11 @@ var require_package = __commonJS({
|
|
|
17
17
|
bin: {
|
|
18
18
|
"kadins-personal-mcp": "./kadins-personal-mcp.js"
|
|
19
19
|
},
|
|
20
|
+
files: [
|
|
21
|
+
"dist/**/*",
|
|
22
|
+
"tools/**/*",
|
|
23
|
+
"kadins-personal-mcp.js"
|
|
24
|
+
],
|
|
20
25
|
scripts: {
|
|
21
26
|
dev: "node --loader ts-node/esm src/index.ts",
|
|
22
27
|
build: "tsup src/index.ts src/toolLoader.ts --format esm --dts --clean",
|
|
@@ -25,6 +30,8 @@ var require_package = __commonJS({
|
|
|
25
30
|
},
|
|
26
31
|
dependencies: {
|
|
27
32
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
33
|
+
glob: "^10.3.10",
|
|
34
|
+
"node-fetch": "^3.3.2",
|
|
28
35
|
zod: "^3.24.2"
|
|
29
36
|
},
|
|
30
37
|
devDependencies: {
|
|
@@ -59,7 +66,6 @@ var require_package = __commonJS({
|
|
|
59
66
|
// src/index.ts
|
|
60
67
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
61
68
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
62
|
-
import { z } from "zod";
|
|
63
69
|
var packageJson = require_package();
|
|
64
70
|
var server = new McpServer(
|
|
65
71
|
{
|
|
@@ -70,26 +76,6 @@ var server = new McpServer(
|
|
|
70
76
|
instructions: "These tools communicate with a reference Model Context Protocol (MCP) server."
|
|
71
77
|
}
|
|
72
78
|
);
|
|
73
|
-
server.tool(
|
|
74
|
-
"ping_pong",
|
|
75
|
-
"Ping the server and receive a pong back",
|
|
76
|
-
{},
|
|
77
|
-
async () => {
|
|
78
|
-
return {
|
|
79
|
-
content: [{ type: "text", text: "pong" }]
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
);
|
|
83
|
-
server.tool(
|
|
84
|
-
"echo",
|
|
85
|
-
"Send a message to the server and receive the message back",
|
|
86
|
-
{ message: z.string() },
|
|
87
|
-
async (params) => {
|
|
88
|
-
return {
|
|
89
|
-
content: [{ type: "text", text: params.message }]
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
);
|
|
93
79
|
async function run() {
|
|
94
80
|
try {
|
|
95
81
|
await loadTools(server);
|
package/dist/toolLoader.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kadins-personal-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Kadin's Personal MCP Server with comprehensive file system and command tools",
|
|
5
5
|
"author": "Kadin",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
"bin": {
|
|
9
9
|
"kadins-personal-mcp": "./kadins-personal-mcp.js"
|
|
10
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**/*",
|
|
13
|
+
"tools/**/*",
|
|
14
|
+
"kadins-personal-mcp.js"
|
|
15
|
+
],
|
|
11
16
|
"scripts": {
|
|
12
17
|
"dev": "node --loader ts-node/esm src/index.ts",
|
|
13
18
|
"build": "tsup src/index.ts src/toolLoader.ts --format esm --dts --clean",
|
|
@@ -16,6 +21,8 @@
|
|
|
16
21
|
},
|
|
17
22
|
"dependencies": {
|
|
18
23
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
24
|
+
"glob": "^10.3.10",
|
|
25
|
+
"node-fetch": "^3.3.2",
|
|
19
26
|
"zod": "^3.24.2"
|
|
20
27
|
},
|
|
21
28
|
"devDependencies": {
|
|
@@ -30,7 +37,9 @@
|
|
|
30
37
|
"jest": {
|
|
31
38
|
"preset": "ts-jest/presets/default-esm",
|
|
32
39
|
"testEnvironment": "node",
|
|
33
|
-
"extensionsToTreatAsEsm": [
|
|
40
|
+
"extensionsToTreatAsEsm": [
|
|
41
|
+
".ts"
|
|
42
|
+
],
|
|
34
43
|
"moduleNameMapper": {
|
|
35
44
|
"^(\\.{1,2}/.*)\\.js$": "$1"
|
|
36
45
|
},
|
|
@@ -43,4 +52,4 @@
|
|
|
43
52
|
]
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
|
-
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_edit";
|
|
4
|
+
export const description = "Edit the content of a file by replacing specific text segments";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
file_path: z.string().describe("The absolute path to the file to edit"),
|
|
7
|
+
old_content: z.string().describe("The exact literal text to replace"),
|
|
8
|
+
new_content: z.string().describe("The exact literal text to replace with"),
|
|
9
|
+
replace_all: z.boolean().describe("Replace all occurrences of old_content (default false)").optional().default(false)
|
|
10
|
+
};
|
|
11
|
+
export const handler = async (params: { file_path: string; old_content: string; new_content: string; replace_all: boolean }) => {
|
|
12
|
+
try {
|
|
13
|
+
const fs = await import("fs");
|
|
14
|
+
const path = await import("path");
|
|
15
|
+
|
|
16
|
+
// Resolve the path relative to the current working directory
|
|
17
|
+
const resolvedPath = path.resolve(params.file_path);
|
|
18
|
+
|
|
19
|
+
// Allow editing any accessible file
|
|
20
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: `Error: File does not exist: ${params.file_path}` }]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const stat = fs.statSync(resolvedPath);
|
|
27
|
+
if (stat.isDirectory()) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: `Error: Path is a directory, not a file: ${params.file_path}` }]
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Limit file size to prevent huge operations
|
|
34
|
+
const maxSize = 1024 * 100; // 100KB limit
|
|
35
|
+
if (stat.size > maxSize) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: `Error: File is too large to edit (${Math.round(stat.size / 1024)}KB). Maximum allowed size is 100KB.` }]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let content = fs.readFileSync(resolvedPath, "utf-8");
|
|
42
|
+
|
|
43
|
+
// Perform the replacement
|
|
44
|
+
let newFileContent;
|
|
45
|
+
if (params.replace_all) {
|
|
46
|
+
newFileContent = content.split(params.old_content).join(params.new_content);
|
|
47
|
+
} else {
|
|
48
|
+
// Only replace first occurrence
|
|
49
|
+
const index = content.indexOf(params.old_content);
|
|
50
|
+
if (index === -1) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: `Error: The specified text to replace was not found in the file: ${params.file_path}` }]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
newFileContent = content.substring(0, index) +
|
|
56
|
+
params.new_content +
|
|
57
|
+
content.substring(index + params.old_content.length);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Write the updated content back to the file
|
|
61
|
+
fs.writeFileSync(resolvedPath, newFileContent, "utf-8");
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: `Successfully updated file: ${params.file_path}. Replaced ${params.replace_all ? 'all' : 'first'} occurrence(s) of specified text.` }]
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: `Error editing file: ${(error as Error).message}` }]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_glob";
|
|
4
|
+
export const description = "Find files using glob patterns";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
pattern: z.string().describe("The glob pattern to match files against (e.g., '**/*.js', '*.ts')")
|
|
7
|
+
};
|
|
8
|
+
export const handler = async (params: { pattern: string }) => {
|
|
9
|
+
try {
|
|
10
|
+
const { glob } = await import("glob");
|
|
11
|
+
|
|
12
|
+
// Execute the glob pattern to find matching files
|
|
13
|
+
const files = await new Promise<string[]>((resolve, reject) => {
|
|
14
|
+
glob(params.pattern, (err, matches) => {
|
|
15
|
+
if (err) {
|
|
16
|
+
reject(err);
|
|
17
|
+
} else {
|
|
18
|
+
resolve(matches);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (files.length === 0) {
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: `No files found matching pattern: ${params.pattern}` }]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Limit results to prevent huge responses
|
|
30
|
+
const limitedFiles = files.slice(0, 100); // Limit to first 100 files
|
|
31
|
+
let response = `Found ${files.length} file(s) matching pattern "${params.pattern}":\n${limitedFiles.join("\n")}`;
|
|
32
|
+
|
|
33
|
+
if (files.length > 100) {
|
|
34
|
+
response += `\n\n... and ${files.length - 100} more files`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: response }]
|
|
39
|
+
};
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: `Error finding files with glob pattern: ${(error as Error).message}` }]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "list_files";
|
|
4
|
+
export const description = "List the names of files and subdirectories in a specified directory";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
path: z.string().describe("The directory path to list").optional().default("."),
|
|
7
|
+
show_hidden: z.boolean().describe("Whether to show hidden files/directories").optional().default(false),
|
|
8
|
+
max_items: z.number().describe("Maximum number of items to return").optional().default(100)
|
|
9
|
+
};
|
|
10
|
+
export const handler = async (params: { path: string; show_hidden: boolean; max_items: number }) => {
|
|
11
|
+
try {
|
|
12
|
+
const fs = await import("fs");
|
|
13
|
+
const path = await import("path");
|
|
14
|
+
|
|
15
|
+
const resolvedPath = path.resolve(params.path);
|
|
16
|
+
|
|
17
|
+
// Allow listing any accessible directory
|
|
18
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: `Error: Directory does not exist: ${params.path}` }]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const stat = fs.statSync(resolvedPath);
|
|
25
|
+
if (!stat.isDirectory()) {
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: `Error: Path is not a directory: ${params.path}` }]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const items = fs.readdirSync(resolvedPath).filter(item => {
|
|
32
|
+
if (!params.show_hidden && item.startsWith('.')) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Limit number of items returned
|
|
39
|
+
const limitedItems = items.slice(0, params.max_items);
|
|
40
|
+
|
|
41
|
+
let response = `Contents of directory "${params.path}":\n`;
|
|
42
|
+
|
|
43
|
+
for (const item of limitedItems) {
|
|
44
|
+
const itemPath = path.join(resolvedPath, item);
|
|
45
|
+
const itemStat = fs.statSync(itemPath);
|
|
46
|
+
const type = itemStat.isDirectory() ? "[DIR]" : "[FILE]";
|
|
47
|
+
const size = itemStat.isDirectory() ? "" : ` (${itemStat.size} bytes)`;
|
|
48
|
+
response += `${type} ${item}${size}\n`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (items.length > params.max_items) {
|
|
52
|
+
response += `\n... and ${items.length - params.max_items} more items`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: response }]
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: `Error listing directory: ${(error as Error).message}` }]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "read_many_files";
|
|
4
|
+
export const description = "Read content from multiple files specified by paths";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
paths: z.array(z.string()).describe("An array of file paths to read"),
|
|
7
|
+
max_file_size: z.number().describe("Maximum file size to read in KB").optional().default(100)
|
|
8
|
+
};
|
|
9
|
+
export const handler = async (params: { paths: string[]; max_file_size: number }) => {
|
|
10
|
+
try {
|
|
11
|
+
const fs = await import("fs");
|
|
12
|
+
const path = await import("path");
|
|
13
|
+
|
|
14
|
+
let response = "";
|
|
15
|
+
const maxSize = params.max_file_size * 1024; // Convert to bytes
|
|
16
|
+
|
|
17
|
+
for (const filePath of params.paths) {
|
|
18
|
+
const resolvedPath = path.resolve(filePath);
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
21
|
+
response += `--- File not found: ${filePath} ---\n\n`;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const stat = fs.statSync(resolvedPath);
|
|
26
|
+
if (stat.isDirectory()) {
|
|
27
|
+
response += `--- Path is a directory: ${filePath} ---\n\n`;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (stat.size > maxSize) {
|
|
32
|
+
response += `--- File too large to read (${Math.round(stat.size / 1024)}KB): ${filePath} ---\n\n`;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
38
|
+
response += `--- ${filePath} ---\n${content}\n--- End of ${filePath} ---\n\n`;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
response += `--- Error reading file: ${filePath} - ${(error as Error).message} ---\n\n`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Limit total response size
|
|
45
|
+
if (response.length > 10000) { // Limit to 10KB
|
|
46
|
+
response = response.substring(0, 10000) + '\n\n... (response truncated)';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: "text", text: response }]
|
|
51
|
+
};
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: `Error reading files: ${(error as Error).message}` }]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "shell_command";
|
|
4
|
+
export const description = "Execute shell commands on the system";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
command: z.string().describe("The shell command to execute"),
|
|
7
|
+
timeout: z.number().describe("Command timeout in seconds").optional().default(30)
|
|
8
|
+
};
|
|
9
|
+
export const handler = async (params: { command: string; timeout: number }) => {
|
|
10
|
+
try {
|
|
11
|
+
// Dynamically import child_process
|
|
12
|
+
const { exec } = await import("child_process");
|
|
13
|
+
const util = await import("util");
|
|
14
|
+
|
|
15
|
+
// Use util.promisify to convert exec to a promise-based function
|
|
16
|
+
const execPromise = util.promisify(exec);
|
|
17
|
+
|
|
18
|
+
// Validate command to prevent potentially dangerous operations
|
|
19
|
+
const dangerousCommands = ['rm -rf /', 'dd', 'mkfs', 'format', '>:', '>/dev/null'];
|
|
20
|
+
for (const dangerousCmd of dangerousCommands) {
|
|
21
|
+
if (params.command.toLowerCase().includes(dangerousCmd)) {
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{ type: "text", text: `Error: Command contains potentially dangerous operation: ${dangerousCmd}` }
|
|
25
|
+
]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Execute the command with a timeout for safety
|
|
31
|
+
const { stdout, stderr } = await Promise.race([
|
|
32
|
+
execPromise(params.command),
|
|
33
|
+
new Promise((_, reject) =>
|
|
34
|
+
setTimeout(() => reject(new Error(`Command timed out after ${params.timeout} seconds`)), params.timeout * 1000)
|
|
35
|
+
)
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// Limit output size to prevent huge responses
|
|
39
|
+
let output = stdout || stderr || 'Command executed successfully with no output';
|
|
40
|
+
if (output.length > 5000) { // Limit to 5000 characters
|
|
41
|
+
output = output.substring(0, 5000) + '\n... (output truncated)';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Return both stdout and stderr
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{ type: "text", text: `Command: ${params.command}\n\nOutput:\n${output}` }
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{ type: "text", text: `Error executing command: ${params.command}\n\nError: ${(error as Error).message}` }
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "text_search";
|
|
4
|
+
export const description = "Search for text patterns within files using grep";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
pattern: z.string().describe("The text pattern to search for (supports regex)"),
|
|
7
|
+
path: z.string().describe("The file or directory path to search in").optional().default("."),
|
|
8
|
+
case_sensitive: z.boolean().describe("Whether the search is case sensitive").optional().default(true)
|
|
9
|
+
};
|
|
10
|
+
export const handler = async (params: { pattern: string; path: string; case_sensitive: boolean }) => {
|
|
11
|
+
try {
|
|
12
|
+
const { exec } = await import("child_process");
|
|
13
|
+
const util = await import("util");
|
|
14
|
+
|
|
15
|
+
// Use util.promisify to convert exec to a promise-based function
|
|
16
|
+
const execPromise = util.promisify(exec);
|
|
17
|
+
|
|
18
|
+
// Build the grep command
|
|
19
|
+
let command = "grep -r -n --color=never ";
|
|
20
|
+
if (!params.case_sensitive) {
|
|
21
|
+
command += "-i ";
|
|
22
|
+
}
|
|
23
|
+
command += `"${params.pattern}" "${params.path}"`;
|
|
24
|
+
|
|
25
|
+
// Execute the command with a timeout for safety
|
|
26
|
+
const { stdout, stderr } = await Promise.race([
|
|
27
|
+
execPromise(command),
|
|
28
|
+
new Promise((_, reject) =>
|
|
29
|
+
setTimeout(() => reject(new Error('Search timed out after 15 seconds')), 15000)
|
|
30
|
+
)
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
// Limit output size to prevent huge responses
|
|
34
|
+
let output = stdout || stderr || `No matches found for pattern: ${params.pattern}`;
|
|
35
|
+
if (output.length > 5000) { // Limit to 5000 characters
|
|
36
|
+
output = output.substring(0, 5000) + '\n... (output truncated)';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{ type: "text", text: `Search results for pattern "${params.pattern}" in path "${params.path}":\n\n${output}` }
|
|
42
|
+
]
|
|
43
|
+
};
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// If grep command fails (e.g., no matches), return a friendly message
|
|
46
|
+
if ((error as any).code === 1) {
|
|
47
|
+
// Exit code 1 in grep means no matches found, which is not a real error
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{ type: "text", text: `No matches found for pattern: ${params.pattern}` }
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
content: [
|
|
57
|
+
{ type: "text", text: `Error searching for pattern: ${(error as Error).message}` }
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "web_fetch";
|
|
4
|
+
export const description = "Fetch content from a specified URL";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
url: z.string().describe("The URL to fetch content from"),
|
|
7
|
+
max_content_size: z.number().describe("Maximum content size to fetch in KB").optional().default(500)
|
|
8
|
+
};
|
|
9
|
+
export const handler = async (params: { url: string; max_content_size: number }) => {
|
|
10
|
+
try {
|
|
11
|
+
const { default: fetch } = await import("node-fetch");
|
|
12
|
+
|
|
13
|
+
// Fetch the content from the URL
|
|
14
|
+
const response = await fetch(params.url);
|
|
15
|
+
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: `Error: Failed to fetch URL. Status: ${response.status} ${response.statusText}` }]
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check content length header if available
|
|
23
|
+
const contentLength = response.headers.get('content-length');
|
|
24
|
+
if (contentLength) {
|
|
25
|
+
const sizeInKB = parseInt(contentLength) / 1024;
|
|
26
|
+
if (sizeInKB > params.max_content_size) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: `Error: Content is too large to fetch (${sizeInKB.toFixed(2)}KB). Maximum allowed size is ${params.max_content_size}KB.` }]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const content = await response.text();
|
|
34
|
+
|
|
35
|
+
// Limit content size
|
|
36
|
+
const maxSize = params.max_content_size * 1024; // Convert to bytes
|
|
37
|
+
let limitedContent = content;
|
|
38
|
+
if (content.length > maxSize) {
|
|
39
|
+
limitedContent = content.substring(0, maxSize) + '\n\n... (content truncated)';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: `Content from ${params.url}:\n\n${limitedContent}` }]
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: `Error fetching URL: ${(error as Error).message}` }]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "write_file";
|
|
4
|
+
export const description = "Create or overwrite a file with specified content";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
file_path: z.string().describe("The absolute path to the file to write"),
|
|
7
|
+
content: z.string().describe("The content to write to the file"),
|
|
8
|
+
create_directories: z.boolean().describe("Whether to create parent directories if they don't exist").optional().default(true)
|
|
9
|
+
};
|
|
10
|
+
export const handler = async (params: { file_path: string; content: string; create_directories: boolean }) => {
|
|
11
|
+
try {
|
|
12
|
+
const fs = await import("fs");
|
|
13
|
+
const path = await import("path");
|
|
14
|
+
|
|
15
|
+
// Resolve the path relative to the current working directory
|
|
16
|
+
const resolvedPath = path.resolve(params.file_path);
|
|
17
|
+
|
|
18
|
+
// Allow writing to any accessible path
|
|
19
|
+
if (params.create_directories) {
|
|
20
|
+
// Create parent directories if they don't exist
|
|
21
|
+
const dir = path.dirname(resolvedPath);
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Write the content to the file
|
|
26
|
+
fs.writeFileSync(resolvedPath, params.content, "utf-8");
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: `Successfully wrote ${params.content.length} characters to ${params.file_path}` }]
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Error writing file: ${(error as Error).message}` }]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|