feat(team-settings): connect your AI assistant [HDX-4440]#2407
Conversation
Add a "Connect your AI assistant" section to the Team Settings page (Integrations tab), rendered directly above the API Keys card. The section gives users a one-click install path for the ClickStack MCP server in Claude Code, Cursor, VS Code + Copilot, Codex CLI, or any MCP-compatible host without hand-rolling JSON. Per-host install primitives: - Claude Code: `claude mcp add ... --transport http <url> --header "Authorization: Bearer ..."` one-liner. - Codex CLI: `codex mcp add ...` one-liner (mirrors Claude Code). - Cursor: `cursor://anysphere.cursor-deeplink/mcp/install?...` deep link with base64-encoded config; manual JSON fallback behind a `Manual setup` toggle. - VS Code + Copilot: `vscode:mcp/install?<encoded JSON>` deep link; manual JSON fallback. Requires VS Code 1.99+ with the Copilot Chat MCP feature enabled. - Other: canonical `mcpServers` JSON block that covers Claude Desktop, Continue, Cline, and the long tail. The MCP server resolves the active team from the bearer token, so the install snippet doesn't need to disambiguate by name on the client. A single fixed `clickstack` server name is used across every host primitive. Header values are shell-escaped against `"`, `$`, backtick, and backslash so a future access-key format with metacharacters can't turn a copy-paste install into a shell-injection vector. Tests: 16 component + snippet tests pass. Lint, TS, prose-lint clean. Co-Authored-By: Claude Opus <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 5bbb2d8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
E2E Test Results✅ All tests passed • 199 passed • 3 skipped • 1266s
Tests ran across 4 shards in parallel. |
🟡 Tier 3 — StandardIntroduces new logic, modifies core functionality, or touches areas with non-trivial risk. Why this tier:
Review process: Full human review — logic, architecture, edge cases. Stats
|
Deep Review✅ No critical issues found. No P0/P1 ship-blockers: the snippet builders, shell-escaping, encoding round-trips, 🟡 P2 — recommended
🔵 P3 nitpicks (6)
Reviewers (9): correctness, security, adversarial, kieran-typescript, testing, maintainability, project-standards, agent-native, learnings-researcher. Testing gaps:
|
- Cursor deeplink: emit URL-safe base64 (-, _, no padding) for the config query value so the standard alphabet's + / / / = cannot be re-interpreted by the host's URL parser. + decodes as space under form-urlencoded, which would corrupt the embedded JSON config. Updated round-trip test to base64url and added a regression case asserting the encoded value only uses the URL-safe alphabet across inputs that would have produced + or / in standard base64. - base64(): UTF-8-encode before btoa so a future access-key or origin format with code points > 0xFF cannot raise InvalidCharacterError and blank the panel (buildAllSnippets builds every host eagerly). - installSnippets: extract shared buildCliOneLiner(binary, deployment) helper; Claude Code and Codex CLI now share the generator and differ only in the binary name. - McpServerSection: drop the unreachable typeof window === 'undefined' guard; the section renders client-only via useMe(). Drop the defensive me.accessKey ?? '' coalesce now that MeApiResponseSchema pins accessKey to z.string(); the downstream empty-string guard in the panel still fires. - McpInstallPanel: replace the value as AgentHost cast on SegmentedControl.onChange with an isAgentHost(value) type guard derived from CHOICES so a future out-of-band value cannot silently install an invalid host. - CopySnippet: fix the stale JSDoc reference (the cited ExporterFormatSelector.tsx is not in the repo) and drop the redundant color="gray" on the subtle Button; variant="subtle" is the canonical pattern per agent_docs/code_style.md. - Add component tests for host-switching, the Cursor / VS Code deep link href shape, the canonical JSON branch on Other, and the Manual setup toggle in DeeplinkInstall. Closes the host-coverage gap without enabling the e2e fixme (the e2e webServer still runs in IS_LOCAL_MODE, which short-circuits useMe / useTeam before the route mocks land). 22 tests pass. Co-Authored-By: Claude Opus <noreply@anthropic.com>
|
Fix-pack for the deep-review findings on P2: Cursor deeplink URL-safe base64. Fixed in P2: real-host install validation. Holding. The PR's test plan already calls this out as the manual end-to-end checkbox against a self-managed deployment, which is the gate on the P2: host-switching / deeplink / manual-setup coverage. Fixed in P3: P3: P3: P3: P3: P3: P3: P3: |
…omments
Strip JSDoc + inline comments in installSnippets.ts and
McpInstallPanel.tsx that referenced internal tracking labels
("AC16", "AC18") and downstream non-OSS deployment branches
("CHC managed (BYC)", "ClickStack Cloud", "CP MCP proxy", "EE
Team Settings page"). Those notes belonged in the tracking doc,
not in the OSS source.
Comments-only change; no logic, no test changes. The remaining
JSDoc still explains each builder's contract without speculating
about future deployment shapes.
Co-Authored-By: Claude Opus <noreply@anthropic.com>
| const CHOICES: HostChoice[] = [ | ||
| { id: 'claude-code', label: 'Claude Code' }, | ||
| { id: 'cursor', label: 'Cursor' }, | ||
| { id: 'vscode-copilot', label: 'VS Code + Copilot' }, |
There was a problem hiding this comment.
We could just call this VS Code imo
| { id: 'claude-code', label: 'Claude Code' }, | ||
| { id: 'cursor', label: 'Cursor' }, | ||
| { id: 'vscode-copilot', label: 'VS Code + Copilot' }, | ||
| { id: 'codex-cli', label: 'Codex CLI' }, |
There was a problem hiding this comment.
Thoughts on dropping Codex for OpenCode (biased maybe). I know we have several devs internally who use OpenCode so this would help with adoption
The config for OpenCOde is slightky different
"clickstack": {
"type": "remote", <- Note not type http
"url": "http://localhost:xxx/api/mcp",
"headers": {
"Authorization": "Bearer xxx"
}
},
| // mocks. Skip in local mode until the e2e build flag is wired | ||
| // up; component coverage in `__tests__/McpServerSection.test.tsx` | ||
| // exercises the render branches in the meantime. | ||
| test.fixme( |
There was a problem hiding this comment.
This test is disabled - we should either remove this (I think thats fine) or we should fix it so it runs if it can (instead of true do a conditional on local mode)
| }, | ||
| { | ||
| id: 'team-integrations-mcp-server', | ||
| content: <McpServerSection />, |
There was a problem hiding this comment.
Thoughs on moving below APiKeys? I think that flows more naturally, I need to understand access keys before I can use the MCP
|
|
||
| return ( | ||
| <Box id="mcp_server" data-testid="mcp-server-section"> | ||
| <Text size="md">Connect your AI assistant</Text> |
There was a problem hiding this comment.
Thoughts?
| <Text size="md">Connect your AI assistant</Text> | |
| <Text size="md">Connect your AI Agents</Text> |
|
|
||
| {!deployment ? ( | ||
| <Alert color="yellow" variant="light"> | ||
| Sign in to load your personal access key before installing. |
There was a problem hiding this comment.
Can someone get to this state??? seems like this should be caught sooner
| </Alert> | ||
| ) : !deployment.accessKey ? ( | ||
| <Alert color="yellow" variant="light"> | ||
| No access key on this account yet. Ask an admin to create one and sign |
There was a problem hiding this comment.
I thought access key was required, is there a state where its not defined?
| } | ||
| // Exhaustiveness check: adding a new AgentHost variant without | ||
| // extending this switch fails the compile here. | ||
| return assertNever(id); |
There was a problem hiding this comment.
We should return null here - crashing the whole page seems extreme
Move ApiKeysSection and McpServerSection out of Integrations and into a new "API & Agents" tab on the Team Settings page. The Integrations tab keeps webhooks only. Rationale: API keys and the MCP install are programmatic-access surfaces that pair naturally (the MCP server authenticates with the same personal access key shown in the API Keys card). Webhooks are a different category of integration and were sharing the tab only by accident of where each section originally landed. Section IDs renamed: team-integrations-api-keys -> team-api-agents-api-keys team-integrations-mcp-server -> team-api-agents-mcp-server Tab URL value is `api-agents`. Placed between Integrations and Query Settings. Co-Authored-By: Claude Opus <noreply@anthropic.com>
|
Follow-up change in Webhooks, API keys, and the MCP install were sharing the Integrations tab only because that's where each section first landed. API keys and the MCP install pair naturally (the MCP server authenticates with the personal access key shown in the API Keys card), and webhooks are a different category of integration. Easier to triage as separate tabs. New tab: Integrations tab: webhooks only. Section IDs renamed for the moved sections:
PR body updated to reflect the new layout. 22 jest tests still pass, tsc / eslint / prose-lint clean. |
The new "API & Agents" tab is the more frequently visited surface for self-managed deployments (personal access key + MCP install sit there), so it should land earlier in the tab strip than Integrations (webhooks). Co-Authored-By: Claude Opus <noreply@anthropic.com>
OpenCode's MCP config lives under an `mcp` key (not `mcpServers`) and uses `type: "remote"` for HTTP transport. Earlier reading of the spec + the open anomalyco/opencode#8058 issue suggested that shape only supports the deprecated HTTP+SSE transport and would fail against our Streamable HTTP server with a 405. Verified empirically against a running ClickStack instance on 2026-06-04: pure `type: "remote"` config connects successfully. Either OpenCode has shipped Streamable HTTP support since the issue was filed or the SDK's StreamableHTTPServerTransport serves the legacy handshake transparently; either way, the snippet works. Adds: - `BuiltSnippets.openCode` + `buildOpenCodeJsonBlock` builder in installSnippets.ts. JSDoc cites the docs URL and the empirical verification. - 'opencode' variant in McpInstallPanel: AgentHost union, CHOICES entry (positioned before "Other"), HostIcon (IconBraces), and the HostInstall render branch. The install primitive is a CopySnippet with a label that names both config-file locations (project-local `opencode.json` and global `~/.config/opencode/config.json`). - 3 new tests: shape verification (mcp + type: "remote"), negative assertion (must NOT emit `mcpServers` or `type: "http"`), and a panel-level test that exercises the render branch. 25 tests pass (was 22). Co-Authored-By: Claude Opus <noreply@anthropic.com>
|
Adding OpenCode in Earlier comment hedged that OpenCode's The earlier observation that other hosts should NOT migrate to Snippet shape: {
"mcp": {
"clickstack": {
"type": "remote",
"url": "<origin>/api/mcp",
"headers": { "Authorization": "Bearer <accessKey>" }
}
}
}UI: positioned between Codex CLI and Other in the segmented control. Install primitive is a CopySnippet with a label that names both config-file locations (project-local Tests: 25 pass (was 22). The 3 new tests are positive shape ( |
The earlier tab split moved ApiKeysSection out of Integrations and into a dedicated "API & Agents" tab on TeamPage. Three e2e tests in team.spec.ts still asserted the old layout (API Keys lives on Integrations) and were failing Shard 4 with the structural change. Changes: TeamPage page-object (tests/e2e/page-objects/TeamPage.ts): - Add apiAndAgentsTabButton locator (matches `API & Agents` tab). - Add mcpServerSection locator (matches `mcp-server-section` testid on the new install panel). - Add openApiAndAgentsTab() helper that mirrors the existing openTab pattern, waiting on the API Keys section to render. - Expose apiAndAgentsTab and mcpServer getters for assertions. team.spec.ts: - "should load team page tabs and show all sections": assert the new API & Agents tab in the visible-tabs check; add steps that open the new tab and assert ApiKeysSection + McpServerSection render with their headings. Integrations tab steps now only assert webhooks (no API Keys). - "should display API keys": switch openIntegrationsTab() to openApiAndAgentsTab(). - "should open and cancel rotate API key modal": same switch. Webhook tests (createWebhook, redact-secrets) are unchanged; they still belong on the Integrations tab and still pass. Co-Authored-By: Claude Opus <noreply@anthropic.com>
|
@brandon-pereira - implemented your feedback, moved API & AI into separate tab to make them easier to find |
Greptile SummaryThis PR adds a "Connect your AI Agents" install panel to Team Settings under a new "API & Agents" tab, giving users a one-click or copy-paste path to register the ClickStack MCP server in Claude Code, Cursor, VS Code, Codex CLI, OpenCode, or any generic MCP host.
Confidence Score: 3/5Safe to merge after fixing the VS Code manual-setup JSON format; all other paths are correct and well-tested. The VS Code manual-setup fallback uses the wrong JSON top-level key (mcpServers instead of servers), silently breaking that install path for users who cannot use the deep link. packages/app/src/components/ClickStackOnboarding/McpInstallPanel.tsx and installSnippets.ts need a dedicated VS Code JSON builder emitting the Important Files Changed
|
| card) lets a user install the HyperDX MCP server in Claude Code, Cursor, | ||
| VS Code + Copilot, Codex CLI, or any MCP-compatible host without hand-rolling | ||
| JSON. Per-host snippets carry the user's personal access key so the install | ||
| works against the existing `/api/mcp` route without extra setup. |
There was a problem hiding this comment.
Changeset description references the wrong tab
The note says the section lives on the "Integrations tab, above the API Keys card", but the actual implementation introduces a dedicated "API & Agents" tab and removes the API Keys card from Integrations entirely. The user-facing change note will be misleading in the changelog.
| card) lets a user install the HyperDX MCP server in Claude Code, Cursor, | |
| VS Code + Copilot, Codex CLI, or any MCP-compatible host without hand-rolling | |
| JSON. Per-host snippets carry the user's personal access key so the install | |
| works against the existing `/api/mcp` route without extra setup. | |
| A new "API & Agents" tab on the Team Settings page lets a user install the | |
| HyperDX MCP server in Claude Code, Cursor, VS Code + Copilot, Codex CLI, | |
| OpenCode, or any MCP-compatible host without hand-rolling JSON. Per-host | |
| snippets carry the user's personal access key so the install works against | |
| the existing `/api/mcp` route without extra setup. The API Keys card has | |
| moved to this tab alongside the new install panel. |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| function shellQuoteHeader(name: string, value: string): string { | ||
| const escaped = value.replace(/(["\\$`])/g, '\\$1'); | ||
| return `--header "${name}: ${escaped}"`; | ||
| } |
There was a problem hiding this comment.
Newline characters in the header value are not escaped
shellQuoteHeader escapes ", \, $, and backtick, but a \n inside the value would split the printed command across multiple lines in a way that corrupts it visually. The comment explicitly calls out future key format changes as the motivation for this escaping, so covering \n and \r would make the defensive story complete.
| function shellQuoteHeader(name: string, value: string): string { | |
| const escaped = value.replace(/(["\\$`])/g, '\\$1'); | |
| return `--header "${name}: ${escaped}"`; | |
| } | |
| function shellQuoteHeader(name: string, value: string): string { | |
| const escaped = value | |
| .replace(/(["\\$`])/g, '\\$1') | |
| .replace(/\n/g, '\\n') | |
| .replace(/\r/g, '\\r'); | |
| return `--header "${name}: ${escaped}"`; | |
| } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Adds a "Connect your AI Agents" section to the Team Settings page in a new "API & Agents" tab, alongside the existing API Keys card. Users get a one-click install path for the ClickStack MCP server in Claude Code, Cursor, VS Code, Codex CLI, OpenCode, or any MCP-compatible host without hand-rolling JSON.
The MCP server resolves the active team from the bearer token, so the install snippet uses a single fixed
clickstackserver name; no per-tenant suffix needed on the client. Header values are shell-escaped against\",$, backtick, and\\so a future access-key format with metacharacters can't turn a copy-paste install into a shell-injection vector.What's here
packages/app/src/components/ClickStackOnboarding/(new directory). Houses the install panel and its pieces:installSnippets.ts: pure per-host snippet builders. Unit-tested round-trip throughbuildAllSnippets.McpInstallPanel.tsx: host picker (MantineSegmentedControl) + render branch per host. Exhaustive switch viasatisfies neverso adding a host variant fails the compile.CopySnippet.tsx: code block with a<CopyButton>affordance.DeeplinkInstall.tsx: primary "Add to " button +Manual setuptoggle that reveals a fallback JSON snippet.packages/app/src/components/TeamSettings/McpServerSection.tsx: wraps the panel, derives the deployment shape fromuseMe(), mounts in the new tab.packages/app/src/TeamPage.tsx: introduces a new "API & Agents" tab (URL valueapi-agents) that houses the API Keys card and the AI Agents install panel. The Integrations tab keeps webhooks only..changeset/connect-your-ai-assistant.md: user-facing change note.Hosts covered
cursor://deep link, JSON fallback behindManual setup)vscode:mcp/installdeep link, JSON fallback; requires Copilot Chat 1.99+)mcpkey,type: "remote"; documented at https://opencode.ai/docs/mcp-servers/)mcpServersJSON block for Claude Desktop, Continue, Cline, and the long tail)Test plan
yarn jest --testPathPatterns="installSnippets|McpServerSection"(25 tests pass).tsc --noEmitclean on every touched file.prose-lintclean on every touched file./api/mcp. Holding this checkbox until I drive the flow once.Notes
alex/(human prefix) keeps the classifier on the standard human thresholds.test.fixme(true, ...)because the e2e webServer runs inIS_LOCAL_MODE, which short-circuitsuseMe/useTeambefore the route mocks can land. That suite was deleted in the review-feedback fix-pack; component coverage in__tests__/McpServerSection.test.tsxexercises every render branch. A real e2e spec can come back once the e2e build-flag work lands.Linear: HDX-4440
Co-Authored-By: Claude Opus noreply@anthropic.com