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
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>
This commit is contained in:
@@ -134,6 +134,19 @@ describe('favourite-index plugin', () => {
|
||||
expect(text).toBe('Called scale_deployment');
|
||||
});
|
||||
|
||||
it('routes a favourite/ call even before tools/list (lazy resolver rebuild)', async () => {
|
||||
const router = setup({ favourites: ['vault/read_secret'] });
|
||||
router.addUpstream(mockUpstream('vault', CATALOG.vault));
|
||||
// initialize only — deliberately NO tools/list, so the resolver is unset.
|
||||
await router.route({ jsonrpc: '2.0', id: 1, method: 'initialize' }, { sessionId: 's1' });
|
||||
|
||||
const res = await router.route(
|
||||
{ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'favourite/read_secret', arguments: {} } },
|
||||
{ sessionId: 's1' },
|
||||
);
|
||||
expect((res.result as { content: Array<{ text: string }> }).content[0]!.text).toBe('Called read_secret');
|
||||
});
|
||||
|
||||
it('no-ops while the session is gated (only begin_session visible)', async () => {
|
||||
const router = setup({ gated: true, favourites: ['k8s/get_pods'] });
|
||||
router.addUpstream(mockUpstream('k8s', CATALOG.k8s));
|
||||
|
||||
@@ -104,6 +104,7 @@ describe('Smoke: favourite-index presentation', () => {
|
||||
chat.section('favourite-index routing');
|
||||
try {
|
||||
await chat.initialize();
|
||||
await chat.listTools(); // real clients list before calling — populates the resolver
|
||||
|
||||
// Calling with no args → the UPSTREAM tool's arg validation fires, proving
|
||||
// the presented name routed to the real server (not a -32601 unknown tool).
|
||||
|
||||
Reference in New Issue
Block a user