Skip to content

Commit 2609e7b

Browse files
refactor: use single sparse index for hybrid and keyword search (reviewer feedback)
refactor: use single sparse index for hybrid and keyword search - Remove keywordIndex; use sparseIndex from ensureIndexes() for keywordSearch and listNamespacesFromKeywordIndex - Remove sparseIndexName config (PINECONE_SPARSE_INDEX_NAME, --sparse-index-name) and DEFAULT_SPARSE_INDEX_NAME - getSparseIndexName() now returns ${indexName}-sparse. Addresses reviewer: sparse_index and keyword_index are the same index
1 parent 54d214d commit 2609e7b

7 files changed

Lines changed: 21 additions & 65 deletions

File tree

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ The server requires a Pinecone API key and supports the following configuration
6262
| ---------------------------------- | -------- | ---------------------- | ------------------------------------------------ |
6363
| `PINECONE_API_KEY` | Yes | - | Your Pinecone API key |
6464
| `PINECONE_INDEX_NAME` | No | `rag-hybrid` | Pinecone index name (dense + sparse for hybrid) |
65-
| `PINECONE_SPARSE_INDEX_NAME` | No | `pinecone-rag-sparse` | Sparse index for `keyword_search` tool |
6665
| `PINECONE_RERANK_MODEL` | No | `bge-reranker-v2-m3` | Reranking model |
6766
| `PINECONE_READ_ONLY_MCP_LOG_LEVEL` | No | `INFO` | Logging level |
6867

@@ -146,7 +145,6 @@ node node_modules/@will-cppa/pinecone-read-only-mcp/dist/index.js --api-key YOUR
146145
```
147146
--api-key TEXT Pinecone API key (or set PINECONE_API_KEY env var)
148147
--index-name TEXT Pinecone index name [default: rag-hybrid]
149-
--sparse-index-name TEXT Sparse index for keyword_search [default: pinecone-rag-sparse]
150148
--rerank-model TEXT Reranking model [default: bge-reranker-v2-m3]
151149
--log-level TEXT Logging level [default: INFO]
152150
--help, -h Show help message
@@ -342,7 +340,7 @@ Returns the **unique document count** matching a metadata filter and semantic qu
342340

343341
### `keyword_search`
344342

345-
Performs **keyword (lexical/sparse-only)** search over the dedicated sparse index (default: `pinecone-rag-sparse`). Use for exact or keyword-style queries. Does not use the dense index or semantic reranking. Call `list_namespaces` first to discover namespaces; `suggest_query_params` is optional.
343+
Performs **keyword (lexical/sparse-only)** search over the dedicated sparse index (default: `rag-hybrid-sparse`, i.e. `{PINECONE_INDEX_NAME}-sparse`). Use for exact or keyword-style queries. Does not use the dense index or semantic reranking. Call `list_namespaces` first to discover namespaces; `suggest_query_params` is optional.
346344

347345
**Parameters:**
348346

@@ -363,7 +361,7 @@ Performs **keyword (lexical/sparse-only)** search over the dedicated sparse inde
363361
"status": "success",
364362
"query": "contracts C++",
365363
"namespace": "wg21-papers",
366-
"index": "pinecone-rag-sparse",
364+
"index": "rag-hybrid-sparse",
367365
"result_count": 5,
368366
"results": [
369367
{
@@ -518,7 +516,7 @@ npm test
518516
```bash
519517
PINECONE_API_KEY=your-key npm run test:search
520518
```
521-
If the sparse index (`pinecone-rag-sparse` by default) does not exist or has no data, the keyword search step is skipped with a warning.
519+
If the sparse index (`rag-hybrid-sparse` by default) does not exist or has no data, the keyword search step is skipped with a warning.
522520

523521
2. **Via MCP client:**
524522
Start the server and call the `keyword_search` tool with `query_text`, `namespace` (from `list_namespaces`), and optional `top_k` or `metadata_filter`. Response shape is the same as the `query` tool (e.g. `results` with ids, metadata, scores; `reranked` is always `false`).

scripts/test-search.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ async function test() {
163163
`⚠️ Keyword search skipped: sparse index has no namespaces (or index unavailable).`
164164
);
165165
console.log(
166-
` Ensure PINECONE_SPARSE_INDEX_NAME (e.g. pinecone-rag-sparse or rag-hybrid-sparse) exists and has data.`
166+
` Ensure the sparse index (e.g. rag-hybrid-sparse) exists and has data.`
167167
);
168168
} else {
169169
const sparseTestNamespace = sparseNamespaces[0].namespace;
@@ -191,7 +191,7 @@ async function test() {
191191
`⚠️ Keyword search skipped: ${kwError instanceof Error ? kwError.message : String(kwError)}`
192192
);
193193
console.log(
194-
` Ensure PINECONE_SPARSE_INDEX_NAME exists and namespace "${sparseTestNamespace}" has data.`
194+
` Ensure the sparse index exists and namespace "${sparseTestNamespace}" has data.`
195195
);
196196
}
197197
}

src/constants.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
*/
44

55
export const DEFAULT_INDEX_NAME = 'rag-hybrid';
6-
/** Default index name for keyword (sparse-only) search. Used by the keyword_search tool. */
7-
export const DEFAULT_SPARSE_INDEX_NAME = 'pinecone-rag-sparse';
86
export const DEFAULT_RERANK_MODEL = 'bge-reranker-v2-m3';
97
export const DEFAULT_TOP_K = 10;
108
export const MAX_TOP_K = 100;
@@ -47,7 +45,7 @@ Features:
4745
- Count: Use the count tool for "how many X?" questions; it uses semantic search only and minimal fields (no content) for performance, returning unique document count.
4846
- URL Generation: Use generate_urls to synthesize URLs for namespaces that support it when metadata lacks url.
4947
- Document reassembly: Use query_documents to get whole documents (chunks grouped and merged by document_number/doc_id/url) for content analysis or summarization.
50-
- Keyword search: Use keyword_search to query the sparse index (e.g. pinecone-rag-sparse) for lexical/keyword-only retrieval without reranking.
48+
- Keyword search: Use keyword_search to query the sparse index (default: rag-hybrid-sparse) for lexical/keyword-only retrieval without reranking.
5149
5250
Usage:
5351
1. Use list_namespaces (cached for 30 minutes) to discover available namespaces in the index

src/index.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@
1111
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
1212
import { PineconeClient } from './pinecone-client.js';
1313
import { setupServer, setPineconeClient } from './server.js';
14-
import {
15-
DEFAULT_INDEX_NAME,
16-
DEFAULT_RERANK_MODEL,
17-
DEFAULT_SPARSE_INDEX_NAME,
18-
} from './constants.js';
14+
import { DEFAULT_INDEX_NAME, DEFAULT_RERANK_MODEL } from './constants.js';
1915
import type { LogLevel } from './config.js';
2016
import { setLogLevel } from './logger.js';
2117
import * as dotenv from 'dotenv';
@@ -26,7 +22,6 @@ dotenv.config();
2622
interface CLIOptions {
2723
apiKey?: string;
2824
indexName?: string;
29-
sparseIndexName?: string;
3025
rerankModel?: string;
3126
logLevel?: string;
3227
}
@@ -49,10 +44,6 @@ function parseArgs(): CLIOptions {
4944
options.indexName = nextArg;
5045
i++;
5146
break;
52-
case '--sparse-index-name':
53-
options.sparseIndexName = nextArg;
54-
i++;
55-
break;
5647
case '--rerank-model':
5748
options.rerankModel = nextArg;
5849
i++;
@@ -81,16 +72,14 @@ Usage: pinecone-read-only-mcp [options]
8172
8273
Options:
8374
--api-key TEXT Pinecone API key (or set PINECONE_API_KEY env var)
84-
--index-name TEXT Pinecone index name [default: ${DEFAULT_INDEX_NAME}]
85-
--sparse-index-name TEXT Sparse index for keyword_search [default: ${DEFAULT_SPARSE_INDEX_NAME}]
75+
--index-name TEXT Pinecone index name [default: ${DEFAULT_INDEX_NAME}]; sparse index is {index-name}-sparse
8676
--rerank-model TEXT Reranking model [default: ${DEFAULT_RERANK_MODEL}]
8777
--log-level TEXT Logging level [default: INFO]
8878
--help, -h Show this help message
8979
9080
Environment Variables:
9181
PINECONE_API_KEY Pinecone API key
92-
PINECONE_INDEX_NAME Pinecone index name
93-
PINECONE_SPARSE_INDEX_NAME Sparse index for keyword_search tool
82+
PINECONE_INDEX_NAME Pinecone index name (sparse index: {name}-sparse)
9483
PINECONE_RERANK_MODEL Reranking model name
9584
PINECONE_READ_ONLY_MCP_LOG_LEVEL Logging level
9685
@@ -130,24 +119,19 @@ async function main(): Promise<void> {
130119

131120
// Get configuration
132121
const indexName = options.indexName || process.env['PINECONE_INDEX_NAME'] || DEFAULT_INDEX_NAME;
133-
const sparseIndexName =
134-
options.sparseIndexName ||
135-
process.env['PINECONE_SPARSE_INDEX_NAME'] ||
136-
DEFAULT_SPARSE_INDEX_NAME;
137122
const rerankModel =
138123
options.rerankModel || process.env['PINECONE_RERANK_MODEL'] || DEFAULT_RERANK_MODEL;
139124

140125
// Initialize Pinecone client
141126
const client = new PineconeClient({
142127
apiKey,
143128
indexName,
144-
sparseIndexName,
145129
rerankModel,
146130
});
147131
setPineconeClient(client);
148132

149133
console.error(`Starting Pinecone Read-Only MCP server with stdio transport`);
150-
console.error(`Using Pinecone index: ${indexName} (keyword search: ${sparseIndexName})`);
134+
console.error(`Using Pinecone index: ${indexName} (sparse: ${indexName}-sparse)`);
151135
console.error(`Log level: ${logLevel}`);
152136

153137
// Setup server

src/pinecone-client.ts

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import type {
2929
import {
3030
DEFAULT_INDEX_NAME,
3131
DEFAULT_RERANK_MODEL,
32-
DEFAULT_SPARSE_INDEX_NAME,
3332
DEFAULT_TOP_K,
3433
MAX_TOP_K,
3534
COUNT_TOP_K,
@@ -57,34 +56,28 @@ function inferMetadataFieldType(value: unknown): string {
5756
export class PineconeClient {
5857
private apiKey: string;
5958
private indexName: string;
60-
private sparseIndexName: string;
6159
private rerankModel: string;
6260
private defaultTopK: number;
6361

6462
// Lazy initialization
6563
private pc: Pinecone | null = null;
6664
private denseIndex: SearchableIndex | null = null;
6765
private sparseIndex: SearchableIndex | null = null;
68-
private keywordIndex: SearchableIndex | null = null;
6966
private initialized = false;
7067

7168
/** Create a client with the given config; env vars override index name, rerank model, and top-k. */
7269
constructor(config: PineconeClientConfig) {
7370
this.apiKey = config.apiKey;
7471
this.indexName = config.indexName || process.env['PINECONE_INDEX_NAME'] || DEFAULT_INDEX_NAME;
75-
this.sparseIndexName =
76-
config.sparseIndexName ||
77-
process.env['PINECONE_SPARSE_INDEX_NAME'] ||
78-
DEFAULT_SPARSE_INDEX_NAME;
7972
this.rerankModel =
8073
config.rerankModel || process.env['PINECONE_RERANK_MODEL'] || DEFAULT_RERANK_MODEL;
8174
this.defaultTopK =
8275
config.defaultTopK || parseInt(process.env['PINECONE_TOP_K'] || String(DEFAULT_TOP_K));
8376
}
8477

85-
/** Returns the configured sparse index name used for keyword search (runtime value). */
78+
/** Returns the sparse index name (same as hybrid sparse: {indexName}-sparse). Used for keyword_search response. */
8679
getSparseIndexName(): string {
87-
return this.sparseIndexName;
80+
return `${this.indexName}-sparse`;
8881
}
8982

9083
/**
@@ -143,31 +136,16 @@ export class PineconeClient {
143136
}
144137

145138
/**
146-
* Ensure the dedicated keyword (sparse-only) index is initialized.
147-
* Used by the keyword_search tool to query pinecone-rag-sparse (or configured name).
148-
*/
149-
private async ensureKeywordIndex(): Promise<SearchableIndex> {
150-
if (this.keywordIndex !== null) {
151-
return this.keywordIndex;
152-
}
153-
const pc = this.ensureClient();
154-
const index = pc.index(this.sparseIndexName) as unknown as SearchableIndex;
155-
this.keywordIndex = index;
156-
logInfo(`Keyword search index: ${this.sparseIndexName}`);
157-
return index;
158-
}
159-
160-
/**
161-
* List namespaces present on the keyword (sparse) index used for keyword_search.
139+
* List namespaces present on the sparse index (same index used for hybrid sparse and keyword_search).
162140
* Use this to choose a namespace for sparse-only queries instead of the dense index list.
163141
*/
164142
async listNamespacesFromKeywordIndex(): Promise<
165143
Array<{ namespace: string; recordCount: number }>
166144
> {
167145
try {
168-
const keywordIndex = await this.ensureKeywordIndex();
169-
const stats = keywordIndex.describeIndexStats
170-
? await keywordIndex.describeIndexStats()
146+
const { sparseIndex } = await this.ensureIndexes();
147+
const stats = sparseIndex.describeIndexStats
148+
? await sparseIndex.describeIndexStats()
171149
: undefined;
172150
const namespaces = stats?.namespaces ?? {};
173151
return Object.entries(namespaces).map(([namespace, info]) => ({
@@ -514,7 +492,7 @@ export class PineconeClient {
514492
/**
515493
* Keyword (sparse-only) search against the dedicated sparse index.
516494
* Performs lexical/keyword retrieval only—no dense index, no reranking.
517-
* Use for exact or keyword-style queries on the pinecone-rag-sparse index.
495+
* Use for exact or keyword-style queries on the configured sparse index.
518496
*/
519497
async keywordSearch(params: KeywordSearchParams): Promise<SearchResult[]> {
520498
const {
@@ -531,11 +509,11 @@ export class PineconeClient {
531509

532510
const topK = this.clampTopK(requestedTopK);
533511

534-
const keywordIndex = await this.ensureKeywordIndex();
512+
const { sparseIndex } = await this.ensureIndexes();
535513
const searchOptions = requestedFields?.length ? { fields: requestedFields } : undefined;
536514

537515
const hits = await this.searchIndex(
538-
keywordIndex,
516+
sparseIndex,
539517
query.trim(),
540518
topK,
541519
namespace,
@@ -563,7 +541,7 @@ export class PineconeClient {
563541
};
564542
});
565543

566-
logInfo(`Keyword search returned ${documents.length} results from ${this.sparseIndexName}`);
544+
logInfo(`Keyword search returned ${documents.length} results from ${this.getSparseIndexName()}`);
567545
return documents;
568546
}
569547

src/server/tools/keyword-search-tool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function registerKeywordSearchTool(server: McpServer): void {
8484
'keyword_search',
8585
{
8686
description:
87-
'Keyword (lexical/sparse-only) search over the Pinecone sparse index (e.g. pinecone-rag-sparse). ' +
87+
'Keyword (lexical/sparse-only) search over the Pinecone sparse index (default: rag-hybrid-sparse). ' +
8888
'Use for exact or keyword-style queries. Does not use semantic reranking. ' +
8989
'Call list_namespaces first to discover namespaces; suggest_query_params is optional.',
9090
inputSchema: {

src/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ export type PineconeMetadataValue = string | number | boolean | string[];
88
export interface PineconeClientConfig {
99
apiKey: string;
1010
indexName?: string;
11-
/** Dedicated sparse index for keyword_search tool (e.g. pinecone-rag-sparse). */
12-
sparseIndexName?: string;
1311
rerankModel?: string;
1412
defaultTopK?: number;
1513
}

0 commit comments

Comments
 (0)