mcp-multitool 0.1.8 → 0.1.10

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,6 +10,7 @@ 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 |
14
15
  | `deleteFileOrDir` | Delete one or more files or directories |
15
16
  | `moveFileOrDir` | Move one or more files or directories to a new location |
@@ -61,6 +62,28 @@ npx mcp-multitool
61
62
 
62
63
  ## Tool Reference
63
64
 
65
+ ### `astGrepSearch`
66
+
67
+ 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.
68
+
69
+ | Parameter | Type | Required | Description |
70
+ | --------- | -------------------- | -------- | -------------------------------------------------------------------------------- |
71
+ | `pattern` | `string` | ✅ | AST pattern to match. Use `$VAR` for metavariables, `$$$VAR` for multiple nodes. |
72
+ | `paths` | `string \| string[]` | ✅ | File or directory path(s) to search. Directories are searched recursively. |
73
+ | `lang` | `string` | ✅ | Language to parse. Built-in: `javascript`, `typescript`, `tsx`, `html`, `css`. |
74
+
75
+ **Response:** JSON object with `results` array (each with `file`, `range`, `text`) and `errors` array.
76
+
77
+ **Examples:**
78
+
79
+ ```
80
+ astGrepSearch pattern="console.log($ARG)" paths="src" lang="typescript"
81
+ astGrepSearch pattern="function $NAME($$$PARAMS) { $$$BODY }" paths=["lib", "src"] lang="javascript"
82
+ astGrepSearch pattern="<div $$$ATTRS>$$$CHILDREN</div>" paths="components" lang="tsx"
83
+ ```
84
+
85
+ ---
86
+
64
87
  ### `checkFileOrDir`
65
88
 
66
89
  Check if a file or directory exists and return its metadata (type, size, timestamps, permissions). Returns an error if the path does not exist.
@@ -215,6 +238,7 @@ wait durationSeconds=1 reason="animation to complete"
215
238
  | ------------------------ | ------- | -------------------------------------------------------------------------------------------------------------- |
216
239
  | `waitMaxDurationSeconds` | `300` | Override the maximum allowed `durationSeconds`. Must be a positive number. Server refuses to start if invalid. |
217
240
  | `readLogFileTimeoutMs` | `5000` | Override the timeout for `readLogFile` processing in milliseconds. Server refuses to start if invalid. |
241
+ | `astGrepSearch` | _(on)_ | Set to `"false"` to disable the `astGrepSearch` tool at startup. |
218
242
  | `checkFileOrDir` | _(on)_ | Set to `"false"` to disable the `checkFileOrDir` tool at startup. |
219
243
  | `deleteFileOrDir` | _(on)_ | Set to `"false"` to disable the `deleteFileOrDir` tool at startup. |
220
244
  | `moveFileOrDir` | _(on)_ | Set to `"false"` to disable the `moveFileOrDir` tool at startup. |
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
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";
6
7
  import { register as registerDeleteFileOrDir } from "./tools/deleteFileOrDir.js";
7
8
  import { register as registerMoveFileOrDir } from "./tools/moveFileOrDir.js";
@@ -12,6 +13,8 @@ const require = createRequire(import.meta.url);
12
13
  const { version } = require("../package.json");
13
14
  const isEnabled = (name) => process.env[name] !== "false";
14
15
  const server = new McpServer({ name: "mcp-multitool", version });
16
+ if (isEnabled("astGrepSearch"))
17
+ registerAstGrepSearch(server);
15
18
  if (isEnabled("checkFileOrDir"))
16
19
  registerCheckFileOrDir(server);
17
20
  if (isEnabled("deleteFileOrDir"))
@@ -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
+ }
@@ -117,7 +117,7 @@ function registerFlush(server) {
117
117
  flushTool = server.registerTool("flushLogFile", {
118
118
  description: "Release a log drain to free memory. Next readLogFile creates fresh drain.",
119
119
  inputSchema: flushSchema,
120
- annotations: { destructiveHint: true, idempotentHint: true },
120
+ annotations: { destructiveHint: false, idempotentHint: true },
121
121
  }, async (input) => {
122
122
  try {
123
123
  const path = resolve(process.cwd(), input.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-multitool",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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"