feat(mcp): DingDuff connector support - OAuth redirect discovery and chat case retrieval
From the PR description
Summary
Adds support for DingDuff as a user MCP connector, so the assistant can use DingDuff tools for case retrieval, case reading, and case-specific document lookup directly from chat.
Two changes make this work:
1. OAuth discovery behind redirects (backend/src/lib/mcp/client.ts, oauth.ts)
DingDuff's server 302s its well-known OAuth metadata to a path-scoped issuer, and the MCP SDK's own discovery does not follow redirects, so the OAuth flow could not complete.
guardedFetchnow follows redirects hop by hop instead of refusing them outright: every 3xx target is re-validated against the existing SSRF guard (HTTPS-only, private-IP/metadata-host blocks, DNS-pinned dispatcher), credentials are stripped on cross-origin hops (per the fetch spec), and redirected POSTs are converted to GET with the body dropped. Undici still never auto-follows (redirect: "manual"on every hop), so no redirect can smuggle egress past the guard.- New
discoverOAuthServerStateplus the SDK'sOAuthClientProvider.discoveryStatehook hand the SDK pre-discovered metadata, so connectors whose well-known documents live behind a redirect complete the flow.
2. Chat integration (backend/src/lib/chat/streaming.ts)
When DingDuff MCP tools are present on a turn, the built-in CourtListener tools are withheld and the system prompt directs case retrieval at the DingDuff tools, so the two case-law sources are never mixed within a single turn.
Tests
- New cases in
client.ssrf.test.ts: redirect following with per-hop re-validation, refusal to follow a redirect to a blocked address, credential stripping on cross-origin redirects, and POST→GET conversion. - Full backend suite passes (711 passed, 24 skipped);
tsc --noEmitclean.
Rebased onto current main
Re-verified that all three pieces are still needed - none has landed upstream in the meantime: guardedFetch still sets redirect: "manual" with no hop-following, oauth.ts has no discoveryState/discoverOAuthServerState, and streaming.ts has no DingDuff handling.
One conflict needed resolving beyond the textual merge. main has since introduced includeAskInputs/conversationTools, which declares its own baseTools; this branch declared a second one. The DingDuff contribution here is really the researchTools gate, and main's line already consumes that, so the duplicate declaration is dropped and the gate feeds main's conversationTools list.
Notes
- No schema changes; no new dependencies.
- The legal-monitors DingDuff connector source is intentionally not part of this PR (that feature set isn't upstream).
Our analysis
Add DingDuff MCP case retrieval to chat — read the full analysis →
Think the analysis missed something the PR description covers?
Capture this PR into my fork
Download a Markdown prompt that tells Claude how to port every
commit in this PR into your working tree. Run it via
claude -p < capture-pull-331.md from
inside the repo you want the changes in.