From 7a3ff5d45218618d583e7edff816b16b8501505e Mon Sep 17 00:00:00 2001 From: Tadas Antanavicius Date: Mon, 27 Apr 2026 19:47:01 +0000 Subject: [PATCH] Bootstrap repo with TypeScript source-of-truth and generated JSON Schema Tracks SEP-2127 (MCP Server Cards) as an experimental extension while the SEP itself is under review. The schema.ts is structured to be lifted directly into schema/draft/schema.ts of the main spec when Server Cards graduate -- only the inlined MetaObject and Icon definitions need to be removed during migration. - schema.ts: ServerCard, Server, Repository, Remote, Package, Argument, Input, KeyValueInput, etc. plus inlined MetaObject and Icon - scripts/generate-schema.ts: typescript-json-schema with 2020-12 transforms - scripts/validate-examples.ts: ajv validator over examples/{ServerCard,Server}/ - examples/: 2 valid + 4 invalid Server Card cases, 1 valid Server case - README: status, layout, schema URL conventions, graduation plan - CI workflow: check + validate + format on every PR --- .github/workflows/ci.yml | 24 + .gitignore | 3 + README.md | 77 +- examples/Server/valid/with-package.json | 27 + .../ServerCard/invalid/bad-name-pattern.json | 6 + .../invalid/date-versioned-schema.json | 6 + examples/ServerCard/invalid/missing-name.json | 5 + .../ServerCard/invalid/missing-schema.json | 5 + examples/ServerCard/valid/minimal.json | 6 + .../ServerCard/valid/templated-remote.json | 38 + package-lock.json | 1163 +++++++++++++++++ package.json | 22 + schema.json | 651 +++++++++ schema.ts | 573 ++++++++ scripts/generate-schema.ts | 56 + scripts/validate-examples.ts | 117 ++ tsconfig.json | 11 + 17 files changed, 2789 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 examples/Server/valid/with-package.json create mode 100644 examples/ServerCard/invalid/bad-name-pattern.json create mode 100644 examples/ServerCard/invalid/date-versioned-schema.json create mode 100644 examples/ServerCard/invalid/missing-name.json create mode 100644 examples/ServerCard/invalid/missing-schema.json create mode 100644 examples/ServerCard/valid/minimal.json create mode 100644 examples/ServerCard/valid/templated-remote.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 schema.json create mode 100644 schema.ts create mode 100644 scripts/generate-schema.ts create mode 100644 scripts/validate-examples.ts create mode 100644 tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f04b8e9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - name: Verify schema.json is in sync with schema.ts and TypeScript compiles + run: npm run check + - name: Validate examples against schema.json + run: npm run validate + - name: Verify formatting + run: npm run format:check diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c45938 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +*.log +.DS_Store diff --git a/README.md b/README.md index 7a22282..7ca95ff 100644 --- a/README.md +++ b/README.md @@ -1 +1,76 @@ -# experimental-ext-server-card \ No newline at end of file +# MCP Server Cards (Experimental Extension) + +> #### **Status:** Experimental. This work is for prototyping and feedback only, and is not an accepted or official MCP extension. + +This repository defines a TypeScript source-of-truth and generated JSON Schema for **MCP Server Cards** — a static metadata document that describes a remote MCP server enough for clients to discover and connect to it before initialization. + +It tracks [SEP-2127](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) and is intended to be lifted directly into the main spec when Server Cards graduate (see [Graduation plan](#graduation-plan) below). + +A prior attempt to land these types directly in the core spec ([modelcontextprotocol#2652](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2652)) is being relaunched here as an experimental extension while the SEP itself is still under review. + +## What is a Server Card? + +A **Server Card** is a JSON document, typically published at `https:///.well-known/mcp/server-card`, describing: + +- The server's identity (`name`, `version`, `description`, optional `title` / `icons` / `repository` / `websiteUrl`) +- Its remote transport endpoints (URLs, headers, variable templates, supported protocol versions) +- Optional registry-style extension metadata (`_meta`) + +The companion **Server** document is a strict superset that adds locally-runnable `packages` — it is the shape the [MCP Registry](https://github.com/modelcontextprotocol/registry) uses for `server.json`. + +Server Cards intentionally omit primitive listings (tools, resources, prompts) — those remain subject to runtime listing via the protocol's standard list operations. + +## Layout + +``` +schema.ts # TypeScript source of truth (single file) +schema.json # Generated JSON Schema 2020-12 (do not edit by hand) +scripts/ + generate-schema.ts # Generates schema.json from schema.ts + validate-examples.ts # Validates examples/ against the generated schema +examples/ + ServerCard/{valid,invalid} # Example Server Card documents + Server/{valid,invalid} # Example Server (registry-shaped) documents +``` + +The generated `schema.json` is checked into the repo so consumers can grab it without running the toolchain. + +## Working on the schema + +```bash +npm install +npm run generate # regenerates schema.json from schema.ts +npm run validate # runs all examples through ajv against schema.json +npm run check # asserts schema.json is in sync with schema.ts and tsc passes +npm run format # prettier write +``` + +When you change `schema.ts`, always run `npm run generate` and commit the updated `schema.json` in the same commit. CI runs `npm run check` and will fail if they drift. + +## Schema URL conventions + +The `$schema` field on every document MUST be a URL of the form: + +``` +https://static.modelcontextprotocol.io/schemas/v1/.schema.json +``` + +Schema URLs are versioned by their `vN` segment rather than by date, so additive revisions of the v1 shape don't bump every published document's `$schema`. Breaking changes would publish a `v2` family. + +## Graduation plan + +When the SEP is accepted and Server Cards graduate from this experimental extension: + +1. The contents of `schema.ts` in this repo move into `schema/draft/schema.ts` of [`modelcontextprotocol/modelcontextprotocol`](https://github.com/modelcontextprotocol/modelcontextprotocol). The two `MetaObject` and `Icon` definitions inlined here at the bottom of `schema.ts` already exist in the main spec and are dropped from the migration. +2. The main spec's existing `scripts/generate-schemas.ts` regenerates `schema/draft/schema.json` (and downstream `docs/specification/draft/schema.mdx`) — no per-extension generator is required there. +3. Published documents update their `$schema` to point at the main spec's hosted schema URL (e.g., `https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json` served from `modelcontextprotocol/static`). +4. This repository is archived with a pointer to the relevant section of `schema/draft/schema.ts` in the main spec. + +The `schema.ts` in this repo is deliberately structured to be copy-pasted into the main spec's `schema/draft/schema.ts` with no transformation other than removing the inlined `MetaObject` / `Icon` definitions. + +## References + +- [SEP-2127: MCP Server Cards (PR)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) +- [SEP-2133: Extensions framework (PR)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133) +- Abandoned core spec PR (superseded by this repo): [modelcontextprotocol#2652](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2652) +- [MCP Registry `server.json` schema](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/generic-server-json.md) diff --git a/examples/Server/valid/with-package.json b/examples/Server/valid/with-package.json new file mode 100644 index 0000000..55159a9 --- /dev/null +++ b/examples/Server/valid/with-package.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server.schema.json", + "name": "example-org/with-package", + "version": "0.4.2", + "description": "Server document with a locally-runnable npm package.", + "repository": { + "url": "https://github.com/example-org/with-package", + "source": "github" + }, + "packages": [ + { + "registryType": "npm", + "identifier": "@example-org/with-package", + "version": "0.4.2", + "runtimeHint": "npx", + "transport": { "type": "stdio" }, + "environmentVariables": [ + { + "name": "EXAMPLE_API_KEY", + "description": "Example API key.", + "isRequired": true, + "isSecret": true + } + ] + } + ] +} diff --git a/examples/ServerCard/invalid/bad-name-pattern.json b/examples/ServerCard/invalid/bad-name-pattern.json new file mode 100644 index 0000000..5f9d7c6 --- /dev/null +++ b/examples/ServerCard/invalid/bad-name-pattern.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + "name": "no-slash-in-name", + "version": "1.0.0", + "description": "Name must contain exactly one slash separating namespace and server name." +} diff --git a/examples/ServerCard/invalid/date-versioned-schema.json b/examples/ServerCard/invalid/date-versioned-schema.json new file mode 100644 index 0000000..53314cd --- /dev/null +++ b/examples/ServerCard/invalid/date-versioned-schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-11-25/server-card.schema.json", + "name": "example-org/wrong-schema-url", + "version": "1.0.0", + "description": "Uses date-versioned schema URL; only /v1/ URLs are valid." +} diff --git a/examples/ServerCard/invalid/missing-name.json b/examples/ServerCard/invalid/missing-name.json new file mode 100644 index 0000000..8269b20 --- /dev/null +++ b/examples/ServerCard/invalid/missing-name.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + "version": "1.0.0", + "description": "Missing required `name` field." +} diff --git a/examples/ServerCard/invalid/missing-schema.json b/examples/ServerCard/invalid/missing-schema.json new file mode 100644 index 0000000..f541b47 --- /dev/null +++ b/examples/ServerCard/invalid/missing-schema.json @@ -0,0 +1,5 @@ +{ + "name": "example-org/no-schema", + "version": "1.0.0", + "description": "Missing the required `$schema` field." +} diff --git a/examples/ServerCard/valid/minimal.json b/examples/ServerCard/valid/minimal.json new file mode 100644 index 0000000..a09d8ed --- /dev/null +++ b/examples/ServerCard/valid/minimal.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + "name": "example-org/minimal", + "version": "1.0.0", + "description": "Smallest valid Server Card." +} diff --git a/examples/ServerCard/valid/templated-remote.json b/examples/ServerCard/valid/templated-remote.json new file mode 100644 index 0000000..c30bb1d --- /dev/null +++ b/examples/ServerCard/valid/templated-remote.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + "name": "example-org/with-remote", + "version": "2.1.0", + "description": "Server Card with a templated remote endpoint and headers.", + "title": "Example Remote Server", + "websiteUrl": "https://example.com", + "remotes": [ + { + "type": "streamable-http", + "url": "https://{tenant}.example.com/mcp", + "headers": [ + { + "name": "Authorization", + "description": "Bearer token for the remote endpoint.", + "isRequired": true, + "isSecret": true, + "value": "Bearer {token}", + "variables": { + "token": { + "description": "API token issued by Example Inc.", + "isRequired": true, + "isSecret": true + } + } + } + ], + "variables": { + "tenant": { + "description": "Tenant subdomain.", + "isRequired": true, + "default": "default" + } + }, + "supportedProtocolVersions": ["2025-06-18", "2025-11-25"] + } + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3a67b8f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1163 @@ +{ + "name": "@modelcontextprotocol/experimental-ext-server-card", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@modelcontextprotocol/experimental-ext-server-card", + "version": "0.0.0", + "devDependencies": { + "ajv": "^8.18.0", + "ajv-formats": "^3.0.1", + "prettier": "^3.6.2", + "tsx": "^4.21.0", + "typescript": "^5.6.2", + "typescript-json-schema": "^0.67.1" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-equal": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/path-equal/-/path-equal-1.2.5.tgz", + "integrity": "sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==", + "dev": true + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-json-schema": { + "version": "0.67.1", + "resolved": "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.67.1.tgz", + "integrity": "sha512-vKTZB/RoYTIBdVP7E7vrgHMCssBuhja91wQy498QIVhvfRimaOgjc98uwAXmZ7mbLUytJmOSbF11wPz+ByQeXg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@types/node": "^18.11.9", + "glob": "^7.1.7", + "path-equal": "^1.2.5", + "safe-stable-stringify": "^2.2.0", + "ts-node": "^10.9.1", + "typescript": "~5.5.0", + "vm2": "^3.10.0", + "yargs": "^17.1.1" + }, + "bin": { + "typescript-json-schema": "bin/typescript-json-schema" + } + }, + "node_modules/typescript-json-schema/node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/vm2": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.10.5.tgz", + "integrity": "sha512-3P/2QDccVFBcujfCOeP8vVNuGfuBJHEuvGR8eMmI10p/iwLL2UwF5PDaNaoOS2pRGQEDmJRyeEcc8kmm2Z59RA==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-walk": "^8.3.4" + }, + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..95493b6 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "@modelcontextprotocol/experimental-ext-server-card", + "version": "0.0.0", + "private": true, + "description": "Experimental schema for SEP-2127 (MCP Server Cards). See README for status and graduation plan.", + "type": "module", + "scripts": { + "generate": "tsx scripts/generate-schema.ts", + "check": "tsx scripts/generate-schema.ts --check && tsc --noEmit", + "validate": "tsx scripts/validate-examples.ts", + "format": "prettier --write \"**/*.{ts,json,md}\"", + "format:check": "prettier --check \"**/*.{ts,json,md}\"" + }, + "devDependencies": { + "ajv": "^8.18.0", + "ajv-formats": "^3.0.1", + "prettier": "^3.6.2", + "tsx": "^4.21.0", + "typescript": "^5.6.2", + "typescript-json-schema": "^0.67.1" + } +} diff --git a/schema.json b/schema.json new file mode 100644 index 0000000..af69faa --- /dev/null +++ b/schema.json @@ -0,0 +1,651 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "Argument": { + "anyOf": [ + { + "$ref": "#/$defs/PositionalArgument" + }, + { + "$ref": "#/$defs/NamedArgument" + } + ], + "description": "A command-line argument supplied to a package's binary or runtime." + }, + "Icon": { + "description": "An optionally-sized icon that can be displayed in a user interface.", + "properties": { + "mimeType": { + "description": "Optional MIME type override if the source MIME type is missing or generic.\nFor example: `\"image/png\"`, `\"image/jpeg\"`, or `\"image/svg+xml\"`.", + "type": "string" + }, + "sizes": { + "description": "Optional array of strings that specify sizes at which the icon can be used.\nEach string should be in WxH format (e.g., `\"48x48\"`, `\"96x96\"`) or `\"any\"` for scalable formats like SVG.\n\nIf not provided, the client should assume that the icon can be used at any size.", + "items": { + "type": "string" + }, + "type": "array" + }, + "src": { + "description": "A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a\n`data:` URI with Base64-encoded image data.\n\nConsumers SHOULD take steps to ensure URLs serving icons are from the\nsame domain as the client/server or a trusted domain.\n\nConsumers SHOULD take appropriate precautions when consuming SVGs as they can contain\nexecutable JavaScript.", + "format": "uri", + "type": "string" + }, + "theme": { + "description": "Optional specifier for the theme this icon is designed for. `\"light\"` indicates\nthe icon is designed to be used with a light background, and `\"dark\"` indicates\nthe icon is designed to be used with a dark background.\n\nIf not provided, the client should assume the icon can be used with any theme.", + "enum": ["dark", "light"], + "type": "string" + } + }, + "required": ["src"], + "type": "object" + }, + "Input": { + "description": "A user-supplied or pre-set input value, used in {@link Package} argument\nand environment-variable definitions.", + "properties": { + "choices": { + "description": "Allowed values for the input. If provided, the user must select one.", + "items": { + "type": "string" + }, + "type": "array" + }, + "default": { + "description": "Default value for the input. SHOULD be a valid value for the input.", + "type": "string" + }, + "description": { + "description": "Human-readable explanation of the input. Clients can use this to provide\ncontext to the user.", + "type": "string" + }, + "format": { + "description": "Specifies the input format. `\"filepath\"` should be interpreted as a file\non the user's filesystem. When the input is converted to a string,\nbooleans should be represented by `\"true\"`/`\"false\"`, and numbers by\ndecimal values.", + "enum": ["boolean", "filepath", "number", "string"], + "type": "string" + }, + "isRequired": { + "description": "Whether the input must be supplied for the package to run.", + "type": "boolean" + }, + "isSecret": { + "description": "Whether the input is a secret value (e.g., password, token). If true,\nclients should handle the value securely.", + "type": "boolean" + }, + "placeholder": { + "description": "Placeholder displayed during configuration to provide examples or\nguidance about the expected form of the input.", + "type": "string" + }, + "value": { + "description": "Pre-set value for the input. If set, the value should not be configurable\nby end users. Identifiers wrapped in `{curly_braces}` will be replaced\nwith the corresponding entries from the input's `variables` map (if any).", + "type": "string" + } + }, + "type": "object" + }, + "InputWithVariables": { + "description": "An {@link Input} whose `value` may reference variables for substitution.", + "properties": { + "choices": { + "description": "Allowed values for the input. If provided, the user must select one.", + "items": { + "type": "string" + }, + "type": "array" + }, + "default": { + "description": "Default value for the input. SHOULD be a valid value for the input.", + "type": "string" + }, + "description": { + "description": "Human-readable explanation of the input. Clients can use this to provide\ncontext to the user.", + "type": "string" + }, + "format": { + "description": "Specifies the input format. `\"filepath\"` should be interpreted as a file\non the user's filesystem. When the input is converted to a string,\nbooleans should be represented by `\"true\"`/`\"false\"`, and numbers by\ndecimal values.", + "enum": ["boolean", "filepath", "number", "string"], + "type": "string" + }, + "isRequired": { + "description": "Whether the input must be supplied for the package to run.", + "type": "boolean" + }, + "isSecret": { + "description": "Whether the input is a secret value (e.g., password, token). If true,\nclients should handle the value securely.", + "type": "boolean" + }, + "placeholder": { + "description": "Placeholder displayed during configuration to provide examples or\nguidance about the expected form of the input.", + "type": "string" + }, + "value": { + "description": "Pre-set value for the input. If set, the value should not be configurable\nby end users. Identifiers wrapped in `{curly_braces}` will be replaced\nwith the corresponding entries from the input's `variables` map (if any).", + "type": "string" + }, + "variables": { + "additionalProperties": { + "$ref": "#/$defs/Input" + }, + "description": "Variables referenced by `{curly_braces}` identifiers in `value`. The map\nkey is the variable name; the value defines the variable's properties.", + "type": "object" + } + }, + "type": "object" + }, + "KeyValueInput": { + "description": "A named input — used for environment variables and HTTP headers.", + "properties": { + "choices": { + "description": "Allowed values for the input. If provided, the user must select one.", + "items": { + "type": "string" + }, + "type": "array" + }, + "default": { + "description": "Default value for the input. SHOULD be a valid value for the input.", + "type": "string" + }, + "description": { + "description": "Human-readable explanation of the input. Clients can use this to provide\ncontext to the user.", + "type": "string" + }, + "format": { + "description": "Specifies the input format. `\"filepath\"` should be interpreted as a file\non the user's filesystem. When the input is converted to a string,\nbooleans should be represented by `\"true\"`/`\"false\"`, and numbers by\ndecimal values.", + "enum": ["boolean", "filepath", "number", "string"], + "type": "string" + }, + "isRequired": { + "description": "Whether the input must be supplied for the package to run.", + "type": "boolean" + }, + "isSecret": { + "description": "Whether the input is a secret value (e.g., password, token). If true,\nclients should handle the value securely.", + "type": "boolean" + }, + "name": { + "description": "Name of the header or environment variable.", + "type": "string" + }, + "placeholder": { + "description": "Placeholder displayed during configuration to provide examples or\nguidance about the expected form of the input.", + "type": "string" + }, + "value": { + "description": "Pre-set value for the input. If set, the value should not be configurable\nby end users. Identifiers wrapped in `{curly_braces}` will be replaced\nwith the corresponding entries from the input's `variables` map (if any).", + "type": "string" + }, + "variables": { + "additionalProperties": { + "$ref": "#/$defs/Input" + }, + "description": "Variables referenced by `{curly_braces}` identifiers in `value`. The map\nkey is the variable name; the value defines the variable's properties.", + "type": "object" + } + }, + "required": ["name"], + "type": "object" + }, + "MetaObject": { + "description": "Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their interactions.\n\nCertain key names are reserved by MCP for protocol-level metadata; implementations MUST NOT make assumptions about values at these keys. Additionally, specific schema definitions may reserve particular names for purpose-specific metadata, as declared in those definitions.\n\nValid keys have two segments:\n\n**Prefix:**\n- Optional — if specified, MUST be a series of _labels_ separated by dots (`.`), followed by a slash (`/`).\n- Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or hyphens (`-`).\n- Any prefix consisting of zero or more labels, followed by `modelcontextprotocol` or `mcp`, followed by any label, is **reserved** for MCP use. For example: `modelcontextprotocol.io/`, `mcp.dev/`, `api.modelcontextprotocol.org/`, and `tools.mcp.com/` are all reserved.\n\n**Name:**\n- Unless empty, MUST start and end with an alphanumeric character (`[a-z0-9A-Z]`).\n- Interior characters may be alphanumeric, hyphens (`-`), underscores (`_`), or dots (`.`).", + "type": "object" + }, + "NamedArgument": { + "description": "A named command-line input — a `--flag={value}` parameter.", + "properties": { + "choices": { + "description": "Allowed values for the input. If provided, the user must select one.", + "items": { + "type": "string" + }, + "type": "array" + }, + "default": { + "description": "Default value for the input. SHOULD be a valid value for the input.", + "type": "string" + }, + "description": { + "description": "Human-readable explanation of the input. Clients can use this to provide\ncontext to the user.", + "type": "string" + }, + "format": { + "description": "Specifies the input format. `\"filepath\"` should be interpreted as a file\non the user's filesystem. When the input is converted to a string,\nbooleans should be represented by `\"true\"`/`\"false\"`, and numbers by\ndecimal values.", + "enum": ["boolean", "filepath", "number", "string"], + "type": "string" + }, + "isRepeated": { + "description": "Whether the argument can be repeated multiple times.", + "type": "boolean" + }, + "isRequired": { + "description": "Whether the input must be supplied for the package to run.", + "type": "boolean" + }, + "isSecret": { + "description": "Whether the input is a secret value (e.g., password, token). If true,\nclients should handle the value securely.", + "type": "boolean" + }, + "name": { + "description": "The flag name, including any leading dashes (e.g., `\"--port\"`).", + "type": "string" + }, + "placeholder": { + "description": "Placeholder displayed during configuration to provide examples or\nguidance about the expected form of the input.", + "type": "string" + }, + "type": { + "const": "named", + "type": "string" + }, + "value": { + "description": "Pre-set value for the input. If set, the value should not be configurable\nby end users. Identifiers wrapped in `{curly_braces}` will be replaced\nwith the corresponding entries from the input's `variables` map (if any).", + "type": "string" + }, + "variables": { + "additionalProperties": { + "$ref": "#/$defs/Input" + }, + "description": "Variables referenced by `{curly_braces}` identifiers in `value`. The map\nkey is the variable name; the value defines the variable's properties.", + "type": "object" + } + }, + "required": ["name", "type"], + "type": "object" + }, + "Package": { + "description": "Metadata for installing and running a packaged MCP server locally.", + "properties": { + "environmentVariables": { + "description": "Environment variables to be set when running the package.", + "items": { + "$ref": "#/$defs/KeyValueInput" + }, + "type": "array" + }, + "fileSha256": { + "description": "SHA-256 hash of the package file for integrity verification. Required for\nMCPB packages and optional for other package types. If present, MCP\nclients MUST validate the downloaded file matches the hash before running\npackages to ensure file integrity.", + "pattern": "^[a-f0-9]{64}$", + "type": "string" + }, + "identifier": { + "description": "Package identifier — either a package name (for registries)\nor a URL (for direct downloads).", + "type": "string" + }, + "packageArguments": { + "description": "Arguments passed to the package's binary.", + "items": { + "$ref": "#/$defs/Argument" + }, + "type": "array" + }, + "registryBaseUrl": { + "description": "Base URL of the package registry.", + "format": "uri", + "type": "string" + }, + "registryType": { + "description": "Registry type indicating how to download packages\n(e.g., `\"npm\"`, `\"pypi\"`, `\"oci\"`, `\"nuget\"`, `\"mcpb\"`).", + "type": "string" + }, + "runtimeArguments": { + "description": "Arguments passed to the package's runtime command (such as `docker` or\n`npx`). The `runtimeHint` field should be provided when `runtimeArguments`\nare present.", + "items": { + "$ref": "#/$defs/Argument" + }, + "type": "array" + }, + "runtimeHint": { + "description": "A hint to help clients determine the appropriate runtime for the package\n(e.g., `\"npx\"`, `\"uvx\"`, `\"docker\"`, `\"dnx\"`). Should be provided when\n`runtimeArguments` are present.", + "type": "string" + }, + "supportedProtocolVersions": { + "description": "MCP protocol versions actively supported by this package.", + "items": { + "type": "string" + }, + "type": "array" + }, + "transport": { + "$ref": "#/$defs/PackageTransport", + "description": "Transport configuration for invoking this package after installation." + }, + "version": { + "description": "Package version.", + "minLength": 1, + "type": "string" + } + }, + "required": ["identifier", "registryType", "transport"], + "type": "object" + }, + "PackageTransport": { + "anyOf": [ + { + "$ref": "#/$defs/StdioTransport" + }, + { + "$ref": "#/$defs/StreamableHttpPackageTransport" + }, + { + "$ref": "#/$defs/SsePackageTransport" + } + ], + "description": "Transport protocol configuration for a locally-runnable package." + }, + "PositionalArgument": { + "description": "A positional command-line input — a value inserted verbatim into the\ncommand line.", + "properties": { + "choices": { + "description": "Allowed values for the input. If provided, the user must select one.", + "items": { + "type": "string" + }, + "type": "array" + }, + "default": { + "description": "Default value for the input. SHOULD be a valid value for the input.", + "type": "string" + }, + "description": { + "description": "Human-readable explanation of the input. Clients can use this to provide\ncontext to the user.", + "type": "string" + }, + "format": { + "description": "Specifies the input format. `\"filepath\"` should be interpreted as a file\non the user's filesystem. When the input is converted to a string,\nbooleans should be represented by `\"true\"`/`\"false\"`, and numbers by\ndecimal values.", + "enum": ["boolean", "filepath", "number", "string"], + "type": "string" + }, + "isRepeated": { + "description": "Whether the argument can be repeated multiple times in the command line.", + "type": "boolean" + }, + "isRequired": { + "description": "Whether the input must be supplied for the package to run.", + "type": "boolean" + }, + "isSecret": { + "description": "Whether the input is a secret value (e.g., password, token). If true,\nclients should handle the value securely.", + "type": "boolean" + }, + "placeholder": { + "description": "Placeholder displayed during configuration to provide examples or\nguidance about the expected form of the input.", + "type": "string" + }, + "type": { + "const": "positional", + "type": "string" + }, + "value": { + "description": "Pre-set value for the input. If set, the value should not be configurable\nby end users. Identifiers wrapped in `{curly_braces}` will be replaced\nwith the corresponding entries from the input's `variables` map (if any).", + "type": "string" + }, + "valueHint": { + "description": "Identifier for the positional argument. It is not part of the command\nline; it may be used by client configuration as a label identifying the\nargument, and it identifies the value in transport URL variable\nsubstitution.\n\nImplementations SHOULD ensure that at least one of `valueHint` or\n`value` is set so the positional argument resolves to a concrete value.", + "type": "string" + }, + "variables": { + "additionalProperties": { + "$ref": "#/$defs/Input" + }, + "description": "Variables referenced by `{curly_braces}` identifiers in `value`. The map\nkey is the variable name; the value defines the variable's properties.", + "type": "object" + } + }, + "required": ["type"], + "type": "object" + }, + "Remote": { + "description": "Metadata for connecting to a remote (HTTP-based) MCP server endpoint.", + "properties": { + "headers": { + "description": "HTTP headers required or accepted when connecting to this remote\nendpoint. Each header is described as a {@link KeyValueInput} so that\nclients can prompt users for required values, mark secrets, surface\ndefaults, and constrain to a list of choices.", + "items": { + "$ref": "#/$defs/KeyValueInput" + }, + "type": "array" + }, + "supportedProtocolVersions": { + "description": "MCP protocol versions actively supported by this remote endpoint. Allows\nclients to negotiate a compatible protocol version before initialization.", + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "description": "The transport type for this remote endpoint.", + "enum": ["sse", "streamable-http"], + "type": "string" + }, + "url": { + "description": "URL template for the remote endpoint. Must start with `http://`,\n`https://`, or a `{template-variable}`. Variables in `{curly_braces}`\nare substituted from the {@link Remote.variables} map before the\nclient connects.", + "pattern": "^(https?://[^\\s]+|\\{[a-zA-Z_][a-zA-Z0-9_]*\\}[^\\s]*)$", + "type": "string" + }, + "variables": { + "additionalProperties": { + "$ref": "#/$defs/Input" + }, + "description": "Configuration variables that can be referenced as `{curly_braces}`\nplaceholders in `url` (and inside header values via\n{@link InputWithVariables.variables}). The map key is the variable\nname; the value defines the variable's properties (e.g., human-readable\ndescription, default, whether it is required or secret).", + "type": "object" + } + }, + "required": ["type", "url"], + "type": "object" + }, + "Repository": { + "description": "Repository metadata for the MCP server source code. Enables users and\nsecurity experts to inspect the code, improving transparency.", + "properties": { + "id": { + "description": "Repository identifier from the hosting service (e.g., GitHub repo ID).\nOwned and determined by the source forge. Should remain stable across\nrepository renames and may be used to detect repository resurrection\nattacks — if a repository is deleted and recreated, the ID should change.", + "type": "string" + }, + "source": { + "description": "Repository hosting service identifier (e.g., `\"github\"`). Used by registries\nto determine validation and API access methods.", + "type": "string" + }, + "subfolder": { + "description": "Optional relative path from repository root to the server location within a\nmonorepo or nested package structure. Must be a clean relative path.", + "type": "string" + }, + "url": { + "description": "Repository URL for browsing source code. Should support both web browsing\nand `git clone` operations.", + "format": "uri", + "type": "string" + } + }, + "required": ["source", "url"], + "type": "object" + }, + "Server": { + "description": "A superset of {@link ServerCard} that additionally describes locally-runnable\npackages. This is the shape used by the MCP Registry's `server.json`.\n\n`Server` documents are typically published to a registry rather than served\nfrom a `.well-known` URI, since they may include instructions for installing\nand executing a server on a client's local machine.", + "properties": { + "$schema": { + "description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`,\nnaming a Server Card / `server.json` schema (e.g.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`\nor `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date so that\nminor, additive revisions of the v1 shape don't bump every published\ndocument's `$schema` URL.", + "format": "uri", + "pattern": "^https://static\\.modelcontextprotocol\\.io/schemas/v1/[^/]+\\.schema\\.json$", + "type": "string" + }, + "_meta": { + "$ref": "#/$defs/MetaObject", + "description": "Extension metadata using reverse-DNS namespacing for vendor-specific data.\n\nFollows the protocol's standard `_meta` definition." + }, + "description": { + "description": "Clear human-readable explanation of server functionality. Should focus on\ncapabilities, not implementation details.", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "icons": { + "description": "Optional set of sized icons that the client can display in a user interface.\n\nClients that support rendering icons MUST support at least the following\nMIME types: `image/png` and `image/jpeg` (safe, universal compatibility).\nClients SHOULD also support: `image/svg+xml` (scalable but requires security\nprecautions) and `image/webp` (modern, efficient format).", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "name": { + "description": "Server name in reverse-DNS format. Must contain exactly one forward slash\nseparating namespace from server name.", + "maxLength": 200, + "minLength": 3, + "pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$", + "type": "string" + }, + "packages": { + "description": "Metadata helpful for running and connecting to local instances of this MCP server.", + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "remotes": { + "description": "Metadata helpful for making HTTP-based connections to this MCP server.", + "items": { + "$ref": "#/$defs/Remote" + }, + "type": "array" + }, + "repository": { + "$ref": "#/$defs/Repository", + "description": "Optional repository metadata for the MCP server source code.\nRecommended for transparency and security inspection." + }, + "title": { + "description": "Optional human-readable title or display name for the MCP server.\nMCP subregistries or clients MAY choose to use this for display purposes.", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "version": { + "description": "Version string for this server. SHOULD follow semantic versioning\n(e.g., '1.0.2', '2.1.0-alpha'). Equivalent of `Implementation.version`\nin the MCP specification. Non-semantic versions are allowed but may not\nsort predictably. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3',\n'>=1.2.3', '1.x', '1.*').", + "maxLength": 255, + "type": "string" + }, + "websiteUrl": { + "description": "Optional URL to the server's homepage, documentation, or project website.\nProvides a central link for users to learn more about the server.\nParticularly useful when the server has custom installation instructions\nor setup requirements.", + "format": "uri", + "type": "string" + } + }, + "required": ["$schema", "description", "name", "version"], + "type": "object" + }, + "ServerCard": { + "description": "A static metadata document describing a remote MCP server, suitable for\npublishing at a `.well-known/mcp-server-card` URI for pre-connection discovery.\n\nServer Cards intentionally describe only what is needed to discover and\nconnect to a remote server: identity, transport, and protocol versions.\nThey do not enumerate primitives (tools, resources, prompts) — those remain\nsubject to runtime listing via the protocol's standard list operations.\n\nThe companion {@link Server} shape is a strict superset that adds local\npackage metadata for use cases like the MCP Registry's `server.json`.", + "properties": { + "$schema": { + "description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`,\nnaming a Server Card / `server.json` schema (e.g.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`\nor `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date so that\nminor, additive revisions of the v1 shape don't bump every published\ndocument's `$schema` URL.", + "format": "uri", + "pattern": "^https://static\\.modelcontextprotocol\\.io/schemas/v1/[^/]+\\.schema\\.json$", + "type": "string" + }, + "_meta": { + "$ref": "#/$defs/MetaObject", + "description": "Extension metadata using reverse-DNS namespacing for vendor-specific data.\n\nFollows the protocol's standard `_meta` definition." + }, + "description": { + "description": "Clear human-readable explanation of server functionality. Should focus on\ncapabilities, not implementation details.", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "icons": { + "description": "Optional set of sized icons that the client can display in a user interface.\n\nClients that support rendering icons MUST support at least the following\nMIME types: `image/png` and `image/jpeg` (safe, universal compatibility).\nClients SHOULD also support: `image/svg+xml` (scalable but requires security\nprecautions) and `image/webp` (modern, efficient format).", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "name": { + "description": "Server name in reverse-DNS format. Must contain exactly one forward slash\nseparating namespace from server name.", + "maxLength": 200, + "minLength": 3, + "pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$", + "type": "string" + }, + "remotes": { + "description": "Metadata helpful for making HTTP-based connections to this MCP server.", + "items": { + "$ref": "#/$defs/Remote" + }, + "type": "array" + }, + "repository": { + "$ref": "#/$defs/Repository", + "description": "Optional repository metadata for the MCP server source code.\nRecommended for transparency and security inspection." + }, + "title": { + "description": "Optional human-readable title or display name for the MCP server.\nMCP subregistries or clients MAY choose to use this for display purposes.", + "maxLength": 100, + "minLength": 1, + "type": "string" + }, + "version": { + "description": "Version string for this server. SHOULD follow semantic versioning\n(e.g., '1.0.2', '2.1.0-alpha'). Equivalent of `Implementation.version`\nin the MCP specification. Non-semantic versions are allowed but may not\nsort predictably. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3',\n'>=1.2.3', '1.x', '1.*').", + "maxLength": 255, + "type": "string" + }, + "websiteUrl": { + "description": "Optional URL to the server's homepage, documentation, or project website.\nProvides a central link for users to learn more about the server.\nParticularly useful when the server has custom installation instructions\nor setup requirements.", + "format": "uri", + "type": "string" + } + }, + "required": ["$schema", "description", "name", "version"], + "type": "object" + }, + "SsePackageTransport": { + "description": "Server-sent events (SSE) transport for a locally-runnable package.", + "properties": { + "headers": { + "description": "HTTP headers to include when connecting to the package's local endpoint.", + "items": { + "$ref": "#/$defs/KeyValueInput" + }, + "type": "array" + }, + "type": { + "const": "sse", + "type": "string" + }, + "url": { + "description": "SSE endpoint URL template. See {@link StreamableHttpPackageTransport.url}\nfor variable-substitution semantics.", + "pattern": "^(https?://[^\\s]+|\\{[a-zA-Z_][a-zA-Z0-9_]*\\}[^\\s]*)$", + "type": "string" + } + }, + "required": ["type", "url"], + "type": "object" + }, + "StdioTransport": { + "description": "Stdio transport — the client launches the package as a subprocess and\ncommunicates over standard input and output.", + "properties": { + "type": { + "const": "stdio", + "type": "string" + } + }, + "required": ["type"], + "type": "object" + }, + "StreamableHttpPackageTransport": { + "description": "Streamable-HTTP transport for a locally-runnable package that exposes\nitself over HTTP after launch.", + "properties": { + "headers": { + "description": "HTTP headers to include when connecting to the package's local endpoint.", + "items": { + "$ref": "#/$defs/KeyValueInput" + }, + "type": "array" + }, + "type": { + "const": "streamable-http", + "type": "string" + }, + "url": { + "description": "URL template for the streamable-http transport. Must start with\n`http://`, `https://`, or a `{template-variable}`. Variables in\n`{curly_braces}` reference argument value-hints, argument names, or\nenvironment variable names from the parent {@link Package}.", + "pattern": "^(https?://[^\\s]+|\\{[a-zA-Z_][a-zA-Z0-9_]*\\}[^\\s]*)$", + "type": "string" + } + }, + "required": ["type", "url"], + "type": "object" + } + } +} diff --git a/schema.ts b/schema.ts new file mode 100644 index 0000000..34cb067 --- /dev/null +++ b/schema.ts @@ -0,0 +1,573 @@ +/** + * MCP Server Cards + * + * Experimental schema for SEP-2127. This file is the single source of truth + * for the Server Card and `server.json` types and is intended to be lifted + * directly into `schema/draft/schema.ts` of the main MCP specification when + * Server Cards graduate from this experimental extension. + * + * @see https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127 + */ + +/** + * A static metadata document describing a remote MCP server, suitable for + * publishing at a `.well-known/mcp-server-card` URI for pre-connection discovery. + * + * Server Cards intentionally describe only what is needed to discover and + * connect to a remote server: identity, transport, and protocol versions. + * They do not enumerate primitives (tools, resources, prompts) — those remain + * subject to runtime listing via the protocol's standard list operations. + * + * The companion {@link Server} shape is a strict superset that adds local + * package metadata for use cases like the MCP Registry's `server.json`. + * + * @see [SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) + * @category Server Cards + */ +export interface ServerCard { + /** + * The Server Card JSON Schema URI that this document conforms to. Required. + * + * Must be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`, + * naming a Server Card / `server.json` schema (e.g., + * `https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json` + * or `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`). + * Schema URLs are versioned by the `vN` segment rather than by date so that + * minor, additive revisions of the v1 shape don't bump every published + * document's `$schema` URL. + * + * @format uri + * @pattern ^https://static\.modelcontextprotocol\.io/schemas/v1/[^/]+\.schema\.json$ + */ + $schema: string; + + /** + * Server name in reverse-DNS format. Must contain exactly one forward slash + * separating namespace from server name. + * + * @minLength 3 + * @maxLength 200 + * @pattern ^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$ + */ + name: string; + + /** + * Version string for this server. SHOULD follow semantic versioning + * (e.g., '1.0.2', '2.1.0-alpha'). Equivalent of `Implementation.version` + * in the MCP specification. Non-semantic versions are allowed but may not + * sort predictably. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3', + * '>=1.2.3', '1.x', '1.*'). + * + * @maxLength 255 + */ + version: string; + + /** + * Clear human-readable explanation of server functionality. Should focus on + * capabilities, not implementation details. + * + * @minLength 1 + * @maxLength 100 + */ + description: string; + + /** + * Optional human-readable title or display name for the MCP server. + * MCP subregistries or clients MAY choose to use this for display purposes. + * + * @minLength 1 + * @maxLength 100 + */ + title?: string; + + /** + * Optional URL to the server's homepage, documentation, or project website. + * Provides a central link for users to learn more about the server. + * Particularly useful when the server has custom installation instructions + * or setup requirements. + * + * @format uri + */ + websiteUrl?: string; + + /** + * Optional repository metadata for the MCP server source code. + * Recommended for transparency and security inspection. + */ + repository?: Repository; + + /** + * Optional set of sized icons that the client can display in a user interface. + * + * Clients that support rendering icons MUST support at least the following + * MIME types: `image/png` and `image/jpeg` (safe, universal compatibility). + * Clients SHOULD also support: `image/svg+xml` (scalable but requires security + * precautions) and `image/webp` (modern, efficient format). + */ + icons?: Icon[]; + + /** + * Metadata helpful for making HTTP-based connections to this MCP server. + */ + remotes?: Remote[]; + + /** + * Extension metadata using reverse-DNS namespacing for vendor-specific data. + * + * Follows the protocol's standard `_meta` definition. + * + * @see {@link MetaObject} + */ + _meta?: MetaObject; +} + +/** + * A superset of {@link ServerCard} that additionally describes locally-runnable + * packages. This is the shape used by the MCP Registry's `server.json`. + * + * `Server` documents are typically published to a registry rather than served + * from a `.well-known` URI, since they may include instructions for installing + * and executing a server on a client's local machine. + * + * @see [SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) + * @category Server Cards + */ +export interface Server extends ServerCard { + /** + * Metadata helpful for running and connecting to local instances of this MCP server. + */ + packages?: Package[]; +} + +/** + * Repository metadata for the MCP server source code. Enables users and + * security experts to inspect the code, improving transparency. + * + * @category Server Cards + */ +export interface Repository { + /** + * Repository URL for browsing source code. Should support both web browsing + * and `git clone` operations. + * + * @format uri + */ + url: string; + + /** + * Repository hosting service identifier (e.g., `"github"`). Used by registries + * to determine validation and API access methods. + */ + source: string; + + /** + * Optional relative path from repository root to the server location within a + * monorepo or nested package structure. Must be a clean relative path. + */ + subfolder?: string; + + /** + * Repository identifier from the hosting service (e.g., GitHub repo ID). + * Owned and determined by the source forge. Should remain stable across + * repository renames and may be used to detect repository resurrection + * attacks — if a repository is deleted and recreated, the ID should change. + */ + id?: string; +} + +/** + * Metadata for connecting to a remote (HTTP-based) MCP server endpoint. + * + * @category Server Cards + */ +export interface Remote { + /** + * The transport type for this remote endpoint. + */ + type: "streamable-http" | "sse"; + + /** + * URL template for the remote endpoint. Must start with `http://`, + * `https://`, or a `{template-variable}`. Variables in `{curly_braces}` + * are substituted from the {@link Remote.variables} map before the + * client connects. + * + * @pattern ^(https?://[^\s]+|\{[a-zA-Z_][a-zA-Z0-9_]*\}[^\s]*)$ + */ + url: string; + + /** + * HTTP headers required or accepted when connecting to this remote + * endpoint. Each header is described as a {@link KeyValueInput} so that + * clients can prompt users for required values, mark secrets, surface + * defaults, and constrain to a list of choices. + */ + headers?: KeyValueInput[]; + + /** + * Configuration variables that can be referenced as `{curly_braces}` + * placeholders in `url` (and inside header values via + * {@link InputWithVariables.variables}). The map key is the variable + * name; the value defines the variable's properties (e.g., human-readable + * description, default, whether it is required or secret). + */ + variables?: { [key: string]: Input }; + + /** + * MCP protocol versions actively supported by this remote endpoint. Allows + * clients to negotiate a compatible protocol version before initialization. + */ + supportedProtocolVersions?: string[]; +} + +/** + * Metadata for installing and running a packaged MCP server locally. + * + * @category Server Cards + */ +export interface Package { + /** + * Registry type indicating how to download packages + * (e.g., `"npm"`, `"pypi"`, `"oci"`, `"nuget"`, `"mcpb"`). + */ + registryType: string; + + /** + * Package identifier — either a package name (for registries) + * or a URL (for direct downloads). + */ + identifier: string; + + /** + * Transport configuration for invoking this package after installation. + */ + transport: PackageTransport; + + /** + * Base URL of the package registry. + * + * @format uri + */ + registryBaseUrl?: string; + + /** + * Package version. + * + * @minLength 1 + */ + version?: string; + + /** + * MCP protocol versions actively supported by this package. + */ + supportedProtocolVersions?: string[]; + + /** + * A hint to help clients determine the appropriate runtime for the package + * (e.g., `"npx"`, `"uvx"`, `"docker"`, `"dnx"`). Should be provided when + * `runtimeArguments` are present. + */ + runtimeHint?: string; + + /** + * Arguments passed to the package's runtime command (such as `docker` or + * `npx`). The `runtimeHint` field should be provided when `runtimeArguments` + * are present. + */ + runtimeArguments?: Argument[]; + + /** + * Arguments passed to the package's binary. + */ + packageArguments?: Argument[]; + + /** + * Environment variables to be set when running the package. + */ + environmentVariables?: KeyValueInput[]; + + /** + * SHA-256 hash of the package file for integrity verification. Required for + * MCPB packages and optional for other package types. If present, MCP + * clients MUST validate the downloaded file matches the hash before running + * packages to ensure file integrity. + * + * @pattern ^[a-f0-9]{64}$ + */ + fileSha256?: string; +} + +/** + * Transport protocol configuration for a locally-runnable package. + * + * @category Server Cards + */ +export type PackageTransport = + | StdioTransport + | StreamableHttpPackageTransport + | SsePackageTransport; + +/** + * Stdio transport — the client launches the package as a subprocess and + * communicates over standard input and output. + * + * @category Server Cards + */ +export interface StdioTransport { + type: "stdio"; +} + +/** + * Streamable-HTTP transport for a locally-runnable package that exposes + * itself over HTTP after launch. + * + * @category Server Cards + */ +export interface StreamableHttpPackageTransport { + type: "streamable-http"; + + /** + * URL template for the streamable-http transport. Must start with + * `http://`, `https://`, or a `{template-variable}`. Variables in + * `{curly_braces}` reference argument value-hints, argument names, or + * environment variable names from the parent {@link Package}. + * + * @pattern ^(https?://[^\s]+|\{[a-zA-Z_][a-zA-Z0-9_]*\}[^\s]*)$ + */ + url: string; + + /** + * HTTP headers to include when connecting to the package's local endpoint. + */ + headers?: KeyValueInput[]; +} + +/** + * Server-sent events (SSE) transport for a locally-runnable package. + * + * @category Server Cards + */ +export interface SsePackageTransport { + type: "sse"; + + /** + * SSE endpoint URL template. See {@link StreamableHttpPackageTransport.url} + * for variable-substitution semantics. + * + * @pattern ^(https?://[^\s]+|\{[a-zA-Z_][a-zA-Z0-9_]*\}[^\s]*)$ + */ + url: string; + + /** + * HTTP headers to include when connecting to the package's local endpoint. + */ + headers?: KeyValueInput[]; +} + +/** + * A command-line argument supplied to a package's binary or runtime. + * + * @remarks + * Arguments construct command-line parameters that may contain user-provided + * input. This creates potential command-injection risks if clients execute + * commands in a shell environment. Clients SHOULD prefer non-shell execution + * methods (e.g., `posix_spawn`) when possible to eliminate injection risks + * entirely. Where not possible, clients SHOULD obtain user consent to run the + * resolved command before execution. + * + * @category Server Cards + */ +export type Argument = PositionalArgument | NamedArgument; + +/** + * A user-supplied or pre-set input value, used in {@link Package} argument + * and environment-variable definitions. + * + * @category Server Cards + */ +export interface Input { + /** + * Human-readable explanation of the input. Clients can use this to provide + * context to the user. + */ + description?: string; + + /** + * Whether the input must be supplied for the package to run. + */ + isRequired?: boolean; + + /** + * Whether the input is a secret value (e.g., password, token). If true, + * clients should handle the value securely. + */ + isSecret?: boolean; + + /** + * Specifies the input format. `"filepath"` should be interpreted as a file + * on the user's filesystem. When the input is converted to a string, + * booleans should be represented by `"true"`/`"false"`, and numbers by + * decimal values. + */ + format?: "string" | "number" | "boolean" | "filepath"; + + /** + * Default value for the input. SHOULD be a valid value for the input. + */ + default?: string; + + /** + * Placeholder displayed during configuration to provide examples or + * guidance about the expected form of the input. + */ + placeholder?: string; + + /** + * Pre-set value for the input. If set, the value should not be configurable + * by end users. Identifiers wrapped in `{curly_braces}` will be replaced + * with the corresponding entries from the input's `variables` map (if any). + */ + value?: string; + + /** + * Allowed values for the input. If provided, the user must select one. + */ + choices?: string[]; +} + +/** + * An {@link Input} whose `value` may reference variables for substitution. + * + * @category Server Cards + */ +export interface InputWithVariables extends Input { + /** + * Variables referenced by `{curly_braces}` identifiers in `value`. The map + * key is the variable name; the value defines the variable's properties. + */ + variables?: { [key: string]: Input }; +} + +/** + * A named input — used for environment variables and HTTP headers. + * + * @category Server Cards + */ +export interface KeyValueInput extends InputWithVariables { + /** + * Name of the header or environment variable. + */ + name: string; +} + +/** + * A positional command-line input — a value inserted verbatim into the + * command line. + * + * @category Server Cards + */ +export interface PositionalArgument extends InputWithVariables { + type: "positional"; + + /** + * Identifier for the positional argument. It is not part of the command + * line; it may be used by client configuration as a label identifying the + * argument, and it identifies the value in transport URL variable + * substitution. + * + * Implementations SHOULD ensure that at least one of `valueHint` or + * `value` is set so the positional argument resolves to a concrete value. + */ + valueHint?: string; + + /** + * Whether the argument can be repeated multiple times in the command line. + */ + isRepeated?: boolean; +} + +/** + * A named command-line input — a `--flag={value}` parameter. + * + * @category Server Cards + */ +export interface NamedArgument extends InputWithVariables { + type: "named"; + + /** + * The flag name, including any leading dashes (e.g., `"--port"`). + */ + name: string; + + /** + * Whether the argument can be repeated multiple times. + */ + isRepeated?: boolean; +} + +/* ---------- Inlined dependencies from the main MCP spec ---------- */ + +/** + * Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their interactions. + * + * Certain key names are reserved by MCP for protocol-level metadata; implementations MUST NOT make assumptions about values at these keys. Additionally, specific schema definitions may reserve particular names for purpose-specific metadata, as declared in those definitions. + * + * Valid keys have two segments: + * + * **Prefix:** + * - Optional — if specified, MUST be a series of _labels_ separated by dots (`.`), followed by a slash (`/`). + * - Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or hyphens (`-`). + * - Any prefix consisting of zero or more labels, followed by `modelcontextprotocol` or `mcp`, followed by any label, is **reserved** for MCP use. For example: `modelcontextprotocol.io/`, `mcp.dev/`, `api.modelcontextprotocol.org/`, and `tools.mcp.com/` are all reserved. + * + * **Name:** + * - Unless empty, MUST start and end with an alphanumeric character (`[a-z0-9A-Z]`). + * - Interior characters may be alphanumeric, hyphens (`-`), underscores (`_`), or dots (`.`). + * + * @see [General fields: `_meta`](https://modelcontextprotocol.io/specification/draft/basic#meta) for more details. + * @category Common Types + */ +export type MetaObject = Record; + +/** + * An optionally-sized icon that can be displayed in a user interface. + * + * @category Common Types + */ +export interface Icon { + /** + * A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + * `data:` URI with Base64-encoded image data. + * + * Consumers SHOULD take steps to ensure URLs serving icons are from the + * same domain as the client/server or a trusted domain. + * + * Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + * executable JavaScript. + * + * @format uri + */ + src: string; + + /** + * Optional MIME type override if the source MIME type is missing or generic. + * For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. + */ + mimeType?: string; + + /** + * Optional array of strings that specify sizes at which the icon can be used. + * Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + * + * If not provided, the client should assume that the icon can be used at any size. + */ + sizes?: string[]; + + /** + * Optional specifier for the theme this icon is designed for. `"light"` indicates + * the icon is designed to be used with a light background, and `"dark"` indicates + * the icon is designed to be used with a dark background. + * + * If not provided, the client should assume the icon can be used with any theme. + */ + theme?: "light" | "dark"; +} diff --git a/scripts/generate-schema.ts b/scripts/generate-schema.ts new file mode 100644 index 0000000..e8b19a8 --- /dev/null +++ b/scripts/generate-schema.ts @@ -0,0 +1,56 @@ +#!/usr/bin/env tsx + +import { exec } from "child_process"; +import { readFileSync, writeFileSync } from "fs"; +import { promisify } from "util"; + +const execAsync = promisify(exec); + +const SCHEMA_TS = "schema.ts"; +const SCHEMA_JSON = "schema.json"; + +const CHECK_MODE = process.argv.includes("--check"); + +/** + * Apply JSON Schema 2020-12 transformations to the generated schema string. + * + * `typescript-json-schema` emits draft-07 by default; mirror the same + * post-processing the upstream MCP spec applies to its 2020-12 schemas. + */ +function applyJsonSchema202012Transformations(content: string): string { + return content + .replace( + /http:\/\/json-schema\.org\/draft-07\/schema#/g, + "https://json-schema.org/draft/2020-12/schema", + ) + .replace(/"definitions":/g, '"$defs":') + .replace(/#\/definitions\//g, "#/$defs/"); +} + +async function generate(): Promise { + const { stdout } = await execAsync( + `npx typescript-json-schema --defaultNumberType integer --required --skipLibCheck "${SCHEMA_TS}" "*"`, + ); + return applyJsonSchema202012Transformations(stdout).trim() + "\n"; +} + +async function main(): Promise { + if (CHECK_MODE) { + const existing = readFileSync(SCHEMA_JSON, "utf-8"); + const expected = await generate(); + if (existing.trim() !== expected.trim()) { + console.error(`✗ ${SCHEMA_JSON} is out of date. Run: npm run generate`); + process.exit(1); + } + console.log(`✓ ${SCHEMA_JSON} is up to date`); + } else { + const content = await generate(); + writeFileSync(SCHEMA_JSON, content, "utf-8"); + console.log(`✓ Generated ${SCHEMA_JSON}`); + } +} + +main().catch((error) => { + console.error("Schema generation failed:", error); + process.exit(1); +}); diff --git a/scripts/validate-examples.ts b/scripts/validate-examples.ts new file mode 100644 index 0000000..1b23adb --- /dev/null +++ b/scripts/validate-examples.ts @@ -0,0 +1,117 @@ +#!/usr/bin/env tsx + +/** + * Validate the example documents under `examples/` against the generated + * `schema.json`. Each example is run against either `#/$defs/ServerCard` or + * `#/$defs/Server` based on the directory it lives under, and against the + * schema's expected acceptance — `valid/` examples must validate cleanly, + * `invalid/` examples must fail at least one constraint. + */ + +import Ajv2020 from "ajv/dist/2020.js"; +import addFormats from "ajv-formats"; +import { readdirSync, readFileSync, statSync } from "fs"; +import { join } from "path"; + +const SCHEMA_JSON = "schema.json"; +const EXAMPLES_DIR = "examples"; + +type Outcome = { + name: string; + expected: "valid" | "invalid"; + passed: boolean; + message: string; +}; + +function loadSchema() { + const schema = JSON.parse(readFileSync(SCHEMA_JSON, "utf-8")); + const ajv = new Ajv2020({ allErrors: true, strict: false }); + addFormats.default(ajv); + ajv.addSchema(schema, "schema.json"); + return ajv; +} + +function listJsonFiles(dir: string): string[] { + try { + return readdirSync(dir) + .filter((f) => f.endsWith(".json")) + .map((f) => join(dir, f)); + } catch { + return []; + } +} + +function runDirectory( + ajv: Ajv2020, + defName: "ServerCard" | "Server", +): Outcome[] { + const root = join(EXAMPLES_DIR, defName); + const outcomes: Outcome[] = []; + if (!safeIsDir(root)) return outcomes; + + const validate = ajv.getSchema(`schema.json#/$defs/${defName}`); + if (!validate) { + throw new Error(`Could not resolve ${defName} schema in ${SCHEMA_JSON}`); + } + + for (const expected of ["valid", "invalid"] as const) { + const dir = join(root, expected); + for (const file of listJsonFiles(dir)) { + const data = JSON.parse(readFileSync(file, "utf-8")); + const ok = validate(data); + const passed = expected === "valid" ? !!ok : !ok; + const errors = validate.errors ?? []; + outcomes.push({ + name: `${defName}/${expected}/${file.split("/").pop()}`, + expected, + passed, + message: passed + ? expected === "valid" + ? "validated cleanly" + : `rejected (${errors.length} error(s))` + : expected === "valid" + ? `unexpectedly invalid: ${ajv.errorsText(errors)}` + : `unexpectedly valid (no errors raised)`, + }); + } + } + + return outcomes; +} + +function safeIsDir(p: string): boolean { + try { + return statSync(p).isDirectory(); + } catch { + return false; + } +} + +function main(): void { + const ajv = loadSchema(); + const outcomes = [ + ...runDirectory(ajv, "ServerCard"), + ...runDirectory(ajv, "Server"), + ]; + + if (outcomes.length === 0) { + console.log(`No examples found under ${EXAMPLES_DIR}/. Skipping.`); + return; + } + + let failed = 0; + for (const o of outcomes) { + const status = o.passed ? "✓" : "✗"; + console.log(`${status} ${o.name} — ${o.message}`); + if (!o.passed) failed++; + } + + console.log(); + if (failed > 0) { + console.error(`${failed} of ${outcomes.length} example(s) failed.`); + process.exit(1); + } + console.log(`All ${outcomes.length} example(s) passed.`); +} + +main(); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ce3f3bc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "noEmit": true, + "target": "es2016", + "rootDir": ".", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["schema.ts"] +}