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
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
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>
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
* index.html so client-side react-router routes work on direct hits.
|
||||
*/
|
||||
import path from 'node:path';
|
||||
import { existsSync, statSync } from 'node:fs';
|
||||
import { existsSync, statSync, readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
@@ -57,13 +57,27 @@ export async function registerWebUi(app: FastifyInstance): Promise<void> {
|
||||
root,
|
||||
prefix: '/ui/',
|
||||
wildcard: false,
|
||||
decorateReply: false,
|
||||
});
|
||||
|
||||
// SPA fallback — react-router URLs like /ui/agents/foo/personalities/bar
|
||||
// need index.html to bootstrap the app.
|
||||
// Read index.html once at startup; the SPA fallback below serves it
|
||||
// verbatim for every unmatched /ui/* path so client-side routing works
|
||||
// on direct hits. Reading once also dodges a per-request `sendFile`
|
||||
// call — there's only one file ever served from this handler.
|
||||
const indexHtmlPath = path.join(root, 'index.html');
|
||||
const indexHtml = existsSync(indexHtmlPath)
|
||||
? readFileSync(indexHtmlPath, 'utf-8')
|
||||
: null;
|
||||
if (indexHtml === null) {
|
||||
app.log.warn({ root }, 'web UI index.html missing; deep links to /ui/<path> will 404');
|
||||
}
|
||||
|
||||
app.get('/ui/*', (_request, reply) => {
|
||||
return reply.sendFile('index.html', root);
|
||||
if (indexHtml === null) {
|
||||
reply.code(404);
|
||||
return { error: 'index.html missing from web UI bundle' };
|
||||
}
|
||||
reply.type('text/html').send(indexHtml);
|
||||
return reply;
|
||||
});
|
||||
// Cover the bare /ui (no trailing slash) too.
|
||||
app.get('/ui', (_request, reply) => {
|
||||
|
||||
Reference in New Issue
Block a user