Repository navigation
feat(studio): Connected agents and MCP client setup, RFC-0044 phase 3b (#144) - #179
Merged
Merged
Conversation
#144) "Connected agents" on /agents lists the apps a member connected by URL, named as the consent screen named them: a metadata document's host first, a self-registered client as not verified, a look-alike name with a warning, names sanitized. Each row shows what the connection can do in words with its mode, when it was last used and connected, and Disconnect, which calls the existing revoke. Administrators see every member's connections under All keys. "Connect an MCP client" gains a Connect by URL tab: the deployment's /mcp URL, per-client steps from RFC-0044 §11 (Claude, Claude Code, ChatGPT and Codex, Cursor, VS Code, MCP Inspector), and a banner where a client needs dynamic registration that is off. The key setup stays as its own tab. The API names each grant's client (kind, host, lookalike) and each owner's role, and /api/instance reports features.mcpOauthClients (resource, cimd, dcr). An e2e test drives the MCP TS SDK's auth() against `cloudhal studio --base-url`: discovery, DCR, PKCE, consent, token, tools/call, then Disconnect and a 401. Docs: docs/guides/mcp-oauth.md, the clients page's Connect by URL section, and the MCP server README's Authentication section. The 18 non-English catalogs are machine drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…f a local /mcp (#144) Review of #179: - Below `sm`, the member, access and last-use columns hide and stack under the agent (the MembersPage pattern), so a row fits next to Disconnect at 390 px without scrolling sideways. - oauthClientStatus takes the /mcp resource and answers needsPublicUrl for Claude.ai and ChatGPT when it is loopback, private or plain http; Connect by URL starts on Claude Code there and shows a banner pointing to --base-url (new key, 18 machine drafts). - The guide's troubleshooting table tells rate_limited (registration), temporarily_unavailable 429 (metadata fetches) and 503 (client caps) apart. - The "stays out of the way" test waits for /api/instance and /api/agent-grants before asserting the card is absent. - Design rules: ClipboardText is required in ConnectMcpCard, not AgentsPage; AgentsPage's unused imports are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RFC-0044 Phase 3b (#144). This adds "Connected agents" and the Connect-by-URL setup in Studio, the e2e test against
cloudhal studio --base-url, and the guides. Phase 3a (registration: CIMD, DCR and limits) was #178.Brief → code → tests
apps/studio/src/components/ConnectedAgents.tsx, on/agentsunder the keys table (routes/AgentsPage.tsx); Disconnect →DeleteResource→api.agentGrants.revoke(DELETE /api/agent-grants/:id, unchanged)apps/studio/test/connected-agents.test.tsx"names each connection … disconnects with the revoke"agents:manage)all, under the All keys tab;GET /api/agent-grants?all=1(exists since #176)connected-agents.test.tsx"lists every member's connections for an Administrator…"packages/api/src/auth/agent-grants.tsnames each grant'sclientwith the sameclient-identity.tshelpers the consent screen uses (clientKind,documentHost,lookalikeOf,displayName); UI:ClientIdentityinConnectedAgents.tsx("Identified by {host}" first, then the name)packages/api/src/testing-mcp-oauth-bounds.ts(sqlite and D1): a CIMD grant has{kind:"document", host, lookalike}, a DCR "Claude · from claude.ai" has{kind:"self-registered", lookalike:"Claude"}and its owner's role; Studio tests above/mcpURL, where a client needs DCR on, and where a hosted client cannot reach the URLcomponents/ConnectMcpCard.tsx(moved out ofAgentsPage.tsx): tabs Connect by URL / With an agent key;components/McpOauthSetup.tsxandlib/oauth-clients.ts(Claude, Claude Code, ChatGPT and Codex, Cursor, VS Code, MCP Inspector; Cursor and the Inspector are DCR-only); the URL and switches come from/api/instancefeatures.mcpOauthClients(packages/api/src/app.ts).oauthClientStatustakes the resource: for Claude.ai and ChatGPT, a loopback, private,.localor plain-http/mcpgivesneedsPublicUrland a banner pointing to--base-url, and the picker then starts on Claude Codeconnected-agents.test.tsx"shows the /mcp URL and each client's steps, and says when a client needs DCR" and "shows only the key setup where OAuth is off", "starts on Claude Code and warns about hosted clients where /mcp is local", and "tells hosted clients from local ones by the /mcp URL" (13 non-public URLs, 3 public ones);testing-mcp-oauth-bounds.tsassertsfeatures.mcpOauthClientsauth()againstcloudhal studio --base-url: discovery, registration, PKCE, programmatic consent, token,tools/callpackages/cli/test/mcp-oauth-e2e.test.ts. Startsstudio()on a free port with--base-url http://localhost:<port>and signs up the owner.auth()returnsREDIRECT(checked: S256 andresource); authorize goes to/oauth/consent, then consent (POST) and the code exchange;auth()returnsAUTHORIZED;callTool list_sitesreturns the site; Disconnect gives 204 and the old token gets 401 withresource_metadata.turbo run test(it is inpackages/cli/test/)docs/guides/mcp-oauth.md(new: connecting, Connected agents, operators, troubleshooting);docs/agents/clients.md(the docs site's MCP clients page: a "Connect by URL (OAuth)" section with a per-client table and troubleshooting);packages/mcp-server/README.md("Authentication"); the docs overview links the guide (apps/docs/src/lib/site-pages.ts)apps/docstestsconnectedAgents(src/i18n/lazy/connectedAgents.<locale>.ts), English by hand and 18 machine drafts viapnpm i18n:importPageLayout/SectionCardonly, via@cloudhal/ui; the required blocks for the three new components are registered intest/design-rules.test.tspnpm design:lintclean (406 files), design-rules 577 passScreenshots (
pnpm design:shots, 1440 and 390 px)These come from a throwaway
cloudhal studio --base-url http://localhost:8899with two connections made through the SDK'sauth(): a DCR "Cursor" (full access, Edits) and a DCR "Claude", read-only, which triggers the look-alike warning. design:shots reported no sideways scroll and no console errors. The/mcpURL is localhost, so Connect by URL starts on Claude Code. The images live on the branchpr-assets/179-screenshots(not for merge); delete it after merging.Your keys: Connected agents under the keys list. On phones each row stacks: agent, access, last use, with Disconnect beside it.
All keys: "Everyone's connected agents", with the member (avatar, name, role). On phones the member stacks under the agent.
Deviations from the RFC and the brief
PATCH /api/agent-grants/:id(mode) andPOST/DELETE :id/elevationwith the key dialogs reused. The brief scoped this PR to list, admin view and Disconnect, and neither those routes nor their tests exist yet. Connected agents shows the mode read-only. To change it, disconnect and connect again; the consent screen sets the mode. This is a follow-up.sm(640 px) the member, access and last-use columns hide and their content stacks under the agent, as on Team (MembersPage), so nothing scrolls sideways. Disconnect is an icon button with a tooltip and the label "Disconnect {client}".connectedAgents.*intrneeds a human pass.McpClientSetupcard moved fromAgentsPage.tsxintocomponents/ConnectMcpCard.tsx(the size ratchet: AgentsPage 269 → 166 lines inscripts/size-baseline.json), andMyKeysTablewas extracted beside it. Behaviour is unchanged where OAuth is off: only the key setup shows, as before.Checks run locally (one package at a time)
pnpm lint(no errors),pnpm size:check,pnpm design:lint,turbo run typecheck --concurrency=1(20/20),node scripts/mcp-tools-doc.ts --checkpackages/api: 613 pass (sqlite and Miniflare D1)apps/studio: 1322 pass after the review fixes (connected-agents 7, design-rules 577)packages/cli: 444 pass, the new e2e includedpackages/cloudflare: 57 pass;apps/docs: 35 pass, andastro buildbuilds 123 pagesReview round 1 (9818f99)
needsPublicUrlfor Claude.ai and ChatGPT, a new banner key (connectedAgents.setup.needsPublicUrl, 18 drafts), the picker defaults to Claude Code, with tests.rate_limited429 for registration (10 per address per hour, 300 a day);temporarily_unavailable429 for metadata fetches (20 a minute, wait);temporarily_unavailable503 for the client caps (ask the operator; links the hardening section).["instance"]and["agent-grants","mine"]queries have succeeded before asserting the card is absent.ClipboardTextis required inConnectMcpCard, notAgentsPage. AgentsPage's unused imports are gone (ClipboardText,SectionCard,PlugsConnected,apiErrorText). AgentsPage's#layoutrule no longer listsSectionCard, since its cards live in the two components that require it.Changeset: patch for
@cloudhal/api,@cloudhal/cli(it ships the Studio build) and@cloudhal/mcp-server(README).🤖 Generated with Claude Code