Skip to content

Commit f09d35c

Browse files
jonathanMLDevzho
andauthored
Add multi-source Pinecone MCP support (#187)
* Implemented multi-api server * update md and tests * fixed lint error * fixed format check * fixed codeQl error * added exit-on-failure.ts * remove the words "private", "public" * addressed ai reviews * addressed all reviews --------- Co-authored-by: zho <jornathanm910923@gmail.com>
1 parent 540cf94 commit f09d35c

63 files changed

Lines changed: 2348 additions & 185 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ PINECONE_READ_ONLY_MCP_LOG_FORMAT=text
3333

3434
# Optional: Request timeout for Pinecone calls, in milliseconds (default: 15000)
3535
# PINECONE_REQUEST_TIMEOUT_MS=15000
36+
37+
# Optional: Multi-source mode (replaces PINECONE_API_KEY + PINECONE_INDEX_NAME when set)
38+
# PINECONE_SOURCES=api_key_1:${PINECONE_API_KEY_1}:index_name_1;api_key_2:${PINECONE_API_KEY_2}:index_name_2
39+
# Or: PINECONE_CONFIG_FILE=./pinecone-sources.json

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ Tagged releases are published to npm from GitHub Actions when a **GitHub Release
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- **Multi-source mode:** configure multiple Pinecone API keys / indexes in one MCP server via `PINECONE_SOURCES`, `--sources`, or a JSON config file (`PINECONE_CONFIG_FILE` / `--config-file`). New `list_sources` tool (when more than one source is configured). Optional `source` parameter on discovery and query tools; `list_namespaces` aggregates across sources and tags each namespace with `source`. See [CONFIGURATION.md](docs/CONFIGURATION.md#multi-source-mode), [TOOLS.md](docs/TOOLS.md#multi-source-mode), and deployment profiles in [CONFIGURATION.md](docs/CONFIGURATION.md#deployment-profiles).
14+
1115
### Changed
1216

1317
- **Library:** `resolveConfig()` returns `CoreServerConfig`; `resolveAllianceConfig()` returns `AllianceServerConfig`. `setupCoreServer` / `setupAllianceServer` accept only their respective branded config and context types (`CoreServerContext` / `AllianceServerContext`). `ServerConfig` remains an alias for `ServerConfigBase` on read paths (`ctx.getConfig()`). See [MIGRATION.md](docs/MIGRATION.md#unreleased-branded-serverconfig-types).

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ For successful `query`, `query_documents`, and `guided_query` payloads, **rerank
5757
- **Hybrid Search**: Combines dense and sparse embeddings for superior recall
5858
- **Semantic Reranking**: Uses BGE reranker model for improved precision
5959
- **Dynamic Namespace Discovery**: Automatically discovers available namespaces in your Pinecone index
60+
- **Multi-Source (optional)**: Connect multiple Pinecone projects in one MCP server via `PINECONE_SOURCES` or a JSON config file; use `list_sources` and per-result `source` tags for routing
6061
- **Metadata Filtering**: Supports optional metadata filters for refined searches
6162
- **Fast presets**: Lazy initialization, connection pooling, and efficient result merging; use the `query` tool `preset=fast | detailed | full` to trade latency vs quality (no published benchmarks yet — treat descriptions as qualitative).
6263
- **Production-oriented defaults**: Input validation, error handling, and configurable logging; upgrading from **0.1.x** — see [MIGRATION.md](docs/MIGRATION.md).
@@ -122,6 +123,8 @@ The codebase is split into two layers:
122123

123124
You need a **Pinecone API key**. **Index** (`PINECONE_INDEX_NAME` or `--index-name`) is required for core/library use; the **published CLI** defaults to `rag-hybrid` when unset (Alliance deployment). Sparse index defaults to `{index}-sparse`. **Rerank:** set `PINECONE_RERANK_MODEL` to enable; the CLI defaults to `bge-reranker-v2-m3` when unset. See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) (core vs Alliance table).
124125

126+
**Multiple projects:** set `PINECONE_SOURCES` or `PINECONE_CONFIG_FILE` instead of a single `PINECONE_API_KEY` — see [Multi-source mode](docs/CONFIGURATION.md#multi-source-mode).
127+
125128
Quick reference (published CLI / Alliance — core embedders require index, no index/rerank defaults):
126129

127130
| Variable | Required | Default (Alliance CLI) |

benchmarks/latency.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { performance } from 'node:perf_hooks';
1212
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1313
import { PineconeClient } from '../src/pinecone-client.js';
1414
import { setLogLevel } from '../src/logger.js';
15+
import { exitOnDemoFailure } from '../examples/lib/exit-on-failure.js';
1516
import { setPineconeClient } from '../src/server/client-context.js';
1617
import { invalidateNamespacesCache, getNamespacesWithCache } from '../src/server/namespaces-cache.js';
1718
import { registerGuidedQueryTool } from '../src/server/tools/guided-query-tool.js';
@@ -323,7 +324,4 @@ async function main(): Promise<void> {
323324
console.log(`Wrote ${baselinePath}`);
324325
}
325326

326-
main().catch((err) => {
327-
console.error(err);
328-
process.exit(1);
329-
});
327+
main().catch(exitOnDemoFailure('latency benchmark'));

docs/CONFIGURATION.md

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ Configuration is built from **CLI flags** (when using the binary), **environment
2525
| `requestTimeoutMs` | `requestTimeoutMs` / `PINECONE_REQUEST_TIMEOUT_MS` | `15000` |
2626
| `disableSuggestFlow` | `disableSuggestFlow` / `PINECONE_DISABLE_SUGGEST_FLOW` | **Core `resolveConfig`:** `true` (gate off). **Alliance `resolveAllianceConfig` / CLI:** `false` (gate on). Bool parsing: true/1/yes/on |
2727
| `checkIndexes` | `checkIndexes` / `PINECONE_CHECK_INDEXES` | `false` |
28+
| `sources` | `sources` / `PINECONE_SOURCES` or JSON config file | Omitted in single-key mode; see [Multi-source mode](#multi-source-mode) |
29+
| `defaultSource` | JSON config `defaultSource` only | First source when using inline `PINECONE_SOURCES` |
2830

29-
**Throws** if `apiKey` or `indexName` is missing after trim.
31+
**Throws** if `apiKey` or `indexName` is missing after trim (single-key mode). In multi-source mode, `PINECONE_API_KEY` is ignored when `PINECONE_SOURCES` or a config file is set; credentials come from each source entry.
3032

3133
For the full Alliance tool surface (including `suggest_query_params`, `guided_query`, and built-in URL generators), import from `@will-cppa/pinecone-read-only-mcp/alliance` and use the three-step instance-first recipe at [Library embedding](#library-embedding) below.
3234

@@ -43,6 +45,113 @@ C++ Alliance deployers can copy [examples/alliance/.env.example](../examples/all
4345

4446
---
4547

48+
## Multi-source mode
49+
50+
Use **one MCP server entry** with multiple Pinecone API keys / projects when `PINECONE_SOURCES`, `--sources`, or a JSON config file (`PINECONE_CONFIG_FILE` / `--config-file`) is set.
51+
52+
### Inline format (`PINECONE_SOURCES` / `--sources`)
53+
54+
Semicolon-separated entries: `name:apiKey:indexName`
55+
56+
```bash
57+
PINECONE_SOURCES=api_key_1:${PINECONE_API_KEY_1}:index_name_1;api_key_2:${PINECONE_API_KEY_2}:index_name_2
58+
```
59+
60+
API keys may contain colons; the parser treats the last `:` segment as `indexName` and everything between `name:` and `:indexName` as the key.
61+
62+
### JSON config file
63+
64+
Set `PINECONE_CONFIG_FILE` (or `--config-file`) to a path such as [examples/multi-source/pinecone-sources.json.example](../examples/multi-source/pinecone-sources.json.example):
65+
66+
```json
67+
{
68+
"defaultSource": "api_key_1",
69+
"sources": {
70+
"api_key_1": { "apiKey": "${PINECONE_API_KEY_1}", "indexName": "index_name_1" },
71+
"api_key_2": { "apiKey": "${PINECONE_API_KEY_2}", "indexName": "index_name_2" }
72+
}
73+
}
74+
```
75+
76+
Values support `${ENV_VAR}` indirection (resolved at startup). Per-source `sparseIndexName` and `rerankModel` are optional; Alliance defaults apply when omitted.
77+
78+
### MCP tools and routing
79+
80+
| Tool | `source` parameter |
81+
| ---- | ------------------ |
82+
| `list_sources` | Registered only when more than one source is configured |
83+
| `list_namespaces`, `namespace_router` | Omit to aggregate all sources; results include `source` when tagged |
84+
| `query`, `count`, `query_documents`, `keyword_search`, `generate_urls`, `suggest_query_params`, `guided_query` | Omit when the namespace uniquely identifies one source; required when the same namespace exists on multiple sources |
85+
86+
Discovery responses may include `source_errors` when one project fails but others succeed. Suggest-flow state uses compound keys `source:namespace` in multi-source mode.
87+
88+
Single-key deployments (`PINECONE_API_KEY` + `PINECONE_INDEX_NAME` only) are unchanged — no `source` field on responses and no `list_sources` tool.
89+
90+
### Deployment profiles
91+
92+
Multi-source mode supports two operational profiles. **Never** ship a merged internal config through the same channel used for external partners.
93+
94+
| Profile | Who | Config | Risk if mis-shared |
95+
| ------- | --- | ------ | ------------------ |
96+
| **External (public-only)** | External companies, public MCP distribution | `PINECONE_API_KEY` + `PINECONE_INDEX_NAME`, or `PINECONE_SOURCES` with **one** entry | Low — single public key only |
97+
| **Internal (merged)** | Staff machines with access to private data | `PINECONE_SOURCES` or JSON config with **two+** entries | **High** — private API key and private namespace names exposed |
98+
99+
**External MCP config (unchanged):**
100+
101+
```json
102+
{
103+
"mcpServers": {
104+
"pinecone-search": {
105+
"command": "npx",
106+
"args": ["-y", "@will-cppa/pinecone-read-only-mcp"],
107+
"env": {
108+
"PINECONE_API_KEY": "your-public-key",
109+
"PINECONE_INDEX_NAME": "rag-hybrid"
110+
}
111+
}
112+
}
113+
}
114+
```
115+
116+
**Internal MCP config (merged sources):**
117+
118+
```json
119+
{
120+
"mcpServers": {
121+
"pinecone-search": {
122+
"command": "npx",
123+
"args": ["-y", "@will-cppa/pinecone-read-only-mcp"],
124+
"env": {
125+
"PINECONE_SOURCES": "api_key_1:${PINECONE_API_KEY_1}:index_name_1;api_key_2:${PINECONE_API_KEY_2}:index_name_2",
126+
"PINECONE_API_KEY_1": "pcsk_...",
127+
"PINECONE_API_KEY_2": "pcsk_..."
128+
}
129+
}
130+
}
131+
}
132+
```
133+
134+
Prefer `PINECONE_CONFIG_FILE` with `${ENV_VAR}` indirection over inline API keys in `PINECONE_SOURCES`. See [SECURITY.md](./SECURITY.md).
135+
136+
### Architecture decision
137+
138+
**Chosen:** Option A — multi-source server with optional `source` parameter on tools (`SourceRegistry` + `PINECONE_SOURCES` / JSON config).
139+
140+
**Rejected:**
141+
142+
- **Option B (Cursor routing rules only):** Does not fix the UX problem when users forget which MCP entry to use; no code changes.
143+
- **Option C (thin proxy MCP):** Extra package and latency; duplicates routing logic already in `ServerContext`.
144+
145+
**Rationale:** Pinecone SDK v8 supports multiple `Pinecone({ apiKey })` instances per process; MCP has no barrier to aggregating backends. Security is enforced by **deployment profiles** (public-only vs merged config), not per-query MCP authorization. All multi-source results include `source` for LLM provenance.
146+
147+
**Execution semantics:** Discovery tools aggregate all sources when `source` is omitted. Execution tools (`query`, `count`, etc.) call `resolveSource`: infer source when the namespace exists on exactly one project; return `VALIDATION` when ambiguous. They do **not** fan out one query to all sources. `guided_query` without `namespace` routes via the aggregated namespace list and sets `selected_source` in `decision_trace`.
148+
149+
**Audit logging:** When a tool resolves a specific source, stderr logs `toolname [source=name]` at INFO (execution tools and discovery tools that pass an explicit `source` filter). Aggregated discovery without `source` does not log per-source lines.
150+
151+
See also [TOOLS.md § Multi-source mode](./TOOLS.md#multi-source-mode).
152+
153+
---
154+
46155
## CLI flags (`parseCli` / `src/cli.ts`)
47156

48157
| Flag | Maps to |
@@ -58,6 +167,8 @@ C++ Alliance deployers can copy [examples/alliance/.env.example](../examples/all
58167
| `--request-timeout-ms` | `requestTimeoutMs` |
59168
| `--disable-suggest-flow` | `disableSuggestFlow: true` |
60169
| `--check-indexes` | `checkIndexes: true` |
170+
| `--sources` | `sources` (inline multi-source string) |
171+
| `--config-file` | `configFile` / `PINECONE_CONFIG_FILE` |
61172
| `--help` / `-h` | Print help and exit |
62173
| `--version` / `-v` | Print version and exit |
63174

docs/MIGRATION.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,26 @@ await setupCoreServer({ context: ctx });
165165

166166
See [deprecation-policy.md § Active deprecations](./deprecation-policy.md#active-deprecations-legacy-module-facades) for the full inventory.
167167

168+
### Multi-source mode and legacy facades
169+
170+
When `PINECONE_SOURCES` or a config file is active, legacy module facades (`getNamespacesWithCache`, `registerUrlGenerator`, `markSuggested`, `requireSuggested`, etc.) operate on the **default source only** (first inline source, or `defaultSource` from JSON). They do not aggregate across projects. Prefer an explicit `ServerContext` built with `sourceRegistry` from `buildSourceRegistry()` for multi-tenant or multi-project embedding.
171+
172+
## Unreleased: Multi-source Pinecone projects
173+
174+
**Who is affected:** Operators running separate MCP entries per Pinecone project, or library embedders needing multiple indexes.
175+
176+
**After (single MCP server):**
177+
178+
```bash
179+
PINECONE_SOURCES=api_key_1:${PINECONE_API_KEY_1}:index_name_1;api_key_2:${PINECONE_API_KEY_2}:index_name_2
180+
```
181+
182+
Or point `PINECONE_CONFIG_FILE` at a JSON file (see [examples/multi-source/pinecone-sources.json.example](../examples/multi-source/pinecone-sources.json.example)).
183+
184+
**Client flow:** `list_sources``list_namespaces` (note `source` on each row) → pass `source` on `query` / `count` / etc. when a namespace name is ambiguous.
185+
186+
Single-key configs are unchanged; no migration required when using one API key.
187+
168188
## Unreleased: trimmed library exports
169189

170190
**Who is affected:** Library embedders that imported `buildQueryExperimental` or `buildGuidedQueryExperimental` from `@will-cppa/pinecone-read-only-mcp` or `/alliance`.

docs/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ Guides for operators, MCP client authors, and library embedders.
44

55
| Guide | Description |
66
| ----- | ----------- |
7-
| [TOOLS.md](./TOOLS.md) | All MCP tools, parameters, success/error shapes, suggest-flow |
8-
| [CONFIGURATION.md](./CONFIGURATION.md) | Environment variables, CLI flags, `resolveConfig`, precedence |
7+
| [TOOLS.md](./TOOLS.md) | All MCP tools, parameters, success/error shapes, suggest-flow, multi-source |
8+
| [CONFIGURATION.md](./CONFIGURATION.md) | Environment variables, CLI flags, `resolveConfig`, multi-source mode, deployment profiles |
99
| [SECURITY.md](./SECURITY.md) | API keys, log redaction, Docker hardening, reporting issues |
1010
| [CONTRIBUTING.md](../CONTRIBUTING.md) | Local dev, tests, lint/format, PR expectations |
1111
| [CI_CD.md](./CI_CD.md) | GitHub Actions, CodeQL, SBOM, Codecov, releases |

docs/SECURITY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
## API keys
44

5-
- **Never** commit real Pinecone API keys. Use environment variables (`PINECONE_API_KEY`) or secret managers in CI.
6-
- The CLI and `resolveConfig` read keys only from argv/env/overrides — logs must not echo raw keys.
5+
- **Never** commit real Pinecone API keys. Use environment variables (`PINECONE_API_KEY`, or per-source keys referenced from `PINECONE_SOURCES` / a JSON config file) or secret managers in CI.
6+
- The CLI and `resolveConfig` read keys only from argv/env/overrides — logs must not echo raw keys. In multi-source mode, each source may use a different API key; all are redacted in logs and MCP responses.
7+
- Use **separate deployment profiles** for external (public-only) vs internal (merged) MCP configs — see [CONFIGURATION.md § Deployment profiles](./CONFIGURATION.md#deployment-profiles).
78

89
## Log redaction
910

0 commit comments

Comments
 (0)