kadins-personal-mcp 1.0.0
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/PUBLISHING.md +43 -0
- package/README.md +101 -0
- package/dist/chunk-T6KZEVGN.js +45 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +104 -0
- package/dist/toolLoader.d.ts +5 -0
- package/dist/toolLoader.js +6 -0
- package/gitignore +7 -0
- package/kadins-personal-mcp.js +5 -0
- package/package.json +46 -0
- package/src/index.ts +63 -0
- package/src/simple.test.ts +9 -0
- package/src/toolLoader.ts +59 -0
- package/tools/calculator.ts +40 -0
- package/tools/currentTime.ts +11 -0
- package/tools/directoryCreate.ts +43 -0
- package/tools/directoryList.ts +70 -0
- package/tools/echo.ts +12 -0
- package/tools/fileCopy.ts +55 -0
- package/tools/fileDelete.ts +48 -0
- package/tools/fileList.ts +88 -0
- package/tools/fileMove.ts +62 -0
- package/tools/filePermissions.ts +50 -0
- package/tools/fileSearch.ts +88 -0
- package/tools/fileWrite.ts +50 -0
- package/tools/readFile.ts +39 -0
- package/tools/runCommand.ts +38 -0
- package/tsconfig.json +12 -0
package/PUBLISHING.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Publishing Kadins Personal MCP
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
1. Make sure you have an npm account at https://www.npmjs.com/
|
|
6
|
+
2. Ensure you're logged in to npm: `npm login`
|
|
7
|
+
|
|
8
|
+
## To Publish
|
|
9
|
+
|
|
10
|
+
1. Update the version in package.json if needed: `npm version patch` (or minor/major)
|
|
11
|
+
2. Run tests to make sure everything works: `npm test`
|
|
12
|
+
3. Build the project: `npm run build`
|
|
13
|
+
4. Publish to npm: `npm publish`
|
|
14
|
+
|
|
15
|
+
## Local Installation for Testing
|
|
16
|
+
|
|
17
|
+
To install locally for testing:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or to link for development:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm link
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
After publishing, you can install your package globally:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g kadins-personal-mcp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
And then run it:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
kadins-personal-mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The server will start and be available for MCP-compatible clients.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Kadins Personal MCP Server
|
|
2
|
+
|
|
3
|
+
Kadin's Personal Model Context Protocol (MCP) Server with comprehensive file system and command tools.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This MCP server provides a wide range of tools to interact with your system, including:
|
|
8
|
+
- File system operations (search, read, write, copy, move, delete, etc.)
|
|
9
|
+
- Command execution capabilities
|
|
10
|
+
- System information tools
|
|
11
|
+
- And more!
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g kadins-personal-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
To start the MCP server:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
kadins-personal-mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or for development:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm run dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Available Tools
|
|
34
|
+
|
|
35
|
+
### File System Tools
|
|
36
|
+
- `file_search`: Search for files by name or content
|
|
37
|
+
- `file_write`: Write content to a file
|
|
38
|
+
- `file_list`: List files in a directory
|
|
39
|
+
- `file_delete`: Delete a specified file
|
|
40
|
+
- `file_copy`: Copy a file from one location to another
|
|
41
|
+
- `file_move`: Move or rename a file
|
|
42
|
+
- `file_permissions`: Change file permissions
|
|
43
|
+
- `directory_create`: Create a new directory
|
|
44
|
+
- `directory_list`: List directory contents with details
|
|
45
|
+
|
|
46
|
+
### System Tools
|
|
47
|
+
- `run_command`: Execute shell commands on the system
|
|
48
|
+
- `read_file`: Read the contents of a file
|
|
49
|
+
- `calculator`: Perform basic mathematical calculations
|
|
50
|
+
- `get_current_time`: Get the current date and time
|
|
51
|
+
- `file_echo`: Echo messages back
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
The server can be configured to work with various MCP-compatible clients like Cursor, Claude Desktop, and Windsurf.
|
|
56
|
+
|
|
57
|
+
To configure your MCP client, add the following entry to your MCP configuration file (e.g., `~/.cursor/mcp.json`, `~/.codeium/windsurf/mcp_config.json`, or Claude Desktop config):
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"kadins-personal-mcp": {
|
|
63
|
+
"command": "node",
|
|
64
|
+
"args": ["/path/to/kadins-personal-mcp/dist/index.js"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Replace `/path/to/kadins-personal-mcp/dist/index.js` with the actual path to the built index.js file in your installation.
|
|
71
|
+
|
|
72
|
+
For Claude Desktop, the configuration file is typically located at:
|
|
73
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS
|
|
74
|
+
|
|
75
|
+
For Cursor, the configuration file is typically located at:
|
|
76
|
+
`~/.cursor/mcp.json`
|
|
77
|
+
|
|
78
|
+
For Windsurf, the configuration file is typically located at:
|
|
79
|
+
`~/.codeium/windsurf/mcp_config.json`
|
|
80
|
+
|
|
81
|
+
## Security
|
|
82
|
+
|
|
83
|
+
This server includes security measures to prevent path traversal and other potential security issues. However, please be cautious when using tools that execute commands or modify files.
|
|
84
|
+
|
|
85
|
+
## Development
|
|
86
|
+
|
|
87
|
+
To build the server:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm run build
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
To run tests:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm run test
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
3
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// src/toolLoader.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
function isValidTool(module) {
|
|
10
|
+
return typeof module.name === "string" && typeof module.description === "string" && typeof module.parameters === "object" && typeof module.handler === "function";
|
|
11
|
+
}
|
|
12
|
+
async function loadTools(server) {
|
|
13
|
+
const toolsDir = path.join(process.cwd(), "tools");
|
|
14
|
+
if (!fs.existsSync(toolsDir)) {
|
|
15
|
+
console.error(`Tools directory does not exist: ${toolsDir}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const files = fs.readdirSync(toolsDir);
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
if (file.endsWith(".ts") || file.endsWith(".js")) {
|
|
21
|
+
try {
|
|
22
|
+
const modulePath = path.join(toolsDir, file);
|
|
23
|
+
const module = await import(modulePath);
|
|
24
|
+
if (isValidTool(module)) {
|
|
25
|
+
server.tool(
|
|
26
|
+
module.name,
|
|
27
|
+
module.description,
|
|
28
|
+
module.parameters,
|
|
29
|
+
module.handler
|
|
30
|
+
);
|
|
31
|
+
console.error(`Registered tool: ${module.name}`);
|
|
32
|
+
} else {
|
|
33
|
+
console.error(`Invalid tool format in file: ${file}. Required exports: name, description, parameters, handler`);
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`Error loading tool from file ${file}:`, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
__commonJS,
|
|
44
|
+
loadTools
|
|
45
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
__commonJS,
|
|
4
|
+
loadTools
|
|
5
|
+
} from "./chunk-T6KZEVGN.js";
|
|
6
|
+
|
|
7
|
+
// package.json
|
|
8
|
+
var require_package = __commonJS({
|
|
9
|
+
"package.json"(exports, module) {
|
|
10
|
+
module.exports = {
|
|
11
|
+
name: "kadins-personal-mcp",
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
description: "Kadin's Personal MCP Server with comprehensive file system and command tools",
|
|
14
|
+
author: "Kadin",
|
|
15
|
+
license: "MIT",
|
|
16
|
+
type: "module",
|
|
17
|
+
bin: {
|
|
18
|
+
"kadins-personal-mcp": "./kadins-personal-mcp.js"
|
|
19
|
+
},
|
|
20
|
+
scripts: {
|
|
21
|
+
dev: "node --loader ts-node/esm src/index.ts",
|
|
22
|
+
build: "tsup src/index.ts src/toolLoader.ts --format esm --dts --clean",
|
|
23
|
+
test: "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
24
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
25
|
+
},
|
|
26
|
+
dependencies: {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
28
|
+
zod: "^3.24.2"
|
|
29
|
+
},
|
|
30
|
+
devDependencies: {
|
|
31
|
+
"@types/jest": "^29.5.14",
|
|
32
|
+
"@types/node": "^22.13.13",
|
|
33
|
+
jest: "^29.7.0",
|
|
34
|
+
"ts-jest": "^29.3.1",
|
|
35
|
+
"ts-node": "^10.9.2",
|
|
36
|
+
tsup: "^8.4.0",
|
|
37
|
+
typescript: "^5.8.2"
|
|
38
|
+
},
|
|
39
|
+
jest: {
|
|
40
|
+
preset: "ts-jest/presets/default-esm",
|
|
41
|
+
testEnvironment: "node",
|
|
42
|
+
extensionsToTreatAsEsm: [".ts"],
|
|
43
|
+
moduleNameMapper: {
|
|
44
|
+
"^(\\.{1,2}/.*)\\.js$": "$1"
|
|
45
|
+
},
|
|
46
|
+
transform: {
|
|
47
|
+
"^.+\\.tsx?$": [
|
|
48
|
+
"ts-jest",
|
|
49
|
+
{
|
|
50
|
+
useESM: true
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// src/index.ts
|
|
60
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
61
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
62
|
+
import { z } from "zod";
|
|
63
|
+
var packageJson = require_package();
|
|
64
|
+
var server = new McpServer(
|
|
65
|
+
{
|
|
66
|
+
name: packageJson.name,
|
|
67
|
+
version: packageJson.version
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
instructions: "These tools communicate with a reference Model Context Protocol (MCP) server."
|
|
71
|
+
}
|
|
72
|
+
);
|
|
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
|
+
async function run() {
|
|
94
|
+
try {
|
|
95
|
+
await loadTools(server);
|
|
96
|
+
const transport = new StdioServerTransport();
|
|
97
|
+
await server.connect(transport);
|
|
98
|
+
console.error("MCP server connected via stdio");
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error("Error starting MCP server:", error);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
run();
|
package/gitignore
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kadins-personal-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Kadin's Personal MCP Server with comprehensive file system and command tools",
|
|
5
|
+
"author": "Kadin",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"kadins-personal-mcp": "./kadins-personal-mcp.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "node --loader ts-node/esm src/index.ts",
|
|
13
|
+
"build": "tsup src/index.ts src/toolLoader.ts --format esm --dts --clean",
|
|
14
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
15
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
19
|
+
"zod": "^3.24.2"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/jest": "^29.5.14",
|
|
23
|
+
"@types/node": "^22.13.13",
|
|
24
|
+
"jest": "^29.7.0",
|
|
25
|
+
"ts-jest": "^29.3.1",
|
|
26
|
+
"ts-node": "^10.9.2",
|
|
27
|
+
"tsup": "^8.4.0",
|
|
28
|
+
"typescript": "^5.8.2"
|
|
29
|
+
},
|
|
30
|
+
"jest": {
|
|
31
|
+
"preset": "ts-jest/presets/default-esm",
|
|
32
|
+
"testEnvironment": "node",
|
|
33
|
+
"extensionsToTreatAsEsm": [".ts"],
|
|
34
|
+
"moduleNameMapper": {
|
|
35
|
+
"^(\\.{1,2}/.*)\\.js$": "$1"
|
|
36
|
+
},
|
|
37
|
+
"transform": {
|
|
38
|
+
"^.+\\.tsx?$": [
|
|
39
|
+
"ts-jest",
|
|
40
|
+
{
|
|
41
|
+
"useESM": true
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { loadTools } from "./toolLoader.js";
|
|
7
|
+
|
|
8
|
+
const packageJson = require("../package.json") as any;
|
|
9
|
+
|
|
10
|
+
// Create a new MCP server
|
|
11
|
+
const server = new McpServer(
|
|
12
|
+
{
|
|
13
|
+
name: packageJson.name,
|
|
14
|
+
version: packageJson.version,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
instructions:
|
|
18
|
+
"These tools communicate with a reference Model Context Protocol (MCP) server.",
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Implement the ping_pong tool
|
|
23
|
+
server.tool(
|
|
24
|
+
"ping_pong",
|
|
25
|
+
"Ping the server and receive a pong back",
|
|
26
|
+
{},
|
|
27
|
+
async () => {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: "pong" }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Implement the echo tool
|
|
35
|
+
server.tool(
|
|
36
|
+
"echo",
|
|
37
|
+
"Send a message to the server and receive the message back",
|
|
38
|
+
{ message: z.string() },
|
|
39
|
+
async (params) => {
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: params.message }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Start the server
|
|
47
|
+
async function run() {
|
|
48
|
+
try {
|
|
49
|
+
// Load tools from the tools directory
|
|
50
|
+
await loadTools(server);
|
|
51
|
+
|
|
52
|
+
// Use stdio for transport
|
|
53
|
+
const transport = new StdioServerTransport();
|
|
54
|
+
await server.connect(transport);
|
|
55
|
+
// Since stdout is used for MCP messages, use stderr for logging
|
|
56
|
+
console.error("MCP server connected via stdio");
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error("Error starting MCP server:", error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
run();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { test } from '@jest/globals';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
|
|
4
|
+
// Simple test to verify the tool loader can be imported without errors
|
|
5
|
+
test('toolLoader can be imported', async () => {
|
|
6
|
+
// We'll just check that the module can be imported without errors
|
|
7
|
+
// Since the actual loading depends on the file system, we'll do a basic import test
|
|
8
|
+
expect(typeof (await import('./toolLoader.js'))).toBe('object');
|
|
9
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
|
|
5
|
+
// Define the interface for tool files
|
|
6
|
+
interface ToolDefinition {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
parameters: Record<string, any>;
|
|
10
|
+
handler: (params: any) => Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Validates that a loaded module has the required tool properties
|
|
14
|
+
function isValidTool(module: any): module is ToolDefinition {
|
|
15
|
+
return (
|
|
16
|
+
typeof module.name === 'string' &&
|
|
17
|
+
typeof module.description === 'string' &&
|
|
18
|
+
typeof module.parameters === 'object' &&
|
|
19
|
+
typeof module.handler === 'function'
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Load and register all tools from the tools directory
|
|
24
|
+
export async function loadTools(server: McpServer) {
|
|
25
|
+
const toolsDir = path.join(process.cwd(), 'tools');
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(toolsDir)) {
|
|
28
|
+
console.error(`Tools directory does not exist: ${toolsDir}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const files = fs.readdirSync(toolsDir);
|
|
33
|
+
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
|
36
|
+
try {
|
|
37
|
+
// Use dynamic import to load the tool file
|
|
38
|
+
const modulePath = path.join(toolsDir, file);
|
|
39
|
+
const module = await import(modulePath);
|
|
40
|
+
|
|
41
|
+
if (isValidTool(module)) {
|
|
42
|
+
// Register the tool with the MCP server
|
|
43
|
+
server.tool(
|
|
44
|
+
module.name,
|
|
45
|
+
module.description,
|
|
46
|
+
module.parameters,
|
|
47
|
+
module.handler
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
console.error(`Registered tool: ${module.name}`);
|
|
51
|
+
} else {
|
|
52
|
+
console.error(`Invalid tool format in file: ${file}. Required exports: name, description, parameters, handler`);
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(`Error loading tool from file ${file}:`, error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "calculator";
|
|
4
|
+
export const description = "Perform basic mathematical calculations";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
operation: z.enum(["add", "subtract", "multiply", "divide"]).describe("The mathematical operation to perform"),
|
|
7
|
+
a: z.number().describe("The first operand"),
|
|
8
|
+
b: z.number().describe("The second operand")
|
|
9
|
+
};
|
|
10
|
+
export const handler = async (params: { operation: string; a: number; b: number }) => {
|
|
11
|
+
let result: number;
|
|
12
|
+
|
|
13
|
+
switch (params.operation) {
|
|
14
|
+
case "add":
|
|
15
|
+
result = params.a + params.b;
|
|
16
|
+
break;
|
|
17
|
+
case "subtract":
|
|
18
|
+
result = params.a - params.b;
|
|
19
|
+
break;
|
|
20
|
+
case "multiply":
|
|
21
|
+
result = params.a * params.b;
|
|
22
|
+
break;
|
|
23
|
+
case "divide":
|
|
24
|
+
if (params.b === 0) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: "Error: Division by zero is not allowed" }]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
result = params.a / params.b;
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Error: Unknown operation ${params.operation}` }]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: `Result: ${params.a} ${params.operation} ${params.b} = ${result}` }]
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "get_current_time";
|
|
4
|
+
export const description = "Get the current date and time";
|
|
5
|
+
export const parameters = {};
|
|
6
|
+
export const handler = async () => {
|
|
7
|
+
const now = new Date();
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: `Current date and time: ${now.toString()}` }]
|
|
10
|
+
};
|
|
11
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "directory_create";
|
|
4
|
+
export const description = "Create a new directory";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
path: z.string().describe("The path to the directory to create"),
|
|
7
|
+
recursive: z.boolean().describe("Whether to create parent directories if they don't exist").optional().default(true)
|
|
8
|
+
};
|
|
9
|
+
export const handler = async (params: { path: string; recursive: boolean }) => {
|
|
10
|
+
try {
|
|
11
|
+
const fs = await import("fs");
|
|
12
|
+
const path = await import("path");
|
|
13
|
+
|
|
14
|
+
// Resolve the path relative to the current working directory
|
|
15
|
+
const resolvedPath = path.resolve(params.path);
|
|
16
|
+
|
|
17
|
+
// Check if the path is within the current working directory to prevent path traversal
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
if (!resolvedPath.startsWith(cwd)) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: "text", text: "Error: Path traversal is not allowed" }]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check if directory already exists
|
|
26
|
+
if (fs.existsSync(resolvedPath)) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: `Error: Directory already exists: ${params.path}` }]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Create the directory
|
|
33
|
+
fs.mkdirSync(resolvedPath, { recursive: params.recursive });
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: `Successfully created directory: ${params.path}` }]
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text", text: `Error creating directory: ${(error as Error).message}` }]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "directory_list";
|
|
4
|
+
export const description = "List directory contents with details (size, type, etc.)";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
directory: z.string().describe("The directory to list").optional().default("."),
|
|
7
|
+
showHidden: z.boolean().describe("Whether to show hidden files/directories").optional().default(false)
|
|
8
|
+
};
|
|
9
|
+
export const handler = async (params: { directory: string; showHidden?: boolean }) => {
|
|
10
|
+
try {
|
|
11
|
+
const fs = await import("fs");
|
|
12
|
+
const path = await import("path");
|
|
13
|
+
|
|
14
|
+
const searchDir = params.directory || ".";
|
|
15
|
+
const resolvedDir = path.resolve(searchDir);
|
|
16
|
+
|
|
17
|
+
// Check if the directory is within the current working directory to prevent path traversal
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
if (!resolvedDir.startsWith(cwd)) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: "text", text: "Error: Directory traversal is not allowed" }]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: `Error: Directory does not exist: ${resolvedDir}` }]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!fs.statSync(resolvedDir).isDirectory()) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Error: Path is not a directory: ${resolvedDir}` }]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const items = fs.readdirSync(resolvedDir);
|
|
38
|
+
const results: string[] = [];
|
|
39
|
+
|
|
40
|
+
for (const item of items) {
|
|
41
|
+
// Skip hidden files if showHidden is false
|
|
42
|
+
if (!params.showHidden && item.startsWith('.')) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const itemPath = path.join(resolvedDir, item);
|
|
47
|
+
const stat = fs.statSync(itemPath);
|
|
48
|
+
|
|
49
|
+
let type = stat.isDirectory() ? "DIR" : "FILE";
|
|
50
|
+
let size = stat.isDirectory() ? "-" : `${stat.size} bytes`;
|
|
51
|
+
let permissions = stat.mode.toString(8).slice(-3);
|
|
52
|
+
|
|
53
|
+
results.push(`${type} ${permissions} ${size} ${item}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (results.length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: `Directory is empty: ${params.directory || "."}` }]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: "text", text: `Contents of ${params.directory || "."}:\n${results.join("\n")}` }]
|
|
64
|
+
};
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text: `Error listing directory: ${(error as Error).message}` }]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
};
|
package/tools/echo.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_echo";
|
|
4
|
+
export const description = "Send a message to the server and receive the message back (file-based tool)";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
message: z.string().describe("The message to echo back")
|
|
7
|
+
};
|
|
8
|
+
export const handler = async (params: { message: string }) => {
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: params.message }]
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_copy";
|
|
4
|
+
export const description = "Copy a file from one location to another";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
source: z.string().describe("The path to the source file"),
|
|
7
|
+
destination: z.string().describe("The path to the destination file")
|
|
8
|
+
};
|
|
9
|
+
export const handler = async (params: { source: string; destination: string }) => {
|
|
10
|
+
try {
|
|
11
|
+
const fs = await import("fs");
|
|
12
|
+
const path = await import("path");
|
|
13
|
+
|
|
14
|
+
// Resolve paths relative to the current working directory
|
|
15
|
+
const resolvedSource = path.resolve(params.source);
|
|
16
|
+
const resolvedDestination = path.resolve(params.destination);
|
|
17
|
+
|
|
18
|
+
// Check if paths are within the current working directory to prevent path traversal
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
if (!resolvedSource.startsWith(cwd) || !resolvedDestination.startsWith(cwd)) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: "Error: Path traversal is not allowed" }]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(resolvedSource)) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: `Error: Source file does not exist: ${params.source}` }]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (fs.statSync(resolvedSource).isDirectory()) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: `Error: Source path is a directory, not a file: ${params.source}` }]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create destination directory if it doesn't exist
|
|
39
|
+
const destDir = path.dirname(resolvedDestination);
|
|
40
|
+
if (!fs.existsSync(destDir)) {
|
|
41
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Copy the file
|
|
45
|
+
fs.copyFileSync(resolvedSource, resolvedDestination);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: "text", text: `Successfully copied file from ${params.source} to ${params.destination}` }]
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: `Error copying file: ${(error as Error).message}` }]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_delete";
|
|
4
|
+
export const description = "Delete a specified file";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
path: z.string().describe("The path to the file to delete")
|
|
7
|
+
};
|
|
8
|
+
export const handler = async (params: { path: string }) => {
|
|
9
|
+
try {
|
|
10
|
+
const fs = await import("fs");
|
|
11
|
+
const path = await import("path");
|
|
12
|
+
|
|
13
|
+
// Resolve the path relative to the current working directory
|
|
14
|
+
const resolvedPath = path.resolve(params.path);
|
|
15
|
+
|
|
16
|
+
// Check if the path is within the current working directory to prevent path traversal
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
if (!resolvedPath.startsWith(cwd)) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: "Error: Path traversal is not allowed" }]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: `Error: File does not exist: ${params.path}` }]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const stat = fs.statSync(resolvedPath);
|
|
31
|
+
if (stat.isDirectory()) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `Error: Path is a directory, not a file: ${params.path}` }]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Delete the file
|
|
38
|
+
fs.unlinkSync(resolvedPath);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `Successfully deleted file: ${params.path}` }]
|
|
42
|
+
};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: `Error deleting file: ${(error as Error).message}` }]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_list";
|
|
4
|
+
export const description = "List files in a directory with optional filtering";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
directory: z.string().describe("The directory to list").optional().default("."),
|
|
7
|
+
filter: z.string().describe("Optional filter pattern to match files").optional(),
|
|
8
|
+
recursive: z.boolean().describe("Whether to list files recursively").optional().default(false)
|
|
9
|
+
};
|
|
10
|
+
export const handler = async (params: { directory: string; filter?: string; recursive?: boolean }) => {
|
|
11
|
+
try {
|
|
12
|
+
const fs = await import("fs");
|
|
13
|
+
const path = await import("path");
|
|
14
|
+
|
|
15
|
+
const searchDir = params.directory || ".";
|
|
16
|
+
const resolvedDir = path.resolve(searchDir);
|
|
17
|
+
|
|
18
|
+
// Check if the directory is within the current working directory to prevent path traversal
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
if (!resolvedDir.startsWith(cwd)) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: "Error: Directory traversal is not allowed" }]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: `Error: Directory does not exist: ${resolvedDir}` }]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!fs.statSync(resolvedDir).isDirectory()) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: `Error: Path is not a directory: ${resolvedDir}` }]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const results: string[] = [];
|
|
39
|
+
|
|
40
|
+
function listDirectory(dir: string, isRoot: boolean = true) {
|
|
41
|
+
const items = fs.readdirSync(dir);
|
|
42
|
+
|
|
43
|
+
for (const item of items) {
|
|
44
|
+
const itemPath = path.join(dir, item);
|
|
45
|
+
const relativePath = path.relative(resolvedDir, itemPath);
|
|
46
|
+
|
|
47
|
+
// Apply filter if provided
|
|
48
|
+
if (params.filter && !item.includes(params.filter)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Skip node_modules and other common directories when not in recursive mode
|
|
53
|
+
if (!params.recursive && !isRoot && (item === "node_modules" || item.startsWith("."))) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const stat = fs.statSync(itemPath);
|
|
58
|
+
|
|
59
|
+
if (stat.isDirectory()) {
|
|
60
|
+
if (params.recursive && item !== "node_modules" && !item.startsWith(".")) {
|
|
61
|
+
results.push(`${relativePath}/ (directory)`);
|
|
62
|
+
listDirectory(itemPath, false);
|
|
63
|
+
} else if (!params.recursive) {
|
|
64
|
+
results.push(`${relativePath}/ (directory)`);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
results.push(`${relativePath} (${stat.size} bytes)`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
listDirectory(resolvedDir);
|
|
73
|
+
|
|
74
|
+
if (results.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: `No files found in directory: ${resolvedDir}` }]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: `Files in ${params.directory || "."}:\n${results.join("\n")}` }]
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: `Error listing files: ${(error as Error).message}` }]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_move";
|
|
4
|
+
export const description = "Move/rename a file";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
source: z.string().describe("The path to the source file"),
|
|
7
|
+
destination: z.string().describe("The path to the destination file")
|
|
8
|
+
};
|
|
9
|
+
export const handler = async (params: { source: string; destination: string }) => {
|
|
10
|
+
try {
|
|
11
|
+
const fs = await import("fs");
|
|
12
|
+
const path = await import("path");
|
|
13
|
+
|
|
14
|
+
// Resolve paths relative to the current working directory
|
|
15
|
+
const resolvedSource = path.resolve(params.source);
|
|
16
|
+
const resolvedDestination = path.resolve(params.destination);
|
|
17
|
+
|
|
18
|
+
// Check if paths are within the current working directory to prevent path traversal
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
if (!resolvedSource.startsWith(cwd) || !resolvedDestination.startsWith(cwd)) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: "Error: Path traversal is not allowed" }]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(resolvedSource)) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: `Error: Source file does not exist: ${params.source}` }]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (fs.statSync(resolvedSource).isDirectory()) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: `Error: Source path is a directory, not a file: ${params.source}` }]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if destination already exists
|
|
39
|
+
if (fs.existsSync(resolvedDestination)) {
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: `Error: Destination file already exists: ${params.destination}` }]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Create destination directory if it doesn't exist
|
|
46
|
+
const destDir = path.dirname(resolvedDestination);
|
|
47
|
+
if (!fs.existsSync(destDir)) {
|
|
48
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Move the file (rename it)
|
|
52
|
+
fs.renameSync(resolvedSource, resolvedDestination);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: `Successfully moved file from ${params.source} to ${params.destination}` }]
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return {
|
|
59
|
+
content: [{ type: "text", text: `Error moving file: ${(error as Error).message}` }]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_permissions";
|
|
4
|
+
export const description = "Change file permissions";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
path: z.string().describe("The path to the file"),
|
|
7
|
+
permissions: z.string().describe("The new permissions in octal format (e.g., '755', '644')")
|
|
8
|
+
};
|
|
9
|
+
export const handler = async (params: { path: string; permissions: string }) => {
|
|
10
|
+
try {
|
|
11
|
+
const fs = await import("fs");
|
|
12
|
+
const path = await import("path");
|
|
13
|
+
|
|
14
|
+
// Resolve the path relative to the current working directory
|
|
15
|
+
const resolvedPath = path.resolve(params.path);
|
|
16
|
+
|
|
17
|
+
// Check if the path is within the current working directory to prevent path traversal
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
if (!resolvedPath.startsWith(cwd)) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: "text", text: "Error: Path traversal is not allowed" }]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: `Error: File does not exist: ${params.path}` }]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate permissions format (should be 3 or 4 digits)
|
|
32
|
+
if (!/^[0-7]{3,4}$/.test(params.permissions)) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: `Error: Invalid permissions format. Use octal format like '755' or '644'.` }]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Change file permissions
|
|
39
|
+
const permissionsNum = parseInt(params.permissions, 8);
|
|
40
|
+
fs.chmodSync(resolvedPath, permissionsNum);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: `Successfully changed permissions of ${params.path} to ${params.permissions}` }]
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: `Error changing permissions: ${(error as Error).message}` }]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_search";
|
|
4
|
+
export const description = "Search for files by name, content, or pattern in a directory";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
pattern: z.string().describe("The pattern to search for (file name or content)"),
|
|
7
|
+
directory: z.string().describe("The directory to search in").optional().default("."),
|
|
8
|
+
searchInContent: z.boolean().describe("Whether to search in file content (true) or just filenames (false)").optional().default(false)
|
|
9
|
+
};
|
|
10
|
+
export const handler = async (params: { pattern: string; directory: string; searchInContent: boolean }) => {
|
|
11
|
+
try {
|
|
12
|
+
const fs = await import("fs");
|
|
13
|
+
const path = await import("path");
|
|
14
|
+
|
|
15
|
+
const searchDir = params.directory || ".";
|
|
16
|
+
const resolvedDir = path.resolve(searchDir);
|
|
17
|
+
|
|
18
|
+
// Check if the directory is within the current working directory to prevent path traversal
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
if (!resolvedDir.startsWith(cwd)) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: "Error: Directory traversal is not allowed" }]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(resolvedDir)) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: `Error: Directory does not exist: ${resolvedDir}` }]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!fs.statSync(resolvedDir).isDirectory()) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: `Error: Path is not a directory: ${resolvedDir}` }]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const results: string[] = [];
|
|
39
|
+
|
|
40
|
+
function searchRecursive(dir: string) {
|
|
41
|
+
const items = fs.readdirSync(dir);
|
|
42
|
+
|
|
43
|
+
for (const item of items) {
|
|
44
|
+
const itemPath = path.join(dir, item);
|
|
45
|
+
const stat = fs.statSync(itemPath);
|
|
46
|
+
|
|
47
|
+
if (stat.isDirectory()) {
|
|
48
|
+
// Skip node_modules and other common directories that shouldn't be searched
|
|
49
|
+
if (item !== "node_modules" && !item.startsWith(".")) {
|
|
50
|
+
searchRecursive(itemPath);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// Check filename match
|
|
54
|
+
if (item.includes(params.pattern)) {
|
|
55
|
+
results.push(itemPath);
|
|
56
|
+
}
|
|
57
|
+
// Check content match if requested
|
|
58
|
+
else if (params.searchInContent) {
|
|
59
|
+
try {
|
|
60
|
+
const content = fs.readFileSync(itemPath, "utf-8");
|
|
61
|
+
if (content.includes(params.pattern)) {
|
|
62
|
+
results.push(itemPath);
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// Skip binary files or files that can't be read
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
searchRecursive(resolvedDir);
|
|
73
|
+
|
|
74
|
+
if (results.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: `No files found matching pattern: ${params.pattern}` }]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text", text: `Found ${results.length} file(s):\n${results.join("\n")}` }]
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: `Error searching files: ${(error as Error).message}` }]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "file_write";
|
|
4
|
+
export const description = "Write content to a file (with safety checks to prevent overwrites)";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
path: z.string().describe("The path to the file to write"),
|
|
7
|
+
content: z.string().describe("The content to write to the file"),
|
|
8
|
+
overwrite: z.boolean().describe("Whether to overwrite an existing file").optional().default(false)
|
|
9
|
+
};
|
|
10
|
+
export const handler = async (params: { path: string; content: string; overwrite: 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.path);
|
|
17
|
+
|
|
18
|
+
// Check if the path is within the current working directory to prevent path traversal
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
if (!resolvedPath.startsWith(cwd)) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: "Error: Path traversal is not allowed" }]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check if file exists and overwrite is not allowed
|
|
27
|
+
if (fs.existsSync(resolvedPath) && !params.overwrite) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: `Error: File already exists at ${params.path}. Use overwrite=true to overwrite.` }]
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Create directory if it doesn't exist
|
|
34
|
+
const dir = path.dirname(resolvedPath);
|
|
35
|
+
if (!fs.existsSync(dir)) {
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Write the content to the file
|
|
40
|
+
fs.writeFileSync(resolvedPath, params.content, "utf-8");
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: `Successfully wrote ${params.content.length} characters to ${params.path}` }]
|
|
44
|
+
};
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: `Error writing file: ${(error as Error).message}` }]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "read_file";
|
|
4
|
+
export const description = "Read the contents of a file";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
path: z.string().describe("The path to the file to read")
|
|
7
|
+
};
|
|
8
|
+
export const handler = async (params: { path: string }) => {
|
|
9
|
+
try {
|
|
10
|
+
const fs = await import("fs");
|
|
11
|
+
const path = await import("path");
|
|
12
|
+
|
|
13
|
+
// Resolve the path relative to the current working directory
|
|
14
|
+
const resolvedPath = path.resolve(params.path);
|
|
15
|
+
|
|
16
|
+
// Check if the path is within the current working directory to prevent path traversal
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
if (!resolvedPath.startsWith(cwd)) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: "Error: Path traversal is not allowed" }]
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: `Error: File does not exist: ${params.path}` }]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: "text", text: content }]
|
|
33
|
+
};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: `Error reading file: ${error.message}` }]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const name = "run_command";
|
|
4
|
+
export const description = "Execute a shell command on the system";
|
|
5
|
+
export const parameters = {
|
|
6
|
+
command: z.string().describe("The shell command to execute")
|
|
7
|
+
};
|
|
8
|
+
export const handler = async (params: { command: string }) => {
|
|
9
|
+
try {
|
|
10
|
+
// Dynamically import child_process
|
|
11
|
+
const { exec } = await import("child_process");
|
|
12
|
+
const util = await import("util");
|
|
13
|
+
|
|
14
|
+
// Use util.promisify to convert exec to a promise-based function
|
|
15
|
+
const execPromise = util.promisify(exec);
|
|
16
|
+
|
|
17
|
+
// Execute the command with a timeout for safety
|
|
18
|
+
const { stdout, stderr } = await Promise.race([
|
|
19
|
+
execPromise(params.command),
|
|
20
|
+
new Promise((_, reject) =>
|
|
21
|
+
setTimeout(() => reject(new Error('Command timed out after 10 seconds')), 10000)
|
|
22
|
+
)
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
// Return both stdout and stderr
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{ type: "text", text: `Command: ${params.command}\n\nOutput:\n${stdout || stderr || 'Command executed successfully with no output'}` }
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{ type: "text", text: `Error executing command: ${params.command}\n\nError: ${(error as Error).message}` }
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "Node16",
|
|
4
|
+
"moduleResolution": "Node16",
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"isolatedModules": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src"
|
|
9
|
+
},
|
|
10
|
+
"exclude": ["node_modules", "dist"],
|
|
11
|
+
"include": ["src/**/*", "package.json"]
|
|
12
|
+
}
|