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 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
+ };
@@ -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();
@@ -0,0 +1,5 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ declare function loadTools(server: McpServer): Promise<void>;
4
+
5
+ export { loadTools };
@@ -0,0 +1,6 @@
1
+ import {
2
+ loadTools
3
+ } from "./chunk-T6KZEVGN.js";
4
+ export {
5
+ loadTools
6
+ };
package/gitignore ADDED
@@ -0,0 +1,7 @@
1
+ dist/
2
+ node_modules/
3
+
4
+ # System
5
+ .DS_Store
6
+ .idea/
7
+ .vscode/
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ // kadins-personal-mcp.js - Entry point for Kadins Personal MCP Server
4
+
5
+ import './dist/index.js';
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
+ }