diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8feea52..f0e28c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
- Added `CallResult.images()` plus opt-in `mcporter call --save-images
` so image content blocks can be persisted without changing existing stdout output contracts. (PR #61, thanks @daniella-11ways)
- OAuth transport retries now classify HTTP 405 as HTTP (not auth) and OAuth promotion applies to configured HTTP servers too, so post-auth fallback flows no longer drop credentials on 405-only endpoints. (PR #48, thanks @caseyg)
- Config loading now parses project and explicit config files as JSONC, so `mcporter.json` / `mcporter.jsonc` can include comments and trailing commas. (PR #42, thanks @aryasaatvik)
+- Added generated `mcporter.schema.json` plus `pnpm generate:schema` for IDE autocomplete/validation, including `$schema` and `oauthScope`/`oauth_scope` coverage. (PR #43, thanks @aryasaatvik)
### Tooling / Dependencies
- Updated dependencies to latest releases (including MCP SDK, Rolldown RC, Zod, Biome, Oxlint, Vitest, Bun types).
diff --git a/mcporter.schema.json b/mcporter.schema.json
index de40155..2b02d78 100644
--- a/mcporter.schema.json
+++ b/mcporter.schema.json
@@ -1,17 +1,13 @@
{
- "$id": "https://raw.githubusercontent.com/steipete/mcporter/main/mcporter.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
- "description": "mcporter configuration file schema",
"type": "object",
"properties": {
"mcpServers": {
- "description": "Map of server names to their configurations",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
- "description": "MCP server definition supporting both HTTP/SSE and stdio transports",
"type": "object",
"properties": {
"description": {
@@ -111,16 +107,24 @@
"description": "Custom OAuth redirect URL (snake_case)",
"type": "string"
},
+ "oauthScope": {
+ "description": "OAuth scope override (camelCase)",
+ "type": "string"
+ },
+ "oauth_scope": {
+ "description": "OAuth scope override (snake_case)",
+ "type": "string"
+ },
"oauthCommand": {
"description": "Custom OAuth command configuration for stdio servers (camelCase)",
"type": "object",
"properties": {
"args": {
- "description": "Arguments for the OAuth command",
"type": "array",
"items": {
"type": "string"
- }
+ },
+ "description": "Arguments for the OAuth command"
}
},
"required": [
@@ -133,11 +137,11 @@
"type": "object",
"properties": {
"args": {
- "description": "Arguments for the OAuth command",
"type": "array",
"items": {
"type": "string"
- }
+ },
+ "description": "Arguments for the OAuth command"
}
},
"required": [
@@ -162,23 +166,21 @@
"type": "string"
},
"lifecycle": {
- "description": "Server connection lifecycle: keep-alive maintains persistent connections, ephemeral connects on-demand",
"anyOf": [
{
- "description": "Keep the server connection alive",
"type": "string",
- "const": "keep-alive"
+ "const": "keep-alive",
+ "description": "Keep the server connection alive"
},
{
- "description": "Connect only when needed",
"type": "string",
- "const": "ephemeral"
+ "const": "ephemeral",
+ "description": "Connect only when needed"
},
{
"type": "object",
"properties": {
"mode": {
- "description": "Connection lifecycle mode",
"anyOf": [
{
"type": "string",
@@ -188,7 +190,8 @@
"type": "string",
"const": "ephemeral"
}
- ]
+ ],
+ "description": "Connection lifecycle mode"
},
"idleTimeoutMs": {
"description": "Idle timeout in milliseconds before disconnecting",
@@ -202,7 +205,8 @@
],
"additionalProperties": false
}
- ]
+ ],
+ "description": "Server connection lifecycle: keep-alive maintains persistent connections, ephemeral connects on-demand"
},
"logging": {
"description": "Logging configuration for the server",
@@ -223,14 +227,15 @@
"additionalProperties": false
}
},
- "additionalProperties": false
- }
+ "additionalProperties": false,
+ "description": "MCP server definition supporting both HTTP/SSE and stdio transports"
+ },
+ "description": "Map of server names to their configurations"
},
"imports": {
"description": "Editor configurations to import servers from. Omit to use defaults, or set to [] to disable imports",
"type": "array",
"items": {
- "description": "Supported editor/client configurations to import MCP servers from",
"type": "string",
"enum": [
"cursor",
@@ -240,7 +245,8 @@
"windsurf",
"opencode",
"vscode"
- ]
+ ],
+ "description": "Supported editor/client configurations to import MCP servers from"
}
},
"$schema": {
@@ -251,5 +257,7 @@
"required": [
"mcpServers"
],
- "additionalProperties": false
+ "additionalProperties": false,
+ "description": "mcporter configuration file schema",
+ "$id": "https://raw.githubusercontent.com/steipete/mcporter/main/mcporter.schema.json"
}
diff --git a/scripts/generate-json-schema.ts b/scripts/generate-json-schema.ts
index f0bf319..b4b6703 100644
--- a/scripts/generate-json-schema.ts
+++ b/scripts/generate-json-schema.ts
@@ -1,37 +1,50 @@
#!/usr/bin/env tsx
+
/**
* Generate JSON Schema from Zod schemas using Zod v4's native toJSONSchema() method.
* Run with: pnpm generate:schema
*/
import { writeFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
import { RawConfigSchema } from '../src/config-schema.js';
-const jsonSchema = RawConfigSchema.toJSONSchema({
- override(ctx) {
- const schema = ctx.jsonSchema;
- // Disallow additional properties on objects for stricter validation
- if (schema?.type === 'object' && schema.additionalProperties === undefined) {
- schema.additionalProperties = false;
- }
- },
-});
+export const CONFIG_SCHEMA_ID = 'https://raw.githubusercontent.com/steipete/mcporter/main/mcporter.schema.json';
+export const CONFIG_SCHEMA_DRAFT = 'https://json-schema.org/draft/2020-12/schema';
-// Allow $schema property in config files for IDE support
-if (jsonSchema.properties && typeof jsonSchema.properties === 'object') {
- (jsonSchema.properties as Record).$schema = {
- type: 'string',
- description: 'JSON Schema URL for IDE validation and autocomplete',
+export function buildConfigJsonSchema(): Record {
+ const jsonSchema = RawConfigSchema.toJSONSchema({
+ override(ctx) {
+ const schema = ctx.jsonSchema;
+ // Disallow additional properties on objects for stricter validation
+ if (schema?.type === 'object' && schema.additionalProperties === undefined) {
+ schema.additionalProperties = false;
+ }
+ },
+ });
+
+ // Allow $schema property in config files for IDE support
+ if (jsonSchema.properties && typeof jsonSchema.properties === 'object') {
+ (jsonSchema.properties as Record).$schema = {
+ type: 'string',
+ description: 'JSON Schema URL for IDE validation and autocomplete',
+ };
+ }
+
+ return {
+ ...jsonSchema,
+ $id: CONFIG_SCHEMA_ID,
+ $schema: CONFIG_SCHEMA_DRAFT,
};
}
-// Add standard JSON Schema metadata with $id first for cleaner ordering
-const orderedSchema = {
- $id: 'https://raw.githubusercontent.com/steipete/mcporter/main/mcporter.schema.json',
- $schema: 'http://json-schema.org/draft-07/schema#',
- ...jsonSchema,
-};
+export function writeConfigJsonSchema(outputPath = 'mcporter.schema.json'): void {
+ const schema = buildConfigJsonSchema();
+ writeFileSync(outputPath, `${JSON.stringify(schema, null, 2)}\n`);
+ console.log(`Generated: ${outputPath}`);
+}
-const outputPath = 'mcporter.schema.json';
-writeFileSync(outputPath, `${JSON.stringify(orderedSchema, null, 2)}\n`);
-console.log(`Generated: ${outputPath}`);
+const entryPath = process.argv[1];
+if (entryPath && fileURLToPath(import.meta.url) === entryPath) {
+ writeConfigJsonSchema();
+}
diff --git a/tests/config-schema-file.test.ts b/tests/config-schema-file.test.ts
new file mode 100644
index 0000000..d3a6f9b
--- /dev/null
+++ b/tests/config-schema-file.test.ts
@@ -0,0 +1,29 @@
+import fs from 'node:fs/promises';
+import { describe, expect, it } from 'vitest';
+import { buildConfigJsonSchema, CONFIG_SCHEMA_DRAFT } from '../scripts/generate-json-schema.js';
+
+describe('generated config schema', () => {
+ it('stays in sync with the checked-in schema file', async () => {
+ const schemaPath = new URL('../mcporter.schema.json', import.meta.url);
+ const checkedIn = JSON.parse(await fs.readFile(schemaPath, 'utf8')) as Record;
+ const generated = buildConfigJsonSchema();
+ expect(checkedIn).toEqual(generated);
+ });
+
+ it('includes top-level $schema and oauthScope properties', async () => {
+ const schemaPath = new URL('../mcporter.schema.json', import.meta.url);
+ const schema = JSON.parse(await fs.readFile(schemaPath, 'utf8')) as {
+ $schema?: string;
+ properties?: Record;
+ };
+ expect(schema.$schema).toBe(CONFIG_SCHEMA_DRAFT);
+ expect(schema.properties?.$schema).toBeDefined();
+
+ const mcpServers = schema.properties?.mcpServers as
+ | { additionalProperties?: { properties?: Record } }
+ | undefined;
+ const entryProperties = mcpServers?.additionalProperties?.properties;
+ expect(entryProperties?.oauthScope).toBeDefined();
+ expect(entryProperties?.oauth_scope).toBeDefined();
+ });
+});