Skip to main content
Glama
ricleedo

JSON MCP Boilerplate

by ricleedo

json_extract

Extract specific data from JSON files using paths, filters, patterns, or slices. Retrieve particular values, filter arrays/objects by conditions, search for patterns, or slice data for targeted extraction and analysis.

Instructions

Extract specific data using paths, filters, patterns, or slices from JSON files. Always use this tool when you need to retrieve particular values, filter arrays/objects by conditions, search for patterns, or slice data. Ideal for targeted data extraction, data transformation, and focused analysis of specific JSON elements.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
file_pathYesPath to the JSON file
pathNoDot notation path to target
filterNoJS condition to filter results (e.g., 'item.age > 18')
patternNoRegex pattern to search for
search_typeNoWhat to search when using pattern
startNoArray slice start index
endNoArray slice end index
keysNoSpecific object keys to extract
default_valueNoFallback if path not found

Implementation Reference

  • The async handler function implementing the core logic of the 'json_extract' tool. Loads JSON file, extracts by path using getValueByPath, applies filter using filterObject, performs regex pattern search recursively, slices arrays if indices provided, extracts specific keys from objects, truncates large results, formats as pretty JSON, handles errors.
    async ({
      file_path,
      path,
      filter,
      pattern,
      search_type,
      start,
      end,
      keys,
      default_value,
    }) => {
      try {
        const data = readJSONFile(file_path);
        let target = path ? getValueByPath(data, path) : data;
    
        // If path was specified but not found, return default value
        if (path && target === undefined) {
          const output = default_value !== undefined ? default_value : null;
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(
                  {
                    result: output,
                    message: `Path not found: ${path}, returning default value`,
                  },
                  null,
                  2
                ),
              },
            ],
          };
        }
    
        // Apply filter if specified
        if (filter) {
          target = filterObject(target, filter);
        }
    
        // Apply pattern search if specified
        if (pattern) {
          const results: any[] = [];
          const flags = "gi"; // Case-insensitive by default
          const regex = new RegExp(pattern, flags);
          const searchFor = search_type || "both";
    
          function searchObject(obj: any, currentPath: string = ""): void {
            if (Array.isArray(obj)) {
              obj.forEach((item, index) => {
                searchObject(item, `${currentPath}[${index}]`);
              });
            } else if (typeof obj === "object" && obj !== null) {
              Object.entries(obj).forEach(([key, value]) => {
                const newPath = currentPath ? `${currentPath}.${key}` : key;
    
                if (
                  (searchFor === "key" || searchFor === "both") &&
                  regex.test(key)
                ) {
                  results.push({ type: "key", path: newPath, key, value });
                }
    
                if (searchFor === "value" || searchFor === "both") {
                  if (value === null && pattern === "null") {
                    results.push({ type: "value", path: newPath, key, value });
                  } else if (typeof value === "string" && regex.test(value)) {
                    results.push({ type: "value", path: newPath, key, value });
                  } else if (
                    typeof value === "number" &&
                    regex.test(value.toString())
                  ) {
                    results.push({ type: "value", path: newPath, key, value });
                  } else if (
                    typeof value === "boolean" &&
                    regex.test(value.toString())
                  ) {
                    results.push({ type: "value", path: newPath, key, value });
                  }
                }
    
                searchObject(value, newPath);
              });
            }
          }
    
          searchObject(target);
          target = results;
        }
    
        // Apply slicing if specified
        if ((start !== undefined || end !== undefined) && Array.isArray(target)) {
          const sliceStart = start || 0;
          const sliceEnd = end || target.length;
          target = target.slice(sliceStart, sliceEnd);
        }
    
        // Extract specific keys if specified
        if (
          keys &&
          typeof target === "object" &&
          target !== null &&
          !Array.isArray(target)
        ) {
          const extracted: any = {};
          keys.forEach((key) => {
            if (key in target) {
              extracted[key] = target[key];
            }
          });
          target = extracted;
        }
    
        const truncatedTarget = truncateForOutput(target);
        let outputText = JSON.stringify(truncatedTarget, null, 2);
    
        // Replace quoted truncation messages with unquoted text for markdown-like output
        outputText = outputText.replace(
          /"\.\.\.(\d+) more items"/g,
          "...$1 more items"
        );
        outputText = outputText.replace(
          /"\.\.\.(\d+) more properties": "\.\.\.?"/g,
          "...$1 more properties"
        );
    
        return {
          content: [{ type: "text", text: outputText }],
        };
      } catch (error: any) {
        return {
          content: [{ type: "text", text: `Error: ${error.message}` }],
        };
      }
    }
  • Zod input schema defining parameters for json_extract: file_path (required), optional path, filter (JS condition), pattern (regex), search_type (key/value/both), start/end (array slice), keys (array of strings), default_value.
      file_path: z.string().describe("Path to the JSON file"),
      path: z.string().optional().describe("Dot notation path to target"),
      filter: z
        .string()
        .optional()
        .describe("JS condition to filter results (e.g., 'item.age > 18')"),
      pattern: z.string().optional().describe("Regex pattern to search for"),
      search_type: z
        .enum(["key", "value", "both"])
        .optional()
        .describe("What to search when using pattern"),
      start: z.number().optional().describe("Array slice start index"),
      end: z.number().optional().describe("Array slice end index"),
      keys: z
        .array(z.string())
        .optional()
        .describe("Specific object keys to extract"),
      default_value: z.any().optional().describe("Fallback if path not found"),
    },
  • src/index.ts:298-456 (registration)
    Registers the 'json_extract' tool on the MCP server using server.tool(), providing name, long description, input schema, and inline async handler function.
    server.tool(
      "json_extract",
      "Extract specific data using paths, filters, patterns, or slices from JSON files. Always use this tool when you need to retrieve particular values, filter arrays/objects by conditions, search for patterns, or slice data. Ideal for targeted data extraction, data transformation, and focused analysis of specific JSON elements.",
      {
        file_path: z.string().describe("Path to the JSON file"),
        path: z.string().optional().describe("Dot notation path to target"),
        filter: z
          .string()
          .optional()
          .describe("JS condition to filter results (e.g., 'item.age > 18')"),
        pattern: z.string().optional().describe("Regex pattern to search for"),
        search_type: z
          .enum(["key", "value", "both"])
          .optional()
          .describe("What to search when using pattern"),
        start: z.number().optional().describe("Array slice start index"),
        end: z.number().optional().describe("Array slice end index"),
        keys: z
          .array(z.string())
          .optional()
          .describe("Specific object keys to extract"),
        default_value: z.any().optional().describe("Fallback if path not found"),
      },
      async ({
        file_path,
        path,
        filter,
        pattern,
        search_type,
        start,
        end,
        keys,
        default_value,
      }) => {
        try {
          const data = readJSONFile(file_path);
          let target = path ? getValueByPath(data, path) : data;
    
          // If path was specified but not found, return default value
          if (path && target === undefined) {
            const output = default_value !== undefined ? default_value : null;
            return {
              content: [
                {
                  type: "text",
                  text: JSON.stringify(
                    {
                      result: output,
                      message: `Path not found: ${path}, returning default value`,
                    },
                    null,
                    2
                  ),
                },
              ],
            };
          }
    
          // Apply filter if specified
          if (filter) {
            target = filterObject(target, filter);
          }
    
          // Apply pattern search if specified
          if (pattern) {
            const results: any[] = [];
            const flags = "gi"; // Case-insensitive by default
            const regex = new RegExp(pattern, flags);
            const searchFor = search_type || "both";
    
            function searchObject(obj: any, currentPath: string = ""): void {
              if (Array.isArray(obj)) {
                obj.forEach((item, index) => {
                  searchObject(item, `${currentPath}[${index}]`);
                });
              } else if (typeof obj === "object" && obj !== null) {
                Object.entries(obj).forEach(([key, value]) => {
                  const newPath = currentPath ? `${currentPath}.${key}` : key;
    
                  if (
                    (searchFor === "key" || searchFor === "both") &&
                    regex.test(key)
                  ) {
                    results.push({ type: "key", path: newPath, key, value });
                  }
    
                  if (searchFor === "value" || searchFor === "both") {
                    if (value === null && pattern === "null") {
                      results.push({ type: "value", path: newPath, key, value });
                    } else if (typeof value === "string" && regex.test(value)) {
                      results.push({ type: "value", path: newPath, key, value });
                    } else if (
                      typeof value === "number" &&
                      regex.test(value.toString())
                    ) {
                      results.push({ type: "value", path: newPath, key, value });
                    } else if (
                      typeof value === "boolean" &&
                      regex.test(value.toString())
                    ) {
                      results.push({ type: "value", path: newPath, key, value });
                    }
                  }
    
                  searchObject(value, newPath);
                });
              }
            }
    
            searchObject(target);
            target = results;
          }
    
          // Apply slicing if specified
          if ((start !== undefined || end !== undefined) && Array.isArray(target)) {
            const sliceStart = start || 0;
            const sliceEnd = end || target.length;
            target = target.slice(sliceStart, sliceEnd);
          }
    
          // Extract specific keys if specified
          if (
            keys &&
            typeof target === "object" &&
            target !== null &&
            !Array.isArray(target)
          ) {
            const extracted: any = {};
            keys.forEach((key) => {
              if (key in target) {
                extracted[key] = target[key];
              }
            });
            target = extracted;
          }
    
          const truncatedTarget = truncateForOutput(target);
          let outputText = JSON.stringify(truncatedTarget, null, 2);
    
          // Replace quoted truncation messages with unquoted text for markdown-like output
          outputText = outputText.replace(
            /"\.\.\.(\d+) more items"/g,
            "...$1 more items"
          );
          outputText = outputText.replace(
            /"\.\.\.(\d+) more properties": "\.\.\.?"/g,
            "...$1 more properties"
          );
    
          return {
            content: [{ type: "text", text: outputText }],
          };
        } catch (error: any) {
          return {
            content: [{ type: "text", text: `Error: ${error.message}` }],
          };
        }
      }
    );
  • getValueByPath: Recursive dot-notation path navigator used to extract target data from JSON object.
    function getValueByPath(obj: any, path: string): any {
      return path.split(".").reduce((current, key) => {
        if (current === null || current === undefined) return undefined;
        if (Array.isArray(current) && !isNaN(Number(key))) {
          return current[Number(key)];
        }
        return current[key];
      }, obj);
    }
  • filterObject: Applies user-provided JavaScript filter condition to arrays (item, index) or objects (value, key, index).
    function filterObject(obj: any, condition: string): any {
      try {
        // For arrays: item, index are available
        // For objects: value, key, index are available
        const conditionFn = new Function(
          "item",
          "key",
          "index",
          "value",
          `return ${condition}`
        );
    
        if (Array.isArray(obj)) {
          return obj.filter((item, index) =>
            conditionFn(item, undefined, index, item)
          );
        } else if (typeof obj === "object" && obj !== null) {
          const result: any = {};
          Object.entries(obj).forEach(([key, value], index) => {
            if (conditionFn(value, key, index, value)) {
              result[key] = value;
            }
          });
          return result;
        }
        return obj;
      } catch (error: any) {
        throw new Error(`Invalid filter condition: ${error.message}`);
      }
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It describes what the tool does (extract data using various methods) and mentions use cases (data transformation, focused analysis), but doesn't address important behavioral aspects like error handling, performance characteristics, memory usage with large files, or what happens when multiple extraction methods are combined. It provides basic operational context but lacks depth on behavioral traits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized with three sentences that each serve distinct purposes: stating the core functionality, providing usage guidelines, and describing ideal use cases. It's front-loaded with the main purpose and avoids redundancy. While efficient, it could be slightly more structured with clearer separation between mandatory and optional parameter usage.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 9 parameters, no annotations, and no output schema, the description provides adequate but incomplete context. It covers the 'what' and 'when' well but lacks details on 'how' the extraction works, error conditions, return formats, or performance considerations. The description compensates somewhat for the lack of annotations by specifying use cases, but doesn't fully address the complexity of a multi-parameter extraction tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 9 parameters thoroughly. The description mentions the extraction methods (paths, filters, patterns, slices) which correspond to parameters, but doesn't add meaningful semantic context beyond what's in the schema descriptions. It doesn't explain how parameters interact or provide usage examples. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose as extracting specific data from JSON files using multiple methods (paths, filters, patterns, slices). It distinguishes from the sibling 'json_read' by emphasizing targeted extraction rather than general reading. The verb 'extract' with the resource 'JSON files' is specific and actionable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool: 'Always use this tool when you need to retrieve particular values, filter arrays/objects by conditions, search for patterns, or slice data.' It also distinguishes from the sibling 'json_read' by specifying this is for 'targeted data extraction' rather than general reading. The 'Ideal for' section further clarifies appropriate contexts.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ricleedo/JSON-MCP-Boilerplate'

If you have feedback or need assistance with the MCP directory API, please join our Discord server