Skip to content

MCP server page: show exactly what a server exposes, preview as a role, who can connect, modes, connect snippets #523

Description

@nicdavidson

Why

The MCP service page currently synthesises its "Built-in Tools" list client-side from EVERY active database/file service and ignores exposed_services, so it shows tools the server does not expose (demo_mcp exposes 3 databases, page shows 10 services all on). The health banner says "No role grants access, unreachable" although any authenticated user can connect. Admins cannot see what a given role will actually get, who can connect, or what lazy loading / merged style / API-key auth change. Customer research (12 calls): "limit what the LLM can see/do" is the #1 ask on every call; setup friction and tool-count confusion are top-8.

An interactive mockup of the target page exists (design reference, not pixel spec): it will be provided in the worktree as .agent-mockup.html (open it in a browser). Keep the existing admin design system (Material, purple primary, Inter).

Backend contracts (df-mcp-server, branches in flight; code against these shapes and degrade gracefully if an endpoint is missing)

  • Service config fields on type mcp (GET /api/v2/system/service/{id} -> config): exposed_services: string[], disabled_tools: string[] (keys are <sanitized_service>_<verb> in BOTH styles), lazy_mode: 'auto'|'on'|'off', tool_style: 'merged'|'prefixed' (merged is the new default for new services), allow_api_key_auth: bool, require_role_access: bool (new; off on upgraded servers, on for new ones), allow_writes: bool (new, default true), custom_tools, oauth_client_id, redirect_uris, registered_redirect_uris. Schema: GET /api/v2/system/service_type/mcp (config_schema, incl. exposed_services.values = attachable DB/file services).
  • GET /_internal/ai/mcp-access?service=<name|id>&period=30d (admin) -> { service_id, service, require_role_access, roles: [{ role_id, name, granted, requests, denied, last_seen }] }.
  • GET /_internal/ai/mcp-catalog?service=<name|id>&role_id=<id>|app_id=<id>&client=<name> (admin, PR in flight) -> { tools: [{ name, title, description, category ('database'|'file'|'global'|'aggregate'|'custom'|'facade'), write: bool, service?: string }], count, bytes, lazy: 'lazy'|'direct'|'passthrough', facade: string[], role_verbs?: { [service]: verb_mask } }. Until it exists, fall back to POST /api/v2/{svc}/rpc tools/list (admin view only) and say so in the UI.
  • GET /_internal/ai/mcp-usage?period=7d&service_id= (exists) -> by_tool, by_role, by_app, by_client, series.
  • GET /_internal/ai/mcp-health (admin, PR in flight) -> { status, checks: [{ id, status, message }], daemons: [{ type, reachable, ... }] }.
  • Roles: GET/PATCH /api/v2/system/role/{id}?related=role_service_access_by_role_id (grant = row { service_id, component: '*', verb_mask: 1, requestor_mask: 3 }; unlink = send the row with role_id: null). Apps/keys: /api/v2/system/app.

What to build (edit page for type mcp; system_mcp gets the same minus exposure grid)

  1. Header: name, endpoint URL, health chip (from mcp-health; link to the System page), Preview-as selector (Any admin | each role from mcp-access + each app key), summary tiles: tools advertised, est. tokens per turn (bytes/4), catalog mode (Direct/Lazy + why), who can connect (OAuth only / OAuth + key).
  2. Modes strip: Lazy (Auto/On/Off), Tool style (Merged/Prefixed legacy), Writes (Allowed/Blocked = allow_writes), Authentication (OAuth only / OAuth + API key), Require role access (On/Off) — each with a one-line consequence that updates live from the catalog preview.
  3. What the client carries each turn: two bars (full catalog bytes vs 5-tool facade) against the 32 KB auto threshold, and the 4 nested steps search_tools -> describe_tool -> call_tool -> fetch_more, greyed when the server is direct.
  4. Who can connect: rows from mcp-access (name, read/write, reach across exposed APIs, key, requests/denied, Preview as). Actions: Add existing role (picker; grants ONLY the server unless the admin ticks specific APIs), Edit access (per exposed API None/Read/Read+write with a plain diff before save; table-level limits link to the role page), Create role (name, per-API read/read-write, optional API key shown once). Every save that touches an API grant states "this changes what the role can do everywhere, not only through MCP". With require_role_access OFF show the warning "Any logged-in user can connect" and the roles seen in the last 30 days without a grant, each with Grant.
  5. Exposure grid: rows = attachable backends grouped by type (Database 16 verbs grouped Read/Spec/Procedures/Write, File 6 verbs), row switch = exposed_services, cell = disabled_tools; write verbs amber with a W badge; cell states: on, off (disabled), writes-off (server switch), denied (advertised but the previewed role has no verb), gone (row hidden from this role). Legend. Replace the current synthesised Built-in Tools panels; keep custom tools editor as is.
  6. Catalog panel: the previewed identity's tool chips (W badge on writes, facade chips distinct), counts.
  7. Connect card: config snippets for Claude, Cursor, VS Code, ChatGPT, curl, switching with the auth mode and selected identity (key header when a key is previewed).
  8. Last 7 days: per exposed backend calls + last used from mcp-usage; zero calls highlighted.
  9. Create flow for a new mcp service: short stepper: name -> attach APIs -> who can connect -> client settings -> review (this page in preview mode) -> connect snippet with a "waiting for the first call" state.

Fix on the way: the create link in df-ai-mcp-servers points at the database type list; system_mcp must not show Tool style.

Constraints

  • Angular standalone components under src/app/adf-ai/mcp/ (or shared/components/df-mcp-*), composed into df-service-details for the MCP group behind the existing isMcp branch; keep the monolith edits small. i18n strings in src/assets/i18n/en.json. Unit specs for the pure logic (catalog shaping, diff text, snippet generation) with the repo's test runner; add one Playwright e2e under e2e/ that logs in, opens demo_mcp on the dev instance (http://localhost:8081, admin@dreamfactory.com / passwordpassword) and asserts the grid reflects exposed_services.
  • The repo commits dist/; rebuild it (npm run build) in a final "chore: rebuild dist" commit so the dev instance can serve it.
  • Lint + tests green; CI runs on PRs.

Behaviour changes

  • The MCP service page shows only what the server actually exposes; tool toggles map 1:1 to disabled_tools keys; new controls for require_role_access and allow_writes; role grants can be created/edited from the page (writes real role rows, with a diff shown first).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions