Skip to content

Commit f2d89f8

Browse files
zhocursoragent
andcommitted
feat: post-rebase hardening, query tool presets, and CI coverage scope
- Unify MCP query surface behind one tool with fast/detailed/full presets; update guided flow and docs. - Abort-aware withTimeout (reject-before-abort race, swallow stray fn rejections); add retry tests. - Pinecone client: shared hit mapping, keyword index namespace discriminated result, metadata filter and logger fixes; builtin URL generators register in setupServer. - README/CHANGELOG/.npmignore align with packaging and deployment model; vitest excludes glue layers from coverage gates. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 047710a commit f2d89f8

21 files changed

Lines changed: 384 additions & 236 deletions

.npmignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ PUBLISHING.md
3131
node_modules/
3232
.vscode/
3333
.idea/
34-
dist/
3534
docs/
3635

3736
# Testing

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Future releases are managed automatically by [release-please](https://github.com
1010

1111
### Added
1212

13+
- `registerBuiltinUrlGenerators()` for built-in URL generators; `setupServer()` invokes it so CLI/library parity stays default.
14+
- Discriminated result type for `listNamespacesFromKeywordIndex()` (`KeywordIndexNamespacesResult`).
15+
- Unit tests for `withRetry` / `withTimeout` in `src/server/retry.test.ts`.
1316
- `SERVER_VERSION` is now read from `package.json` at runtime so MCP `serverInfo` always matches the published package version.
1417
- `--version` CLI flag prints the package version and exits.
1518
- `list_namespaces` response now includes `expires_at_iso` so clients see the cache expiry as an ISO-8601 timestamp without converting `cache_ttl_seconds`.
@@ -22,6 +25,13 @@ Future releases are managed automatically by [release-please](https://github.com
2225

2326
### Changed
2427

28+
- **Breaking (MCP):** Single hybrid `query` tool with `preset` (`fast` | `detailed` | `full`); removed separate `query_fast` / `query_detailed` tool registrations.
29+
- **Breaking (library):** Stopped re-exporting `withRetry` / `withTimeout` from the package entry (`server.ts`).
30+
- `withTimeout` aborts an internal `AbortSignal` on deadline (cooperative cancellation).
31+
- `PineconeClient`: shared hit-field extraction, safer merge dedup without empty `_id` collisions, metadata sampling skips zero-vector probe when dimension is unknown, `listNamespacesFromKeywordIndex` surfaces errors via `{ ok: false }`.
32+
- Metadata filter manual validation accepts primitive arrays for `$in`/`$nin` including numbers (matches Zod).
33+
- README: deployment model for process-global gate/cache/registry; adjusted feature wording vs pre-1.0 semver.
34+
- `.npmignore` no longer excludes `dist/` (still shipped via `package.json` `files`).
2535
- `.env.example` log-level options corrected to the four levels actually supported (`DEBUG`, `INFO`, `WARN`, `ERROR`); the stale `WARNING`/`CRITICAL` values are gone.
2636
- README Slack URL example now matches the generator output (`https://app.slack.com/client/{team_id}/{channel_id}/p{messageId}`).
2737
- README "Comparison with Python Version" no longer claims an identical API interface; the new TypeScript-only tools (`guided_query`, `query_documents`, `keyword_search`, `namespace_router`, `suggest_query_params`, `count`, `generate_urls`) are listed explicitly.

README.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ A Model Context Protocol (MCP) server that provides semantic search over Pinecon
2727
- **Semantic Reranking**: Uses BGE reranker model for improved precision
2828
- **Dynamic Namespace Discovery**: Automatically discovers available namespaces in your Pinecone index
2929
- **Metadata Filtering**: Supports optional metadata filters for refined searches
30-
- **Fast & Optimized**: Lazy initialization, connection pooling, and efficient result merging
31-
- **Production Ready**: Input validation, error handling, and configurable logging
30+
- **Fast presets**: Lazy initialization, connection pooling, and efficient result merging; use the `query` tool `preset=fast` / `detailed` to trade latency vs quality (no published benchmarks yet — treat descriptions as qualitative).
31+
- **Production-oriented defaults**: Input validation, error handling, and configurable logging (semantic versioning is pre-1.0 — review CHANGELOG before upgrading).
3232
- **TypeScript Support**: Full TypeScript support with type definitions
3333

3434
## Installation
@@ -82,6 +82,10 @@ Quick reference:
8282

8383
Run `pinecone-read-only-mcp --help` for CLI equivalents (`--cache-ttl-seconds`, `--request-timeout-ms`, `--disable-suggest-flow`, etc.).
8484

85+
### Deployment model
86+
87+
The server uses **process-global** memory for the suggest-flow gate (`suggest_query_params` context), namespaces cache, URL generator registry, and active configuration. **Stdio MCP (one client per Node process)** matches this model. If you embed `setupServer` behind a multi-tenant HTTP transport, isolate those structures per session yourself or treat the suggest-flow guard as best-effort only.
88+
8589
### Claude Desktop Configuration
8690

8791
Add to your `claude_desktop_config.json`:
@@ -254,7 +258,7 @@ Discovers and lists all available namespaces in the configured Pinecone index, i
254258

255259
### `suggest_query_params`
256260

257-
Suggests which **fields** to request and which tool to use (`count`, `query_fast`, or `query_detailed`), based on the namespace’s schema (from `list_namespaces`) and the user’s natural language query. This is a mandatory flow step before `count`/`query` tools.
261+
Suggests which **fields** to request and which path to use (`count`, or hybrid query as **fast** vs **detailed** — implemented via the `query` tool `preset` argument), based on the namespace’s schema (from `list_namespaces`) and the user’s natural language query. This is a mandatory flow step before `count` / `query` tools.
258262

259263
**Parameters:**
260264

@@ -286,7 +290,7 @@ Single orchestrator tool that runs the full flow in one call:
286290

287291
1. namespace routing (if namespace is omitted),
288292
2. query param suggestion,
289-
3. execution via `count`, `query_fast`, or `query_detailed`.
293+
3. execution via `count` or hybrid `query` (fast vs detailed behavior).
290294

291295
It returns both the final result and a `decision_trace` for transparency.
292296

@@ -298,7 +302,7 @@ It returns both the final result and a `decision_trace` for transparency.
298302
| `namespace` | string | No | - | Optional explicit namespace |
299303
| `metadata_filter` | object | No | - | Optional metadata filter |
300304
| `top_k` | integer | No | `10` | Query result size for query paths (1-100) |
301-
| `preferred_tool` | enum | No | `auto` | One of `auto`, `count`, `query_fast`, `query_detailed` |
305+
| `preferred_tool` | enum | No | `auto` | One of `auto`, `count`, `fast`, `detailed` |
302306
| `enrich_urls` | boolean | No | `true` | Auto-generate URLs for `mailing` and `slack-Cpplang` when `metadata.url` is missing |
303307

304308
**Returns:** JSON containing `decision_trace` and `result`.

scripts/test-search.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,11 @@ async function test() {
155155
// Test 5: Keyword (sparse-only) search — use namespace from sparse index, not dense
156156
let duration5: number | undefined;
157157
let test5Skipped = false;
158-
const sparseNamespaces = await client.listNamespacesFromKeywordIndex();
158+
const sparseResult = await client.listNamespacesFromKeywordIndex();
159+
const sparseNamespaces = sparseResult.ok ? sparseResult.namespaces : [];
160+
if (!sparseResult.ok) {
161+
console.warn(`⚠️ listNamespacesFromKeywordIndex failed: ${sparseResult.error}`);
162+
}
159163
if (sparseNamespaces.length === 0) {
160164
test5Skipped = true;
161165
console.log(`\n🔤 Test 5: Keyword search (sparse-only index)`);

src/cli.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
* for {@link resolveConfig}; environment variables remain the fallback there.
44
*/
55

6-
import {
7-
DEFAULT_INDEX_NAME,
8-
DEFAULT_RERANK_MODEL,
9-
SERVER_VERSION,
10-
} from './constants.js';
6+
import { DEFAULT_INDEX_NAME, DEFAULT_RERANK_MODEL, SERVER_VERSION } from './constants.js';
117
import type { ConfigOverrides } from './config.js';
128

139
export type ParseCliResult =

src/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const QUERY_DOCUMENTS_MAX_CHUNKS = 500;
3434
export const SERVER_NAME = 'Pinecone Read-Only MCP';
3535
export { SERVER_VERSION } from './server-version.js';
3636

37-
export const 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 a decision_trace you can show the user. For manual flows, call \`list_namespaces\` -> \`suggest_query_params\` -> \`query_fast\` / \`query_detailed\` / \`count\` (the suggest step is a mandatory gate). Use \`query_documents\` for full-document reading, \`keyword_search\` for exact-keyword retrieval against the sparse index, and \`generate_urls\` when records need URLs synthesized from metadata.
37+
export const 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 a decision_trace you can show the user. For manual flows, call \`list_namespaces\` -> \`suggest_query_params\` -> \`query\` (use preset fast/detailed/full per suggestion) or \`count\` (the suggest step is a mandatory gate). Use \`query_documents\` for full-document reading, \`keyword_search\` for exact-keyword retrieval against the sparse index, and \`generate_urls\` when records need URLs synthesized from metadata.
3838
3939
A semantic search server that provides hybrid search capabilities over Pinecone vector indexes with automatic namespace discovery.
4040
@@ -53,7 +53,7 @@ Usage:
5353
1. Use list_namespaces (cached) to discover available namespaces in the index. The response includes \`expires_at_iso\` so you know when to refresh.
5454
2. Optionally use namespace_router to choose candidate namespace(s) from user intent.
5555
3. Call suggest_query_params before query/count/query_documents tools (mandatory flow gate) to get suggested_fields and recommended_tool.
56-
4. Use count for count questions, query_fast/query_detailed for chunk-level retrieval, or query_documents for full-document content.
56+
4. Use count for count questions, \`query\` with the appropriate preset for chunk-level retrieval, or query_documents for full-document content.
5757
5858
Notes:
5959
- 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.

src/logger.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,16 @@ function shouldLog(level: LogLevel): boolean {
5151
/**
5252
* Pinecone API keys are typically UUID-like strings. We mask anything that
5353
* looks like a key (`xxxxxxxx-xxxx-...`) plus tokens marked with `apiKey`,
54-
* `api_key`, or `Authorization: Bearer ...`. The intent is defence in
55-
* depth: callers should already be careful, this is the last guard.
54+
* `api_key`, or `Authorization: Bearer ...`.
5655
*/
57-
const API_KEY_PATTERNS: RegExp[] = [
58-
/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,
59-
/(api[_-]?key["':\s=]+)([^\s"',}]+)/gi,
60-
/(Authorization:\s*Bearer\s+)([^\s"',}]+)/gi,
61-
];
62-
6356
/** Replace API-key-shaped substrings in `s` with `***`. Idempotent and safe to call on any string. */
6457
export function redactApiKey(s: string): string {
65-
let out = s;
66-
for (const re of API_KEY_PATTERNS) {
67-
out = out.replace(re, (_match, prefix?: string) => (prefix ? `${prefix}***` : '***'));
68-
}
58+
let out = s.replace(
59+
/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,
60+
'***'
61+
);
62+
out = out.replace(/(api[_-]?key["':\s=]+)([^\s"',}]+)/gi, '$1***');
63+
out = out.replace(/(Authorization:\s*Bearer\s+)([^\s"',}]+)/gi, '$1***');
6964
return out;
7065
}
7166

0 commit comments

Comments
 (0)