Skip to content

Add an MCP-server adapter and four hosted-server tool packages - #413

Closed
TheGreatAxios wants to merge 2 commits into
mainfrom
cl-5728-mcp-adapter-and-server-packages
Closed

Add an MCP-server adapter and four hosted-server tool packages#413
TheGreatAxios wants to merge 2 commits into
mainfrom
cl-5728-mcp-adapter-and-server-packages

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • @corbits/mcp-adapter: shared logic exposing a hosted, OAuth-protected MCP server as an interchange.tools factory. Tool definitions (with real per-tool argument schemas, not empty objects) are set synchronously at bundle construction; the real connection (and OAuth, when required) happens lazily on first run(), threading the tool call's own abort signal through so a hung authorization wait is cancellable. Self-contained OAuth callback server and token store, ported (not imported) from src/mcp/ so the package has no dependency back into this repo's src/ tree.
  • @corbits/linear-mcp, @corbits/slack-mcp, @corbits/granola-mcp, @corbits/exa-mcp: thin per-server config over the adapter. Slack's transport was confirmed hosted (https://mcp.slack.com/mcp, OAuth, GA Feb 2026) before writing the package — see https://mcpservers.org/remote-mcp-servers/slack. Exa's API key is optional end to end — the package connects and works with it unset.
  • Workspace-local for now (packages/*), matching the operator's stated goal of eventual independent repos/publishes.

The tool-declaration tradeoff (static list vs. single dispatch tool vs. cache-from-prior-connection) is documented in packages/mcp-adapter/README.md, including what happens when a server's real tool list drifts from the declared one.

Second commit fixes three defects reviewers found in the first pass: a failed connect cached its rejection forever (no retry), dispose() racing an in-flight connect leaked the connection, and every declared tool used an empty input schema instead of a real one.

Known open item, not fixed in this PR: the OAuth/token-store/callback-server logic in packages/mcp-adapter/src/ is a deliberate port (not an import) of src/mcp/'s existing implementation, per this pass's explicit scope (no imports back into src/, so the package can move to its own repo later without a rewrite). Reviewers correctly flagged this as two independent implementations of security-sensitive OAuth handling that can drift; resolving it (e.g. extracting a shared package both src/mcp/ and mcp-adapter import) is a real tradeoff against the extraction goal and is left for the operator to decide, not fixed unilaterally here.

Linear: CL-5728 (this work), CL-5729 (tracks the four packages), CL-5730 (follow-up CLI, not in this PR).

Test plan

  • bun run typecheck — no new errors (pre-existing errors elsewhere, confirmed present without this change)
  • bun run test (full suite) — 4263 pass / 0 fail
  • Unit tests in packages/mcp-adapter/src/*.test.ts cover: structural tool-factory shape, no network activity before first run(), lazy OAuth-URL surfacing (against factory.ts's contract with connect.ts, not connect.ts's real network path, which remains untested), optional-API-key query-param wiring, retry after a failed connect, dispose racing an in-flight connect, and signal propagation into the connect call — all with fake credentials only

interchange.tools can load ordinary npm packages that declare a static
tool factory, but Interchange has no MCP support of its own. Each hosted,
OAuth-protected MCP server (Linear, Slack, Granola, Exa) now ships as its
own thin workspace package configuring a shared adapter, rather than a
bundle or duplicated connection logic per server.

Tool names are declared statically at construction and the adapter
connects lazily -- including any OAuth round trip -- on a bundle's first
run(), reusing the callback-server and token-store patterns Corbits Code
already uses for its own MCP client, ported here so the packages carry no
import back into this repository's src/ tree and can move to their own
repos later without a rewrite.
@linear-code

linear-code Bot commented Aug 8, 2026

Copy link
Copy Markdown

CL-5728

Reviewers found three real defects in the MCP adapter: a failed connect
cached its rejection forever so every later tool call replayed the same
stale error instead of retrying; a dispose() racing an in-flight connect
never closed the connection once it resolved, leaking its transport and
callback server; and every declared tool across all four server packages
used an empty input schema, giving the model no signal on what arguments
a tool takes -- exactly the dispatch-proxy ergonomics problem the static
per-tool declarations were meant to avoid.

run() now also threads its own abort signal into the connect call, since
a first-time authorization blocks on a loopback HTTP callback with no
other way to bound or cancel that wait.
@TheGreatAxios

Copy link
Copy Markdown
Collaborator Author

Closing until the plugin registration surface exists.

Blocked on three findings, each sufficient alone: nothing in src/ can reach the packages (workspace glob makes them typecheck; no config, registry, or consumer wires them); they are built against a tool-definition shape upstream has already changed, so the next dependency bump breaks all four at once; and the static tool declarations are never reconciled against what a server reports on connect, so drift is silent in both directions.

The adapter inside — connect, OAuth provider, loopback callback server, hardened token store — was judged genuine and worth keeping. When the registration surface lands, rebuild from this branch: land the adapter with ONE reachable, integration-tested server package, then replicate. The branch is preserved.

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