feat(llm): resolve the gate's selection Llm by tier, not by name #91
Reference in New Issue
Block a user
Delete Branch "feat/llm-tier-resolution"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
mcpctl consumers now ask the Llm registry for a role (
tier) instead of naming a row, so a homelab model swap needs zero mcpctl edits.Why
Pulumi (
deployments/mcpctl/llm-target.ts) already repointsvllm-current(fast) andvllm-think(heavy) at whatever LiteLLM serves — currentlydeepseek-v4-flash/deepseek-v4-think. But nothing in mcpctl resolved those rows. Gate prompt-selection readMCPCTL_GATE_SELECTION_LLM || project.llmProvider, and neither is set on any project or on either deployment, so unpinned projects fell through to the local personal-token provider:Every gated session has been running on priority order, not LLM ranking.
How
New
src/mcplocal/src/server-llm.ts. A row fills a tier whentiermatches,statusisactive, andkindis notvirtual— virtual rows are backed by some user's mcplocal over SSE, and automatic resolution must not route project traffic through a laptop nobody chose. An explicit pin may still name one.heavyalready holds two rows (vllm-think+anthropic-fallback), so the tiebreak matters today: an ordered well-known-name list, seeded with both the current names and the symmetric names the rows might be renamed to (vllm-fast,vllm-thinking). Names matching nothing are inert, so a rename stays a Pulumi-only change and neither deploy order breaks the other. Unlisted rows still resolve, sorting last then by name.MCPCTL_LLM_PREFER_FAST/_HEAVYoverride without a release.Resolution never throws — mcpd unreachable is a normal degraded state, and returning
nullpreserves the caller's existing fallback.Gate order is now: env pin → project
llmProvider→ fast-tier lookup. Explicit pins still win.Naming
Rows keep
vllm-current/vllm-think.nameis immutable in mcpd (agents/projects reference it), so a rename costs a data migration plus a Pulumi resource replace and buys nothing once nothing reads the names.vllm-fastwould also collide in meaning with LiteLLM'sdeepseek-v4-fastroute (pinsthinking: false) while the row points at the basedeepseek-v4-flash.Tests
tests/server-llm.test.ts— 15 unit tests: tiebreak, rename window, unknown names, inactive/virtual exclusion, env override, unreachable mcpd.tests/smoke/llm-tier.smoke.test.ts— live stack: both tiers resolve; the resolved fast row answers real inference (the drift check — a row left on a suspended model fails here); an unpinned gated project gets LLM-ranked selection with no⚠ Smart prompt-selection unavailablebanner.770/770 mcplocal unit tests pass. Typecheck clean. Lint on the touched file goes 28 → 26 pre-existing errors.
⚠️ Smoke not yet run against live — it needs this build deployed and mcplocal restarted, which drops in-flight MCP sessions.
Docs
docs/llm-tiers.md(new), READMEcreate llmexample de-staled fromqwen3-thinking, CLAUDE.md cheatsheet note.🤖 Generated with Claude Code
https://claude.ai/code/session_01B5NAoE7VJA5TWvHsVfEmUr
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.