chore(deps): bump conformance to 0.1.15, port auth/pre-registration from v1.x (#1650)
This commit is contained in:
parent
8cdb4bb402
commit
54051729c6
5 changed files with 234 additions and 31 deletions
5
test/conformance/expected-failures.yaml
Normal file
5
test/conformance/expected-failures.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Conformance scenarios not yet implemented in the v2 TypeScript SDK.
|
||||
# CI exits 0 if only these fail, exits 1 on unexpected failures or stale entries.
|
||||
|
||||
client:
|
||||
- auth/cross-app-access-complete-flow
|
||||
|
|
@ -28,16 +28,16 @@
|
|||
"start": "npm run server",
|
||||
"server": "tsx watch --clear-screen=false scripts/cli.ts server",
|
||||
"client": "tsx scripts/cli.ts client",
|
||||
"test:conformance:client": "conformance client --command 'npx tsx ./src/everythingClient.ts'",
|
||||
"test:conformance:client:all": "conformance client --command 'npx tsx ./src/everythingClient.ts' --suite all",
|
||||
"test:conformance:client": "conformance client --command 'npx tsx ./src/everythingClient.ts' --expected-failures ./expected-failures.yaml",
|
||||
"test:conformance:client:all": "conformance client --command 'npx tsx ./src/everythingClient.ts' --suite all --expected-failures ./expected-failures.yaml",
|
||||
"test:conformance:client:run": "npx tsx ./src/everythingClient.ts",
|
||||
"test:conformance:server": "scripts/run-server-conformance.sh",
|
||||
"test:conformance:server:all": "scripts/run-server-conformance.sh --suite all",
|
||||
"test:conformance:server": "scripts/run-server-conformance.sh --expected-failures ./expected-failures.yaml",
|
||||
"test:conformance:server:all": "scripts/run-server-conformance.sh --suite all --expected-failures ./expected-failures.yaml",
|
||||
"test:conformance:server:run": "npx tsx ./src/everythingServer.ts",
|
||||
"test:conformance:all": "pnpm run test:conformance:client:all && pnpm run test:conformance:server:all"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modelcontextprotocol/conformance": "0.1.10",
|
||||
"@modelcontextprotocol/conformance": "0.1.15",
|
||||
"@modelcontextprotocol/client": "workspace:^",
|
||||
"@modelcontextprotocol/server": "workspace:^",
|
||||
"@modelcontextprotocol/core": "workspace:^",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
import { Client, ClientCredentialsProvider, PrivateKeyJwtProvider, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
||||
import * as z from 'zod/v4';
|
||||
|
||||
import { ConformanceOAuthProvider } from './helpers/conformanceOAuthProvider.js';
|
||||
import { logger } from './helpers/logger.js';
|
||||
import { handle401, withOAuthRetry } from './helpers/withOAuthRetry.js';
|
||||
|
||||
|
|
@ -42,6 +43,11 @@ const ClientConformanceContextSchema = z.discriminatedUnion('name', [
|
|||
name: z.literal('auth/client-credentials-basic'),
|
||||
client_id: z.string(),
|
||||
client_secret: z.string()
|
||||
}),
|
||||
z.object({
|
||||
name: z.literal('auth/pre-registration'),
|
||||
client_id: z.string(),
|
||||
client_secret: z.string()
|
||||
})
|
||||
]);
|
||||
|
||||
|
|
@ -240,6 +246,43 @@ async function runClientCredentialsBasic(serverUrl: string): Promise<void> {
|
|||
|
||||
registerScenario('auth/client-credentials-basic', runClientCredentialsBasic);
|
||||
|
||||
// ============================================================================
|
||||
// Pre-registration scenario (no dynamic client registration)
|
||||
// ============================================================================
|
||||
|
||||
async function runPreRegistrationClient(serverUrl: string): Promise<void> {
|
||||
const ctx = parseContext();
|
||||
if (ctx.name !== 'auth/pre-registration') {
|
||||
throw new Error(`Expected pre-registration context, got ${ctx.name}`);
|
||||
}
|
||||
|
||||
// Create a provider pre-populated with registered credentials,
|
||||
// so the SDK skips dynamic client registration.
|
||||
const provider = new ConformanceOAuthProvider('http://localhost:3000/callback', {
|
||||
client_name: 'conformance-pre-registration',
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
});
|
||||
provider.saveClientInformation({
|
||||
client_id: ctx.client_id,
|
||||
client_secret: ctx.client_secret,
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
});
|
||||
|
||||
const oauthFetch = withOAuthRetry('conformance-pre-registration', new URL(serverUrl), handle401, undefined, provider)(fetch);
|
||||
|
||||
const client = new Client({ name: 'conformance-pre-registration', version: '1.0.0' }, { capabilities: {} });
|
||||
const transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
|
||||
fetch: oauthFetch
|
||||
});
|
||||
|
||||
await client.connect(transport);
|
||||
await client.listTools();
|
||||
await client.callTool({ name: 'test-tool', arguments: {} });
|
||||
await transport.close();
|
||||
}
|
||||
|
||||
registerScenario('auth/pre-registration', runPreRegistrationClient);
|
||||
|
||||
// ============================================================================
|
||||
// Elicitation defaults scenario
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { localhostHostValidation } from '@modelcontextprotocol/express';
|
||||
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
|
||||
import type { CallToolResult, EventId, EventStore, GetPromptResult, ReadResourceResult, StreamId } from '@modelcontextprotocol/server';
|
||||
import { isInitializeRequest, McpServer, ResourceTemplate } from '@modelcontextprotocol/server';
|
||||
|
|
@ -876,6 +877,9 @@ function createMcpServer() {
|
|||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// DNS rebinding protection: reject non-localhost Host headers
|
||||
app.use(localhostHostValidation());
|
||||
|
||||
// Configure CORS to expose Mcp-Session-Id header for browser-based clients
|
||||
app.use(
|
||||
cors({
|
||||
|
|
|
|||
|
|
@ -55,16 +55,19 @@ export const withOAuthRetry = (
|
|||
clientName: string,
|
||||
baseUrl?: string | URL,
|
||||
handle401Fn: typeof handle401 = handle401,
|
||||
clientMetadataUrl?: string
|
||||
clientMetadataUrl?: string,
|
||||
existingProvider?: ConformanceOAuthProvider
|
||||
): Middleware => {
|
||||
const provider = new ConformanceOAuthProvider(
|
||||
'http://localhost:3000/callback',
|
||||
{
|
||||
client_name: clientName,
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
},
|
||||
clientMetadataUrl
|
||||
);
|
||||
const provider =
|
||||
existingProvider ??
|
||||
new ConformanceOAuthProvider(
|
||||
'http://localhost:3000/callback',
|
||||
{
|
||||
client_name: clientName,
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
},
|
||||
clientMetadataUrl
|
||||
);
|
||||
return (next: FetchLike) => {
|
||||
return async (input: string | URL, init?: RequestInit): Promise<Response> => {
|
||||
const makeRequest = async (): Promise<Response> => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue