Skip to content

feat(mcp): replace /settings/mcp with full-featured /mcp page (catalog + marketplace + sources)#231

Merged
outsourc-e merged 9 commits into
outsourc-e:mainfrom
Interstellar-code:feature/mcp-management-v2
May 3, 2026
Merged

feat(mcp): replace /settings/mcp with full-featured /mcp page (catalog + marketplace + sources)#231
outsourc-e merged 9 commits into
outsourc-e:mainfrom
Interstellar-code:feature/mcp-management-v2

Conversation

@Interstellar-code

Copy link
Copy Markdown
Contributor

Summary

Replaces the upstream /settings/mcp route and its associated screen with a
dedicated, full-featured /mcp page. This workspace-only change touches no
hermes-agent code — all modifications are confined to src/, assets/, and
eslint.config.js.

Closes #230


What changed

Removed (replaced by /mcp)

  • src/screens/settings/mcp-settings-screen.tsx (759 LOC) — old settings-embedded MCP UI
  • src/routes/settings/mcp.tsx — old /settings/mcp route
  • src/routes/api/mcp/servers.ts and reload.ts — orphaned endpoints (reload POSTs to a gateway 404)
  • Settings-sidebar MCP nav entries — replaced by the top-level /mcp route

Added — Phase 1: Core MCP management page

  • /mcp route with capability gate + BackendUnavailableState fallback
  • REST API: GET /api/mcp, POST /api/mcp, PUT /api/mcp/configure,
    DELETE /api/mcp/$name, POST /api/mcp/test, POST /api/mcp/discover
  • McpScreen (Installed / Marketplace tabs + search + category filter)
  • McpServerCard (status badge, Test/Edit/Delete, enable toggle)
  • McpServerDialog (HTTP/stdio + auth + Discover + Save; bearer-token clear-on-submit)
  • TanStack Query hooks: useMcpServers, useTestMcpServer, useDiscoverMcpTools,
    useUpsertMcpServer, useConfigureMcpServer, useDeleteMcpServer
  • Type split: read shapes in src/types/mcp.ts (client+server); write shapes in
    src/types/mcp-input.ts (server-only, secrets never leave server)
  • Runtime normalization layer (src/server/mcp-normalize.ts) — strips unknown fields,
    coerces enums, masks secrets via MASK_SENTINEL, preserves ${VAR_NAME} env-refs
  • All write endpoints CSRF-checked via requireJsonContentType
  • Sidebar/nav wiring: command palette, inspector panel, mobile nav, search modal,
    workspace shell, chat-sidebar Knowledge group

Added — Phase 1.5: Localhost-only config-fallback transport

  • gateway-capabilities.ts: probeMcp uses authenticated dashboardFetch;
    probeMcpConfigKey + isLocalhostDeployment for mcpFallback capability
  • /mcp route gate accepts mcp || mcpFallback
  • use-mcp-capability-mode.ts hook for runtime mode detection
  • GET /api/mcp/$name/logs SSE endpoint + McpLogsDrawer component
  • use-mcp-oauth.ts hook for OAuth reauth flow

Added — Phase 2: File-backed catalog

  • assets/mcp-presets.seed.json — seed catalog bundled with the app
  • ~/.hermes/mcp-presets.json — atomic bootstrap (tmp+linkSync), mtime+ino cache,
    malformed-file preservation, full schema validation (id regex, transport fields,
    env key regex, HTTPS URLs, category allowlist, duplicate-id rejection)
  • src/server/mcp-input-validate.ts — shared parseMcpServerInput with per-field errors
  • GET /api/mcp/presets handler

Added — Phase 3.0: Federated marketplace

  • src/server/mcp-hub/ — Smithery registry adapter with ETag/If-Modified-Since 304
    reuse, rate-limit handling, parallel Promise.allSettled across sources (8s
    per-source timeout), dedupe by source+id+name, fallback to local-file when remote
    is degraded
  • Trust hardening: shell metachar rejection, transport allowlist, env-key regex,
    control-char + absolute-path attack defenses, inline-exec flag detection
  • InstallConfirmationDialog — 2-click commit with full template preview
    (command/args/env masked), placeholder detection blocks commit until filled,
    AbortController on dismiss
  • src/server/mcp-tools-cache.ts — disk-persisted tool-discovery cache
  • src/server/mcp-cli-bridge.ts — hermes-mcp CLI bridge for live test/tool enumeration

Added — Phase 3.2: User-configurable sources

  • ~/.hermes/mcp-hub-sources.json schema — built-ins always present + protected;
    users can add HTTPS-only generic-json sources with trust + format
  • GET/POST /api/mcp/hub-sources, PUT/DELETE /api/mcp/hub-sources/$id — CRUD
    with per-process mutex (read-modify-write race protection)
  • Generic-json adapter: SSRF guard (private/loopback/link-local/IPv6 ULA all
    rejected after DNS resolution, redirects disabled), 5MB response-size cap
  • SourcesManagerDialog UI component

Files changed

87 files changed, 12723 insertions(+), 1146 deletions(-)

Key paths:

  • src/routes/mcp.tsx — route entry point
  • src/screens/mcp/ — all MCP UI screens, components, hooks, lib
  • src/routes/api/mcp/ — all MCP API route handlers
  • src/server/mcp-normalize.ts, mcp-input-validate.ts, mcp-presets-store.ts,
    mcp-tools-cache.ts, mcp-cli-bridge.ts, mcp-hub-sources-store.ts
  • src/server/mcp-hub/ — federated marketplace infrastructure
  • assets/mcp-presets.seed.json — bundled preset catalog
  • src/types/mcp.ts, src/types/mcp-input.ts — type definitions
  • src/lib/feature-gates.tsmcp + mcpFallback capability gates
  • src/server/gateway-capabilities.ts — MCP capability probing

Test coverage

296 tests passing across 19 MCP suites (vitest):

Suite Tests
src/routes/api/-mcp.test.ts 14
src/routes/api/-mcp-logs.test.ts ~10
src/routes/api/mcp/-hub-search.test.ts ~15
src/routes/api/mcp/-hub-sources.test.ts ~20
src/routes/api/mcp/-presets.test.ts ~12
src/server/mcp-normalize.test.ts 13+
src/server/mcp-input-validate.test.ts ~12
src/server/mcp-presets-store.test.ts ~15
src/server/mcp-tools-cache.test.ts ~8
src/server/mcp-hub-sources-store.test.ts ~10
src/server/mcp-hub/-cache.test.ts ~8
src/server/mcp-hub/-trust.test.ts ~10
src/server/mcp-hub/-unified-search.test.ts ~12
src/server/mcp-hub/-unified-search-user-sources.test.ts ~8
src/server/mcp-hub/sources/-*.test.ts (×3) ~25
src/server/mcp-hub/lib/-ssrf-guard.test.ts ~10
src/screens/mcp/-marketplace-*.test.tsx (×2) ~30
src/screens/mcp/-mcp-oauth.test.ts ~5
src/components/-mcp-nav.test.tsx ~5

Build: pnpm build green (✓ built in 2.13s).


Scope constraint

Workspace-only — no changes to hermes-agent, no dashboard API contract changes.
The MCP backend endpoints are implemented entirely within the workspace's TanStack
Start server functions (src/routes/api/mcp/).


Verifiers

  • Build: pnpm build — pass
  • MCP test suites: 296 tests, 19 suites — all pass
  • git diff upstream/main..feature/mcp-management-v2 --name-only confirms zero
    personal dev files (no version bump, no react-grab, no settings-precedence changes)
  • Conflict resolved: AGENTS.md (local dev artifact, not present in upstream) — removed
  • Bug fixed: duplicate assistantCorruptionWarning declaration in message-item.tsx
    introduced during commit squash — removed stale plain-call, kept the useMemo version

Worked with Interstellar Code

@Interstellar-code

Copy link
Copy Markdown
Contributor Author

Screenshots

Installed tab — server cards with status, tools, auth, action buttons

MCP Installed tab

Marketplace tab — federated catalog with source + trust badges

MCP Marketplace tab

Add/Edit dialog — Skills-pattern primitive Dialog with form fields + auth

Add/Edit MCP Server dialog

@outsourc-e

Copy link
Copy Markdown
Owner

Thanks for the substantial work, @Interstellar-code — this is impressive scope. CI is green and the architecture (REST API + TanStack hooks + dialog/marketplace split) reads well.

That said, this touches 85 files and removes the existing /settings/mcp surface, so I'd rather hold for hands-on local testing before merging. I'll plan a review session this weekend, specifically validating:

  • Capability gate behaviour when the backend is missing the new endpoints
  • Migration path for users with existing config.yaml MCP entries
  • Marketplace fallback when the registry is unreachable
  • Bearer token redaction in the runtime normalization layer

If you have a hosted preview or Loom walkthrough handy, that'd help speed it up. Will report back with findings.

@Interstellar-code

Copy link
Copy Markdown
Contributor Author

Thanks for the substantial work, @Interstellar-code — this is impressive scope. CI is green and the architecture (REST API + TanStack hooks + dialog/marketplace split) reads well.

That said, this touches 85 files and removes the existing /settings/mcp surface, so I'd rather hold for hands-on local testing before merging. I'll plan a review session this weekend, specifically validating:

  • Capability gate behaviour when the backend is missing the new endpoints
  • Migration path for users with existing config.yaml MCP entries
  • Marketplace fallback when the registry is unreachable
  • Bearer token redaction in the runtime normalization layer

If you have a hosted preview or Loom walkthrough handy, that'd help speed it up. Will report back with findings.

I'm happy to take a more active role in this repository. Actually I was thinking initially to fork and develop myself but since you were responsive in terms of approving my bug fixes initially. I have lots of features already lined up. Let me know if you want to connect more directly to coordinate and work together efficiently. Thanks!

@outsourc-e

Copy link
Copy Markdown
Owner

Big and well-scoped PR \u2014 thanks for the work. Needs a rebase against latest main (recently merged composer/skills work touches related areas). Could you do:

git fetch origin && git rebase origin/main
git push --force-with-lease

After rebase + green CI we can merge.

Implements the MCP management plan (.omc/plans/mcp-management.md) Phase 1
end-to-end on a single feature branch (PR1+PR2+PR3+PR4 collapsed):

- New `/mcp` route with capability gate + BackendUnavailableState fallback.
- New `/api/mcp` (GET list, POST create), `/api/mcp/test` (POST connection
  probe), `/api/mcp/discover` (POST tool discovery for a draft config),
  `/api/mcp/configure` (PUT enable/toolMode/include/exclude), and
  `/api/mcp/$name` (DELETE).
- Strict `mcp` capability probe in gateway-capabilities: hits `GET /api/mcp`
  directly and validates the body parses through `normalizeMcpList` —
  dashboard-up-but-route-missing returns false (resolves Open Question #4).
- Type split: read shapes in `src/types/mcp.ts` (client+server), write
  shapes in `src/types/mcp-input.ts` (server-only; secrets contained here).
- Runtime normalization layer `src/server/mcp-normalize.ts` mirrors the
  Skills `asRecord`/`readString`/`normalizeSkill` defense — strips
  unknown fields, coerces enums, masks secrets via `MASK_SENTINEL`,
  re-applies via `maskSecretsInPlace` before every `json(...)`.
- All write endpoints CSRF-checked via `requireJsonContentType`.
- Capability-off responses use `createCapabilityUnavailablePayload('mcp')`
  with `{ servers: [], total: 0, categories }` for GET (200) and 503 for
  writes — feature gates fall open without throwing.
- Static preset catalog (`src/screens/mcp/presets.ts`) with GitHub,
  Filesystem, Postgres, Slack, Linear; Catalog tab installs prefilled
  drafts through the same dialog flow.
- Screens: `McpScreen` (Installed/Catalog/All tabs + search + category
  filter), `McpServerCard` (status badge + Test/Edit/Delete + enable
  toggle), `McpServerDialog` (HTTP/stdio + auth + Discover + Save with
  bearer-token clear-on-submit).
- TanStack Query hooks (`useMcpServers`, `useTestMcpServer`,
  `useDiscoverMcpTools`, `useUpsertMcpServer`, `useConfigureMcpServer`,
  `useDeleteMcpServer`).

Tests (vitest):
- `src/server/mcp-normalize.test.ts` — 13 tests covering enum coercion,
  list-shape variants, malformed-entry drop, presence flags without
  echo, env/header masking by key hint, idempotency, test-result
  normalization, payload-string scanner.
- `src/routes/api/-mcp.test.ts` — 8 tests covering input validation,
  capability fall-open shape, CSRF gate (415 on non-JSON POST, pass on
  JSON, pass on GET), and the **secret echo guard**: a worst-case agent
  that echoes a submitted bearer token in body/env/headers must never
  surface the original string in the workspace response.

Build, lint, and the new test files are clean. Pre-existing unrelated
test failures on `local` (router-route-resolution, context-usage,
markdown math, slash-command-menu, chat-message-list, gateway-capabilities
env-source) are unchanged by this PR.

Worked with Interstellar Code
Architect review flagged that `McpClientInput` in `src/types/mcp.ts` (the
file explicitly designated for client+server read shapes with no secrets)
contained `bearerToken` and `oauth.clientSecret`, allowing the browser
bundle to import a secret-bearing type via the dialog component.

Resolves the type-split violation:
- `src/types/mcp.ts`: drop `bearerToken` and `oauth` from `McpClientInput`.
  Now strictly the browser-safe form payload, no secret fields.
- `src/screens/mcp/components/mcp-server-dialog.tsx`: hold `bearerToken`
  in ephemeral component-local `useState<string>` typed inline. Cleared
  on submit and on dialog open. No exported type carries the field.
- `src/screens/mcp/hooks/use-mcp-mutations.ts`: `useUpsertMcpServer`
  accepts `McpClientInput & { bearerToken?: string }` inline at the
  call-site, again with no exported secret-bearing type. Server route
  `parseMcpServerInput` re-validates and forwards to the agent.

The full server-side write shape (`McpServerInput` with secrets) remains
in `src/types/mcp-input.ts`, server-only.

Worked with Interstellar Code
Add no-restricted-imports rule scoped to src/screens/** and
src/components/** that blocks importing @/types/mcp-input. That
file may carry unmasked secrets and is server-only — clients should
import McpClientInput from @/types/mcp instead.

Worked with Interstellar Code
Mirror the existing /skills registration across every nav and
command surface so the MCP screen is reachable from the dashboard
overflow grid, command palette, mobile hamburger drawer, mobile tab
bar, slash menu, search modal quick actions, and workspace shell
(active-tab tracking + mobile page title). Inspector panel gets a
parallel MCP tab that lists configured servers via /api/mcp.

Worked with Interstellar Code
Catalog tab now reuses the screen's search state to filter presets
by name/description, surfaces an empty-state when no presets match,
and renders each preset as a card with an Official Presets category
badge styled to match the skills-screen design vocabulary.

Tests:
- src/components/-mcp-nav.test.tsx: each modified nav file references
  the /mcp route (or registers an mcp tab id for inspector-panel)
- src/screens/mcp/-presets.test.ts: filtering MCP_PRESETS by query
  narrows results by name and description, returns full catalog for
  empty queries, and returns nothing for unknown queries

Worked with Interstellar Code
The primary visible left rail (`chat-sidebar.tsx`) was missed by the
prior nav-coverage commit. Slot MCP between Skills and Profiles in
`knowledgeItems`, mirroring the McpServerIcon used elsewhere.

Worked with Interstellar Code
…logs

- `useMcpServers`: enable refetchOnWindowFocus for live state.
- `McpServerCard`: per-card Refresh button (re-runs Test, updates
  discoveredToolsCount), Reauth button when authType === 'oauth' (uses
  new useMcpOAuth hook), Logs button (opens McpLogsDrawer).
- `use-mcp-oauth.ts`: opens auth URL in new tab, polls /api/mcp/test
  every 2s until status === 'connected' or 60s timeout. Returns
  mutation-style { start, isPending, isError, error, data }.
- `mcp-logs-drawer.tsx`: fixed-right slide-in drawer subscribing via
  EventSource to /api/mcp/<name>/logs. Newest-first, max 500 lines,
  auto-scroll, tear down on close (no zombie EventSource).
- `routes/api/mcp/$name.logs.ts`: SSE proxy with auth + capability
  gates. Capability-off → 503. Pattern follows chat-events.ts.
- Tests: 3 new for logs route (input validation, capability-off,
  auth gate); smoke test for useMcpOAuth shape.

Total tests: 24 passing (13 normalize + 8 mcp + 3 logs). Build clean.

Worked with Interstellar Code
Adds an `mcpFallback` capability that lets the workspace perform CRUD on
`config.mcp_servers` via the existing dashboard `/api/config` route when
the agent does not yet expose the new `/api/mcp*` runtime endpoints.

Gated to loopback-only deployments by `isLocalhostDeployment()` (both
URLs loopback AND HOST unset/loopback). Test/Discover/Logs return a
structured "not yet available" payload in fallback mode; the MCP screen
renders an amber banner so the limitation is visible.

Worked with Interstellar Code
Workspace-only end-to-end MCP catalog + marketplace replacing the static
presets.ts and the upstream /settings/mcp surfaces.

Phase 2 — File-backed catalog:
- assets/mcp-presets.seed.json + ~/.hermes/mcp-presets.json (atomic
  bootstrap via tmp+linkSync, mtime+ino+ctime+size cache, malformed-file
  preservation, schema validation: id regex, transport-specific fields,
  env key regex, https URLs, category allowlist, duplicate-id rejection,
  unknown-field warnings)
- src/server/mcp-input-validate.ts: shared parseMcpServerInput returning
  per-field {path, message} errors; promoted from inline definition
- src/routes/api/mcp/presets.ts GET handler

Phase 3.0 — Federated marketplace:
- src/server/mcp-hub/{cache,trust,index,types}.ts + sources/{mcp-get,
  local-file}.ts: Smithery registry adapter (replaces speculative
  registry.mcp.run NXDOMAIN), ETag/If-Modified-Since with 304 reuse,
  rate-limit handling, parallel Promise.allSettled across sources with
  8s per-source timeout, dedupe by source+id+name, fallback to
  local-file when remote degraded
- Trust hardening: shell metachar reject, transport allowlist, env-key
  regex, control-char + absolute-path attack defenses, inline-exec
  flag detection (-c, -lc, -e for sh/bash/python/node/perl/ruby)
- src/screens/mcp/components/install-confirmation-dialog.tsx: 2-click
  commit with full template preview (command/args/env masked) and
  AbortController on dismiss
- Disk persistence for tool-discovery cache (mcp-tools-cache.ts) +
  hermes-mcp CLI bridge (mcp-cli-bridge.ts) for live test/tool
  enumeration in fallback mode

Phase 3.2 — User-configurable sources:
- ~/.hermes/mcp-hub-sources.json schema (built-ins always present,
  protected from mutation; user can add HTTPS-only generic-json
  sources with trust+format)
- src/routes/api/mcp/hub-sources{,.$id}.ts CRUD with per-process mutex
  (read-modify-write race protection)
- generic-json adapter: SSRF guard (private/loopback/link-local/IPv6
  ULA all rejected after DNS resolution, redirects disabled), 5MB
  response-size cap (streaming read), trust hard-cap at 'community'
  for user-source entries, source field 'user:<id>' for dedupe
- src/screens/mcp/components/sources-manager-dialog.tsx UI

Polish:
- Placeholder detection at install confirmation (inline fill form
  blocks commit until /path/to/, <your-...>, empty *_TOKEN/_KEY/etc
  resolved)
- Test result UX hints when stdio Connection closed + placeholder args
  or http fetch failed + placeholder url
- Env-ref preserved in normalize (${VAR_NAME} no longer masked) +
  Edit dialog diagnostic

UI: Skills-pattern parity for /mcp screen (Tabs + Marketplace tab,
Switch primitive, Button primitives, DialogRoot/Content, primary-*
Tailwind classes matching skills-screen.tsx). Single-row toolbar
(tabs + search + filter). Removed All + Catalog tabs, kept Installed +
Marketplace.

Backend:
- gateway-capabilities probeMcp uses authenticated dashboardFetch
  (Codex MAJOR fix); probeMcpConfigKey + isLocalhostDeployment for
  mcpFallback capability
- routes/mcp.tsx route gate accepts mcp || mcpFallback
- mcp-normalize.ts headers.Authorization + env *_TOKEN/_KEY/_SECRET
  /_AUTH/_APIKEY auth detection upgrades authType to 'bearer'

Removed (replaced by /mcp):
- src/screens/settings/mcp-settings-screen.tsx (759 LOC)
- src/routes/settings/mcp.tsx
- src/routes/api/mcp/{servers,reload}.ts (orphaned endpoints; reload
  posted to gateway 404s)
- src/screens/mcp/presets.ts (static array, replaced by file-backed)
- settings-sidebar MCP nav entries (replaced by main /mcp route)

Tests: 263+ passing across 19+ MCP suites — input-validate, presets-
store, hub-cache/trust/unified-search, sources/{mcp-get,local-file,
generic-json}, hub-sources-store, mcp-tools-cache, ssrf-guard,
marketplace-install-confirmation, marketplace-placeholder-detection,
hub-search/-presets/-hub-sources route tests. Pre-existing 2
gateway-capabilities env-resolution failures unrelated.

Reviewers: Codex critic 4 passes (Phase 2 REJECTED → 8 fixes applied,
Phase 3.0 APPROVED-WITH-CHANGES → 4 fixes, Phase 3.2 REJECTED → 6
fixes including SSRF guard + response-size cap + concurrent-CRUD
mutex + trust cap). Architect approved final pass.

Worked with Interstellar Code
@Interstellar-code Interstellar-code force-pushed the feature/mcp-management-v2 branch from cfda538 to 6a830df Compare May 3, 2026 14:08
@Interstellar-code

Copy link
Copy Markdown
Contributor Author

Big and well-scoped PR \u2014 thanks for the work. Needs a rebase against latest main (recently merged composer/skills work touches related areas). Could you do:

git fetch origin && git rebase origin/main
git push --force-with-lease

After rebase + green CI we can merge.
@outsourc-e - Done

@outsourc-e outsourc-e merged commit c021ef5 into outsourc-e:main May 3, 2026
3 checks passed
outsourc-e added a commit that referenced this pull request May 3, 2026
The README's feature list and roadmap were stale: missing the new MCP
page (#231), Operations dashboard, Agent View panel, Conductor screen,
and capability-gate work landed in the past 24-48h. Roadmap still
listed shipped features as 'In Development'.

Changes:
- Replace short 'Features' bullet list with a comprehensive 'What's
  inside' that mentions every major surface, including the explicit
  Conductor caveat with a link to #262.
- Roadmap restructured into Shipped / In progress / Coming sections.
  Conductor gets its own 'in progress' row with the upstream-plugin
  caveat. Native Desktop App moved to 'in progress' per spec status.
  Multi-provider support called out explicitly so users know what
  works on day one.
- Drop the in-house 'Agent W Managed Companion' subsection from public
  README \u2014 it's internal-team-only deployment notes that don't apply
  to anyone running upstream.
- Collapse the duplicate '## Features' section near the bottom into the
  consolidated security section. Avoids saying the same thing twice.
- Tighten security headings: now '## Security & deployment env vars'
  with two clean subsections: 'Built-in safeguards' and 'Env vars for
  remote / Docker deployments'.

Build clean. No code changes.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
JohnGuidry pushed a commit to JohnGuidry/hermes-workspace that referenced this pull request May 26, 2026
…g + marketplace + sources) (outsourc-e#231)

* feat(mcp): MCP server management page (Phase 1)

Implements the MCP management plan (.omc/plans/mcp-management.md) Phase 1
end-to-end on a single feature branch (PR1+PR2+PR3+PR4 collapsed):

- New `/mcp` route with capability gate + BackendUnavailableState fallback.
- New `/api/mcp` (GET list, POST create), `/api/mcp/test` (POST connection
  probe), `/api/mcp/discover` (POST tool discovery for a draft config),
  `/api/mcp/configure` (PUT enable/toolMode/include/exclude), and
  `/api/mcp/$name` (DELETE).
- Strict `mcp` capability probe in gateway-capabilities: hits `GET /api/mcp`
  directly and validates the body parses through `normalizeMcpList` —
  dashboard-up-but-route-missing returns false (resolves Open Question #4).
- Type split: read shapes in `src/types/mcp.ts` (client+server), write
  shapes in `src/types/mcp-input.ts` (server-only; secrets contained here).
- Runtime normalization layer `src/server/mcp-normalize.ts` mirrors the
  Skills `asRecord`/`readString`/`normalizeSkill` defense — strips
  unknown fields, coerces enums, masks secrets via `MASK_SENTINEL`,
  re-applies via `maskSecretsInPlace` before every `json(...)`.
- All write endpoints CSRF-checked via `requireJsonContentType`.
- Capability-off responses use `createCapabilityUnavailablePayload('mcp')`
  with `{ servers: [], total: 0, categories }` for GET (200) and 503 for
  writes — feature gates fall open without throwing.
- Static preset catalog (`src/screens/mcp/presets.ts`) with GitHub,
  Filesystem, Postgres, Slack, Linear; Catalog tab installs prefilled
  drafts through the same dialog flow.
- Screens: `McpScreen` (Installed/Catalog/All tabs + search + category
  filter), `McpServerCard` (status badge + Test/Edit/Delete + enable
  toggle), `McpServerDialog` (HTTP/stdio + auth + Discover + Save with
  bearer-token clear-on-submit).
- TanStack Query hooks (`useMcpServers`, `useTestMcpServer`,
  `useDiscoverMcpTools`, `useUpsertMcpServer`, `useConfigureMcpServer`,
  `useDeleteMcpServer`).

Tests (vitest):
- `src/server/mcp-normalize.test.ts` — 13 tests covering enum coercion,
  list-shape variants, malformed-entry drop, presence flags without
  echo, env/header masking by key hint, idempotency, test-result
  normalization, payload-string scanner.
- `src/routes/api/-mcp.test.ts` — 8 tests covering input validation,
  capability fall-open shape, CSRF gate (415 on non-JSON POST, pass on
  JSON, pass on GET), and the **secret echo guard**: a worst-case agent
  that echoes a submitted bearer token in body/env/headers must never
  surface the original string in the workspace response.

Build, lint, and the new test files are clean. Pre-existing unrelated
test failures on `local` (router-route-resolution, context-usage,
markdown math, slash-command-menu, chat-message-list, gateway-capabilities
env-source) are unchanged by this PR.

Worked with Interstellar Code

* fix(mcp): strip secret fields from client-safe McpClientInput

Architect review flagged that `McpClientInput` in `src/types/mcp.ts` (the
file explicitly designated for client+server read shapes with no secrets)
contained `bearerToken` and `oauth.clientSecret`, allowing the browser
bundle to import a secret-bearing type via the dialog component.

Resolves the type-split violation:
- `src/types/mcp.ts`: drop `bearerToken` and `oauth` from `McpClientInput`.
  Now strictly the browser-safe form payload, no secret fields.
- `src/screens/mcp/components/mcp-server-dialog.tsx`: hold `bearerToken`
  in ephemeral component-local `useState<string>` typed inline. Cleared
  on submit and on dialog open. No exported type carries the field.
- `src/screens/mcp/hooks/use-mcp-mutations.ts`: `useUpsertMcpServer`
  accepts `McpClientInput & { bearerToken?: string }` inline at the
  call-site, again with no exported secret-bearing type. Server route
  `parseMcpServerInput` re-validates and forwards to the agent.

The full server-side write shape (`McpServerInput` with secrets) remains
in `src/types/mcp-input.ts`, server-only.

Worked with Interstellar Code

* fix(mcp): block client imports of server-only mcp-input types

Add no-restricted-imports rule scoped to src/screens/** and
src/components/** that blocks importing @/types/mcp-input. That
file may carry unmasked secrets and is server-only — clients should
import McpClientInput from @/types/mcp instead.

Worked with Interstellar Code

* feat(mcp): wire /mcp into all sidebar/nav surfaces

Mirror the existing /skills registration across every nav and
command surface so the MCP screen is reachable from the dashboard
overflow grid, command palette, mobile hamburger drawer, mobile tab
bar, slash menu, search modal quick actions, and workspace shell
(active-tab tracking + mobile page title). Inspector panel gets a
parallel MCP tab that lists configured servers via /api/mcp.

Worked with Interstellar Code

* feat(mcp): catalog tab search, category badges, and nav coverage tests

Catalog tab now reuses the screen's search state to filter presets
by name/description, surfaces an empty-state when no presets match,
and renders each preset as a card with an Official Presets category
badge styled to match the skills-screen design vocabulary.

Tests:
- src/components/-mcp-nav.test.tsx: each modified nav file references
  the /mcp route (or registers an mcp tab id for inspector-panel)
- src/screens/mcp/-presets.test.ts: filtering MCP_PRESETS by query
  narrows results by name and description, returns full catalog for
  empty queries, and returns nothing for unknown queries

Worked with Interstellar Code

* feat(mcp): add MCP entry to chat-sidebar Knowledge group

The primary visible left rail (`chat-sidebar.tsx`) was missed by the
prior nav-coverage commit. Slot MCP between Skills and Profiles in
`knowledgeItems`, mirroring the McpServerIcon used elsewhere.

Worked with Interstellar Code

* feat(mcp): Phase 3 — live tool refresh, OAuth reauth, per-server SSE logs

- `useMcpServers`: enable refetchOnWindowFocus for live state.
- `McpServerCard`: per-card Refresh button (re-runs Test, updates
  discoveredToolsCount), Reauth button when authType === 'oauth' (uses
  new useMcpOAuth hook), Logs button (opens McpLogsDrawer).
- `use-mcp-oauth.ts`: opens auth URL in new tab, polls /api/mcp/test
  every 2s until status === 'connected' or 60s timeout. Returns
  mutation-style { start, isPending, isError, error, data }.
- `mcp-logs-drawer.tsx`: fixed-right slide-in drawer subscribing via
  EventSource to /api/mcp/<name>/logs. Newest-first, max 500 lines,
  auto-scroll, tear down on close (no zombie EventSource).
- `routes/api/mcp/$name.logs.ts`: SSE proxy with auth + capability
  gates. Capability-off → 503. Pattern follows chat-events.ts.
- Tests: 3 new for logs route (input validation, capability-off,
  auth gate); smoke test for useMcpOAuth shape.

Total tests: 24 passing (13 normalize + 8 mcp + 3 logs). Build clean.

Worked with Interstellar Code

* feat(mcp): localhost-only config-fallback transport (Phase 1.5)

Adds an `mcpFallback` capability that lets the workspace perform CRUD on
`config.mcp_servers` via the existing dashboard `/api/config` route when
the agent does not yet expose the new `/api/mcp*` runtime endpoints.

Gated to loopback-only deployments by `isLocalhostDeployment()` (both
URLs loopback AND HOST unset/loopback). Test/Discover/Logs return a
structured "not yet available" payload in fallback mode; the MCP screen
renders an amber banner so the limitation is visible.

Worked with Interstellar Code

* feat(mcp): full catalog + marketplace + sources manager (Phase 2-3.2)

Workspace-only end-to-end MCP catalog + marketplace replacing the static
presets.ts and the upstream /settings/mcp surfaces.

Phase 2 — File-backed catalog:
- assets/mcp-presets.seed.json + ~/.hermes/mcp-presets.json (atomic
  bootstrap via tmp+linkSync, mtime+ino+ctime+size cache, malformed-file
  preservation, schema validation: id regex, transport-specific fields,
  env key regex, https URLs, category allowlist, duplicate-id rejection,
  unknown-field warnings)
- src/server/mcp-input-validate.ts: shared parseMcpServerInput returning
  per-field {path, message} errors; promoted from inline definition
- src/routes/api/mcp/presets.ts GET handler

Phase 3.0 — Federated marketplace:
- src/server/mcp-hub/{cache,trust,index,types}.ts + sources/{mcp-get,
  local-file}.ts: Smithery registry adapter (replaces speculative
  registry.mcp.run NXDOMAIN), ETag/If-Modified-Since with 304 reuse,
  rate-limit handling, parallel Promise.allSettled across sources with
  8s per-source timeout, dedupe by source+id+name, fallback to
  local-file when remote degraded
- Trust hardening: shell metachar reject, transport allowlist, env-key
  regex, control-char + absolute-path attack defenses, inline-exec
  flag detection (-c, -lc, -e for sh/bash/python/node/perl/ruby)
- src/screens/mcp/components/install-confirmation-dialog.tsx: 2-click
  commit with full template preview (command/args/env masked) and
  AbortController on dismiss
- Disk persistence for tool-discovery cache (mcp-tools-cache.ts) +
  hermes-mcp CLI bridge (mcp-cli-bridge.ts) for live test/tool
  enumeration in fallback mode

Phase 3.2 — User-configurable sources:
- ~/.hermes/mcp-hub-sources.json schema (built-ins always present,
  protected from mutation; user can add HTTPS-only generic-json
  sources with trust+format)
- src/routes/api/mcp/hub-sources{,.$id}.ts CRUD with per-process mutex
  (read-modify-write race protection)
- generic-json adapter: SSRF guard (private/loopback/link-local/IPv6
  ULA all rejected after DNS resolution, redirects disabled), 5MB
  response-size cap (streaming read), trust hard-cap at 'community'
  for user-source entries, source field 'user:<id>' for dedupe
- src/screens/mcp/components/sources-manager-dialog.tsx UI

Polish:
- Placeholder detection at install confirmation (inline fill form
  blocks commit until /path/to/, <your-...>, empty *_TOKEN/_KEY/etc
  resolved)
- Test result UX hints when stdio Connection closed + placeholder args
  or http fetch failed + placeholder url
- Env-ref preserved in normalize (${VAR_NAME} no longer masked) +
  Edit dialog diagnostic

UI: Skills-pattern parity for /mcp screen (Tabs + Marketplace tab,
Switch primitive, Button primitives, DialogRoot/Content, primary-*
Tailwind classes matching skills-screen.tsx). Single-row toolbar
(tabs + search + filter). Removed All + Catalog tabs, kept Installed +
Marketplace.

Backend:
- gateway-capabilities probeMcp uses authenticated dashboardFetch
  (Codex MAJOR fix); probeMcpConfigKey + isLocalhostDeployment for
  mcpFallback capability
- routes/mcp.tsx route gate accepts mcp || mcpFallback
- mcp-normalize.ts headers.Authorization + env *_TOKEN/_KEY/_SECRET
  /_AUTH/_APIKEY auth detection upgrades authType to 'bearer'

Removed (replaced by /mcp):
- src/screens/settings/mcp-settings-screen.tsx (759 LOC)
- src/routes/settings/mcp.tsx
- src/routes/api/mcp/{servers,reload}.ts (orphaned endpoints; reload
  posted to gateway 404s)
- src/screens/mcp/presets.ts (static array, replaced by file-backed)
- settings-sidebar MCP nav entries (replaced by main /mcp route)

Tests: 263+ passing across 19+ MCP suites — input-validate, presets-
store, hub-cache/trust/unified-search, sources/{mcp-get,local-file,
generic-json}, hub-sources-store, mcp-tools-cache, ssrf-guard,
marketplace-install-confirmation, marketplace-placeholder-detection,
hub-search/-presets/-hub-sources route tests. Pre-existing 2
gateway-capabilities env-resolution failures unrelated.

Reviewers: Codex critic 4 passes (Phase 2 REJECTED → 8 fixes applied,
Phase 3.0 APPROVED-WITH-CHANGES → 4 fixes, Phase 3.2 REJECTED → 6
fixes including SSRF guard + response-size cap + concurrent-CRUD
mutex + trust cap). Architect approved final pass.

Worked with Interstellar Code
JohnGuidry pushed a commit to JohnGuidry/hermes-workspace that referenced this pull request May 26, 2026
…urc-e#274)

The README's feature list and roadmap were stale: missing the new MCP
page (outsourc-e#231), Operations dashboard, Agent View panel, Conductor screen,
and capability-gate work landed in the past 24-48h. Roadmap still
listed shipped features as 'In Development'.

Changes:
- Replace short 'Features' bullet list with a comprehensive 'What's
  inside' that mentions every major surface, including the explicit
  Conductor caveat with a link to outsourc-e#262.
- Roadmap restructured into Shipped / In progress / Coming sections.
  Conductor gets its own 'in progress' row with the upstream-plugin
  caveat. Native Desktop App moved to 'in progress' per spec status.
  Multi-provider support called out explicitly so users know what
  works on day one.
- Drop the in-house 'Agent W Managed Companion' subsection from public
  README \u2014 it's internal-team-only deployment notes that don't apply
  to anyone running upstream.
- Collapse the duplicate '## Features' section near the bottom into the
  consolidated security section. Avoids saying the same thing twice.
- Tighten security headings: now '## Security & deployment env vars'
  with two clean subsections: 'Built-in safeguards' and 'Env vars for
  remote / Docker deployments'.

Build clean. No code changes.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP server management UI is incomplete (read-only, no live test/discover, no marketplace)

2 participants