json-explorer-mcp 1.0.4 → 1.0.6

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/dist/index.js CHANGED
@@ -3,11 +3,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
5
  import { jsonInspect, jsonSchema, jsonValidate } from "./tools/structure.js";
6
- import { jsonKeys, jsonGet, jsonSet } from "./tools/navigation.js";
6
+ import { jsonKeys, jsonGet, jsonSet, jsonFormat } from "./tools/navigation.js";
7
7
  import { jsonSearch, jsonSample, jsonStats } from "./tools/query.js";
8
8
  const server = new McpServer({
9
9
  name: "json-explorer",
10
- version: "1.0.4",
10
+ version: "1.0.6",
11
11
  });
12
12
  // Tool: json_inspect - Get file overview
13
13
  server.tool("json_inspect", "Get an overview of a JSON file including size, structure type, and a depth-limited preview. Use this first to understand what you're working with.", {
@@ -103,13 +103,49 @@ server.tool("json_set", "Set a value at a specific JSONPath. Supports schema val
103
103
  inferSchema: z.boolean().optional().describe("If true, infers schema from existing value at path and validates against it. Defaults to false."),
104
104
  dryRun: z.boolean().optional().describe("If true, validates and returns result without writing to file. Defaults to false."),
105
105
  outputFile: z.string().optional().describe("Output file path. If not provided, writes to the source file."),
106
- }, { readOnlyHint: false }, async ({ file, path, value, schema, inferSchema, dryRun, outputFile }) => {
106
+ strict: z.boolean().optional().describe("Enable AJV strict mode. Set to false for schemas with custom keywords. Defaults to true."),
107
+ }, { readOnlyHint: false }, async ({ file, path, value, schema, inferSchema, dryRun, outputFile, strict }) => {
107
108
  try {
108
109
  const result = await jsonSet(file, path, value, {
109
110
  schema,
110
111
  inferSchema,
111
112
  dryRun,
112
113
  outputFile,
114
+ strict,
115
+ });
116
+ return {
117
+ content: [
118
+ {
119
+ type: "text",
120
+ text: JSON.stringify(result, null, 2),
121
+ },
122
+ ],
123
+ };
124
+ }
125
+ catch (error) {
126
+ return {
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
131
+ },
132
+ ],
133
+ isError: true,
134
+ };
135
+ }
136
+ });
137
+ // Tool: json_format - Reformat JSON file
138
+ server.tool("json_format", "Reformat a JSON file with configurable indentation and optional key sorting. Useful for normalizing JSON formatting or preparing files for version control.", {
139
+ file: z.string().describe("Absolute path to the JSON file to format"),
140
+ indent: z.number().optional().describe("Number of spaces for indentation. Use 0 for compact single-line output. Defaults to 2."),
141
+ sortKeys: z.boolean().optional().describe("Sort object keys alphabetically (recursive). Defaults to false."),
142
+ outputFile: z.string().optional().describe("Output file path. If not provided, overwrites the source file."),
143
+ }, { readOnlyHint: false }, async ({ file, indent, sortKeys, outputFile }) => {
144
+ try {
145
+ const result = await jsonFormat(file, {
146
+ indent,
147
+ sortKeys,
148
+ outputFile,
113
149
  });
114
150
  return {
115
151
  content: [
@@ -171,7 +207,8 @@ server.tool("json_validate", "Validate JSON data against a JSON Schema. Schema c
171
207
  schemas: z.record(z.union([z.string(), z.object({}).passthrough()])).optional().describe("Pre-register schemas by URI. Map of URI to schema file path or inline schema object."),
172
208
  schemaDir: z.string().optional().describe("Directory containing schema files. All .json files will be loaded and registered by their $id."),
173
209
  schemaId: z.string().optional().describe("When using schemaDir, the $id of the schema to use as entrypoint."),
174
- }, { readOnlyHint: true }, async ({ file, schema, path, errorLimit, resolveLocalRefs, resolveNetworkRefs, schemas, schemaDir, schemaId }) => {
210
+ strict: z.boolean().optional().describe("Enable AJV strict mode. Set to false for schemas with custom keywords. Defaults to true."),
211
+ }, { readOnlyHint: true }, async ({ file, schema, path, errorLimit, resolveLocalRefs, resolveNetworkRefs, schemas, schemaDir, schemaId, strict }) => {
175
212
  try {
176
213
  const result = await jsonValidate(file, schema ?? {}, path, {
177
214
  errorLimit,
@@ -180,6 +217,7 @@ server.tool("json_validate", "Validate JSON data against a JSON Schema. Schema c
180
217
  schemas,
181
218
  schemaDir,
182
219
  schemaId,
220
+ strict,
183
221
  });
184
222
  return {
185
223
  content: [
@@ -28,6 +28,8 @@ export interface SetOptions {
28
28
  dryRun?: boolean;
29
29
  /** Output file path. If not provided, writes to the source file. */
30
30
  outputFile?: string;
31
+ /** Enable AJV strict mode. Set to false for schemas with custom keywords. Defaults to true. */
32
+ strict?: boolean;
31
33
  }
32
34
  export interface SetResult {
33
35
  success: boolean;
@@ -42,3 +44,19 @@ export interface SetResult {
42
44
  dryRun: boolean;
43
45
  }
44
46
  export declare function jsonSet(filePath: string, path: string, value: unknown, options?: SetOptions): Promise<SetResult>;
47
+ export interface FormatOptions {
48
+ /** Number of spaces for indentation. Use 0 for compact output. Defaults to 2. */
49
+ indent?: number;
50
+ /** Sort object keys alphabetically. Defaults to false. */
51
+ sortKeys?: boolean;
52
+ /** Output file path. If not provided, writes to the source file. */
53
+ outputFile?: string;
54
+ }
55
+ export interface FormatResult {
56
+ success: boolean;
57
+ inputFile: string;
58
+ outputFile: string;
59
+ originalSize: number;
60
+ formattedSize: number;
61
+ }
62
+ export declare function jsonFormat(filePath: string, options?: FormatOptions): Promise<FormatResult>;
@@ -131,7 +131,7 @@ function inferSimpleSchema(value) {
131
131
  return { type: typeof value };
132
132
  }
133
133
  export async function jsonSet(filePath, path, value, options = {}) {
134
- const { schema, inferSchema: shouldInferSchema = false, dryRun = false, outputFile, } = options;
134
+ const { schema, inferSchema: shouldInferSchema = false, dryRun = false, outputFile, strict = true, } = options;
135
135
  // Load the JSON file
136
136
  const data = await loadJson(filePath);
137
137
  // Deep clone to avoid mutating cached data
@@ -156,7 +156,7 @@ export async function jsonSet(filePath, path, value, options = {}) {
156
156
  // Validate the new value against schema
157
157
  if (schemaToValidate) {
158
158
  const { Ajv, addFormats } = await getAjv();
159
- const ajv = new Ajv({ allErrors: true });
159
+ const ajv = new Ajv({ allErrors: true, strict });
160
160
  addFormats(ajv);
161
161
  const validate = ajv.compile(schemaToValidate);
162
162
  const valid = validate(value);
@@ -200,3 +200,45 @@ export async function jsonSet(filePath, path, value, options = {}) {
200
200
  dryRun: false,
201
201
  };
202
202
  }
203
+ // Recursively sort object keys
204
+ function sortObjectKeys(obj) {
205
+ if (obj === null || typeof obj !== "object") {
206
+ return obj;
207
+ }
208
+ if (Array.isArray(obj)) {
209
+ return obj.map(sortObjectKeys);
210
+ }
211
+ const sorted = {};
212
+ const keys = Object.keys(obj).sort();
213
+ for (const key of keys) {
214
+ sorted[key] = sortObjectKeys(obj[key]);
215
+ }
216
+ return sorted;
217
+ }
218
+ export async function jsonFormat(filePath, options = {}) {
219
+ const { indent = 2, sortKeys = false, outputFile, } = options;
220
+ // Load the JSON file
221
+ const data = await loadJson(filePath);
222
+ // Get original size
223
+ const originalContent = JSON.stringify(data);
224
+ const originalSize = originalContent.length;
225
+ // Sort keys if requested
226
+ const dataToFormat = sortKeys ? sortObjectKeys(data) : data;
227
+ // Format the JSON
228
+ const formattedContent = indent === 0
229
+ ? JSON.stringify(dataToFormat)
230
+ : JSON.stringify(dataToFormat, null, indent);
231
+ // Add trailing newline
232
+ const outputContent = formattedContent + "\n";
233
+ const formattedSize = formattedContent.length;
234
+ // Write to file
235
+ const targetFile = outputFile || filePath;
236
+ await writeFile(targetFile, outputContent, "utf-8");
237
+ return {
238
+ success: true,
239
+ inputFile: filePath,
240
+ outputFile: targetFile,
241
+ originalSize,
242
+ formattedSize,
243
+ };
244
+ }
@@ -41,6 +41,8 @@ export interface ValidateOptions {
41
41
  schemaDir?: string;
42
42
  /** When using schemaDir, specify the $id of the schema to use as entrypoint (instead of schema param). */
43
43
  schemaId?: string;
44
+ /** Disable AJV strict mode for schemas with custom keywords. Defaults to true. */
45
+ strict?: boolean;
44
46
  }
45
47
  export declare function jsonValidate(filePath: string, schema: object | string, path?: string, options?: ValidateOptions | number): Promise<ValidateResult>;
46
48
  export {};
@@ -267,7 +267,7 @@ export async function jsonValidate(filePath, schema, path, options = {}) {
267
267
  : options;
268
268
  // Environment variable can completely disable network refs for security
269
269
  const networkRefsDisabled = process.env.JSON_EXPLORER_NO_NETWORK === "1";
270
- const { errorLimit = 10, resolveLocalRefs = true, resolveNetworkRefs = false, schemas = {}, schemaDir, schemaId, } = opts;
270
+ const { errorLimit = 10, resolveLocalRefs = true, resolveNetworkRefs = false, schemas = {}, schemaDir, schemaId, strict = true, } = opts;
271
271
  const effectiveResolveNetworkRefs = resolveNetworkRefs && !networkRefsDisabled;
272
272
  const data = await loadJson(filePath);
273
273
  const targetData = path ? getValueAtPath(data, path) : data;
@@ -301,7 +301,7 @@ export async function jsonValidate(filePath, schema, path, options = {}) {
301
301
  const { Ajv, addFormats } = await getAjv();
302
302
  // Configure ajv options
303
303
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
304
- const ajvOptions = { allErrors: true };
304
+ const ajvOptions = { allErrors: true, strict };
305
305
  // Set up network schema loading if enabled
306
306
  if (effectiveResolveNetworkRefs) {
307
307
  ajvOptions.loadSchema = async (uri) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-explorer-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "MCP server for efficiently exploring large JSON files",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",