fix: stabilize generated config schema + oauthScope coverage (#43) (thanks @aryasaatvik)
This commit is contained in:
parent
1dc2e70061
commit
74dd794645
@ -12,6 +12,7 @@
|
||||
- Added `CallResult.images()` plus opt-in `mcporter call --save-images <dir>` 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).
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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<string, unknown>).$schema = {
|
||||
type: 'string',
|
||||
description: 'JSON Schema URL for IDE validation and autocomplete',
|
||||
export function buildConfigJsonSchema(): Record<string, unknown> {
|
||||
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<string, unknown>).$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();
|
||||
}
|
||||
|
||||
29
tests/config-schema-file.test.ts
Normal file
29
tests/config-schema-file.test.ts
Normal file
@ -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<string, unknown>;
|
||||
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<string, unknown>;
|
||||
};
|
||||
expect(schema.$schema).toBe(CONFIG_SCHEMA_DRAFT);
|
||||
expect(schema.properties?.$schema).toBeDefined();
|
||||
|
||||
const mcpServers = schema.properties?.mcpServers as
|
||||
| { additionalProperties?: { properties?: Record<string, unknown> } }
|
||||
| undefined;
|
||||
const entryProperties = mcpServers?.additionalProperties?.properties;
|
||||
expect(entryProperties?.oauthScope).toBeDefined();
|
||||
expect(entryProperties?.oauth_scope).toBeDefined();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user