Skip to content

feat(connections): open a remote MCP server the way a Composio provider opens (#821) - #829

Merged
oxoxDev merged 3 commits into
tinyhumansai:mainfrom
CodeGhost21:feat/821-mcp-detail
Aug 13, 2026
Merged

feat(connections): open a remote MCP server the way a Composio provider opens (#821)#829
oxoxDev merged 3 commits into
tinyhumansai:mainfrom
CodeGhost21:feat/821-mcp-detail

Conversation

@CodeGhost21

@CodeGhost21 CodeGhost21 commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Closes #821.

Follows #819, which is already merged — ProviderDetail.tsx and
connection-detail.ts come from it, and this branch was cut from its head. The
diff here is #821's own: three commits, eight files.

What this does

#819 gave a Composio provider a detail view and deliberately left MCP as a list
— the uneven half of #404, and the wrong half to leave for a company routing its
real work through MCP servers. This opens a remote MCP server into the same
panel, on a ConnectionSubject union. Not a second MCP-specific panel, which is
exactly what #404 exists to prevent, and what happened to the MCP screen once
already (#414).

Read-only, as the issue's "smallest first slice" asks. Enable, Test, Tools
and Remove stay on the row; the panel adds what the row cannot say:

Claim How it is backed
Connected, and as what mcpStanding()enabled and the last probe kept apart
Usage byProvider under mcp:<server> (src/metering/oauth.rs, #698/#744)
When it was connected Stated as not recorded; MCP has no connect step
What a disconnect reaches Tool belt next turn; nothing at the server's own end

Two decisions worth review

callsForProvider was not taught the prefix, though the issue suggests it
"would take the prefix". Its existing test pins the opposite, and correctly:
prefix-matching would re-create by hand the gmail / mcp:gmail collision the
namespace was chosen to prevent. mcpProviderSlug() builds the namespaced key
instead and the lookup stays one exact match on one key.

The panel renders from McpServersSection, not ConnectionsView. The live
health an operator just pressed Test for lives in that component's state, so
hoisting it would let the panel and the row it opened from report different
health. It also means Settings, MCP Servers gets the detail view for free.

Two smaller ones. mcpStanding keeps "never probed" apart from every probe
status including unknown — a server nobody has pressed Test on is neither
reachable nor broken, and it is the common case on a host without the
openhuman feature. And probedOn returns null rather than the epoch for a
zero checkedAtMillis, on the same grounds as the issue's rule about zeros.

The row's name got a chevron rather than hover styling alone — caught by looking
at a screenshot of the real page, where the clickable name was indistinguishable
from plain text.

Commands run

  • npm run typecheck, typecheck:unit, typecheck:e2e, npm run build
  • npm test — 645 pass (52 files), including 22 render tests over both arms
  • npx playwright test against a real target/debug/opencompany host —
    124 pass, 10 skipped (live-brain), including a new mcp.spec.ts case
    asserting the four claims above
  • scripts/ci/assert-design-tokens.sh, scripts/ci/assert-md-line-cap.sh

Not covered

The Composio arm has no e2e walk. The default-feature host serves no Composio,
so the grid renders no openable tile — its coverage is the vitest render suite,
which is where #819 put it for the same reason.

🤖 Generated with Claude Code

CodeGhost21 and others added 3 commits August 13, 2026 14:45
)

The two lookups a detail view for an MCP server needs, as pure functions
so they can be pinned without a DOM.

`mcpProviderSlug` mirrors `mcp_provider` in `src/metering/oauth.rs` and
is fed to the existing `callsForProvider` rather than teaching that
function a prefix: prefix-matching there would re-create by hand the
`gmail` / `mcp:gmail` collision the namespace was chosen to prevent.

`mcpStanding` keeps `enabled` apart from the last probe. MCP has no
connection object, so "connected" is assembled from two facts, and a
server nobody has pressed Test on has no probe at all — neither
reachable nor broken, and no single badge renders that honestly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ProviderDetail` takes a `ConnectionSubject` union instead of a
`GridProvider`, and grows an MCP arm beside the Composio one. A second
MCP-specific panel was the alternative and is what tinyhumansai#404 exists to
prevent: two surfaces for one idea acquire two vocabularies and drift.

The handlers travel inside the Composio variant rather than as sibling
props, so an MCP subject cannot be handed a Composio revoke and a
Composio subject cannot be opened without one. That is the same defect
`ConnectionsView.disconnect` routes around — `disconnectConnection`
answers 200 for a Composio provider while releasing nothing — expressed
as a type rather than as a comment asking callers to be careful.

The MCP arm is read-only: enable, Test, Tools and Remove stay on the row
until the panel is proven. What it adds is what the row cannot say.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The row's name becomes the handle on its detail view, with a chevron
rather than hover styling alone: a name that opens something has to be
distinguishable from one that does not before the pointer is on it,
which on a touch screen is never.

Rendered from this section rather than from the page above, because the
live health an operator just pressed Test for lives in this component's
state — and because this section is also the whole of Settings, MCP
Servers, which gets the detail view for free.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@CodeGhost21, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 18 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f5669b8-790a-49f9-80c6-c43082f01e5c

📥 Commits

Reviewing files that changed from the base of the PR and between 0de7a50 and 54c35ef.

📒 Files selected for processing (8)
  • docs/modules/mcp.md
  • frontend/src/lib/connection-detail.ts
  • frontend/src/views/ConnectionsView.tsx
  • frontend/src/views/connections/McpServersSection.tsx
  • frontend/src/views/connections/ProviderDetail.tsx
  • frontend/test/e2e/mcp.spec.ts
  • frontend/test/unit/connection-detail.test.ts
  • frontend/test/unit/provider-detail-render.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@oxoxDev oxoxDev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opening MCP into the same panel on a ConnectionSubject union, rather than building a second MCP-specific one, is the decision #404 exists to force — and citing #414 as the time that already went wrong here is the right way to justify it. A second panel is always the cheaper next step and always the thing that has to be undone later.

The usage lookup is the part I checked hardest, because I asked about this namespace on #744, and it is handled better than I hoped:

Prefix-matching here would re-create by hand the collision that naming was chosen to prevent.

Matching on the bare slug against mcp:<server> — rather than stripping a prefix and comparing — means a company with a Composio gmail and an MCP server its operator also called gmail cannot read one total as the other's. That is the consumer-side half of #698's namespacing, and getting it wrong here would have quietly undone the reason the prefix exists. The prefix is a key, not a label, which also answers the question I left on #744 about whether mcp:linear reaches the operator's eye.

"Zero is a real answer, not a missing one" is the same discipline #819 and #827 apply, and it is worth the sentence: because every successful call is metered, a provider with no row has genuinely had none, so the panel can say zero rather than hedging. A claim table that says how each line is backed is the right shape for a read-only slice.

0 major. 1 question. Approving.

Question — this is the third cross-language mirror, and drift here makes "zero" lie

mcpProviderKey is described as "the mirror of mcp_provider in src/metering/oauth.rs: trimmed, lowercased…". That makes three mirrored predicates now living in this repo:

  • mayCarryACredential (TS) mirroring may_carry_a_credential (Rust) — #747
  • hub_accepts_redirect_uri (Rust) mirroring the backend's isLoopbackHttpUri (JS) — #818
  • this one

The first two have something this one does not. #747's is advisory: the core enforces, so a drift yields a confusing UI rather than a wrong result. #818's mirrors an external contract and is built to be deleted when backend#1243 lands, with a test that goes red to force it.

This one mirrors an internal Rust function that can change at any time, and a drift is silent and wrong in a specific way: if mcp_provider's normalisation moves — a different sanitisation, a trailing-dot rule, anything — the TS key stops matching, byProvider returns no row, and the panel reports zero. Which this PR's own doc defines as "a real answer, not a missing one". The failure mode is the panel confidently telling an operator their MCP server has had no calls.

Cheap ways to close it, both with precedent here: assert the two agree on a shared fixture list of server names, the way I suggested for mayCarryACredential; or generate the constant from the Rust side, which is what #728 proposed for the gated-tool labels and #761 discussed again. Either turns a silent wrong answer into a failing test.

Not blocking — the normalisation is simple today and unlikely to move soon. But "unlikely to move" is what makes a mirror worth pinning rather than watching.

Before merging: base is 8 commits behind and the lanes are still reporting. #828 and #827 both land on the same surface, so worth a rebase and a re-check rather than trusting the current run.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

             $0.0901 · 117,235 in / 18,905 out · 70,519 cached (60%) · z-ai/glm-5.2
critique:    $0.0505 · 42,440 in  / 11,196 out · 13,405 cached (32%) · z-ai/glm-5.2
security:    $0.0156 · 30,118 in  / 3,284 out  · 24,160 cached (80%) · z-ai/glm-5.2
tests:       $0.0151 · 21,897 in  / 3,454 out  · 15,615 cached (71%) · z-ai/glm-5.2
description: $0.0088 · 22,780 in  / 971 out    · 17,339 cached (76%) · z-ai/glm-5.2

@tinysweeper

tinysweeper Bot commented Aug 13, 2026

Copy link
Copy Markdown

What this change touches

8 files, +980 -215 across 6 components. It reaches 6 untouched components (60 graph nodes walked). 2 further components left out to keep the diagram readable.

flowchart LR
  n0["frontend/src/views/connections<br/>2 files +493 -187<br/>1 finding"]:::flagged
  n1["frontend/test/unit<br/>2 files +262 -17"]:::changed
  n2["frontend/src/lib<br/>1 file +123 -3"]:::changed
  n3["frontend/test/e2e<br/>1 file +49 -0"]:::changed
  n4["frontend/src/views<br/>1 file +27 -8"]:::changed
  n5["docs/modules<br/>1 file +26 -0"]:::changed
  n6["frontend/src/components/ui<br/>7 files reached"]:::impacted
  n7["frontend/src/api<br/>4 files reached"]:::impacted
  n8["frontend/src/views/connections<br/>3 files reached"]:::impacted
  n9["frontend/src/lib<br/>1 file reached"]:::impacted
  n10["frontend/src/tour<br/>1 file reached"]:::impacted
  n11["frontend/src/views<br/>1 file reached"]:::impacted
  n8 -->|48 refs| n6
  n8 -->|18 refs| n7
  n6 -->|16 refs| n9
  n11 -->|12 refs| n6
  n8 -->|9 refs| n11
  n11 -->|7 refs| n8
  n11 -->|4 refs| n7
  n11 -->|2 refs| n9
  n11 -->|1 ref| n10
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed. Grey: untouched, reached through an import or a call. Orange: has findings. Red: has a finding that blocks the merge.

Component Files Lines Findings
frontend/src/views/connections changed 2 +493 -187 1 (medium)
frontend/test/unit changed 2 +262 -17
frontend/src/lib changed 1 +123 -3
frontend/test/e2e changed 1 +49 -0
frontend/src/views changed 1 +27 -8
docs/modules changed 1 +26 -0
frontend/src/components/ui reached 7
frontend/src/api reached 4
frontend/src/views/connections reached 3
frontend/src/lib reached 1
frontend/src/tour reached 1
frontend/src/views reached 1
Changed files

frontend/src/views/connections

  • frontend/src/views/connections/McpServersSection.tsx
  • frontend/src/views/connections/ProviderDetail.tsx

frontend/test/unit

  • frontend/test/unit/connection-detail.test.ts
  • frontend/test/unit/provider-detail-render.test.ts

frontend/src/lib

  • frontend/src/lib/connection-detail.ts

frontend/test/e2e

  • frontend/test/e2e/mcp.spec.ts

frontend/src/views

  • frontend/src/views/ConnectionsView.tsx

docs/modules

  • docs/modules/mcp.md

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Aug 13, 2026

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

             $0.0810 · 114,712 in / 20,874 out · 89,828 cached (78%) · z-ai/glm-5.2
critique:    $0.0447 · 40,013 in  / 14,967 out · 34,502 cached (86%) · z-ai/glm-5.2
security:    $0.0141 · 30,128 in  / 2,316 out  · 22,915 cached (76%) · z-ai/glm-5.2
tests:       $0.0109 · 21,897 in  / 1,696 out  · 15,628 cached (71%) · z-ai/glm-5.2
description: $0.0113 · 22,674 in  / 1,895 out  · 16,783 cached (74%) · z-ai/glm-5.2

{/* The host scrubs this string — it can carry no credential, no
response body and no query string — which is why it can be shown
verbatim rather than re-spelled into a category. */}
{health && health.status !== "ok" && health.message && (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium tests likely

Cover the MCP probe-message branch in the render suite

The new McpBody component renders the probe's error message when a probe ran but did not succeed, but no test exercises this path. The render suite covers the probe line (reachable, not-probed, needs_config, error) via mcpStanding, and the unit tests check mcpStanding's probe string, but no test passes a health with status !== "ok" and a non-empty message and asserts that health.message appears in the rendered panel. If this branch were removed or its condition changed, nothing in the suite would fail.

[RULE] Add focused tests with every behavior change. ·

@tinysweeper tinysweeper Bot added priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. and removed priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. labels Aug 13, 2026
@oxoxDev
oxoxDev merged commit 8b7626e into tinyhumansai:main Aug 13, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(connections): open a remote MCP server the way a Composio provider opens (#404)

2 participants