diff --git a/.changeset/agent-key-bearer.md b/.changeset/agent-key-bearer.md new file mode 100644 index 00000000..3b720bd7 --- /dev/null +++ b/.changeset/agent-key-bearer.md @@ -0,0 +1,5 @@ +--- +"@cloudhal/api": minor +--- + +Agent keys are also accepted as `Authorization: Bearer ` (what most MCP clients and Codex's `bearer_token_env_var` send), on `/mcp` and wherever `x-api-key` was. `/api/admin/*` keeps its deployment-token bearer and never reads it as an agent key. diff --git a/apps/studio/src/components/CreateAgentKeyDialog.tsx b/apps/studio/src/components/CreateAgentKeyDialog.tsx index 1d894ab8..e9927c60 100644 --- a/apps/studio/src/components/CreateAgentKeyDialog.tsx +++ b/apps/studio/src/components/CreateAgentKeyDialog.tsx @@ -8,26 +8,23 @@ import { Input, InsetPanel, LayerDialog, - Tabs, Text, } from "@cloudhal/ui"; -import { CodeHighlighted } from "@cloudhal/ui/code"; import { ArrowCounterClockwise, Info, Warning } from "@phosphor-icons/react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { listOf, useT } from "../i18n/index.tsx"; import { type GrantableScope, - mcpSnippet, SCOPE_GROUPS, type ScopeGroup, - type SnippetKind, sameScopes, scopeOptions, } from "../lib/agents.ts"; import { api, type CreatedAgentKey, errorCode, type KeyMode } from "../lib/api.ts"; import { apiErrorText } from "../lib/api-errors.ts"; import { AutoModeConfirm, BypassHint, ModeBadge, ModePicker } from "./AgentModes.tsx"; +import { McpClientSetup } from "./McpClientSetup.tsx"; import { useAgentModeRules } from "./useAgentModeRules.ts"; type Step = "mode" | "details"; @@ -61,7 +58,6 @@ export function CreateAgentKeyDialog({ const [advancedOpen, setAdvancedOpen] = useState(false); const [submitted, setSubmitted] = useState(false); const [created, setCreated] = useState(); - const [snippet, setSnippet] = useState("json"); const modeScopes = rules.scopesFor(mode); const customised = !sameScopes(scopes, modeScopes); @@ -89,7 +85,6 @@ export function CreateAgentKeyDialog({ setScopes(rules.scopesFor(rules.defaultMode)); setAdvancedOpen(false); setSubmitted(false); - setSnippet("json"); create.reset(); } else { setCreated(undefined); @@ -160,30 +155,11 @@ export function CreateAgentKeyDialog({ tooltip={{ text: t("common.copy"), copiedText: t("common.copied") }} /> -
-
- - {t("agents.reveal.config")} - - setSnippet(v as SnippetKind)} - tabs={(["json", "claude"] as const).map((k) => ({ - value: k, - label: t(`agents.connect.tabs.${k}`), - }))} - /> -
-
- -
-
+ ) : step === "mode" ? (
diff --git a/apps/studio/src/components/McpClientSetup.tsx b/apps/studio/src/components/McpClientSetup.tsx new file mode 100644 index 00000000..57a6a91f --- /dev/null +++ b/apps/studio/src/components/McpClientSetup.tsx @@ -0,0 +1,48 @@ +import { Select, Text } from "@cloudhal/ui"; +import { CodeHighlighted } from "@cloudhal/ui/code"; +import { useState } from "react"; +import { useT } from "../i18n/index.tsx"; +import { type McpClient, mcpSnippet, REMOTE_CLIENTS, snippetLang } from "../lib/agents.ts"; + +/** + * Ready-to-copy setup for one MCP client at a time (docs/agents/clients.md): a client picker, the + * snippet for this deployment's `/mcp`, and where it goes. With `agentKey` (the one-time reveal) + * the key is filled in; `local` adds the stdio server for the person's own machine. + */ +export function McpClientSetup({ + endpoint, + agentKey, + local = false, + label, +}: { + endpoint: string; + agentKey?: string; + local?: boolean; + /** The picker's label; "Client" by default. */ + label?: string; +}) { + const t = useT(); + const [client, setClient] = useState("claude"); + const clients: McpClient[] = local ? [...REMOTE_CLIENTS, "stdio"] : [...REMOTE_CLIENTS]; + return ( +
+