mcp-multitool 0.1.9 → 0.1.11

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
@@ -10,7 +10,9 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server with **
10
10
 
11
11
  | Tool | Description |
12
12
  | ----------------- | ------------------------------------------------------- |
13
+ | `astGrepSearch` | Search code using AST patterns |
13
14
  | `checkFileOrDir` | Check if a file or directory exists and return metadata |
15
+ | `cloneFileOrDir` | Copy one or more files or directories to a destination |
14
16
  | `deleteFileOrDir` | Delete one or more files or directories |
15
17
  | `moveFileOrDir` | Move one or more files or directories to a new location |
16
18
  | `readLogFile` | Read and compress logs with 60-90% token reduction |
@@ -61,6 +63,28 @@ npx mcp-multitool
61
63
 
62
64
  ## Tool Reference
63
65
 
66
+ ### `astGrepSearch`
67
+
68
+ Search code using AST patterns. Matches code structure, not text. Use `$VAR` for single-node wildcards (e.g., `console.log($ARG)`), `$$$VAR` for multiple nodes. More precise than regex for code search.
69
+
70
+ | Parameter | Type | Required | Description |
71
+ | --------- | -------------------- | -------- | -------------------------------------------------------------------------------- |
72
+ | `pattern` | `string` | ✅ | AST pattern to match. Use `$VAR` for metavariables, `$$$VAR` for multiple nodes. |
73
+ | `paths` | `string \| string[]` | ✅ | File or directory path(s) to search. Directories are searched recursively. |
74
+ | `lang` | `string` | ✅ | Language to parse. Built-in: `javascript`, `typescript`, `tsx`, `html`, `css`. |
75
+
76
+ **Response:** JSON object with `results` array (each with `file`, `range`, `text`) and `errors` array.
77
+
78
+ **Examples:**
79
+
80
+ ```
81
+ astGrepSearch pattern="console.log($ARG)" paths="src" lang="typescript"
82
+ astGrepSearch pattern="function $NAME($$$PARAMS) { $$$BODY }" paths=["lib", "src"] lang="javascript"
83
+ astGrepSearch pattern="<div $$$ATTRS>$$$CHILDREN</div>" paths="components" lang="tsx"
84
+ ```
85
+
86
+ ---
87
+
64
88
  ### `checkFileOrDir`
65
89
 
66
90
  Check if a file or directory exists and return its metadata (type, size, timestamps, permissions). Returns an error if the path does not exist.
@@ -81,6 +105,28 @@ checkFileOrDir path="./src/index.ts"
81
105
 
82
106
  ---
83
107
 
108
+ ### `cloneFileOrDir`
109
+
110
+ Copy one or more files or directories to a destination directory.
111
+
112
+ | Parameter | Type | Required | Description |
113
+ | ----------- | -------------------- | -------- | ---------------------------------- |
114
+ | `from` | `string \| string[]` | ✅ | Source path(s) to clone. |
115
+ | `to` | `string` | ✅ | Destination directory. |
116
+ | `overwrite` | `boolean` | ✅ | If true, overwrite existing files. |
117
+
118
+ **Response:** JSON array of `{source, destination}` objects showing each cloned path.
119
+
120
+ **Examples:**
121
+
122
+ ```
123
+ cloneFileOrDir from="config.json" to="backup/" overwrite=false
124
+ cloneFileOrDir from=["a.txt", "b.txt"] to="copies/" overwrite=false
125
+ cloneFileOrDir from="src/" to="archive/" overwrite=true
126
+ ```
127
+
128
+ ---
129
+
84
130
  ### `deleteFileOrDir`
85
131
 
86
132
  Delete one or more files or directories.
@@ -215,7 +261,9 @@ wait durationSeconds=1 reason="animation to complete"
215
261
  | ------------------------ | ------- | -------------------------------------------------------------------------------------------------------------- |
216
262
  | `waitMaxDurationSeconds` | `300` | Override the maximum allowed `durationSeconds`. Must be a positive number. Server refuses to start if invalid. |
217
263
  | `readLogFileTimeoutMs` | `5000` | Override the timeout for `readLogFile` processing in milliseconds. Server refuses to start if invalid. |
264
+ | `astGrepSearch` | _(on)_ | Set to `"false"` to disable the `astGrepSearch` tool at startup. |
218
265
  | `checkFileOrDir` | _(on)_ | Set to `"false"` to disable the `checkFileOrDir` tool at startup. |
266
+ | `cloneFileOrDir` | _(on)_ | Set to `"false"` to disable the `cloneFileOrDir` tool at startup. |
219
267
  | `deleteFileOrDir` | _(on)_ | Set to `"false"` to disable the `deleteFileOrDir` tool at startup. |
220
268
  | `moveFileOrDir` | _(on)_ | Set to `"false"` to disable the `moveFileOrDir` tool at startup. |
221
269
  | `readLogFile` | _(on)_ | Set to `"false"` to disable the `readLogFile` tool at startup. |
package/dist/index.js CHANGED
@@ -2,7 +2,9 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { register as registerAstGrepSearch } from "./tools/astGrepSearch.js";
5
6
  import { register as registerCheckFileOrDir } from "./tools/checkFileOrDir.js";
7
+ import { register as registerCloneFileOrDir } from "./tools/cloneFileOrDir.js";
6
8
  import { register as registerDeleteFileOrDir } from "./tools/deleteFileOrDir.js";
7
9
  import { register as registerMoveFileOrDir } from "./tools/moveFileOrDir.js";
8
10
  import { register as registerReadLogFile } from "./tools/readLogFile.js";
@@ -12,8 +14,12 @@ const require = createRequire(import.meta.url);
12
14
  const { version } = require("../package.json");
