feat(mcp): DingDuff connector support - OAuth redirect discovery and chat case retrieval

🟢 open · #331 · open-legal-products/mike ← duncanmcqueen/mike · opened 2mo ago by duncanmcqueen · +263-56 across 4 files · ↗ on GitHub

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.

  • guardedFetch now 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 discoverOAuthServerState plus the SDK's OAuthClientProvider.discoveryState hook 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 --noEmit clean.

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.

⬇ Download capture-pull-331.md