feat(cli)+fix(mcpd): server-side LLM status + SPA fallback 500 #60

Merged
michal merged 1 commits from feat/status-server-llms-and-spa-fix into main 2026-04-27 10:28:10 +00:00
Owner

Summary

Two related fixes:

1. `mcpctl status` shows server-side LLMs. A new "Server LLMs:" section lists the mcpd-managed Llm rows (the ones registered via `mcpctl create llm`), grouped by tier with type, model, upstream URL, and key reference. JSON/YAML output gains a `serverLlms` array.

```
mcpctl v0.0.1
mcplocal: ... (connected)
mcpd: https://mcpctl.ad.itaz.eu (connected)
Auth: logged in as Michal
...
LLM: ... ✓ ok
Server LLMs: 2 registered
fast qwen3-thinking (openai → qwen3-thinking) http://litellm.../v1 key:litellm/MCPCTL_GATEWAY_TOKEN
heavy sonnet (anthropic → claude-sonnet-4-5) provider default no key
```

If mcpd is unreachable or returns a non-200 the section is silently omitted (the existing mcpd connectivity line already says that).

2. `/ui/` was 500ing. The SPA fallback called `reply.sendFile` but `@fastify/static` was registered with `decorateReply: false`, so the method was undefined. Read `index.html` once at startup and `reply.send(html)` instead — same effect, no per-request stat, no method missing.

Test plan

  • CLI: 24/24 (was 18, +6 new for server-LLMs render + token plumbing + JSON shape)
  • Workspace: 2005/2005 across 149 files
  • Typecheck clean across cli + mcpd
  • Manual: `mcpctl status` shows the new section against the live cluster.
  • Manual: hit https://mcpctl.ad.itaz.eu/ui/projects directly — index.html served instead of 500.

🤖 Generated with Claude Code

## Summary Two related fixes: **1. \`mcpctl status\` shows server-side LLMs.** A new "Server LLMs:" section lists the mcpd-managed Llm rows (the ones registered via \`mcpctl create llm\`), grouped by tier with type, model, upstream URL, and key reference. JSON/YAML output gains a \`serverLlms\` array. \`\`\` mcpctl v0.0.1 mcplocal: ... (connected) mcpd: https://mcpctl.ad.itaz.eu (connected) Auth: logged in as Michal ... LLM: ... ✓ ok Server LLMs: 2 registered fast qwen3-thinking (openai → qwen3-thinking) http://litellm.../v1 key:litellm/MCPCTL_GATEWAY_TOKEN heavy sonnet (anthropic → claude-sonnet-4-5) provider default no key \`\`\` If mcpd is unreachable or returns a non-200 the section is silently omitted (the existing mcpd connectivity line already says that). **2. \`/ui/<deeplink>\` was 500ing.** The SPA fallback called \`reply.sendFile\` but \`@fastify/static\` was registered with \`decorateReply: false\`, so the method was undefined. Read \`index.html\` once at startup and \`reply.send(html)\` instead — same effect, no per-request stat, no method missing. ## Test plan - [x] CLI: 24/24 (was 18, +6 new for server-LLMs render + token plumbing + JSON shape) - [x] Workspace: 2005/2005 across 149 files - [x] Typecheck clean across cli + mcpd - [ ] Manual: \`mcpctl status\` shows the new section against the live cluster. - [ ] Manual: hit https://mcpctl.ad.itaz.eu/ui/projects directly — index.html served instead of 500. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
michal added 1 commit 2026-04-27 10:28:01 +00:00
feat(cli)+fix(mcpd): server-side LLM status + SPA fallback 500
Some checks failed
CI/CD / typecheck (pull_request) Successful in 58s
CI/CD / test (pull_request) Successful in 1m9s
CI/CD / lint (pull_request) Successful in 2m14s
CI/CD / smoke (pull_request) Failing after 1m39s
CI/CD / build (pull_request) Successful in 2m14s
CI/CD / publish (pull_request) Has been skipped
0db37e92a4
Two related fixes:

1. \`mcpctl status\` now lists mcpd-managed Llm rows (the ones created via
   \`mcpctl create llm\`) under a new "Server LLMs:" section, grouped by
   tier with type, model, upstream URL, and key reference. JSON/YAML
   output gains a \`serverLlms\` array.

   Bearer token (from \`mcpctl auth login\` / saved credentials) is
   passed through; if mcpd is unreachable or returns non-200 the
   section is silently omitted (the existing mcpd connectivity line
   already conveys that). 6 new tests cover happy path, empty list,
   token plumbing, and JSON shape.

2. SPA fallback at \`/ui/<deeplink>\` was returning 500 because we
   registered \`@fastify/static\` with \`decorateReply: false\` and then
   called \`reply.sendFile\`. Read index.html once at startup and serve
   it with \`reply.send(html)\` instead — also dodges a per-request
   stat call. Drop \`decorateReply: false\` so future code can use
   reply.sendFile if it ever needs to.

Full suite: 2005/2005 across 149 files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
michal merged commit de96af7bf6 into main 2026-04-27 10:28:10 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: michal/mcpctl#60