@will-cppa/pinecone-read-only-mcp
v0.1.6
Published
A Model Context Protocol (MCP) server that provides semantic search over Pinecone vector databases using hybrid search (dense + sparse) with reranking.
Maintainers
Readme
Pinecone Read-Only MCP (TypeScript)
A Model Context Protocol (MCP) server that provides semantic search over Pinecone vector databases using hybrid search (dense + sparse) with reranking.
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
- 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
Installation
As a Package
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-mcpGlobal Installation
npm install -g @will-cppa/pinecone-read-only-mcpFrom Source
git clone https://github.com/CppDigest/pinecone-read-only-mcp-typescript.git
cd pinecone-read-only-mcp-typescript
npm install
npm run buildConfiguration
The server requires a Pinecone API key and supports the following configuration options:
Environment Variables
| 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 |
Claude Desktop Configuration
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"]
}
}
}Usage
Command Line
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_KEYAvailable Options
--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 messageDeployment
Production Readiness Defaults
- 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.
Deploy with npm Package
# install
npm i @will-cppa/pinecone-read-only-mcp
# run
npx @will-cppa/pinecone-read-only-mcp --api-key YOUR_API_KEYDeploy with Docker
# 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:latestRelease Gate (recommended)
Before tagging/releasing:
npm run release:checkThis 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:
{
"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"
}
}
]
}suggest_query_params
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.
guided_query
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.
generate_urls
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_idmessage_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.
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:
{
"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 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
}
]
}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:
{
"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)
How It Works
- 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
Development
Setup Development Environment
git clone https://github.com/CppDigest/pinecone-read-only-mcp-typescript.git
cd pinecone-read-only-mcp-typescript
npm installBuild
npm run buildRun Tests
npm testTesting the keyword_search tool
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:searchIf 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).
Code Quality
# 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 typecheckDevelopment Server
Run the server in development mode with auto-reload:
npm run dev -- --api-key YOUR_API_KEYContribution Guidelines
- 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
Dependencies
Production Dependencies
- @modelcontextprotocol/sdk - MCP SDK for TypeScript
- @pinecone-database/pinecone - Pinecone client SDK
- zod - TypeScript-first schema validation
- dotenv - Environment variable management
Development Dependencies
- TypeScript - Type-safe JavaScript
- ESLint - Code linting
- Prettier - Code formatting
- Vitest - Testing framework
Comparison with Python Version
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
Troubleshooting
API Key Issues
If you see "Pinecone API key is required" error:
- Ensure
PINECONE_API_KEYenvironment variable is set, OR - Pass
--api-keyoption when running the server
Index Not Found
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
Connection Issues
If you experience connection issues:
- Check your internet connection
- Verify Pinecone service status
- Ensure firewall/proxy settings allow connections to Pinecone
License
This project is licensed under the Boost Software License 1.0 - see the LICENSE file for details.
Authors
- Will Pak - cppalliance.org
Acknowledgements
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
Related Projects
- Python version - Original Python implementation
- Pinecone MCP - Full-featured Pinecone MCP with write capabilities
Support
For issues and questions:
- GitHub Issues: https://github.com/CppDigest/pinecone-read-only-mcp-typescript/issues
- Email: [email protected]
Changelog
See CHANGELOG.md for a list of changes in each version.
