A Model Context Protocol (MCP) server that provides semantic search over Pinecone vector databases using hybrid search (dense + sparse) with reranking.
- 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
- Metadata Filtering: Supports optional metadata filters for refined searches
- Fast & Optimized: Lazy initialization, connection pooling, and efficient result merging
- Production Ready: Input validation, error handling, and configurable logging
- TypeScript Support: Full TypeScript support with type definitions
npm install @will-cppa/pinecone-read-only-mcpOr using yarn:
yarn add @will-cppa/pinecone-read-only-mcpOr using pnpm:
pnpm add @will-cppa/pinecone-read-only-mcpnpm install -g @will-cppa/pinecone-read-only-mcpgit clone https://github.com/CppDigest/pinecone-read-only-mcp-typescript.git
cd pinecone-read-only-mcp-typescript
npm install
npm run buildThe server requires a Pinecone API key and supports the following configuration options:
| Variable | Required | Default | Description |
|---|---|---|---|
PINECONE_API_KEY |
Yes | - | Your Pinecone API key |
PINECONE_INDEX_NAME |
No | rag-hybrid |
Pinecone index name (dense + sparse for hybrid) |
PINECONE_RERANK_MODEL |
No | bge-reranker-v2-m3 |
Reranking model |
PINECONE_READ_ONLY_MCP_LOG_LEVEL |
No | INFO |
Logging level |
Add to your claude_desktop_config.json:
{
"mcpServers": {
"pinecone-search": {
"command": "npx",
"args": ["-y", "@will-cppa/pinecone-read-only-mcp"],
"env": {
"PINECONE_API_KEY": "your-api-key-here"
}
}
}
}Or with explicit options:
{
"mcpServers": {
"pinecone-search": {
"command": "npx",
"args": [
"-y",
"@will-cppa/pinecone-read-only-mcp",
"--api-key",
"your-api-key-here",
"--index-name",
"your-index-name",
"--rerank-model",
"bge-reranker-v2-m3"
]
}
}
}For a global installation:
{
"mcpServers": {
"pinecone-search": {
"command": "pinecone-read-only-mcp",
"args": ["--api-key", "your-api-key-here"]
}
}
}Run the server using npx (no installation required):
npx @will-cppa/pinecone-read-only-mcp --api-key YOUR_API_KEYOr if installed globally:
pinecone-read-only-mcp --api-key YOUR_API_KEYOr if installed locally in your project:
node node_modules/@will-cppa/pinecone-read-only-mcp/dist/index.js --api-key YOUR_API_KEY--api-key TEXT Pinecone API key (or set PINECONE_API_KEY env var)
--index-name TEXT Pinecone index name [default: rag-hybrid]
--rerank-model TEXT Reranking model [default: bge-reranker-v2-m3]
--log-level TEXT Logging level [default: INFO]
--help, -h Show help message
- Build now fails fast on TypeScript errors (
npm run buildno longer suppresses failures). - CI validates typecheck, lint, format, build, smoke run, tests, and package dry-run.
list_namespacesdata is cached in-memory for 30 minutes to reduce repeated Pinecone calls.- Query/count flow has guardrails (
suggest_query_paramsbefore execution) to prevent wasteful calls.
# install
npm i @will-cppa/pinecone-read-only-mcp
# run
npx @will-cppa/pinecone-read-only-mcp --api-key YOUR_API_KEY# 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=rag-hybrid \
pinecone-read-only-mcp:latestBefore tagging/releasing:
npm run release:checkThis runs full CI-equivalent checks and validates publish contents with npm pack --dry-run.
The server exposes the following tools via MCP:
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:
{
"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"
}
}
]
}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.
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:
{
"status": "success",
"suggested_fields": ["document_number", "title", "url", "author"],
"use_count_tool": false,
"recommended_tool": "query_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.
Single orchestrator tool that runs the full flow in one call:
- namespace routing (if namespace is omitted),
- query param suggestion,
- execution via
count,query_fast, orquery_detailed.
It returns both the final result and a 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, query_fast, query_detailed |
enrich_urls |
boolean | No | true |
Auto-generate URLs for mailing and slack-Cpplang when metadata.url is missing |
Returns: JSON containing decision_trace and result.
Generates URLs for retrieved records when metadata does not contain url and URL is required.
Supported namespaces:
mailingslack-Cpplang
Rules:
mailing: usesdoc_idorthread_id
Format:https://lists.boost.org/archives/list/{doc_id_or_thread_id}/slack-Cpplang: prefersourcedirectly if present; otherwise useteam_id,channel_id, anddoc_id
message_id = doc_id.replace('.', '')
Format:https://app.slack.com/{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.
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:
{
"status": "success",
"count": 42,
"truncated": false,
"namespace": "wg21-papers",
"metadata_filter": { "author": { "$in": ["John Doe"] } }
}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.
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:
{
"status": "success",
"query": "contracts C++",
"namespace": "wg21-papers",
"index": "rag-hybrid-sparse",
"result_count": 5,
"results": [
{
"paper_number": "P0548",
"title": "Contracts for C++",
"author": "John Doe",
"url": "https://...",
"content": "...",
"score": 0.85,
"reranked": false
}
]
}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:
{
"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:
// 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
- You must match the entire string:
$inand$ninoperators: Only work on array-type fields, not comma-separated strings- Unsupported operators are rejected: Unknown operators (for example
$regex) return a validation error $inand$ninmust 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)
- Namespace Discovery: The
list_namespacestool queries your Pinecone index stats to discover available namespaces - Hybrid Search: When querying, the tool searches both dense and sparse indexes in parallel
- Result Merging: Results from both indexes are merged and deduplicated
- Reranking (optional): The merged results are reranked using a semantic reranker for improved relevance
git clone https://github.com/CppDigest/pinecone-read-only-mcp-typescript.git
cd pinecone-read-only-mcp-typescript
npm installnpm run buildnpm test-
Connectivity and keyword search (script):
Run the search test script (includes a keyword search step against the sparse index):PINECONE_API_KEY=your-key npm run test:search
If the sparse index (
rag-hybrid-sparseby default) does not exist or has no data, the keyword search step is skipped with a warning. -
Via MCP client:
Start the server and call thekeyword_searchtool withquery_text,namespace(fromlist_namespaces), and optionaltop_kormetadata_filter. Response shape is the same as thequerytool (e.g.resultswith ids, metadata, scores;rerankedis alwaysfalse).
# 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 typecheckRun the server in development mode with auto-reload:
npm run dev -- --api-key YOUR_API_KEY- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Make your changes and add tests
- Ensure all tests pass:
npm test - Ensure code quality checks pass:
npm run lint && npm run format:check && npm run typecheck - Commit your changes:
git commit -am 'Add some feature' - Push to the branch:
git push origin feature-name - Submit a pull request
- @modelcontextprotocol/sdk - MCP SDK for TypeScript
- @pinecone-database/pinecone - Pinecone client SDK
- zod - TypeScript-first schema validation
- dotenv - Environment variable management
- TypeScript - Type-safe JavaScript
- ESLint - Code linting
- Prettier - Code formatting
- Vitest - Testing framework
This TypeScript implementation provides the same functionality as the Python version with the following benefits:
- Native Node.js integration
- Better npm ecosystem integration
- TypeScript type safety
- Similar performance characteristics
- Same API interface
If you see "Pinecone API key is required" error:
- Ensure
PINECONE_API_KEYenvironment variable is set, OR - Pass
--api-keyoption when running the server
If you see index-related errors:
- Verify your index name is correct
- Ensure your API key has access to the index
- Check that both
your-index-nameandyour-index-name-sparseindexes exist
If you experience connection issues:
- Check your internet connection
- Verify Pinecone service status
- Ensure firewall/proxy settings allow connections to Pinecone
This project is licensed under the Boost Software License 1.0 - see the LICENSE file for details.
- Will Pak - cppalliance.org
This project uses:
- Pinecone for vector storage and retrieval
- Model Context Protocol for standardized AI integration
- Hybrid search approach combining dense embeddings with sparse BM25-style retrieval
- Python version - Original Python implementation
- Pinecone MCP - Full-featured Pinecone MCP with write capabilities
For issues and questions:
- GitHub Issues: https://github.com/CppDigest/pinecone-read-only-mcp-typescript/issues
- Email: will@cppalliance.org
See CHANGELOG.md for a list of changes in each version.