13
15
  const isEnabled = (name) => process.env[name] !== "false";
14
16
  const server = new McpServer({ name: "mcp-multitool", version });
17
+ if (isEnabled("astGrepSearch"))
18
+ registerAstGrepSearch(server);
15
19
  if (isEnabled("checkFileOrDir"))
16
20
  registerCheckFileOrDir(server);
21
+ if (isEnabled("cloneFileOrDir"))
22
+ registerCloneFileOrDir(server);
17
23
  if (isEnabled("deleteFileOrDir"))
18
24
  registerDeleteFileOrDir(server);
19
25
  if (isEnabled("moveFileOrDir"))
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
@@ -0,0 +1,76 @@
1
+ import { z } from "zod";
2
+ import { findInFiles } from "@ast-grep/napi";
3
+ const builtinLangs = [
4
+ "javascript",
5
+ "typescript",
6
+ "tsx",
7
+ "html",
8
+ "css",
9
+ ];
10
+ const langMap = {
11
+ javascript: "JavaScript",
12
+ typescript: "TypeScript",
13
+ tsx: "Tsx",
14
+ html: "Html",
15
+ css: "Css",
16
+ };
17
+ const schema = z.object({
18
+ pattern: z
19
+ .string()
20
+ .min(1)
21
+ .describe("AST pattern to match. Use $VAR for single-node metavariables, $$$VAR for multiple nodes. Example: 'console.log($ARG)' matches any console.log call."),
22
+ paths: z
23
+ .union([z.string(), z.array(z.string())])
24
+ .describe("File or directory path(s) to search. Directories are searched recursively."),
25
+ lang: z
26
+ .enum(builtinLangs)
27
+ .describe("Language to parse. Built-in: javascript, typescript, tsx, html, css"),
28
+ });
29
+ export function register(server) {
30
+ server.registerTool("astGrepSearch", {
31
+ description: "Search code using AST patterns. Matches code structure, not text. " +
32
+ "Use $VAR for single-node wildcards (e.g., 'console.log($ARG)'), $$$VAR for multiple nodes. " +
33
+ "More precise than regex for code search. " +
34
+ "Languages: javascript, typescript, tsx, html, css.",
35
+ inputSchema: schema,
36
+ annotations: { readOnlyHint: true, openWorldHint: false },
37
+ }, async (input) => {
38
+ try {
39
+ const lang = langMap[input.lang];
40
+ const paths = Array.isArray(input.paths) ? input.paths : [input.paths];
41
+ const results = [];
42
+ const errors = [];
43
+ await findInFiles(lang, {
44
+ paths,
45
+ matcher: { rule: { pattern: input.pattern } },
46
+ }, (err, nodes) => {
47
+ if (err) {
48
+ errors.push(err);
49
+ return;
50
+ }
51
+ const file = nodes[0]?.getRoot().filename() ?? "";
52
+ for (const node of nodes) {
53
+ results.push({
54
+ file,
55
+ range: node.range(),
56
+ text: node.text(),
57
+ });
58
+ }
59
+ });
60
+ return {
61
+ content: [
62
+ {
63
+ type: "text",
64
+ text: JSON.stringify({ results, errors: errors.map(String) }, null, 2),
65
+ },
66
+ ],
67
+ };
68
+ }
69
+ catch (err) {
70
+ return {
71
+ isError: true,
72
+ content: [{ type: "text", text: String(err) }],
73
+ };
74
+ }
75
+ });
76
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
@@ -0,0 +1,55 @@
1
+ import { access, cp } from "node:fs/promises";
2
+ import { basename, join, resolve } from "node:path";
3
+ import { z } from "zod";
4
+ const schema = z.object({
5
+ from: z
6
+ .union([z.string(), z.array(z.string())])
7
+ .describe("Source path(s) to clone."),
8
+ to: z.string().describe("Destination directory."),
9
+ overwrite: z.boolean().describe("If true, overwrite existing files."),
10
+ });
11
+ async function exists(path) {
12
+ try {
13
+ await access(path);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ export function register(server) {
21
+ server.registerTool("cloneFileOrDir", {
22
+ description: "Copy one or more files or directories to a destination directory.",
23
+ inputSchema: schema,
24
+ annotations: {
25
+ destructiveHint: true,
26
+ openWorldHint: false,
27
+ },
28
+ }, async (input) => {
29
+ try {
30
+ const sources = Array.isArray(input.from) ? input.from : [input.from];
31
+ const results = [];
32
+ for (const src of sources) {
33
+ const srcPath = resolve(process.cwd(), src);
34
+ const destPath = join(resolve(process.cwd(), input.to), basename(src));
35
+ if (!input.overwrite && (await exists(destPath))) {
36
+ throw new Error(`Destination exists: ${destPath}. Set overwrite=true to replace.`);
37
+ }
38
+ await cp(srcPath, destPath, {
39
+ recursive: true,
40
+ force: input.overwrite,
41
+ });
42
+ results.push({ source: srcPath, destination: destPath });
43
+ }
44
+ return {
45
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
46
+ };
47
+ }
48
+ catch (err) {
49
+ return {
50
+ isError: true,
51
+ content: [{ type: "text", text: String(err) }],
52
+ };
53
+ }
54
+ });
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-multitool",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "MCP server with file operations (delete, move) and timing utilities.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,6 +29,7 @@
29
29
  "patch": "npm version patch"
30
30
  },
31
31
  "dependencies": {
32
+ "@ast-grep/napi": "^0.42.1",
32
33
  "@modelcontextprotocol/sdk": "1.29.0",
33
34
  "logpare": "^0.1.0",
34
35
  "zod": "^3.24.0"