diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 8f7195e..82f5922 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -27,14 +27,14 @@ Promotion process: [deprecation-policy.md § Stable vs experimental](./deprecati | Setup | Tools | MCP instructions | | ----- | ----- | ------------------ | -| `setupCoreServer` (package root) | **8** (single-key) or **9** (multi-source adds `list_sources`): `list_namespaces`, `namespace_router`, `count`, `query`, `keyword_search`, `query_documents`, `generate_urls`, `guided_query` | `CORE_SERVER_INSTRUCTIONS` — includes `guided_query`; no `suggest_query_params` | -| `setupAllianceServer` / published CLI | **9** (single-key) or **10** (multi-source): core tools plus `suggest_query_params` | `ALLIANCE_SERVER_INSTRUCTIONS` — includes suggest-flow quickstart | +| `setupCoreServer` (package root) | **9** (single-key) or **10** (multi-source adds `list_sources`): `list_namespaces`, `namespace_router`, `count`, `query`, `keyword_search`, `query_documents`, `generate_urls`, `guided_query`, `suggest_query_params` | `CORE_SERVER_INSTRUCTIONS` — includes `guided_query`; registers `suggest_query_params` but the suggest-flow gate is off by default | +| `setupAllianceServer` / published CLI | **9** (single-key) or **10** (multi-source): the same core tool surface — Alliance adds built-in Boost/Slack URL generators for `generate_urls` (not an extra MCP tool) and enables the gate by default | `ALLIANCE_SERVER_INSTRUCTIONS` — includes suggest-flow quickstart | ## Suggest-flow gate When **`disableSuggestFlow`** is **`false`** (Alliance default via `resolveAllianceConfig` / CLI), tools **`query`**, **`count`**, and **`query_documents`** require a prior successful **`suggest_query_params`** call for the **same namespace string** within the cache TTL (see `PINECONE_CACHE_TTL_SECONDS`). The gate is in-process memory (`requireSuggested`). -When **`disableSuggestFlow`** is **`true`** (core default via `resolveConfig`), the gate is bypassed — suitable for `setupCoreServer` embedders that do not register `suggest_query_params`. +When **`disableSuggestFlow`** is **`true`** (core default via `resolveConfig`), the gate is bypassed: `setupCoreServer` still registers `suggest_query_params`, but embedders can call it optionally rather than being required to before `query` / `count` / `query_documents`. **Namespace consistency:** use the **exact same** `namespace` value (including trimming — avoid leading/trailing spaces in one call and not the other) for `suggest_query_params` and downstream gated tools. Mismatches yield `FLOW_GATE` with a suggestion to call `suggest_query_params` first. diff --git a/src/__tests__/cross-entry-point.test.ts b/src/__tests__/cross-entry-point.test.ts index f3d267e..54004cd 100644 --- a/src/__tests__/cross-entry-point.test.ts +++ b/src/__tests__/cross-entry-point.test.ts @@ -5,6 +5,7 @@ import { setPineconeClient } from '../core/index.js'; import { resolveConfig } from '../core/config.js'; import { getDefaultServerContext } from '../core/server/server-context.js'; import * as guidedQueryTool from '../core/server/tools/guided-query-tool.js'; +import * as suggestQueryParamsTool from '../core/server/tools/suggest-query-params-tool.js'; import { registerQueryTool } from '../core/server/tools/query-tool.js'; import { assertToolErrorCode, @@ -119,4 +120,22 @@ describe('cross-entry-point: core vs Alliance defaults', () => { expect(ctx.hasToolsRegistered()).toBe(true); registerSpy.mockRestore(); }); + + it('core setup registers suggest_query_params handler (#221)', async () => { + const cfg = resolveConfig({ apiKey: 'sk-cross', indexName: 'test-index' }); + const ctx = createTestServerContext({ + config: cfg, + client: new PineconeClient({ + apiKey: cfg.apiKey, + indexName: cfg.indexName, + defaultTopK: cfg.defaultTopK, + }), + }); + const registerSpy = vi.spyOn(suggestQueryParamsTool, 'registerSuggestQueryParamsTool'); + + await setupCoreServer({ context: ctx }); + + expect(registerSpy).toHaveBeenCalledOnce(); + registerSpy.mockRestore(); + }); }); diff --git a/src/__tests__/mcp-rc-readiness.test.ts b/src/__tests__/mcp-rc-readiness.test.ts index d08fe89..2b06d48 100644 --- a/src/__tests__/mcp-rc-readiness.test.ts +++ b/src/__tests__/mcp-rc-readiness.test.ts @@ -35,6 +35,7 @@ const CORE_TOOLS = [ 'query_documents', 'generate_urls', 'guided_query', + 'suggest_query_params', ].sort(); /** A fresh, isolated core server (own context + mock client) so several can coexist. */ @@ -158,6 +159,25 @@ describe('MCP RC-readiness harness (#202)', () => { await client.close(); }); + it('core server executes suggest_query_params over the transport (#221)', async () => { + // Proves the tool is not just listed but callable on a core-initialized + // server, so a broken registration can't pass by only being enumerated. + const server = await freshCoreServer(['wg21']); + const client = await connectClient(server); + + const res = await client.callTool({ + name: 'suggest_query_params', + arguments: { namespace: 'wg21', user_query: 'list papers' }, + }); + expect(res.isError ?? false).toBe(false); + const text = (res.content as Array<{ type: string; text: string }>)[0]?.text ?? ''; + const body = JSON.parse(text) as { status?: string; namespace_found?: boolean }; + expect(body.status).toBe('success'); + expect(body.namespace_found).toBe(true); + + await client.close(); + }); + it('negotiates the SDK latest protocol version and falls back for an unknown request', async () => { // A server connects to one transport for its lifetime, so use a fresh one per probe. const negotiated = await rawInitialize(await freshCoreServer(), LATEST_PROTOCOL_VERSION); diff --git a/src/alliance/setup.ts b/src/alliance/setup.ts index 267a950..8acd720 100644 --- a/src/alliance/setup.ts +++ b/src/alliance/setup.ts @@ -5,7 +5,6 @@ import { createServer, type AllianceServerContext } from '../core/server/server- import { resolveAllianceConfig } from './config.js'; import { setupCoreServerOnContext, type ServerHandle } from '../core/setup.js'; import { registerBuiltinUrlGenerators } from './url-builtins.js'; -import { registerSuggestQueryParamsTool } from './tools/suggest-query-params-tool.js'; /** * Options for {@link setupAllianceServer}. @@ -68,8 +67,10 @@ function normalizeSetupAllianceArgs( } /** - * Create and configure the MCP server with the full Alliance tool surface: - * all core tools (including `guided_query`) plus `suggest_query_params` and built-in URL generators. + * Create and configure the MCP server with the full Alliance tool surface: the same + * core tools (including `guided_query` and `suggest_query_params`) plus built-in + * Boost/Slack URL generators wired into `generate_urls`, and the suggest-flow gate + * enabled by default. * * When `config` is omitted, resolves env via {@link resolveAllianceConfig} (Alliance index/rerank defaults when unset). */ @@ -114,6 +115,5 @@ export async function setupAllianceServer( } registerBuiltinUrlGenerators(resolvedCtx); - registerSuggestQueryParamsTool(server, resolvedCtx); return server; } diff --git a/src/constants.ts b/src/constants.ts index 2366623..cf40275 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -49,7 +49,7 @@ Features: Notes: - Result rows include both \`document_id\` (canonical) and \`paper_number\` (deprecated alias kept for one minor cycle; will be removed in the next major release). Prefer \`document_id\` in new code.`; -/** MCP instructions for {@link setupCoreServer} (eight core tools including guided_query). */ +/** MCP instructions for {@link setupCoreServer} (nine core tools including guided_query and suggest_query_params). */ export const CORE_SERVER_INSTRUCTIONS = `Quickstart for AI clients: for most user questions, call \`guided_query\` with the user's question — it does namespace routing, suggestion, and execution in one shot and returns \`experimental.decision_trace\` you can show the user. Alternatively call \`list_namespaces\` to discover namespaces, optionally \`namespace_router\` to rank candidates from user intent, then \`query\` (preset fast/detailed/full), \`count\`, \`query_documents\`, \`keyword_search\`, or \`generate_urls\` as needed. ${SERVER_FEATURES_AND_NOTES} diff --git a/src/core/server/tools/guided-query-tool.context.test.ts b/src/core/server/tools/guided-query-tool.context.test.ts index 976742a..65974c5 100644 --- a/src/core/server/tools/guided-query-tool.context.test.ts +++ b/src/core/server/tools/guided-query-tool.context.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'; import { registerBuiltinUrlGenerators } from '../../../alliance/url-builtins.js'; import { guidedQueryResponseSchema } from '../response-schemas.js'; import { registerQueryTool } from './query-tool.js'; -import { registerSuggestQueryParamsTool } from '../../../alliance/tools/suggest-query-params-tool.js'; +import { registerSuggestQueryParamsTool } from './suggest-query-params-tool.js'; import { registerGuidedQueryTool } from './guided-query-tool.js'; import { assertToolErrorCode, diff --git a/src/alliance/tools/suggest-query-params-tool.context.test.ts b/src/core/server/tools/suggest-query-params-tool.context.test.ts similarity index 94% rename from src/alliance/tools/suggest-query-params-tool.context.test.ts rename to src/core/server/tools/suggest-query-params-tool.context.test.ts index 829c78a..d378c04 100644 --- a/src/alliance/tools/suggest-query-params-tool.context.test.ts +++ b/src/core/server/tools/suggest-query-params-tool.context.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { registerQueryTool } from '../../core/server/tools/query-tool.js'; +import { registerQueryTool } from './query-tool.js'; import { registerSuggestQueryParamsTool } from './suggest-query-params-tool.js'; -import { suggestQueryParamsResponseSchema } from '../../core/server/response-schemas.js'; +import { suggestQueryParamsResponseSchema } from '../response-schemas.js'; import { createMockServer, createTestServerContext, @@ -9,7 +9,7 @@ import { makeHybridQueryResult, mockNamespacesWithMetadataResult, parseToolJson, -} from '../../core/server/tools/test-helpers.js'; +} from './test-helpers.js'; const namespaceMetadata = { document_number: 'string', diff --git a/src/alliance/tools/suggest-query-params-tool.test.ts b/src/core/server/tools/suggest-query-params-tool.test.ts similarity index 94% rename from src/alliance/tools/suggest-query-params-tool.test.ts rename to src/core/server/tools/suggest-query-params-tool.test.ts index 36e580c..1429b3e 100644 --- a/src/alliance/tools/suggest-query-params-tool.test.ts +++ b/src/core/server/tools/suggest-query-params-tool.test.ts @@ -1,19 +1,19 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { getNamespacesWithCache } from '../../core/server/namespaces-cache.js'; -import { markSuggested } from '../../core/server/suggestion-flow.js'; +import { getNamespacesWithCache } from '../namespaces-cache.js'; +import { markSuggested } from '../suggestion-flow.js'; import { registerSuggestQueryParamsTool } from './suggest-query-params-tool.js'; import { createMockServer, makeNamespaceCacheEntry, parseToolJson, assertToolErrorCode, -} from '../../core/server/tools/test-helpers.js'; +} from './test-helpers.js'; -vi.mock('../../core/server/namespaces-cache.js', () => ({ +vi.mock('../namespaces-cache.js', () => ({ getNamespacesWithCache: vi.fn(), })); -vi.mock('../../core/server/suggestion-flow.js', () => ({ +vi.mock('../suggestion-flow.js', () => ({ markSuggested: vi.fn(), })); diff --git a/src/alliance/tools/suggest-query-params-tool.ts b/src/core/server/tools/suggest-query-params-tool.ts similarity index 85% rename from src/alliance/tools/suggest-query-params-tool.ts rename to src/core/server/tools/suggest-query-params-tool.ts index 8112245..bef5c27 100644 --- a/src/alliance/tools/suggest-query-params-tool.ts +++ b/src/core/server/tools/suggest-query-params-tool.ts @@ -1,28 +1,28 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; -import { normalizeNamespace } from '../../core/server/namespace-utils.js'; -import { getNamespacesWithCache } from '../../core/server/namespaces-cache.js'; -import { suggestQueryParams } from '../../core/server/query-suggestion.js'; -import type { ServerContext } from '../../core/server/server-context.js'; -import { markSuggested } from '../../core/server/suggestion-flow.js'; +import { normalizeNamespace } from '../namespace-utils.js'; +import { getNamespacesWithCache } from '../namespaces-cache.js'; +import { suggestQueryParams } from '../query-suggestion.js'; +import type { ServerContext } from '../server-context.js'; +import { markSuggested } from '../suggestion-flow.js'; import { optionalSourceField, rejectSourceWithoutContext, resolveSourceForTool, sourceParamSchema, sourceValidationError, -} from '../../core/server/source-tool-utils.js'; +} from '../source-tool-utils.js'; import { classifyToolCatchError, lifecycleToolError, logToolError, validationToolError, -} from '../../core/server/tool-error.js'; +} from '../tool-error.js'; import { suggestQueryParamsResponseSchema, type SuggestQueryParamsResponse, -} from '../../core/server/response-schemas.js'; -import { jsonErrorResponse, validatedJsonResponse } from '../../core/server/tool-response.js'; +} from '../response-schemas.js'; +import { jsonErrorResponse, validatedJsonResponse } from '../tool-response.js'; /** Register the suggest_query_params tool on the MCP server. */ export function registerSuggestQueryParamsTool(server: McpServer, ctx?: ServerContext): void { @@ -33,7 +33,7 @@ export function registerSuggestQueryParamsTool(server: McpServer, ctx?: ServerCo "Suggest which fields to request and whether to use the count tool, based on the namespace schema (from list_namespaces) and the user's natural language query. " + 'Call list_namespaces first to get available namespaces and metadata fields. Then call this tool with the target namespace and the user query; ' + 'it returns suggested_fields (only fields that exist in that namespace), use_count_tool (true if the query is a count question), recommended_tool (count | fast | detailed | full — same vocabulary as the query tool preset), and an explanation. ' + - 'This step is mandatory before query/count tools; use the returned suggested_fields in query tools to reduce payload and cost.', + 'When the suggest-flow gate is enabled (the Alliance default) this call is required before the query/count tools; when the gate is off (the core default) it is optional. Either way, use the returned suggested_fields in query tools to reduce payload and cost.', inputSchema: { namespace: z .string() diff --git a/src/core/setup.ts b/src/core/setup.ts index fdf23b2..cd446fb 100644 --- a/src/core/setup.ts +++ b/src/core/setup.ts @@ -19,6 +19,7 @@ import { registerKeywordSearchTool } from './server/tools/keyword-search-tool.js import { registerListNamespacesTool } from './server/tools/list-namespaces-tool.js'; import { registerNamespaceRouterTool } from './server/tools/namespace-router-tool.js'; import { registerQueryDocumentsTool } from './server/tools/query-documents-tool.js'; +import { registerSuggestQueryParamsTool } from './server/tools/suggest-query-params-tool.js'; import { registerQueryTool } from './server/tools/query-tool.js'; /** MCP server handle with automatic teardown via `await using`. */ @@ -36,10 +37,13 @@ export function teardownServer(): void { /** * Create and configure the MCP server with generic (core) tools. * - * Registers eight core tools including `guided_query`. Does not register Alliance-specific - * `suggest_query_params` or built-in Boost/Slack URL generators. Use - * {@link setupAllianceServer} from `@will-cppa/pinecone-read-only-mcp/alliance` for the - * full Alliance tool surface (suggest-flow gate on by default). + * Registers nine core tools including `guided_query` and `suggest_query_params` (ten + * with multi-source `list_sources`). `suggest_query_params` is registered, but the + * suggest-flow gate stays off by default for core (`disableSuggestFlow: true`), so core + * consumers can query without calling it first. Alliance additionally wires built-in + * Boost/Slack URL generators (registered into the `generate_urls` registry, not a + * separate MCP tool) and enables the gate by default; use {@link setupAllianceServer} + * from `@will-cppa/pinecone-read-only-mcp/alliance` for that. */ export type SetupCoreServerOptions = { config?: CoreServerConfig; @@ -164,6 +168,7 @@ async function registerCoreToolSurface( registerQueryDocumentsTool(server, ctx); registerGenerateUrlsTool(server, ctx); registerGuidedQueryTool(server, ctx); + registerSuggestQueryParamsTool(server, ctx); ctx.getConfig(); if (ctx.isMultiSource()) {