Content
# Pinecone Read-Only MCP (TypeScript)
[](https://www.npmjs.com/package/@will-cppa/pinecone-read-only-mcp)
[](https://nodejs.org)
[](https://opensource.org/licenses/BSL-1.0)
[](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/actions)
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that implements the MCP specification via `@modelcontextprotocol/sdk` v1.29+ and provides semantic search over Pinecone vector databases using hybrid search (dense + sparse) with reranking.
Current version: 0.4.0 (npm `latest` after publish). Pin `@0.4.0` in install and MCP config for reproducible upgrades.
## Upgrading to 0.4.0
Version **0.4.0** adds **multi-source mode** (`PINECONE_SOURCES`, `list_sources`, optional `source` on query tools) and **breaking (types)** branded `CoreServerConfig` / `AllianceServerConfig` — `setupCoreServer` and `setupAllianceServer` now accept only matching config/context types. See [docs/MIGRATION.md](docs/MIGRATION.md#040-multi-source-pinecone-projects), [docs/MIGRATION.md § branded ServerConfig](docs/MIGRATION.md#040-branded-serverconfig-types), and the [CHANGELOG](CHANGELOG.md#040---2026-06-24).
## Upgrading to 0.3.0
Version **0.3.0** includes breaking MCP and library changes: experimental response fields nested under `experimental`, legacy module-facade deprecations, and core `resolveConfig` requiring an index name with suggest-flow off by default. See [docs/MIGRATION.md](docs/MIGRATION.md) for before/after examples and the [CHANGELOG](CHANGELOG.md#030---2026-06-23) **Changed** section for the full list.
## Upgrading from 0.1.x
Version **0.2.0** includes breaking MCP and type changes. See [docs/MIGRATION.md](docs/MIGRATION.md) for before/after examples and the [CHANGELOG](CHANGELOG.md#020---2026-05-29) **Changed** section for the full list.
## Release policy
While the package is **`0.y.z`**, minor releases may include breaking changes ([semver §4](https://semver.org/spec/v2.0.0.html#spec-item-4)) — pin an exact npm version for reproducible MCP and library use. Deprecated APIs stay available for at least **two minor releases** before removal; breaking releases ship with CHANGELOG entries, [MIGRATION.md](docs/MIGRATION.md) steps, and (for publishes) structured GitHub Release notes. Full rules: [docs/deprecation-policy.md](docs/deprecation-policy.md).
## Documentation
| Doc | Description |
| -------------------------------------------------------- | -------------------------------------- |
| [docs/README.md](docs/README.md) | Index of all guides |
| [docs/TOOLS.md](docs/TOOLS.md) | Tool catalog & flows |
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | Env vars, CLI flags, library config |
| [docs/FAQ.md](docs/FAQ.md) | Common questions |
| [docs/MIGRATION.md](docs/MIGRATION.md) | Deprecations & breaking changes |
| [docs/deprecation-policy.md](docs/deprecation-policy.md) | Release & deprecation policy |
| [docs/CI_CD.md](docs/CI_CD.md) | GitHub Actions, SBOM, Docker, releases |
| [docs/RELEASING.md](docs/RELEASING.md) | npm publish via GitHub Releases |
| [CONTRIBUTING.md](CONTRIBUTING.md) | How to contribute |
| [docs/SECURITY.md](docs/SECURITY.md) | Vulnerability reporting |
## Error responses
When a tool fails, the MCP tool result sets **`isError: true`**. The `text` content is JSON matching **`ToolError`** (parse with `toolErrorSchema` from `@will-cppa/pinecone-read-only-mcp`).
| Field | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code` | `FLOW_GATE` — `suggest_query_params` was not run for this namespace (or context expired). `VALIDATION` — bad input or metadata filter. `PINECONE_ERROR` — SDK / network / server failure. `TIMEOUT` — outbound Pinecone call exceeded `--request-timeout-ms`. |
| `message` | Human-readable detail (`DEBUG` log level may surface raw SDK messages in the message for `PINECONE_ERROR` / `TIMEOUT`). |
| `recoverable` | Whether the client can plausibly fix the issue and retry (`true` for flow gate, validation, timeouts; typically `false` for generic Pinecone errors). |
| `suggestion` | Optional hint. **`FLOW_GATE`** always includes: `Call suggest_query_params for namespace '<ns>' first`. **`TIMEOUT`** suggests retrying or increasing the request timeout. |
| `field` | **Required when `code` is `VALIDATION`:** the input parameter name (e.g. `query_text`, `namespace`) or a dot-path into `metadata_filter` (e.g. `author.$in`). |
Success payloads are unchanged and do **not** wrap `ToolError`. Clients that still expect `{ "status": "error", "message": "..." }` must migrate to the shape above.
For successful `query`, `query_documents`, and `guided_query` payloads, **rerank/hybrid fidelity** is described in [docs/TOOLS.md](docs/TOOLS.md#rerank-and-hybrid-degradation) (row-level `reranked`, top-level `degraded` / `degradation_reason`, and optional `hybrid_leg_failed`; `query_documents` propagates the same fields on its nested query payload when applicable).
## Features
- **Hybrid Search**: Combines dense and sparse embeddings for superior recall
- **Semantic Reranking**: Uses BGE reranker model for improved precision
- **Dynamic Namespace Discovery**: Automatically discovers available namespaces in your Pinecone index
- **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
- **Metadata Filtering**: Supports optional metadata filters for refined searches
- **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).
- **Production-oriented defaults**: Input validation, error handling, and configurable logging; upgrading from **0.1.x** — see [MIGRATION.md](docs/MIGRATION.md).
- **TypeScript Support**: Full TypeScript support with type definitions
## Installation
**Node.js [20.12](https://nodejs.org/en/download) or later** is required (`engines` in `package.json`).
### As a Package
```bash
npm install @will-cppa/pinecone-read-only-mcp@0.4.0
```
Or using yarn:
```bash
yarn add @will-cppa/pinecone-read-only-mcp@0.4.0
```
Or using pnpm:
```bash
pnpm add @will-cppa/pinecone-read-only-mcp@0.4.0
```
### Global Installation
```bash
npm install -g @will-cppa/pinecone-read-only-mcp@0.4.0
```
### From Source
```bash
git clone https://github.com/cppallance/pinecone-read-only-mcp-typescript.git
cd pinecone-read-only-mcp-typescript
npm install
npm run build
```
## Stability
This package ships as **`0.y.z`**. Under [semver §4](https://semver.org/spec/v2.0.0.html#spec-item-4), **minor versions may contain breaking changes** until `1.0.0`. Pin an exact version or use `~` (tilde) ranges — not `^` (caret) — until 1.0 to avoid absorbing breaking minors automatically.
Breaking changes include: type signature changes, tool schema changes, default behavior changes, and removed exports.
See [docs/deprecation-policy.md](docs/deprecation-policy.md) for the full deprecation window (minimum two minors before removal).
## Quick start
To try the server on **your own** Pinecone project (free tier, no Alliance index), follow [examples/quickstart/README.md](examples/quickstart/README.md): create two integrated-embedding indexes, copy [examples/quickstart/.env.example](examples/quickstart/.env.example), seed sample data, and run the MCP demo. Use an explicit `PINECONE_INDEX_NAME` in that flow rather than relying on Alliance default index names.
## Architecture
The codebase is split into two layers:
- **`src/core/`** — generic MCP–Pinecone bridge (`PineconeClient`, `resolveConfig`, eight MCP tools including `guided_query`). Import from `@will-cppa/pinecone-read-only-mcp` (package root).
- **`src/alliance/`** — C++ Alliance app layer (`suggest_query_params`, Boost/Slack URL builtins). Import from `@will-cppa/pinecone-read-only-mcp/alliance` and use `createServer(config)` → `ctx.setClient(...)` → `setupAllianceServer({ context: ctx })` for the full tool surface (what the CLI uses); suggest-flow gate on by default. See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for core vs Alliance defaults and [Library embedding](#library-embedding) below.
## Configuration
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).
**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).
Quick reference (published CLI / Alliance — core embedders require index, no index/rerank defaults):
| Variable | Required | Default (Alliance CLI) |
| ----------------------------------- | ----------------------- | --------------------------------- |
| `PINECONE_API_KEY` | Yes (for live Pinecone) | — |
| `PINECONE_INDEX_NAME` | No (CLI) / Yes (core) | `rag-hybrid` (CLI only) |
| `PINECONE_RERANK_MODEL` | No | `bge-reranker-v2-m3` (CLI only) |
| `PINECONE_SPARSE_INDEX_NAME` | No | `{index}-sparse` |
| `PINECONE_READ_ONLY_MCP_LOG_LEVEL` | No | `INFO` (`DEBUG`–`ERROR`) |
| `PINECONE_READ_ONLY_MCP_LOG_FORMAT` | No | `text` (`json` for log pipelines) |
Run `pinecone-read-only-mcp --help` for CLI equivalents (`--cache-ttl-seconds`, `--request-timeout-ms`, `--disable-suggest-flow`, etc.).
### Deployment model
Each **`ServerContext`** owns its own suggest-flow gate, namespaces cache, URL generator registry, and config. **Stdio MCP (one client per Node process)** typically uses one context. For **multi-tenant HTTP** embedding, create one `ServerContext` per session and pass it explicitly to `setupAllianceServer({ context: ctx })` or `setupCoreServer({ context: ctx })`.
Pass `config` at setup only when the context is not yet configured; after `createServer` + `setClient`, pass `{ context: ctx }` only.
Module-level singleton facades (`setPineconeClient`, `registerUrlGenerator`, `getDefaultServerContext`, etc.) are **deprecated** — they delegate to a process-default context when you omit `context` at setup. Prefer explicit `ServerContext` per [Library embedding](#library-embedding). See [docs/deprecation-policy.md](docs/deprecation-policy.md#active-deprecations-legacy-module-facades).
### Library embedding
**Recommended (instance-first):** create a `ServerContext`, inject the client, and pass it to setup:
- **Generic bridge only:** `import { createServer, setupCoreServer, teardownServer, ... } from '@will-cppa/pinecone-read-only-mcp'`
- **Full Alliance surface (CLI parity):** `import { setupAllianceServer, resolveAllianceConfig } from '@will-cppa/pinecone-read-only-mcp/alliance'`
```ts
const config = resolveAllianceConfig({ apiKey: '...' });
const ctx = createServer(config);
ctx.setClient(
new PineconeClient({
/* ... */
})
);
const server = await setupAllianceServer({ context: ctx });
```
Use **`await using server = await setupAllianceServer({ context: ctx })`** for automatic teardown, or call **`ctx.teardown()`** when done.
For the **generic bridge only**, see [examples/quickstart/mcp-demo.ts](examples/quickstart/mcp-demo.ts). For the **full Alliance surface**, see [examples/alliance/library-embedding-demo.ts](examples/alliance/library-embedding-demo.ts) and [docs/TOOLS.md](docs/TOOLS.md#suggest-flow-gate).
#### Legacy (deprecated)
Process-default / facade-based setup remains available during the deprecation window (removal per [deprecation-policy.md](docs/deprecation-policy.md) — two minors after the deprecation minor):
```ts
import { PineconeClient, setPineconeClient } from '@will-cppa/pinecone-read-only-mcp';
import {
resolveAllianceConfig,
setupAllianceServer,
} from '@will-cppa/pinecone-read-only-mcp/alliance';
setPineconeClient(
new PineconeClient({
/* ... */
})
);
const server = await setupAllianceServer(resolveAllianceConfig({ apiKey: '...' }));
// Call teardownServer() before re-initializing the process-default context.
```
Do not use this pattern for new code or multi-tenant embedding. See [docs/MIGRATION.md](docs/MIGRATION.md#030-legacy-module-facade-deprecations).
### Custom URL generators
Namespaces other than `mailing` and `slack-Cpplang` (or different URL rules for any namespace) can use programmatic registration — no fork required.
Register **additional** namespaces on your `ServerContext` before tools that emit URLs run. Built-in `mailing` / `slack-Cpplang` generators are installed by `setupAllianceServer` (not by `setupCoreServer`).
```ts
import {
createServer,
PineconeClient,
type UrlGenerationResult,
type UrlGeneratorFn,
} from '@will-cppa/pinecone-read-only-mcp';
import {
resolveAllianceConfig,
setupAllianceServer,
} from '@will-cppa/pinecone-read-only-mcp/alliance';
const config = resolveAllianceConfig({ apiKey: '...' }); // optional: indexName, rerankModel
const ctx = createServer(config);
ctx.setClient(
new PineconeClient({
apiKey: config.apiKey,
indexName: config.indexName,
sparseIndexName: config.sparseIndexName,
rerankModel: config.rerankModel,
})
);
const server = await setupAllianceServer({ context: ctx });
const myDocs: UrlGeneratorFn = (metadata): UrlGenerationResult => {
const id = typeof metadata.doc_id === 'string' ? metadata.doc_id : null;
return id
? { url: `https://docs.example.com/${id}`, method: 'generated.custom' }
: { url: null, method: 'unavailable', reason: 'doc_id missing' };
};
ctx.registerUrlGenerator('product-docs', myDocs);
```
A fuller embedding sample lives in [examples/alliance/custom-url-generator.ts](examples/alliance/custom-url-generator.ts).
### Examples
**Generic quickstart** — [examples/quickstart/](examples/quickstart/) (setup guide, seed script, `setupCoreServer` MCP demo).
**Alliance / advanced** — [examples/alliance/](examples/alliance/):
| File | Description |
| ------------------------------------------------------------------------------------------ | -------------------------------------------------- |
| [examples/alliance/suggest-flow-demo.ts](examples/alliance/suggest-flow-demo.ts) | Manual **suggest_query_params → query** flow |
| [examples/alliance/guided-query-demo.ts](examples/alliance/guided-query-demo.ts) | **guided_query** and `experimental.decision_trace` |
| [examples/alliance/library-embedding-demo.ts](examples/alliance/library-embedding-demo.ts) | **setupAllianceServer** without the CLI |
| [examples/alliance/custom-url-generator.ts](examples/alliance/custom-url-generator.ts) | Custom **URL generator** registration |
Run with `npx tsx examples/<path>.ts` from a checkout (requires valid Pinecone env for live paths). See [examples/README.md](examples/README.md).
### Claude Desktop Configuration
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"pinecone-search": {
"command": "npx",
"args": ["-y", "@will-cppa/pinecone-read-only-mcp@0.4.0"],
"env": {
"PINECONE_API_KEY": "your-api-key-here",
"PINECONE_INDEX_NAME": "your-index-name",
"PINECONE_RERANK_MODEL": "your-rerank-model"
}
}
}
}
```
Or with explicit options:
```json
{
"mcpServers": {
"pinecone-search": {
"command": "npx",
"args": [
"-y",
"@will-cppa/pinecone-read-only-mcp@0.4.0",
"--api-key",
"your-api-key-here",
"--index-name",
"your-index-name",
"--rerank-model",
"bge-reranker-v2-m3"
]
}
}
}
```
For a global installation:
```json
{
"mcpServers": {
"pinecone-search": {
"command": "pinecone-read-only-mcp",
"args": ["--api-key", "your-api-key-here", "--index-name", "your-index-name"]
}
}
}
```
## Usage
### Command Line
Run the server using npx (no installation required):
```bash
npx @will-cppa/pinecone-read-only-mcp@0.4.0 --api-key YOUR_API_KEY --index-name YOUR_INDEX
```
Or if installed globally:
```bash
pinecone-read-only-mcp --api-key YOUR_API_KEY --index-name YOUR_INDEX
```
Or if installed locally in your project:
```bash
node node_modules/@will-cppa/pinecone-read-only-mcp/dist/index.js --api-key YOUR_API_KEY --index-name YOUR_INDEX
```
### Available Options
```
--api-key TEXT Pinecone API key (or set PINECONE_API_KEY env var)
--index-name TEXT Dense index (required, or PINECONE_INDEX_NAME)
--rerank-model TEXT Reranker model (defalut: bge-reranker-v2-m3)
--log-level TEXT Logging level [default: INFO]
--help, -h Show help message
```
Run `pinecone-read-only-mcp --help` for the full option list.
## Deployment
### Production Readiness Defaults
- Build now **fails fast** on TypeScript errors (`npm run build` no longer suppresses failures).
- CI validates typecheck, lint, format, build, smoke run, tests, and package dry-run.
- `list_namespaces` data is cached in-memory for 30 minutes to reduce repeated Pinecone calls.
- Query/count flow has guardrails (`suggest_query_params` before execution) to prevent wasteful calls.
### Deploy with npm Package
```bash
# install
npm i @will-cppa/pinecone-read-only-mcp@0.4.0
# run
npx @will-cppa/pinecone-read-only-mcp@0.4.0 --api-key YOUR_API_KEY
```
### Deploy with Docker
```bash
# build image
docker build -t pinecone-read-only-mcp:latest .
# run (stdio MCP server)
docker run --rm -i \
-e PINECONE_API_KEY=YOUR_API_KEY \
-e PINECONE_INDEX_NAME=your-index-name \
pinecone-read-only-mcp:latest
```
### Release Gate (recommended)
Before tagging/releasing:
```bash
npm run release:check
```
This runs full CI-equivalent checks and validates publish contents with `npm pack --dry-run`.
## API Documentation
The server exposes the following tools via MCP:
### `list_namespaces`
Discovers and lists all available namespaces in the configured Pinecone index, including metadata fields and record counts for each namespace.
**Parameters:** None
**Returns:** JSON object with namespace details including available metadata fields
`metadata_fields` values represent inferred field types from sampled records. Common values include:
`string`, `number`, `boolean`, `string[]`, and `array`.
**Example response:**
```json
{
"status": "success",
"count": 3,
"namespaces": [
{
"name": "namespace1",
"record_count": 1500,
"metadata_fields": {
"author": "string",
"year": "number",
"category": "string"
}
},
{
"name": "namespace2",
"record_count": 850,
"metadata_fields": {
"title": "string",
"date": "string"
}
}
]
}
```
### Retrieval tool decision matrix
Use this when choosing among overlapping retrieval tools. **Semantic vs lexical:** use `query` / `query_fast` / `query_detailed` or `query_documents` for meaning-based search; use `keyword_search` for exact or keyword-style matches on the sparse index. **Chunks vs whole documents:** use `query` / `query_fast` / `query_detailed` for ranked chunks; use `query_documents` when you need merged full-document text. **One-shot vs manual flow:** use `guided_query` to run routing, suggestion, and execution in a single call; otherwise call `suggest_query_params` before gated tools.
- **`query` / `query_fast` / `query_detailed`** — Semantic chunk retrieval. Requires `suggest_query_params` to be called first for the target namespace.
- **`query_documents`** — Semantic search with chunks reassembled into whole documents. Requires `suggest_query_params` to be called first for the target namespace.
- **`keyword_search`** — Lexical (sparse-only) search. Does not require `suggest_query_params`.
- **`guided_query`** — Combines namespace routing, suggestion, and query into a single call; no prerequisite tools needed.
- **`count`** — “How many …?” style counts via semantic search. Requires `suggest_query_params` before use (same gate as `query` / `query_documents`).
### `suggest_query_params`
Suggests which **fields** to request and which path to use (`count`, or hybrid query presets **fast** / **detailed** / **full** — same vocabulary as 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.
**Parameters:**
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | -------------------------------------------------------------------------------------------------- |
| `namespace` | string | Yes | Namespace to query (must match a name from `list_namespaces`) |
| `user_query` | string | Yes | User’s question or intent (e.g. "list papers by John Doe with titles", "how many papers by Wong?") |
**Returns:** `suggested_fields` (only fields that exist in that namespace), `use_count_tool`, `recommended_tool`, `explanation`, and `namespace_found`.
**Example response:**
```json
{
"status": "success",
"suggested_fields": ["document_number", "title", "url", "author"],
"use_count_tool": false,
"recommended_tool": "fast",
"explanation": "User asked for a list or browse; use minimal fields (no chunk_text) for smaller payload and cost.",
"namespace_found": true
}
```
Use `suggested_fields` as the `fields` parameter when calling query tools.
### `guided_query`
Single orchestrator tool that runs the full flow in one call:
1. namespace routing (if namespace is omitted),
2. query param suggestion,
3. execution via `count` or hybrid `query` (`fast` / `detailed` / `full` presets).
It returns both the final result and `experimental.decision_trace` for transparency.
**Parameters:**
| Parameter | Type | Required | Default | Description |
| ----------------- | ------- | -------- | ------- | ----------------------------------------------------------------------------------- |
| `user_query` | string | Yes | - | User question/intent |
| `namespace` | string | No | - | Optional explicit namespace |
| `metadata_filter` | object | No | - | Optional metadata filter |
| `top_k` | integer | No | `10` | Query result size for query paths (1-100) |
| `preferred_tool` | enum | No | `auto` | One of `auto`, `count`, `fast`, `detailed`, `full` |
| `enrich_urls` | boolean | No | `true` | Auto-generate URLs for `mailing` and `slack-Cpplang` when `metadata.url` is missing |
**Returns:** JSON containing `experimental.decision_trace` and `result`.
### `generate_urls`
Generates URLs for retrieved records when metadata does not contain `url` and URL is required.
Supported namespaces:
- `mailing`
- `slack-Cpplang`
Rules:
- **`mailing`**: uses `doc_id` or `thread_id`
Format: `https://lists.boost.org/archives/list/{doc_id_or_thread_id}/`
- **`slack-Cpplang`**: prefer `source` directly if present; otherwise use `team_id`, `channel_id`, and `doc_id`
`message_id = doc_id.replace('.', '')`
Format: `https://app.slack.com/client/{team_id}/{channel_id}/p{message_id}`
**Parameters:**
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | --------------------------------------------------------------------------------------------- |
| `namespace` | string | Yes | Namespace for URL-generation logic |
| `records` | array | Yes | Retrieved records; each item can be either metadata itself or an object with `metadata` field |
**Returns:** Per-record generated URL, generation method, and reason if unavailable.
### `count`
Returns the **unique document count** matching a metadata filter and semantic query. Use for questions like "how many papers by John Doe?" instead of the `query` tool. For performance, the count tool uses **semantic (dense) search only** (no hybrid or lexical) and requests only document identifiers (`document_number`, `url`, `doc_id`)—no chunk content—then deduplicates by document.
**Parameters:**
| Parameter | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `namespace` | string | Yes | Namespace to count in (use `list_namespaces` to discover) |
| `query_text` | string | Yes | Search query; use a broad term (e.g. `"paper"`, `"document"`) when counting by metadata only |
| `metadata_filter` | object | No | Same operators as `query` (e.g. `{"author": {"$in": ["John Doe"]}}` for wg21-papers) |
**Returns:** JSON with `count` (unique documents, up to 10,000), and `truncated: true` if there are at least 10,000 matches.
**Example response:**
```json
{
"status": "success",
"count": 42,
"truncated": false,
"namespace": "wg21-papers",
"metadata_filter": { "author": { "$in": ["John Doe"] } }
}
```
### `keyword_search`
Performs **keyword (lexical/sparse-only)** search over the sparse index (`{PINECONE_INDEX_NAME}-sparse` by default). 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.
**Parameters:**
| Parameter | Type | Required | Default | Description |
| ----------------- | -------- | -------- | ------- | ------------------------------------------------------- |
| `query_text` | string | Yes | - | Search query text (keyword/lexical match) |
| `namespace` | string | Yes | - | Namespace to search (use `list_namespaces` to discover) |
| `top_k` | integer | No | `10` | Number of results to return (1-100) |
| `metadata_filter` | object | No | - | Optional metadata filter (same operators as `query`) |
| `fields` | string[] | No | - | Optional field names to return; omit for all fields |
**Returns:** JSON with `status`, `query`, `namespace`, `index` (sparse index name), `result_count`, and `results` (ids, metadata, scores). Result rows match the `query` tool shape (e.g. `paper_number`, `title`, `author`, `url`, `content`, `score`, `reranked: false`).
**Example response:**
```json
{
"status": "success",
"query": "contracts C++",
"namespace": "wg21-papers",
"index": "your-index-sparse",
"result_count": 5,
"results": [
{
"paper_number": "P0548",
"title": "Contracts for C++",
"author": "John Doe",
"url": "https://...",
"content": "...",
"score": 0.85,
"reranked": false
}
]
}
```
### `query`
Performs hybrid semantic search over the specified namespace in the Pinecone index with optional metadata filtering. For **count** questions, use the `count` tool instead.
**Parameters:**
| Parameter | Type | Required | Default | Description |
| ----------------- | -------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query_text` | string | Yes | - | Search query text |
| `namespace` | string | Yes | - | Namespace to search (use `list_namespaces` to discover) |
| `top_k` | integer | No | `10` | Number of results (1-100) |
| `use_reranking` | boolean | No | `true` | Enable semantic reranking |
| `metadata_filter` | object | No | - | Metadata filter to narrow results (e.g., `{"author": "John", "year": 2023}`) |
| `fields` | string[] | No | - | Field names to return (e.g. `["document_number", "title", "url"]`). Omit for all fields; include `chunk_text` for content. Reduces payload and cost. |
**Returns:** JSON object with search results (only requested fields when `fields` is set), relevance scores, and metadata
**Example response:**
```json
{
"status": "success",
"query": "your search query",
"namespace": "namespace1",
"metadata_filter": { "author": "John Doe" },
"result_count": 10,
"results": [
{
"paper_number": "DOC-001",
"title": "Document Title",
"author": "John Doe",
"url": "https://example.com/doc",
"content": "Document content preview...",
"score": 0.9234,
"reranked": true
}
]
}
```
**Using Metadata Filters:**
Metadata filters allow you to narrow down search results based on document properties. First, use `list_namespaces` to see available metadata fields, then apply filters.
**Supported Operators (10 total):**
| Operator | Syntax | Description | Example |
| --------------------- | ----------------------- | ------------------------ | ------------------------------------------------------------------------ |
| Equal | `$eq` or value directly | Exact match | `{"status": "published"}` or `{"status": {"$eq": "published"}}` |
| Not Equal | `$ne` | Not equal to | `{"status": {"$ne": "draft"}}` |
| Greater Than | `$gt` | Greater than | `{"year": {"$gt": 2022}}` |
| Greater Than or Equal | `$gte` | Greater than or equal | `{"timestamp": {"$gte": 1704067200}}` |
| Less Than | `$lt` | Less than | `{"score": {"$lt": 0.5}}` |
| Less Than or Equal | `$lte` | Less than or equal | `{"priority": {"$lte": 3}}` |
| In Array | `$in` | Value is in array field | `{"tags": {"$in": ["cpp", "contracts"]}}` (only for array-type fields) |
| Not In Array | `$nin` | Value not in array field | `{"tags": {"$nin": ["draft", "archived"]}}` (only for array-type fields) |
**Filter Examples:**
```json
// Exact match (implicit $eq) - works for single-value string fields
{"status": "published"}
// Exact string match - NOTE: requires full exact match
{"author": "John Doe"} // Only matches if author field is exactly "John Doe"
// Array field contains value (use $in only for array-type fields)
{"tags": {"$in": ["cpp", "contracts"]}} // Only if tags is stored as an array
// Numeric comparison
{"year": {"$gte": 2023}}
// Timestamp range (papers from last 2 years)
{"timestamp": {"$gte": 1704067200}}
// Multiple conditions on same field
{"score": {"$gt": 0.8, "$lt": 1.0}}
{"timestamp": {"$gte": 1704067200, "$lte": 1735689600}}
// Multiple fields (AND logic)
{
"year": {"$gte": 2023},
"status": "published",
"timestamp": {"$gte": 1704067200}
}
// Array field not in list (only for array-type fields)
{"tags": {"$nin": ["draft", "template"]}}
```
**Important Limitations:**
- **String fields require EXACT match** - No wildcards, partial matches, or substring searches
- **Comma-separated strings**: If a field contains `"John Doe, Herb Sutter"`, you cannot filter for just `"John Doe"`
- You must match the entire string: `{"author": "John Doe, Herb Sutter"}`
- To filter by individual authors, the data must be stored as an array field
- **`$in` and `$nin` operators**: Only work on array-type fields, not comma-separated strings
- **Unsupported operators are rejected**: Unknown operators (for example `$regex`) return a validation error
- **`$in` and `$nin` must use arrays**: `{"tags": {"$in": "cpp"}}` is invalid; use `{"tags": {"$in": ["cpp"]}}`
- Multiple conditions at the top level are combined with **AND** logic
- Use comparison operators (`$gt`, `$gte`, `$lt`, `$lte`) for numeric and timestamp fields
- Direct value assignment implies `$eq` (exact match)
## How It Works
1. **Namespace Discovery**: The `list_namespaces` tool queries your Pinecone index stats to discover available namespaces
2. **Hybrid Search**: When querying, the tool searches both dense and sparse indexes in parallel
3. **Result Merging**: Results from both indexes are merged and deduplicated
4. **Reranking** (optional): The merged results are reranked using a semantic reranker for improved relevance
## Development
### Setup Development Environment
```bash
git clone https://github.com/cppalliance/pinecone-read-only-mcp-typescript.git
cd pinecone-read-only-mcp-typescript
npm install
```
### Build
```bash
npm run build
```
### Run Tests
```bash
npm test
```
### Benchmarks
Measure server-side processing overhead with mocked Pinecone responses (no live API calls, no API key required):
```bash
npm run benchmark
```
The script prints a table of p50, p95, and p99 latencies in milliseconds and writes results to [`benchmarks/baseline.json`](benchmarks/baseline.json). Compare a new run to the committed baseline (for example with `git diff benchmarks/baseline.json` after re-running the command) to spot regressions.
### Testing the keyword_search tool
1. **Connectivity and keyword search (script):**
Run the search test script (includes a keyword search step against the sparse index):
```bash
PINECONE_API_KEY=your-key npm run test:search
```
If the sparse index (`{PINECONE_INDEX_NAME}-sparse`) does not exist or has no data, the keyword search step is skipped with a warning.
2. **Via MCP client:**
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`).
### Code Quality
```bash
# Run linting
npm run lint
# Fix linting issues
npm run lint:fix
# Check formatting
npm run format:check
# Format code
npm run format
# Type check
npm run typecheck
```
### Development Server
Run the server in development mode with auto-reload:
```bash
npm run dev -- --api-key YOUR_API_KEY
```
### Contribution Guidelines
See [CONTRIBUTING.md](CONTRIBUTING.md) for prerequisites, dev setup, architecture overview, PR conventions, and code style. Run `npm run ci` before opening a pull request.
## Dependencies
### Production Dependencies
- [@modelcontextprotocol/sdk](https://www.npmjs.com/package/@modelcontextprotocol/sdk) - MCP SDK for TypeScript
- [@pinecone-database/pinecone](https://www.npmjs.com/package/@pinecone-database/pinecone) - Pinecone client SDK
- [zod](https://www.npmjs.com/package/zod) - TypeScript-first schema validation
- [dotenv](https://www.npmjs.com/package/dotenv) - Environment variable management
### Development Dependencies
- [TypeScript](https://www.typescriptlang.org/) - Type-safe JavaScript
- [ESLint](https://eslint.org/) - Code linting
- [Prettier](https://prettier.io/) - Code formatting
- [Vitest](https://vitest.dev/) - Testing framework
## Comparison with Python Version
This TypeScript implementation grew out of the [Python version](https://github.com/cppalliance/pinecone-read-only-mcp) and now exposes a strict superset of its tool surface, including:
- `guided_query` (single-call orchestrator with decision trace)
- `query_documents` (full-document reassembly from chunks)
- `keyword_search` (sparse-index-only retrieval)
- `namespace_router` and `suggest_query_params` (flow guidance)
- `count` and `generate_urls`
Other benefits:
- Native Node.js integration
- Better npm ecosystem integration
- TypeScript type safety
- Similar performance characteristics
## Troubleshooting
### API Key Issues
If you see "Missing Pinecone API key" at startup:
1. Ensure `PINECONE_API_KEY` environment variable is set, OR
2. Pass `--api-key` option when running the server
### Missing Index Name
If you see "Missing Pinecone index name" at startup:
1. Set `PINECONE_INDEX_NAME` in your MCP config or `.env`, OR
2. Pass `--index-name` when running the server
3. Alliance deployers: see [examples/alliance/.env.example](examples/alliance/.env.example)
### Index Not Found
If you see index-related errors:
1. Verify your index name is correct
2. Ensure your API key has access to the index
3. Check that both `your-index-name` and `your-index-name-sparse` indexes exist
### Connection Issues
If you experience connection issues:
1. Check your internet connection
2. Verify Pinecone service status
3. Ensure firewall/proxy settings allow connections to Pinecone
## License
This project is licensed under the Boost Software License 1.0 - see the [LICENSE](LICENSE) file for details.
## Authors
- **Will Pak** - [cppalliance.org](https://cppalliance.org/)
## Acknowledgements
This project uses:
- [Pinecone](https://www.pinecone.io/) for vector storage and retrieval
- [Model Context Protocol](https://modelcontextprotocol.io/) for standardized AI integration
- Hybrid search approach combining dense embeddings with sparse BM25-style retrieval
## Related Projects
- [Python version](https://github.com/cppalliance/pinecone-read-only-mcp) - Original Python implementation
- [Pinecone MCP](https://github.com/pinecone-io/pinecone-mcp) - Full-featured Pinecone MCP with write capabilities
## Support
For issues and questions:
- GitHub Issues: [https://github.com/cppalliance/pinecone-read-only-mcp-typescript/issues](https://github.com/cppalliance/pinecone-read-only-mcp-typescript/issues)
- Email: will@cppalliance.org
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for a list of changes in each version.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
markitdown
Python tool for converting files and office documents to Markdown.
Filesystem
Node.js MCP Server for filesystem operations with dynamic access control.
TrendRadar
TrendRadar: Your hotspot assistant for real news in just 30 seconds.
mempalace
The highest-scoring AI memory system ever benchmarked. And it's free.
mempalace
The highest-scoring AI memory system ever benchmarked. And it's free.