feat(proxy): favourite-index tool presentation #86

Merged
michal merged 4 commits from feat/favourite-index-presentation into main 2026-07-23 21:28:23 +00:00

4 Commits

Author SHA1 Message Date
Michal
366b505c00 fix(providers): anthropic surfaces HTTP errors too (gate parity with openai)
Some checks failed
CI/CD / lint (pull_request) Successful in 1m6s
CI/CD / typecheck (pull_request) Successful in 1m5s
CI/CD / test (pull_request) Successful in 1m19s
CI/CD / build (pull_request) Successful in 2m14s
CI/CD / smoke (pull_request) Failing after 3m0s
CI/CD / publish (pull_request) Has been skipped
The local mcplocal gate resolves heavy=[anthropic]; its OAuth-gated key 404s the
configured opus-4 model, and anthropic.ts (like openai.ts before this series)
JSON.parsed the error body as a completion → empty content → the gate's
misleading "did not contain valid selection JSON". Reject on status >= 400 with
"Anthropic HTTP <status>: <body>" so the true reason (model-not-found / auth)
surfaces as the degradedReason. + parity test.

Note: this makes the error honest; it does not make the local gate SUCCEED — that
needs a working heavy provider (the anthropic OAuth token is fundamentally gated).
The qwen3-thinking gate path (k8s mcplocal) is addressed by the budget+reasoning
fix in the previous commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 22:23:37 +01:00
Michal
c9364a2f2a fix(favourite-index): route favourite/ calls even before tools/list (lazy resolver)
Some checks failed
CI/CD / typecheck (pull_request) Successful in 1m4s
CI/CD / lint (pull_request) Successful in 2m12s
CI/CD / test (pull_request) Successful in 1m20s
CI/CD / smoke (pull_request) Failing after 1m54s
CI/CD / build (pull_request) Successful in 4m31s
CI/CD / publish (pull_request) Has been skipped
The presented→canonical resolver was only built during tools/list, so a
favourite/<tool> call arriving before any list this session fell through to the
namespaced router and 404'd (all/ was fine — it prefix-strips). Real MCP clients
list first, but the smoke test's initialize→call flow exposed it. Extract
buildPresentation() and lazily rebuild the resolver in onToolCallBefore when a
favourite//all/ name isn't resolved yet. + unit test (favourite/ call with no
prior list) and realistic listTools() in the smoke routing case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 22:19:05 +01:00
Michal
9dbd2f175d fix(gate): stop masking backend errors + rescue thinking-model prompt selection
Some checks failed
CI/CD / lint (pull_request) Successful in 1m6s
CI/CD / typecheck (pull_request) Successful in 2m14s
CI/CD / test (pull_request) Successful in 1m22s
CI/CD / smoke (pull_request) Failing after 1m54s
CI/CD / build (pull_request) Successful in 4m17s
CI/CD / publish (pull_request) Has been skipped
Two independent, live-reproduced causes of "Smart prompt-selection unavailable
(LLM response did not contain valid selection JSON)" in the gate:

- Cause B (openai.ts request): the response handler JSON.parsed the body with no
  res.statusCode check, so a LiteLLM/vLLM HTTP 500 ("Connection error") parsed as
  an empty completion and surfaced as generic "bad JSON", masking backend-down.
  Now reject on status >= 400 with "OpenAI HTTP <status>: <body>" so the selector
  reports the true degradedReason.
- Cause A (parseResponse + selector budget): a reasoning model (qwen3-thinking)
  spends its whole 1024-token budget in reasoning_content and emits content=null,
  so the {selectedNames} JSON never appears. parseResponse now falls back to
  reasoning_content (incl. provider_specific_fields) when content is empty, and
  the selector budget is raised 1024 -> 4000 (env MCPCTL_GATE_SELECT_MAX_TOKENS)
  so thinking + the JSON answer both fit.

Note: mcpd's chat path got a reasoning_content fallback in PR #67; this is the
separate mcplocal gate-selection provider, which never did. Tests: openai-provider
(500 rejects, reasoning_content capture/preference). mcplocal suite green (749).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 22:10:08 +01:00
Michal
f614e9bb98 feat(proxy): favourite-index tool presentation (favourite/ + all/ + prefer instruction)
Some checks failed
CI/CD / lint (pull_request) Successful in 1m4s
CI/CD / test (pull_request) Successful in 1m23s
CI/CD / typecheck (pull_request) Successful in 2m36s
CI/CD / smoke (pull_request) Failing after 1m53s
CI/CD / build (pull_request) Successful in 4m16s
CI/CD / publish (pull_request) Has been skipped
Measured winner from the DGX-Spark bake-off (toolsim.py, 145-tool catalog): a
curated favourite/<tool> shortlist + the full all/<server>/<tool> catalog + a
load-bearing "prefer favourite/ first" instruction nearly halved wander (37→20)
and 2.5x'd first-pick (2→5/8) vs a flat catalog. The instruction is load-bearing;
enriching descriptions did not help.

- New mcplocal plugin `favourite-index.ts`: composes AFTER gate (no-ops while
  gated), reshapes the ungated upstream catalog into favourite/ + all/, injects
  the instruction (onInitialize), and rewrites presented names back to canonical
  server/tool in onToolCallBefore so normal routing + content-pipeline still run.
  Gate/agent virtual tools pass through untouched; favourites are upstream-only.
- compose.ts: onInitialize now concatenates plugin instructions (was first-non-null)
  so favindex can contribute its banner alongside the gate's.
- Per-project config `Project.favouriteIndex` {enabled, tools[], maxFavourites};
  surfaced to the proxy via discovery; wired at project-mcp-endpoint when enabled.
- Usage derivation: mcpd tool-usage ranking over tool_call_trace events
  (normalizing presented names → canonical), GET /api/v1/audit/tool-usage, and
  `mcpctl favourites suggest|list`.
- CLI: `create project` gains --favourite/--favourite-index/--max-favourites;
  favouriteIndex round-trips through get -o yaml | apply -f. Completions regenerated.
- Tests: plugin unit (presentation, rewrite routing, gated no-op, collisions),
  compose merge, canonicalizeToolName, buildFavouriteIndex, + a live smoke test.
- Docs: docs/tool-presentation.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 01:20:30 +01:00