refactor: move the schema source modules into @modelcontextprotocol/core (#2477)
This commit is contained in:
parent
7c49b47fb3
commit
8e1d2e92b1
54 changed files with 3597 additions and 2914 deletions
|
|
@ -2,7 +2,14 @@
|
|||
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "modelcontextprotocol/typescript-sdk" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"fixed": [
|
||||
[
|
||||
"@modelcontextprotocol/core",
|
||||
"@modelcontextprotocol/client",
|
||||
"@modelcontextprotocol/server",
|
||||
"@modelcontextprotocol/server-legacy"
|
||||
]
|
||||
],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
|
|
|
|||
8
.changeset/schemas-source-home.md
Normal file
8
.changeset/schemas-source-home.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
'@modelcontextprotocol/core': minor
|
||||
'@modelcontextprotocol/client': minor
|
||||
'@modelcontextprotocol/server': minor
|
||||
'@modelcontextprotocol/server-legacy': minor
|
||||
---
|
||||
|
||||
Move the schema source modules (spec schemas, OAuth schemas, protocol constants) into `@modelcontextprotocol/core` and resolve them from there as a regular runtime dependency instead of bundling a private copy into each package. An application importing more than one of the packages now evaluates a single shared schema graph with shared object identity. `@modelcontextprotocol/core` gains a `./internal` subpath (SDK-internal contract; may change in any release) and the four packages now version together.
|
||||
19
CLAUDE.md
19
CLAUDE.md
|
|
@ -44,7 +44,7 @@ Include what changed, why, and how to migrate. Search for related sections and g
|
|||
|
||||
### JSDoc `@example` Code Snippets
|
||||
|
||||
JSDoc `@example` tags should pull type-checked code from companion `.examples.ts` files (e.g., `client.ts` → `client.examples.ts`). Use `` ```ts source="./file.examples.ts#regionName" `` fences referencing `//#region regionName` blocks; region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `applyMiddlewares_basicUsage`, `Client_connect_basicUsage`). For whole-file inclusion (any file type), omit the `#regionName`.
|
||||
JSDoc `@example` tags should pull type-checked code from companion `.examples.ts` files (e.g., `client.ts` → `client.examples.ts`). Use ` ```ts source="./file.examples.ts#regionName" ` fences referencing `//#region regionName` blocks; region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `applyMiddlewares_basicUsage`, `Client_connect_basicUsage`). For whole-file inclusion (any file type), omit the `#regionName`.
|
||||
|
||||
Run `pnpm sync:snippets` to sync example content into JSDoc comments and markdown files.
|
||||
|
||||
|
|
@ -70,9 +70,10 @@ The SDK separates internal code from the public API surface:
|
|||
- **`@modelcontextprotocol/core-internal`** (main entry, `packages/core-internal/src/index.ts`) — Internal barrel. Exports everything (including Zod schemas, Protocol class, stdio utils). Only consumed by sibling packages within the monorepo (`private: true`).
|
||||
- **`@modelcontextprotocol/core-internal/public`** (`packages/core-internal/src/exports/public/index.ts`) — Curated public API. Exports only TypeScript types, error classes, constants, and guards. Re-exported by client and server packages.
|
||||
- **`@modelcontextprotocol/client`** and **`@modelcontextprotocol/server`** (`packages/*/src/index.ts`) — Final public surface. Package-specific exports (named explicitly) plus re-exports from `core-internal/public`.
|
||||
- **`@modelcontextprotocol/core`** (`packages/core/src/index.ts`) — Public Zod-schema package. Re-exports **only** the `*Schema` Zod constants (MCP spec + OAuth/OpenID), bundled from `core-internal` at build time. The published home for raw runtime validation (`CallToolResultSchema.parse(...)`); runtime-neutral (`zod` is its only dependency). Not consumed by the sibling packages — `client`/`server` keep their own bundled schema copies and stay Zod-free in their public surface.
|
||||
- **`@modelcontextprotocol/core`** (`packages/core/src/index.ts`) — Public Zod-schema package and the canonical home of the schema source modules (`src/schemas.ts`, `src/auth.ts`, `src/constants.ts`). The root entry re-exports **only** the `*Schema` Zod constants (MCP spec + OAuth/OpenID) — the published home for raw runtime validation (`CallToolResultSchema.parse(...)`); runtime-neutral (`zod` is its only dependency). The `./internal` subpath re-exports the schema modules wholesale for the sibling packages: `core-internal` re-exports them at the old module paths, and the `client`/`server`/`server-legacy` bundles resolve `@modelcontextprotocol/core/internal` as a real external dependency instead of carrying their own schema copies (their public surfaces stay Zod-free).
|
||||
|
||||
When modifying exports:
|
||||
|
||||
- Use explicit named exports, not `export *`, in package `index.ts` files and `core-internal/public`.
|
||||
- Adding a symbol to a package `index.ts` makes it public API — do so intentionally.
|
||||
- Internal helpers should stay in the core internal barrel and not be added to `core-internal/public` or package index files.
|
||||
|
|
@ -197,14 +198,14 @@ The `ctx` parameter in handlers provides a structured context:
|
|||
|
||||
- `sessionId?`: Transport session identifier
|
||||
- `mcpReq`: Request-level concerns
|
||||
- `id`: JSON-RPC message ID
|
||||
- `method`: Request method string (e.g., 'tools/call')
|
||||
- `_meta?`: Request metadata
|
||||
- `signal`: AbortSignal for cancellation
|
||||
- `send(request, schema, options?)`: Send related request (for bidirectional flows)
|
||||
- `notify(notification)`: Send related notification back
|
||||
- `id`: JSON-RPC message ID
|
||||
- `method`: Request method string (e.g., 'tools/call')
|
||||
- `_meta?`: Request metadata
|
||||
- `signal`: AbortSignal for cancellation
|
||||
- `send(request, schema, options?)`: Send related request (for bidirectional flows)
|
||||
- `notify(notification)`: Send related notification back
|
||||
- `http?`: HTTP transport info (undefined for stdio)
|
||||
- `authInfo?`: Validated auth token info
|
||||
- `authInfo?`: Validated auth token info
|
||||
|
||||
**`ServerContext`** extends `BaseContext.mcpReq` and `BaseContext.http?` via type intersection:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
shape: how-to
|
||||
---
|
||||
|
||||
# Wire schemas
|
||||
|
||||
`@modelcontextprotocol/core` exports the **wire schemas** — the exact Zod constants the SDK validates protocol and OAuth payloads against — for code that holds raw JSON instead of SDK objects.
|
||||
|
|
@ -56,7 +57,7 @@ These are the `*Schema` constants v1 exported from `@modelcontextprotocol/sdk/ty
|
|||
|
||||
If you build with `McpServer` or `Client`, skip this package: [tools](../servers/tools.md) arrive in your handler already validated, and [tool calls](../clients/calling.md) come back as typed results. Reach for `@modelcontextprotocol/core` when nothing stands between you and the JSON — gateways, proxies, test harnesses, [worker fleets](./gateway.md).
|
||||
|
||||
Install it separately (`npm install @modelcontextprotocol/core`) — `@modelcontextprotocol/server` and `@modelcontextprotocol/client` keep a Zod-free public surface and never depend on it. The package is runtime-neutral; `zod` is its only dependency.
|
||||
`@modelcontextprotocol/server` and `@modelcontextprotocol/client` keep a Zod-free public surface, but they resolve their shared schema graph from this package at runtime, so it already arrives transitively in your tree. Add it to your own `dependencies` (`npm install @modelcontextprotocol/core`) when you import from it directly. The package is runtime-neutral; `zod` is its only dependency.
|
||||
|
||||
## Pick the schema for the message you hold
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ Four adapters exist: `@modelcontextprotocol/node` for Node's built-in `http` ser
|
|||
|
||||
## Reach for `core` only to validate raw wire JSON
|
||||
|
||||
`@modelcontextprotocol/core` exports the Zod schema constants the SDK validates protocol payloads against, for code that handles raw JSON-RPC payloads itself — gateways, proxies, log pipelines. Neither `server` nor `client` exports a Zod schema, and the matching TypeScript types ship with both, so if you only call `registerTool` and `callTool` you never install it. [Wire schemas](../advanced/wire-schemas.md) is the how-to.
|
||||
`@modelcontextprotocol/core` exports the Zod schema constants the SDK validates protocol payloads against, for code that handles raw JSON-RPC payloads itself — gateways, proxies, log pipelines. Neither `server` nor `client` exports a Zod schema, and the matching TypeScript types ship with both, so if you only call `registerTool` and `callTool` you never import it directly — it arrives transitively, since `server` and `client` resolve their shared schema graph from it at runtime. [Wire schemas](../advanced/wire-schemas.md) is the how-to.
|
||||
|
||||
## Leave `server-legacy` and `codemod` to the migration guide
|
||||
|
||||
|
|
|
|||
|
|
@ -848,7 +848,7 @@ hierarchy also exposes the same check as an explicit static guard
|
|||
TypeScript — use whichever style your codebase prefers; both read the same brand.
|
||||
Fine print (applies equally to `instanceof` and `isInstance`):
|
||||
|
||||
- **Version skew** — matching needs *both* copies at a brand-aware release; against an
|
||||
- **Version skew** — matching needs _both_ copies at a brand-aware release; against an
|
||||
older copy, behavior degrades to plain prototype `instanceof` (false across bundles).
|
||||
During mixed-version rollouts, recognize errors without class identity: match
|
||||
`error.name` plus the class's discriminant field (`code`, `status`), or reconstruct
|
||||
|
|
@ -1325,8 +1325,9 @@ if (CallToolResultSchema.safeParse(value).success) { ... }
|
|||
|
||||
`@modelcontextprotocol/core` is the canonical home for the spec's Zod schema constants
|
||||
(and the OAuth/OpenID metadata schemas). It is runtime-neutral (its only dependency is
|
||||
`zod`) and is **not** required by `client` / `server` — install it only if you import the
|
||||
raw schemas directly.
|
||||
`zod`) and arrives transitively as the shared runtime schema graph of `client` /
|
||||
`server` — add it to your own `dependencies` only when you import the raw schemas
|
||||
directly.
|
||||
|
||||
If you would rather keep your project Zod-free, the **`isSpecType` / `specTypeSchemas`**
|
||||
alternatives are exported from `@modelcontextprotocol/client` and `…/server`:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
"@modelcontextprotocol/core-internal": [
|
||||
"./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core-internal/src/index.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core/internal": [
|
||||
"./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core/src/internal.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core-internal/public": [
|
||||
"./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
"@modelcontextprotocol/core-internal": [
|
||||
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core/internal": [
|
||||
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/internal.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core-internal/public": [
|
||||
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
"@modelcontextprotocol/core-internal": [
|
||||
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core/internal": [
|
||||
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/internal.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core-internal/public": [
|
||||
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -22,15 +22,10 @@
|
|||
"@modelcontextprotocol/core-internal": [
|
||||
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core/internal": ["./node_modules/@modelcontextprotocol/core/src/internal.ts"],
|
||||
"@modelcontextprotocol/core-internal/public": [
|
||||
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core-internal/schemas": [
|
||||
"./node_modules/@modelcontextprotocol/core/node_modules/@modelcontextprotocol/core-internal/src/types/schemas.ts"
|
||||
],
|
||||
"@modelcontextprotocol/core-internal/auth": [
|
||||
"./node_modules/@modelcontextprotocol/core/node_modules/@modelcontextprotocol/core-internal/src/shared/auth.ts"
|
||||
],
|
||||
"@mcp-examples/shared": ["./node_modules/@mcp-examples/shared/src/index.ts"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@
|
|||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/core": "workspace:*",
|
||||
"cross-spawn": "catalog:runtimeClientOnly",
|
||||
"eventsource": "catalog:runtimeClientOnly",
|
||||
"eventsource-parser": "catalog:runtimeClientOnly",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { beforeAll, describe, expect, test } from 'vitest';
|
||||
|
||||
import { ensureBuilt } from '../helpers/ensureBuilt';
|
||||
|
||||
const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const distDir = join(pkgDir, 'dist');
|
||||
const requireDist = createRequire(join(pkgDir, 'package.json'));
|
||||
|
|
@ -37,11 +38,9 @@ function rootExportBlockOf(content: string): string {
|
|||
}
|
||||
|
||||
describe('@modelcontextprotocol/client root entry is browser-safe', () => {
|
||||
beforeAll(() => {
|
||||
if (!existsSync(join(distDir, 'index.mjs')) || !existsSync(join(distDir, 'stdio.mjs'))) {
|
||||
execFileSync('pnpm', ['build'], { cwd: pkgDir, stdio: 'inherit' });
|
||||
}
|
||||
}, 60_000);
|
||||
beforeAll(async () => {
|
||||
await ensureBuilt(pkgDir);
|
||||
}, 180_000);
|
||||
|
||||
test('dist/index.mjs contains no process-spawning runtime imports', () => {
|
||||
const entry = join(distDir, 'index.mjs');
|
||||
|
|
|
|||
168
packages/client/test/client/coreBoundary.test.ts
Normal file
168
packages/client/test/client/coreBoundary.test.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
/**
|
||||
* Schema-module boundary pins: the built client resolves its schemas from
|
||||
* @modelcontextprotocol/core instead of carrying its own copies.
|
||||
*
|
||||
* The client bundle keeps `@modelcontextprotocol/core/internal` as an external
|
||||
* runtime import (see tsdown.config.ts), so two things can silently go wrong:
|
||||
*
|
||||
* 1. Skew — the client dist imports a name that core's built entries no longer
|
||||
* export (e.g. a schema added to core-internal's shims without adding it to
|
||||
* core, or a rename that only landed on one side). That fails at consumer
|
||||
* runtime, not at build time.
|
||||
* 2. Re-inlining — a config change (dropping the `external` entry, a paths
|
||||
* alias resolving too early) makes the bundler inline the schema sources
|
||||
* again, duplicating hundreds of Zod schemas into every sibling package.
|
||||
*
|
||||
* Both directions are asserted against the real build outputs. Only the ESM
|
||||
* chunks are parsed; the CJS build is produced from the same module graph, so
|
||||
* skew shows up identically in both.
|
||||
*/
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { beforeAll, describe, expect, test } from 'vitest';
|
||||
|
||||
import { ensureBuilt } from '../helpers/ensureBuilt';
|
||||
|
||||
const clientPkgDir = join(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const clientDistDir = join(clientPkgDir, 'dist');
|
||||
const corePkgDir = join(clientPkgDir, '..', 'core');
|
||||
const coreDistDir = join(corePkgDir, 'dist');
|
||||
|
||||
/** Built core entry file per import specifier the client may use. */
|
||||
const CORE_ENTRIES: Record<string, string> = {
|
||||
'@modelcontextprotocol/core': 'index.mjs',
|
||||
'@modelcontextprotocol/core/internal': 'internal.mjs'
|
||||
};
|
||||
|
||||
/**
|
||||
* Sentinel schemas that exist ONLY in core's source modules. The frozen
|
||||
* wire-era modules (bundled into the client on purpose) define their own
|
||||
* copies of many spec schema names, but not these — so a `const` definition
|
||||
* of any of them inside the client dist can only mean the neutral schema
|
||||
* modules got re-inlined. The first two are names the client actually uses,
|
||||
* so they must also show up as imports; SafeUrlSchema is unused by the client
|
||||
* (legitimately tree-shaken away) and is pinned as never-defined only.
|
||||
*/
|
||||
const IMPORTED_SENTINELS = ['OAuthMetadataSchema', 'JSONRPCMessageSchema'];
|
||||
const NEVER_DEFINED_SENTINELS = [...IMPORTED_SENTINELS, 'SafeUrlSchema'];
|
||||
|
||||
function clientChunks(): string[] {
|
||||
return readdirSync(clientDistDir, { recursive: true })
|
||||
.map(String)
|
||||
.filter(f => f.endsWith('.mjs'));
|
||||
}
|
||||
|
||||
/** Exported names of a built core entry (its trailing `export { a, b as c };` blocks). */
|
||||
function coreExportedNames(entryFile: string): Set<string> {
|
||||
const src = readFileSync(join(coreDistDir, entryFile), 'utf8');
|
||||
const blocks = [...src.matchAll(/export \{([\s\S]*?)\}/g)];
|
||||
expect(blocks.length, `no export block found in core dist/${entryFile}`).toBeGreaterThan(0);
|
||||
const names = new Set<string>();
|
||||
for (const block of blocks) {
|
||||
for (const entry of block[1]!.split(',')) {
|
||||
const name = entry
|
||||
.trim()
|
||||
.split(/\s+as\s+/)
|
||||
.pop()
|
||||
?.trim();
|
||||
if (name) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/** All names a client chunk pulls from a core specifier, keyed by specifier. */
|
||||
function coreImportsOf(chunkSource: string): Map<string, Set<string>> {
|
||||
const imports = new Map<string, Set<string>>();
|
||||
// import { A, B as C } from "@modelcontextprotocol/core[/internal]"
|
||||
// export { A, B as C } from "@modelcontextprotocol/core[/internal]"
|
||||
for (const m of chunkSource.matchAll(
|
||||
/(?:import|export)\s*\{([^}]*)\}\s*from\s*["'](@modelcontextprotocol\/core(?:\/internal)?)["']/g
|
||||
)) {
|
||||
const names = imports.get(m[2]!) ?? new Set<string>();
|
||||
for (const entry of m[1]!.split(',')) {
|
||||
// In both clause forms the name resolved against core is the one BEFORE `as`.
|
||||
const name = entry
|
||||
.trim()
|
||||
.split(/\s+as\s+/)[0]
|
||||
?.trim();
|
||||
if (name) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
imports.set(m[2]!, names);
|
||||
}
|
||||
return imports;
|
||||
}
|
||||
|
||||
describe('@modelcontextprotocol/client ↔ core schema boundary', () => {
|
||||
beforeAll(async () => {
|
||||
await ensureBuilt(corePkgDir);
|
||||
await ensureBuilt(clientPkgDir);
|
||||
}, 240_000);
|
||||
|
||||
test('every name the client dist imports from core resolves against core’s built exports', () => {
|
||||
let sawCoreImport = false;
|
||||
for (const chunk of clientChunks()) {
|
||||
const source = readFileSync(join(clientDistDir, chunk), 'utf8');
|
||||
for (const [specifier, names] of coreImportsOf(source)) {
|
||||
sawCoreImport = true;
|
||||
const entryFile = CORE_ENTRIES[specifier];
|
||||
expect(entryFile, `client dist/${chunk} imports unknown core subpath ${specifier}`).toBeDefined();
|
||||
const exported = coreExportedNames(entryFile!);
|
||||
const missing = [...names].filter(name => !exported.has(name));
|
||||
expect(
|
||||
missing,
|
||||
`client dist/${chunk} imports names from ${specifier} that core's built ${entryFile} does not export`
|
||||
).toEqual([]);
|
||||
}
|
||||
// Non-named forms (namespace/default/bare imports, `export *`) would dodge the check above.
|
||||
expect(
|
||||
source,
|
||||
`client dist/${chunk} uses a non-named import/re-export of @modelcontextprotocol/core — update this test to cover it`
|
||||
).not.toMatch(/(?:import|export)\s+(?!\{)[^;]*?from\s*["']@modelcontextprotocol\/core(?:\/internal)?["']/);
|
||||
// Bare side-effect imports have no `from` clause and would dodge the pattern above.
|
||||
expect(
|
||||
source,
|
||||
`client dist/${chunk} uses a bare side-effect import of @modelcontextprotocol/core — update this test to cover it`
|
||||
).not.toMatch(/import\s*["']@modelcontextprotocol\/core(?:\/internal)?["']/);
|
||||
}
|
||||
expect(sawCoreImport, 'no client chunk imports @modelcontextprotocol/core at all — external wiring changed?').toBe(true);
|
||||
});
|
||||
|
||||
test('the neutral schema bodies stay OUT of the client dist (imported, never re-inlined)', () => {
|
||||
const sources = clientChunks().map(chunk => ({
|
||||
chunk,
|
||||
source: readFileSync(join(clientDistDir, chunk), 'utf8')
|
||||
}));
|
||||
|
||||
const importedNames = new Set<string>();
|
||||
for (const { source } of sources) {
|
||||
for (const names of coreImportsOf(source).values()) {
|
||||
for (const name of names) {
|
||||
importedNames.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const sentinel of IMPORTED_SENTINELS) {
|
||||
expect(importedNames.has(sentinel), `${sentinel} is not imported from core by any client chunk`).toBe(true);
|
||||
}
|
||||
|
||||
for (const sentinel of NEVER_DEFINED_SENTINELS) {
|
||||
// `$N` suffix included: a re-inlined copy colliding with the import gets renamed by the
|
||||
// bundler. The `[=(]` tail covers both plain bindings and function-form definitions.
|
||||
const definition = new RegExp(`\\b(?:const|let|var|function)\\s+${sentinel}(?:\\$\\d+)?\\s*[=(]`);
|
||||
for (const { chunk, source } of sources) {
|
||||
expect(
|
||||
source,
|
||||
`client dist/${chunk} DEFINES ${sentinel} instead of importing it from @modelcontextprotocol/core — the neutral schema modules were re-inlined`
|
||||
).not.toMatch(definition);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
98
packages/client/test/helpers/ensureBuilt.ts
Normal file
98
packages/client/test/helpers/ensureBuilt.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { execFile } from 'node:child_process';
|
||||
import { existsSync, mkdirSync, renameSync, rmSync, statSync } from 'node:fs';
|
||||
import { basename, join } from 'node:path';
|
||||
import { setTimeout as sleep } from 'node:timers/promises';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
/**
|
||||
* Canonical dist sentinels per package. Every caller waits on the same
|
||||
* complete set: if callers checked their own subsets, a partial dist (from an
|
||||
* interrupted build) could satisfy one caller's fast path while another still
|
||||
* rebuilds — and the rebuild's clean step would wipe files under the first
|
||||
* caller's running tests, which is exactly the race this helper exists to
|
||||
* prevent.
|
||||
*/
|
||||
const DIST_SENTINELS: Record<string, string[]> = {
|
||||
// Include late-written artifacts (dts pass, CJS validators) so an
|
||||
// interrupted build never satisfies the fast path.
|
||||
client: ['index.mjs', 'stdio.mjs', 'validators/ajv.cjs', 'index.d.mts', 'index.d.cts'],
|
||||
core: ['index.mjs', 'internal.mjs', 'index.d.mts', 'internal.d.mts']
|
||||
};
|
||||
|
||||
/** The build is killed after this long; execFile's kill still runs our finally. */
|
||||
const BUILD_TIMEOUT_MS = 60_000;
|
||||
/** How long a waiter polls for another worker's build — outlasts BUILD_TIMEOUT_MS. */
|
||||
const WAIT_DEADLINE_MS = 90_000;
|
||||
/** A lock older than this belongs to a dead worker (finally never ran) — steal it. */
|
||||
const STALE_LOCK_MS = 120_000;
|
||||
|
||||
/**
|
||||
* Build a package's dist on demand, safely across parallel vitest workers.
|
||||
*
|
||||
* Vitest runs test files in separate worker processes, so two files that each
|
||||
* "build if dist is missing" can race on a cold checkout: both see no dist,
|
||||
* both spawn `pnpm build`, and tsdown's clean step deletes files out from
|
||||
* under whichever worker is already reading them. This helper makes the build
|
||||
* single-flight with an atomic mkdir lock: the first worker builds, everyone
|
||||
* else waits for the sentinel files to appear and the lock to clear, and once
|
||||
* the dist exists nobody ever rebuilds (so no later clean can wipe it
|
||||
* mid-read).
|
||||
*/
|
||||
export async function ensureBuilt(pkgDir: string): Promise<void> {
|
||||
const sentinels = DIST_SENTINELS[basename(pkgDir)];
|
||||
if (!sentinels) {
|
||||
throw new Error(`No dist sentinels registered for ${pkgDir} — add the package to DIST_SENTINELS`);
|
||||
}
|
||||
const lockDir = join(pkgDir, '.dist-build-lock');
|
||||
const haveAll = () => sentinels.every(s => existsSync(join(pkgDir, 'dist', s)));
|
||||
const deadline = Date.now() + WAIT_DEADLINE_MS;
|
||||
for (;;) {
|
||||
if (haveAll() && !existsSync(lockDir)) return;
|
||||
try {
|
||||
mkdirSync(lockDir);
|
||||
} catch (err) {
|
||||
if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;
|
||||
// Another worker holds the lock. If the holder died without its
|
||||
// finally running (SIGKILL/OOM), the lock never clears — steal it
|
||||
// once it is older than any live build could be.
|
||||
try {
|
||||
if (Date.now() - statSync(lockDir).mtimeMs > STALE_LOCK_MS) {
|
||||
// renameSync is atomic: exactly one waiter steals; losers throw and re-check.
|
||||
const graveyard = `${lockDir}.stale-${process.pid}-${Date.now()}`;
|
||||
renameSync(lockDir, graveyard);
|
||||
rmSync(graveyard, { recursive: true, force: true });
|
||||
continue;
|
||||
}
|
||||
} catch {
|
||||
continue; // lock vanished or another waiter stole it — re-check now
|
||||
}
|
||||
if (Date.now() > deadline) {
|
||||
throw new Error(
|
||||
`Timed out waiting for ${pkgDir}/dist (${sentinels.join(', ')}) ` +
|
||||
`while another worker held the build lock at ${lockDir}`
|
||||
);
|
||||
}
|
||||
await sleep(250);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (!haveAll()) {
|
||||
try {
|
||||
await execFileAsync('pnpm', ['build'], {
|
||||
cwd: pkgDir,
|
||||
timeout: BUILD_TIMEOUT_MS,
|
||||
maxBuffer: 16 * 1024 * 1024
|
||||
});
|
||||
} catch (err) {
|
||||
const stderr = (err as { stderr?: string }).stderr ?? '';
|
||||
throw new Error(`pnpm build failed in ${pkgDir}: ${(err as Error).message}\n${stderr.slice(-2000)}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} finally {
|
||||
rmSync(lockDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
"paths": {
|
||||
"*": ["./*"],
|
||||
"@modelcontextprotocol/core-internal": ["./node_modules/@modelcontextprotocol/core-internal/src/index.ts"],
|
||||
"@modelcontextprotocol/core/internal": ["./node_modules/@modelcontextprotocol/core/src/internal.ts"],
|
||||
"@modelcontextprotocol/core-internal/public": [
|
||||
"./node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -34,5 +34,8 @@ export default defineConfig({
|
|||
}
|
||||
},
|
||||
noExternal: ['@modelcontextprotocol/core-internal', 'ajv', 'ajv-formats', '@cfworker/json-schema'],
|
||||
external: ['@modelcontextprotocol/client/_shims']
|
||||
// The schema modules live in @modelcontextprotocol/core (a real runtime dependency); the
|
||||
// bundled core-internal shims import them via the './internal' subpath, which must stay an
|
||||
// external import (explicit entry — the tsconfig paths alias would otherwise inline it).
|
||||
external: ['@modelcontextprotocol/client/_shims', '@modelcontextprotocol/core/internal']
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ describe('AUTH_SCHEMA_NAMES (codemod auth schema-routing allowlist)', () => {
|
|||
// the rewritten import would have no exported member. AUTH_SCHEMA_NAMES is the v1 auth-schema set,
|
||||
// a SUBSET of core's auth exports: core may export more (v2-only schemas such as
|
||||
// IdJagTokenExchangeResponseSchema) that v1 never had and the codemod never encounters. Read
|
||||
// core's barrel directly (the `export { … } from '…/core-internal/auth'` block) so they cannot drift.
|
||||
// core's barrel directly (the `export { … } from './auth'` block) so they cannot drift.
|
||||
const src = readFileSync(fileURLToPath(new URL('../../../core/src/index.ts', import.meta.url)), 'utf8');
|
||||
const closeIdx = src.indexOf("} from '@modelcontextprotocol/core-internal/auth'");
|
||||
const closeIdx = src.indexOf("} from './auth'");
|
||||
const openIdx = src.lastIndexOf('export {', closeIdx);
|
||||
const block = src.slice(openIdx + 'export {'.length, closeIdx);
|
||||
const coreAuthExports = new Set([...block.matchAll(/\b(\w+Schema)\b/g)].map(m => m[1]));
|
||||
|
|
|
|||
|
|
@ -42,11 +42,17 @@ export default [
|
|||
{
|
||||
patterns: [
|
||||
{
|
||||
group: ['**/types/schemas', '**/types/schemas.js'],
|
||||
group: [
|
||||
'**/types/schemas',
|
||||
'**/types/schemas.js',
|
||||
'@modelcontextprotocol/core',
|
||||
'@modelcontextprotocol/core/*'
|
||||
],
|
||||
allowTypeImports: true,
|
||||
message:
|
||||
'Wire revision modules must be self-contained. Freeze a copy of the schema into the ' +
|
||||
'rev*/ directory instead of importing the mutable public-layer types/schemas.ts.'
|
||||
'rev*/ directory instead of importing the mutable public-layer schemas ' +
|
||||
'(types/schemas.ts or @modelcontextprotocol/core).'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/core": "workspace:*",
|
||||
"content-type": "catalog:runtimeShared",
|
||||
"json-schema-typed": "catalog:runtimeShared",
|
||||
"zod": "catalog:runtimeShared"
|
||||
|
|
|
|||
|
|
@ -1,285 +1,39 @@
|
|||
import * as z from 'zod/v4';
|
||||
|
||||
/**
|
||||
* Reusable URL validation that disallows `javascript:` scheme
|
||||
*/
|
||||
export const SafeUrlSchema = z
|
||||
.url()
|
||||
.superRefine((val, ctx) => {
|
||||
if (!URL.canParse(val)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'URL must be parseable',
|
||||
fatal: true
|
||||
});
|
||||
|
||||
return z.NEVER;
|
||||
}
|
||||
})
|
||||
.refine(
|
||||
url => {
|
||||
const u = new URL(url);
|
||||
return u.protocol !== 'javascript:' && u.protocol !== 'data:' && u.protocol !== 'vbscript:';
|
||||
},
|
||||
{ message: 'URL cannot use javascript:, data:, or vbscript: scheme' }
|
||||
);
|
||||
|
||||
/**
|
||||
* RFC 9728 OAuth Protected Resource Metadata
|
||||
*/
|
||||
export const OAuthProtectedResourceMetadataSchema = z.looseObject({
|
||||
resource: z.string().url(),
|
||||
authorization_servers: z.array(SafeUrlSchema).optional(),
|
||||
jwks_uri: z.string().url().optional(),
|
||||
scopes_supported: z.array(z.string()).optional(),
|
||||
bearer_methods_supported: z.array(z.string()).optional(),
|
||||
resource_signing_alg_values_supported: z.array(z.string()).optional(),
|
||||
resource_name: z.string().optional(),
|
||||
resource_documentation: z.string().optional(),
|
||||
resource_policy_uri: z.string().url().optional(),
|
||||
resource_tos_uri: z.string().url().optional(),
|
||||
tls_client_certificate_bound_access_tokens: z.boolean().optional(),
|
||||
authorization_details_types_supported: z.array(z.string()).optional(),
|
||||
dpop_signing_alg_values_supported: z.array(z.string()).optional(),
|
||||
dpop_bound_access_tokens_required: z.boolean().optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* RFC 8414 OAuth 2.0 Authorization Server Metadata
|
||||
*/
|
||||
export const OAuthMetadataSchema = z.looseObject({
|
||||
issuer: z.string(),
|
||||
authorization_endpoint: SafeUrlSchema,
|
||||
token_endpoint: SafeUrlSchema,
|
||||
registration_endpoint: SafeUrlSchema.optional(),
|
||||
scopes_supported: z.array(z.string()).optional(),
|
||||
response_types_supported: z.array(z.string()),
|
||||
response_modes_supported: z.array(z.string()).optional(),
|
||||
grant_types_supported: z.array(z.string()).optional(),
|
||||
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
||||
token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
|
||||
service_documentation: SafeUrlSchema.optional(),
|
||||
revocation_endpoint: SafeUrlSchema.optional(),
|
||||
revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
||||
revocation_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
|
||||
introspection_endpoint: z.string().optional(),
|
||||
introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
||||
introspection_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
|
||||
code_challenge_methods_supported: z.array(z.string()).optional(),
|
||||
client_id_metadata_document_supported: z.boolean().optional(),
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await -- Zod .catch(), not a Promise chain
|
||||
authorization_response_iss_parameter_supported: z.boolean().optional().catch(undefined)
|
||||
});
|
||||
|
||||
/**
|
||||
* OpenID Connect Discovery 1.0 Provider Metadata
|
||||
*
|
||||
* @see https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
||||
*/
|
||||
export const OpenIdProviderMetadataSchema = z.looseObject({
|
||||
issuer: z.string(),
|
||||
authorization_endpoint: SafeUrlSchema,
|
||||
token_endpoint: SafeUrlSchema,
|
||||
userinfo_endpoint: SafeUrlSchema.optional(),
|
||||
jwks_uri: SafeUrlSchema,
|
||||
registration_endpoint: SafeUrlSchema.optional(),
|
||||
scopes_supported: z.array(z.string()).optional(),
|
||||
response_types_supported: z.array(z.string()),
|
||||
response_modes_supported: z.array(z.string()).optional(),
|
||||
grant_types_supported: z.array(z.string()).optional(),
|
||||
acr_values_supported: z.array(z.string()).optional(),
|
||||
subject_types_supported: z.array(z.string()),
|
||||
id_token_signing_alg_values_supported: z.array(z.string()),
|
||||
id_token_encryption_alg_values_supported: z.array(z.string()).optional(),
|
||||
id_token_encryption_enc_values_supported: z.array(z.string()).optional(),
|
||||
userinfo_signing_alg_values_supported: z.array(z.string()).optional(),
|
||||
userinfo_encryption_alg_values_supported: z.array(z.string()).optional(),
|
||||
userinfo_encryption_enc_values_supported: z.array(z.string()).optional(),
|
||||
request_object_signing_alg_values_supported: z.array(z.string()).optional(),
|
||||
request_object_encryption_alg_values_supported: z.array(z.string()).optional(),
|
||||
request_object_encryption_enc_values_supported: z.array(z.string()).optional(),
|
||||
token_endpoint_auth_methods_supported: z.array(z.string()).optional(),
|
||||
token_endpoint_auth_signing_alg_values_supported: z.array(z.string()).optional(),
|
||||
display_values_supported: z.array(z.string()).optional(),
|
||||
claim_types_supported: z.array(z.string()).optional(),
|
||||
claims_supported: z.array(z.string()).optional(),
|
||||
service_documentation: z.string().optional(),
|
||||
claims_locales_supported: z.array(z.string()).optional(),
|
||||
ui_locales_supported: z.array(z.string()).optional(),
|
||||
claims_parameter_supported: z.boolean().optional(),
|
||||
request_parameter_supported: z.boolean().optional(),
|
||||
request_uri_parameter_supported: z.boolean().optional(),
|
||||
require_request_uri_registration: z.boolean().optional(),
|
||||
op_policy_uri: SafeUrlSchema.optional(),
|
||||
op_tos_uri: SafeUrlSchema.optional(),
|
||||
client_id_metadata_document_supported: z.boolean().optional(),
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await -- Zod .catch(), not a Promise chain
|
||||
authorization_response_iss_parameter_supported: z.boolean().optional().catch(undefined)
|
||||
});
|
||||
|
||||
/**
|
||||
* OpenID Connect Discovery metadata that may include OAuth 2.0 fields
|
||||
* This schema represents the real-world scenario where OIDC providers
|
||||
* return a mix of OpenID Connect and OAuth 2.0 metadata fields
|
||||
*/
|
||||
export const OpenIdProviderDiscoveryMetadataSchema = z.object({
|
||||
...OpenIdProviderMetadataSchema.shape,
|
||||
...OAuthMetadataSchema.pick({
|
||||
code_challenge_methods_supported: true
|
||||
}).shape
|
||||
});
|
||||
|
||||
/**
|
||||
* OAuth 2.1 token response
|
||||
*/
|
||||
export const OAuthTokensSchema = z
|
||||
.object({
|
||||
access_token: z.string(),
|
||||
id_token: z.string().optional(), // Optional for OAuth 2.1, but necessary in OpenID Connect
|
||||
token_type: z.string(),
|
||||
expires_in: z.coerce.number().optional(),
|
||||
scope: z.string().optional(),
|
||||
refresh_token: z.string().optional()
|
||||
})
|
||||
.strip();
|
||||
|
||||
/**
|
||||
* RFC 8693 §2.2.1 Token Exchange response for ID-JAG tokens.
|
||||
*
|
||||
* `token_type` is intentionally optional: per RFC 8693 §2.2.1 it is informational when
|
||||
* the issued token is not an access token, and per RFC 6749 §5.1 it is case-insensitive,
|
||||
* so strict checking rejects conformant IdPs.
|
||||
*/
|
||||
export const IdJagTokenExchangeResponseSchema = z
|
||||
.object({
|
||||
issued_token_type: z.literal('urn:ietf:params:oauth:token-type:id-jag'),
|
||||
access_token: z.string(),
|
||||
token_type: z.string().optional(),
|
||||
expires_in: z.number().optional(),
|
||||
scope: z.string().optional()
|
||||
})
|
||||
.strip();
|
||||
|
||||
export type IdJagTokenExchangeResponse = z.infer<typeof IdJagTokenExchangeResponseSchema>;
|
||||
|
||||
/**
|
||||
* OAuth 2.1 error response
|
||||
*/
|
||||
export const OAuthErrorResponseSchema = z.object({
|
||||
error: z.string(),
|
||||
error_description: z.string().optional(),
|
||||
error_uri: z.string().optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* Optional version of {@linkcode SafeUrlSchema} that allows empty string for backward compatibility on `tos_uri` and `logo_uri`
|
||||
*/
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
export const OptionalSafeUrlSchema = SafeUrlSchema.optional().or(z.literal('').transform(() => undefined));
|
||||
|
||||
/**
|
||||
* RFC 7591 OAuth 2.0 Dynamic Client Registration metadata
|
||||
*/
|
||||
export const OAuthClientMetadataSchema = z
|
||||
.object({
|
||||
redirect_uris: z.array(SafeUrlSchema),
|
||||
token_endpoint_auth_method: z.string().optional(),
|
||||
grant_types: z.array(z.string()).optional(),
|
||||
response_types: z.array(z.string()).optional(),
|
||||
/**
|
||||
* OIDC Dynamic Client Registration `application_type`. MCP clients MUST set
|
||||
* this to `'native'` or `'web'` when registering (SEP-837); the SDK defaults
|
||||
* it from `redirect_uris` when omitted. Typed as `string` (not an enum) so
|
||||
* that parsing an authorization server's registration response — which under
|
||||
* RFC 7591 may echo extension values — never rejects the document on this
|
||||
* field alone.
|
||||
*/
|
||||
application_type: z.string().optional(),
|
||||
client_name: z.string().optional(),
|
||||
client_uri: SafeUrlSchema.optional(),
|
||||
logo_uri: OptionalSafeUrlSchema,
|
||||
scope: z.string().optional(),
|
||||
contacts: z.array(z.string()).optional(),
|
||||
tos_uri: OptionalSafeUrlSchema,
|
||||
policy_uri: z.string().optional(),
|
||||
jwks_uri: SafeUrlSchema.optional(),
|
||||
jwks: z.any().optional(),
|
||||
software_id: z.string().optional(),
|
||||
software_version: z.string().optional(),
|
||||
software_statement: z.string().optional()
|
||||
})
|
||||
.strip();
|
||||
|
||||
/**
|
||||
* RFC 7591 OAuth 2.0 Dynamic Client Registration client information
|
||||
*/
|
||||
export const OAuthClientInformationSchema = z
|
||||
.object({
|
||||
client_id: z.string(),
|
||||
client_secret: z.string().optional(),
|
||||
client_id_issued_at: z.number().optional(),
|
||||
client_secret_expires_at: z.number().optional()
|
||||
})
|
||||
.strip();
|
||||
|
||||
/**
|
||||
* RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata)
|
||||
*/
|
||||
export const OAuthClientInformationFullSchema = OAuthClientMetadataSchema.merge(OAuthClientInformationSchema);
|
||||
|
||||
/**
|
||||
* RFC 7591 OAuth 2.0 Dynamic Client Registration error response
|
||||
*/
|
||||
export const OAuthClientRegistrationErrorSchema = z
|
||||
.object({
|
||||
error: z.string(),
|
||||
error_description: z.string().optional()
|
||||
})
|
||||
.strip();
|
||||
|
||||
/**
|
||||
* RFC 7009 OAuth 2.0 Token Revocation request
|
||||
*/
|
||||
export const OAuthTokenRevocationRequestSchema = z
|
||||
.object({
|
||||
token: z.string(),
|
||||
token_type_hint: z.string().optional()
|
||||
})
|
||||
.strip();
|
||||
|
||||
export type OAuthMetadata = z.infer<typeof OAuthMetadataSchema>;
|
||||
export type OpenIdProviderMetadata = z.infer<typeof OpenIdProviderMetadataSchema>;
|
||||
export type OpenIdProviderDiscoveryMetadata = z.infer<typeof OpenIdProviderDiscoveryMetadataSchema>;
|
||||
|
||||
export type OAuthTokens = z.infer<typeof OAuthTokensSchema>;
|
||||
export type OAuthErrorResponse = z.infer<typeof OAuthErrorResponseSchema>;
|
||||
export type OAuthClientMetadata = z.infer<typeof OAuthClientMetadataSchema>;
|
||||
export type OAuthClientInformation = z.infer<typeof OAuthClientInformationSchema>;
|
||||
export type OAuthClientInformationFull = z.infer<typeof OAuthClientInformationFullSchema>;
|
||||
export type OAuthClientInformationMixed = OAuthClientInformation | OAuthClientInformationFull;
|
||||
|
||||
/**
|
||||
* {@linkcode OAuthTokens} as persisted by an `OAuthClientProvider`. Adds an
|
||||
* SDK-stamped authorization-server `issuer` identifier so stored tokens are
|
||||
* bound to the AS that issued them. The `issuer` field is **not** part of the
|
||||
* RFC 6749 wire response and is intentionally absent from the wire-response
|
||||
* schema; the client SDK writes it before calling `saveTokens`.
|
||||
*/
|
||||
export type StoredOAuthTokens = OAuthTokens & { issuer?: string };
|
||||
|
||||
/**
|
||||
* {@linkcode OAuthClientInformationMixed} as persisted by an
|
||||
* `OAuthClientProvider`. Adds an SDK-stamped authorization-server `issuer`
|
||||
* identifier so stored client credentials are bound to the AS that issued them.
|
||||
* The `issuer` field is **not** part of the RFC 7591 wire response and is
|
||||
* intentionally absent from the wire-response schema; the client SDK writes it
|
||||
* before calling `saveClientInformation`.
|
||||
*/
|
||||
export type StoredOAuthClientInformation = OAuthClientInformationMixed & { issuer?: string };
|
||||
|
||||
export type OAuthClientRegistrationError = z.infer<typeof OAuthClientRegistrationErrorSchema>;
|
||||
export type OAuthTokenRevocationRequest = z.infer<typeof OAuthTokenRevocationRequestSchema>;
|
||||
export type OAuthProtectedResourceMetadata = z.infer<typeof OAuthProtectedResourceMetadataSchema>;
|
||||
|
||||
// Unified type for authorization server metadata
|
||||
export type AuthorizationServerMetadata = OAuthMetadata | OpenIdProviderDiscoveryMetadata;
|
||||
// Moved: the OAuth/OpenID schemas + types now live in @modelcontextprotocol/core (packages/core/src/auth.ts).
|
||||
// This module re-exports them one-to-one so every existing import path keeps working.
|
||||
//
|
||||
// Add new schemas in packages/core/src/auth.ts, never here — this file only forwards, and
|
||||
// core-internal's schemaShims test enforces that it stays free of zod imports and definitions.
|
||||
export type {
|
||||
AuthorizationServerMetadata,
|
||||
IdJagTokenExchangeResponse,
|
||||
OAuthClientInformation,
|
||||
OAuthClientInformationFull,
|
||||
OAuthClientInformationMixed,
|
||||
OAuthClientMetadata,
|
||||
OAuthClientRegistrationError,
|
||||
OAuthErrorResponse,
|
||||
OAuthMetadata,
|
||||
OAuthProtectedResourceMetadata,
|
||||
OAuthTokenRevocationRequest,
|
||||
OAuthTokens,
|
||||
OpenIdProviderDiscoveryMetadata,
|
||||
OpenIdProviderMetadata,
|
||||
StoredOAuthClientInformation,
|
||||
StoredOAuthTokens
|
||||
} from '@modelcontextprotocol/core/internal';
|
||||
export {
|
||||
IdJagTokenExchangeResponseSchema,
|
||||
OAuthClientInformationFullSchema,
|
||||
OAuthClientInformationSchema,
|
||||
OAuthClientMetadataSchema,
|
||||
OAuthClientRegistrationErrorSchema,
|
||||
OAuthErrorResponseSchema,
|
||||
OAuthMetadataSchema,
|
||||
OAuthProtectedResourceMetadataSchema,
|
||||
OAuthTokenRevocationRequestSchema,
|
||||
OAuthTokensSchema,
|
||||
OpenIdProviderDiscoveryMetadataSchema,
|
||||
OpenIdProviderMetadataSchema,
|
||||
OptionalSafeUrlSchema,
|
||||
SafeUrlSchema
|
||||
} from '@modelcontextprotocol/core/internal';
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue