Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/TOOLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Comment thread
timon0305 marked this conversation as resolved.

**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.

Expand Down
19 changes: 19 additions & 0 deletions src/__tests__/cross-entry-point.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
});
Comment thread
timon0305 marked this conversation as resolved.
});
20 changes: 20 additions & 0 deletions src/__tests__/mcp-rc-readiness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions src/alliance/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -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).
*/
Expand Down Expand Up @@ -114,6 +115,5 @@ export async function setupAllianceServer(
}

registerBuiltinUrlGenerators(resolvedCtx);
registerSuggestQueryParamsTool(server, resolvedCtx);
return server;
}
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/tools/guided-query-tool.context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
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,
expectMatchesResponseSchema,
makeHybridQueryResult,
mockNamespacesWithMetadataResult,
parseToolJson,
} from '../../core/server/tools/test-helpers.js';
} from './test-helpers.js';

const namespaceMetadata = {
document_number: 'string',
Expand Down
Original file line number Diff line number Diff line change
@@ -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(),
}));

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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()
Expand Down
13 changes: 9 additions & 4 deletions src/core/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`. */
Expand All @@ -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;
Expand Down Expand Up @@ -164,6 +168,7 @@ async function registerCoreToolSurface(
registerQueryDocumentsTool(server, ctx);
registerGenerateUrlsTool(server, ctx);
registerGuidedQueryTool(server, ctx);
registerSuggestQueryParamsTool(server, ctx);

ctx.getConfig();
if (ctx.isMultiSource()) {
Expand Down