Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- Restored the MCP SDK v1 client for compatibility with deployed MCP servers and OAuth providers. This rollback temporarily removes SDK v2-only protocol negotiation while retaining OAuth issuer binding and callback issuer validation. See issue #236.

## [2.15.0] - 2026-07-25

### Added
Expand Down
6 changes: 3 additions & 3 deletions OAUTH.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,15 @@ The OAuth implementation uses the following modules:

## SDK Integration

The implementation uses these MCP SDK v2 exports:
The implementation uses the MCP SDK v1 client and OAuth APIs:

```typescript
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import {
auth,
StreamableHTTPClientTransport,
UnauthorizedError,
type OAuthClientProvider,
} from "@modelcontextprotocol/client"
} from "@modelcontextprotocol/sdk/client/auth.js"
```

The `McpOAuthProvider` class implements `OAuthClientProvider` and is passed to `StreamableHTTPClientTransport`:
Expand Down
2 changes: 2 additions & 0 deletions __tests__/abort-signal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe("AbortSignal propagation", () => {
expect(result.details.error).toBe("aborted");
expect(callTool).toHaveBeenCalledWith(
{ name: "slow", arguments: {}, _meta: undefined },
undefined,
{ signal: controller.signal },
);
expect(state.manager.decrementInFlight).toHaveBeenCalledWith("demo");
Expand All @@ -90,6 +91,7 @@ describe("AbortSignal propagation", () => {
expect(result.details.error).toBe("aborted");
expect(callTool).toHaveBeenCalledWith(
{ name: "slow", arguments: {}, _meta: undefined },
undefined,
{ signal: controller.signal },
);
expect(state.manager.decrementInFlight).toHaveBeenCalledWith("demo");
Expand Down
4 changes: 3 additions & 1 deletion __tests__/direct-tools-auto-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ describe("direct tools auto auth", () => {
arguments: { q: "hello" },
_meta: undefined,
},
undefined,
{ timeout: 4321 },
);
expect(result.content[0].text).toContain("ok");
Expand Down Expand Up @@ -156,6 +157,7 @@ describe("direct tools auto auth", () => {
expect(state.manager.getRequestOptions).toHaveBeenCalledWith("demo", controller.signal);
expect(connection.client.callTool).toHaveBeenCalledWith(
{ name: "search", arguments: {}, _meta: undefined },
undefined,
requestOptions,
);
expect(result.details).toMatchObject({ error: "aborted", server: "demo" });
Expand Down Expand Up @@ -244,7 +246,7 @@ describe("direct tools auto auth", () => {
});

it("runs URL elicitations returned by a URL-required tool error", async () => {
const { UrlElicitationRequiredError } = await import("@modelcontextprotocol/client");
const { UrlElicitationRequiredError } = await import("@modelcontextprotocol/sdk/types.js");
const { createDirectToolExecutor } = await import("../direct-tools.ts");
const error = new UrlElicitationRequiredError([{
mode: "url",
Expand Down
2 changes: 1 addition & 1 deletion __tests__/elicitation-handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ElicitRequest } from "@modelcontextprotocol/client";
import type { ElicitRequest } from "@modelcontextprotocol/sdk/types.js";

const mocks = vi.hoisted(() => ({
open: vi.fn(async () => undefined),
Expand Down
9 changes: 5 additions & 4 deletions __tests__/fixtures/delayed-mcp-server.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { rename, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { Server } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListResourcesRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";

const pidPath = process.env.MCP_RELOAD_PID_DIR ? join(process.env.MCP_RELOAD_PID_DIR, `${process.pid}.json`) : undefined;
const identity = { pid: process.pid, toolName: "reload_identity" };
Expand All @@ -18,11 +19,11 @@ const server = new Server(
{ name: "delayed-reload-fixture", version: "1.0.0" },
{ capabilities: { tools: {}, resources: {} } },
);
server.setRequestHandler("tools/list", async () => {
server.setRequestHandler(ListToolsRequestSchema, async () => {
await new Promise(resolve => setTimeout(resolve, 100));
return {
tools: [{ name: identity.toolName, description: "reload identity", inputSchema: { type: "object", properties: {} } }],
};
});
server.setRequestHandler("resources/list", async () => ({ resources: [] }));
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
await server.connect(new StdioServerTransport());
54 changes: 34 additions & 20 deletions __tests__/fixtures/elicitation-server.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Server, UrlElicitationRequiredError } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ElicitResultSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
UrlElicitationRequiredError,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
{ name: "elicitation-integration-server", version: "1.0.0" },
Expand All @@ -15,7 +23,7 @@ function urlRequiredError() {
}]);
}

server.setRequestHandler("tools/list", async () => ({
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{ name: "capabilities", inputSchema: { type: "object", properties: {} } },
{ name: "form", inputSchema: { type: "object", properties: {} } },
Expand All @@ -24,21 +32,21 @@ server.setRequestHandler("tools/list", async () => ({
],
}));

server.setRequestHandler("resources/list", async () => ({
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{ name: "URL-required resource", uri: "test://url-required" },
{ name: "URL-required UI resource", uri: "ui://url-required" },
],
}));

server.setRequestHandler("resources/read", async request => {
server.setRequestHandler(ReadResourceRequestSchema, async request => {
if (request.params.uri === "test://url-required" || request.params.uri === "ui://url-required") {
throw urlRequiredError();
}
return { contents: [] };
});

server.setRequestHandler("tools/call", async request => {
server.setRequestHandler(CallToolRequestSchema, async request => {
if (request.params.name === "capabilities") {
return {
content: [{ type: "text", text: JSON.stringify(server.getClientCapabilities()?.elicitation ?? null) }],
Expand All @@ -48,25 +56,31 @@ server.setRequestHandler("tools/call", async request => {
if (request.params.name === "url-required") throw urlRequiredError();

if (request.params.name === "form") {
const result = await server.elicitInput({
mode: "form",
message: "Provide a name",
requestedSchema: {
type: "object",
properties: { name: { type: "string", minLength: 1 } },
required: ["name"],
const result = await server.request({
method: "elicitation/create",
params: {
mode: "form",
message: "Provide a name",
requestedSchema: {
type: "object",
properties: { name: { type: "string", minLength: 1 } },
required: ["name"],
},
},
});
}, ElicitResultSchema);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}

if (request.params.name === "url") {
const result = await server.elicitInput({
mode: "url",
message: "Connect your account",
elicitationId: "requested-1",
url: "https://example.com/authorize",
});
const result = await server.request({
method: "elicitation/create",
params: {
mode: "url",
message: "Connect your account",
elicitationId: "requested-1",
url: "https://example.com/authorize",
},
}, ElicitResultSchema);
if (result.action === "accept") {
for (const elicitationId of ["unknown", "requested-1", "requested-1"]) {
await server.notification({
Expand Down
44 changes: 44 additions & 0 deletions __tests__/fixtures/legacy-no-discover-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import readline from "node:readline";

const lines = readline.createInterface({ input: process.stdin });

function respond(id, result) {
process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}\n`);
}

function reject(id, message) {
process.stdout.write(`${JSON.stringify({
jsonrpc: "2.0",
id,
error: { code: -32601, message },
})}\n`);
}

lines.on("line", line => {
const request = JSON.parse(line);
if (request.method === "server/discover") {
reject(request.id, "server/discover is not supported");
return;
}
if (request.method === "initialize") {
respond(request.id, {
protocolVersion: "2024-11-05",
capabilities: { tools: {} },
serverInfo: { name: "legacy-no-discover", version: "1.0.0" },
});
return;
}
if (request.method === "tools/list") {
respond(request.id, {
tools: [{
name: "classic_initialize_reached",
description: "Classic initialize completed",
inputSchema: { type: "object", properties: {} },
}],
});
return;
}
if (request.id !== undefined) {
reject(request.id, "Method not found");
}
});
9 changes: 5 additions & 4 deletions __tests__/fixtures/output-schema-server.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Server } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
{ name: "output-schema-integration-server", version: "1.0.0" },
Expand Down Expand Up @@ -36,7 +37,7 @@ const draft2020Tuple = {
};
const draft2020Schema = { $schema: "https://json-schema.org/draft/2020-12/schema", ...draft2020Tuple };

server.setRequestHandler("tools/list", async () => ({
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{ name: "draft07-valid", inputSchema: { type: "object" }, outputSchema: draft07Schema },
{ name: "draft07-invalid", inputSchema: { type: "object" }, outputSchema: draft07InvalidSchema },
Expand All @@ -45,7 +46,7 @@ server.setRequestHandler("tools/list", async () => ({
],
}));

server.setRequestHandler("tools/call", async request => {
server.setRequestHandler(CallToolRequestSchema, async request => {
const name = request.params.name;
if (name === "draft07-valid" || name === "draft2020-valid") {
return { structuredContent: { values: ["ok", 1] }, content: [{ type: "text", text: name }] };
Expand Down
21 changes: 14 additions & 7 deletions __tests__/fixtures/prompts-server.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Server } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
GetPromptRequestSchema,
ListPromptsRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

// A minimal MCP server that advertises the `prompts` capability and returns
// deterministic content, used by prompts-sdk-integration.test.ts to exercise
Expand All @@ -9,17 +16,17 @@ const server = new Server(
{ capabilities: { tools: {}, resources: {}, prompts: { listChanged: false } } },
);

server.setRequestHandler("tools/list", async () => ({
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{ name: "noop", inputSchema: { type: "object", properties: {} } }],
}));

server.setRequestHandler("resources/list", async () => ({ resources: [] }));
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));

server.setRequestHandler("tools/call", async () => ({
server.setRequestHandler(CallToolRequestSchema, async () => ({
content: [{ type: "text", text: "ok" }],
}));

server.setRequestHandler("prompts/list", async () => ({
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: [
{
name: "brief",
Expand All @@ -37,7 +44,7 @@ server.setRequestHandler("prompts/list", async () => ({
],
}));

server.setRequestHandler("prompts/get", async (request) => {
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args = {} } = request.params;
if (name === "brief") {
const topic = typeof args.topic === "string" ? args.topic : "(missing)";
Expand Down
9 changes: 5 additions & 4 deletions __tests__/fixtures/tools-only-server.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Server } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";

// A minimal MCP server that advertises tools only — no `resources`, no
// `prompts`. Used by resources-capability.test.ts to check that the adapter
Expand All @@ -9,11 +10,11 @@ const server = new Server(
{ capabilities: { tools: {} } },
);

server.setRequestHandler("tools/list", async () => ({
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{ name: "noop", inputSchema: { type: "object", properties: {} } }],
}));

server.setRequestHandler("tools/call", async () => ({
server.setRequestHandler(CallToolRequestSchema, async () => ({
content: [{ type: "text", text: "ok" }],
}));

Expand Down
26 changes: 21 additions & 5 deletions __tests__/json-schema-validator.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import type { JsonSchemaType } from "@modelcontextprotocol/client";
import type { JsonSchemaType } from "@modelcontextprotocol/sdk/validation/types.js";
import { createJsonSchemaValidator } from "../json-schema-validator.ts";

const draft07 = "http://json-schema.org/draft-07/schema#";
Expand Down Expand Up @@ -64,16 +64,32 @@ describe("createJsonSchemaValidator", () => {
required: ["values"],
};

expect(validate(schema, { values: ["ok", 1] }).valid).toBe(true);
expect(validate(schema, { values: ["ok", 1, true] }).valid).toBe(false);
expect(validate({ $schema: "https://json-schema.org/draft/2020-12/schema", ...schema }, { values: ["ok", 1] }).valid).toBe(true);
for (const candidate of [schema, { $schema: "https://json-schema.org/draft/2020-12/schema", ...schema }]) {
expect(validate(candidate, { values: ["ok", 1] }).valid).toBe(true);
expect(validate(candidate, { values: ["ok", 1, true] }).valid).toBe(false);
}
});

it("accepts unstamped schemas that use draft-07-compatible keywords", () => {
const schema = {
type: "object",
properties: {
name: { type: "string", minLength: 1 },
},
required: ["name"],
additionalProperties: false,
};

expect(validate(schema, { name: "ok" }).valid).toBe(true);
expect(validate(schema, { name: "" }).valid).toBe(false);
expect(validate(schema, { name: "ok", extra: true }).valid).toBe(false);
});

it("does not downgrade an unsupported explicit dialect", () => {
expect(() => validate({
$schema: "https://example.com/custom-schema",
type: "object",
}, {})).toThrow(/unsupported dialect|2020-12/i);
}, {})).toThrow(/unsupported.*dialect|2020-12/i);
});

it("creates isolated validator providers", () => {
Expand Down
Loading