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 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
- - `calculator`: Perform basic mathematical calculations
50
- - `get_current_time`: Get the current date and time
51
- - `file_echo`: Echo messages back
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
- 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):
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": ["/path/to/kadins-personal-mcp/dist/index.js"]
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 `/path/to/kadins-personal-mcp/dist/index.js` with the actual path to the built index.js file in your installation.
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 toolsDir = path.join(process.cwd(), "tools");
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-T6KZEVGN.js";
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);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  loadTools
3
- } from "./chunk-T6KZEVGN.js";
3
+ } from "./chunk-IFFK3HCL.js";
4
4
  export {
5
5
  loadTools
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kadins-personal-mcp",
3
- "version": "1.0.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": [".ts"],
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
+ };