Commit fead02c
authored
🤖 feat: add MCP headers table editor (#1287)
Adds a structured HTTP headers editor for remote MCP servers, replacing
the raw JSON textarea in:
- **Add server** (http/sse/auto)
- **Edit server** (inline)
Notable behavior:
- Per-header **Text** vs **Secret** mode (secret references project
secrets by key, with suggestions)
- Validation for common pitfalls (case-insensitive duplicates,
accidental `:` in names, newline rejection)
- Keeps persisted schema unchanged: `headers?: Record<string, string | {
secret: string }>`
Tests:
- `src/browser/utils/mcpHeaders.test.ts`
---
<details>
<summary>📋 Implementation Plan</summary>
# Plan: Friendly HTTP headers editor for remote MCP servers
## Goals
- Replace the **raw “Headers (JSON)” textarea** with a **table-style
header editor** in both places it appears:
1) **Add server** form (HTTP/SSE/Auto transports)
2) **Edit existing server** inline editor (HTTP/SSE/Auto)
- Make it easy to configure:
- Normal header values (strings)
- Secret-backed header values (`{ secret: "…" }`) without writing JSON
- Keep config storage/back-end APIs **unchanged** (`headers?:
Record<string, string | {secret: string}>`).
## What we have today (repo findings)
- The MCP server UI lives in
`src/browser/components/Settings/sections/ProjectSettingsSection.tsx`.
- State uses `headersJson: string` and `parseHeadersJson()` to
validate/convert it.
- Both “Add server” and “Edit server” show a `textarea` for JSON.
- Header types are already modeled:
- `MCPHeaderValue = string | { secret: string }`
(`src/common/types/mcp.ts`)
- Zod schema mirrors this (`src/common/orpc/schemas/mcp.ts`).
- **Radix UI doesn’t provide a Table component**; this repo uses Radix
primitives + shadcn wrappers (`src/browser/components/ui/*`) and
Tailwind.
- The closest existing “key/value table editor” is **SecretsModal**
(`src/browser/components/SecretsModal.tsx`), which uses a simple
Tailwind **CSS grid** to provide add/remove rows + inline editing.
## Recommended approach (best UX / moderate scope) — **net +~320 LoC
(product code)**
### UX proposal
- Replace the textarea with a **Headers** editor that looks/behaves like
a small table:
| Header name | Value type | Value | |
|---|---|---|---|
| `Authorization` | `Secret` / `Text` | secret picker or text input |
delete |
- Features:
- **Add/remove rows** (`+ Add header`)
- **Value type toggle** per row:
- **Text** → stores `"value"`
- **Secret** → stores `{ secret: "SECRET_NAME" }`
- **Secret picker** based on project secrets (plus freeform entry for
custom secret names)
- **Inline validation** (no more “JSON parse error” banner):
- Duplicate header names (case-insensitive)
- Empty name/value rows are ignored on save/test
- Newlines in header values rejected
- Secret key missing from project secrets → warning (still allow save if
user wants; test will fail anyway)
- Optional: an **“Advanced: JSON”** collapsible showing the generated
JSON (read-only by default; editable only if we want to support
paste/import)
### Implementation steps
1. **Introduce a reusable editor component**
- Add `MCPHeadersEditor` (new file, likely under
`src/browser/components/` or
`src/browser/components/Settings/components/`).
- Internally model rows as:
- `type HeaderRow = { id: string; name: string; kind: "text" | "secret";
value: string }`
- Provide helper conversions:
- `rowsToHeadersRecord(rows) => Record<string, MCPHeaderValue> |
undefined`
- `headersRecordToRows(headers) => HeaderRow[]`
- Return both:
- `headers` (converted record) for parent to pass to API
- `validation` info (so parent can disable **Test/Add/Save** when
invalid)
2. **Load project secrets for the secret picker**
- In `ProjectSettingsSection.tsx`, use
`useProjectContext().getSecrets(selectedProject)`.
- Keep `projectSecrets: Secret[]` in state; refresh when selected
project changes.
3. **Update Add server flow**
- Replace `headersJson: string` in `EditableServer` with `headersRows:
HeaderRow[]`.
- Swap the textarea for `<MCPHeadersEditor … />`.
- On **Test** / **Add**:
- Convert rows → record and pass `headers` to
`api.projects.mcp.test/add`.
- If validation fails, show inline error and do not call the API.
4. **Update Edit existing server flow**
- On `handleStartEdit`, initialize `headersRows` from `entry.headers`.
- Replace the edit-mode textarea with the same editor.
- On **Save**, use the same row → record conversion.
5. **Polish / discoverability**
- Rename label from **“Headers (JSON)”** → **“HTTP headers
(optional)”**.
- In non-edit display, show a small summary like `Headers: 2` (optional
tooltip listing keys).
- Add helper text nudging users to secrets for auth tokens.
### Tests / validation
- Add unit tests for the conversion + validation helpers (pure logic):
- Round-trip conversion (`record → rows → record`)
- Secret rows convert to `{ secret: … }`
- Duplicate-key detection is case-insensitive
- Newline rejection
### Manual QA checklist
- Add server (HTTP/SSE/Auto): add headers, switch Text/Secret, test,
add.
- Edit existing server: headers load correctly, save works.
- Existing configs with JSON headers continue working and render in the
table.
- Missing secret key: editor shows warning; “Test” fails with clear
error.
---
## Alternative approaches
<details>
<summary>Option A (simpler, fewer features) — net +~200 LoC</summary>
- Table editor only supports `string` values.
- Keep the old JSON textarea behind “Advanced” for `{ secret: … }`
use-cases.
- Pros: faster to ship.
- Cons: users still need JSON for the most common auth use-case.
</details>
<details>
<summary>Option B (more ambitious) — net +~420 LoC</summary>
- Extract a generic `KeyValueGridEditor` from `SecretsModal.tsx`.
- Reuse it for both Secrets and MCP Headers (reduces duplicate patterns
long-term).
- Pros: cleaner component library.
- Cons: bigger refactor; more surface area for regression.
</details>
</details>
---
_Generated with `mux` • Model: `openai:gpt-5.2` • Thinking: `xhigh`_
---------
Signed-off-by: Thomas Kosiewski <tk@coder.com>1 parent 21848f5 commit fead02c
File tree
6 files changed
+642
-87
lines changed- .storybook/mocks
- src/browser
- components
- Settings/sections
- stories
- utils
6 files changed
+642
-87
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
87 | 88 | | |
88 | 89 | | |
89 | 90 | | |
| 91 | + | |
| 92 | + | |
90 | 93 | | |
91 | 94 | | |
92 | 95 | | |
| |||
132 | 135 | | |
133 | 136 | | |
134 | 137 | | |
| 138 | + | |
135 | 139 | | |
136 | 140 | | |
137 | 141 | | |
| |||
229 | 233 | | |
230 | 234 | | |
231 | 235 | | |
232 | | - | |
233 | | - | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
234 | 242 | | |
235 | 243 | | |
236 | 244 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
0 commit comments