Skip to content

feat(mcp): move to @langchain/mcp-adapters v2 on MCP SDK v2 - #57

Merged
IBJunior merged 1 commit into
mainfrom
feat/mcp-adapters-v2
Nov 8, 2026
Merged

IBJunior merged 1 commit into
mainfrom
feat/mcp-adapters-v2

Conversation

@IBJunior

@IBJunior IBJunior commented Nov 8, 2026

Copy link
Copy Markdown
Member

Summary

Moves Cameron's MCP client to @langchain/mcp-adapters 2.x, which is built on the MCP TypeScript SDK v2 (@modelcontextprotocol/client). @modelcontextprotocol/sdk (v1) is removed, along with its pnpm override.

Adapter

  • connector.ts uses MCPAdapter / listTools() and is now the only file using the adapter: the Connections page's tool lister opens its session through it too.
  • Tool names are the adapter's own. v2 names tools {server}_{tool} by default. The disclosure package now builds the same names (prefixedToolName() in policy.ts), so the tool the model is shown and the tool that runs carry one name and the connector renames nothing. getTool / resolveTool take the name as the model called it. Server names containing _ are already disambiguated by createToolServerIndex.
  • Elicitation is off per server (elicitation: false). v2 raises it as a LangGraph interrupt, which the approval flow can't answer, so the thread would hang. Listed as deferred in docs/MCP_DISCLOSURE.md.
  • The server config is typed against v2's strict schema (no more as any). Stdio args is now always sent.
  • @langchain/core and @langchain/langgraph are bumped to the adapter's peer ranges, in the disclosure package too, so only one copy of core is installed.

OAuth

Found while live-testing against a local Keycloak-backed MCP server:

  • Registration was rejected (HTTP 403): the client metadata hardcoded scope: "read write". The check route now delegates to the SDK's auth(), which takes the scope from the server's challenge or resource metadata, registers the client, and builds the authorization URL with PKCE and the RFC 8707 resource parameter.
  • The callback forwards all params to finishAuth, so the SDK can check iss (RFC 9207).
  • Connect never appeared for a new server. Nothing set an OAuth status on add, and the button only renders for REQUIRED / EXPIRED / UNKNOWN. Saving an HTTP server (create, or a URL change) now probes it and records the status. A 401 at connect time (isAuthFailure, which walks the error's cause chain) records REQUIRED, or EXPIRED if the server was connected, and returns auth_required.
  • The provider implements v2's invalidateCredentials() (new clearOAuthCredentials repository function).

Test plan

  • tsc --noEmit, pnpm typecheck:eval, pnpm test (455 passing, 3 new for isAuthFailure), pnpm build
  • MCP eval cases: 4/4 pass, with the model calling the tools under the new server_tool names
  • Live, dev stack: Notion (stdio) read and write in an existing thread, whose saved server__tool names dropped out cleanly and were reloaded; result confirmed by a direct read through the connector
  • Live, dev stack: OAuth HTTP server on Keycloak, from adding the server through sign-in (CONNECTED, refresh token, scope offline_access mcp:tools) to whoami / list_notes / create_note / get_note through the agent, confirmed directly
  • Token refresh and the 401 → EXPIRED path (unit-tested only)

Notes

  • MCP credentials are still stored in plaintext, as before this PR, and run_sql can currently read the mcp_server table. Both are being fixed separately: a database role that limits run_sql to the finance tables, and encryption at rest.
  • Threads that loaded MCP tools before this change lose them: the saved server__tool names no longer match. A new load_tools call loads them again.

🤖 Generated with Claude Code

Upgrade the MCP adapter to 2.x, built on @modelcontextprotocol/client,
and drop @modelcontextprotocol/sdk (v1) everywhere, OAuth included.

- Connector: MCPAdapter + listTools(). Tools keep the adapter's default
  `server_tool` names; the disclosure package builds the same names via
  prefixedToolName(), so the connector renames nothing.
- Elicitation is off per server: its interrupt is one the approval flow
  cannot answer, so the thread would hang.
- The Connections probe opens its session through the connector, which
  is now the only file using the adapter.
- OAuth: the check route delegates to the SDK's auth(), which picks the
  scope from the server instead of a hardcoded "read write" (Keycloak
  rejected registration with it), and adds the RFC 8707 resource. The
  callback forwards all params so the SDK can check `iss` (RFC 9207).
  The provider implements invalidateCredentials().
- Saving an HTTP server probes it and records its OAuth status, so the
  Connect button appears; a 401 at connect time records REQUIRED or
  EXPIRED and returns auth_required.
- Bump @langchain/core and @langchain/langgraph to the adapter's peers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IBJunior
IBJunior merged commit 508aaac into main Nov 8, 2026
1 check passed
@IBJunior
IBJunior deleted the feat/mcp-adapters-v2 branch November 8, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant