diff --git a/servers/pinecone/Dockerfile b/servers/pinecone/Dockerfile new file mode 100644 index 0000000..478aaf2 --- /dev/null +++ b/servers/pinecone/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Copy requirements and install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy server code +COPY server.py . + +# Expose port +EXPOSE 8000 + +# Run the FastMCP server +CMD ["python", "-m", "fastmcp", "run", "server.py", "--transport", "streamable-http", "--port", "8000"] diff --git a/servers/pinecone/README.md b/servers/pinecone/README.md new file mode 100644 index 0000000..19c5e4c --- /dev/null +++ b/servers/pinecone/README.md @@ -0,0 +1,443 @@ +# Pinecone MCP Server + +MCP server for Pinecone vector database. Store and search embeddings for similarity search, semantic search, and RAG (Retrieval Augmented Generation) applications. + +## Features + +- **Index Management**: Create, list, describe, and delete vector indexes +- **Vector Operations**: Upsert, query, fetch, update, and delete vectors +- **Similarity Search**: Find similar vectors with cosine, euclidean, or dot product metrics +- **Metadata Filtering**: Hybrid search with metadata filters +- **Namespaces**: Data isolation for multi-tenancy +- **Collections**: Create backups from indexes +- **Statistics**: Get vector counts and index stats + +## Setup + +### Prerequisites + +- Pinecone account +- API key and environment name + +### Environment Variables + +- `PINECONE_API_KEY` (required): Your Pinecone API key +- `PINECONE_ENVIRONMENT` (required): Your Pinecone environment + +**How to get credentials:** +1. Go to [app.pinecone.io](https://app.pinecone.io) +2. Sign up or log in +3. Navigate to API Keys section +4. Copy your API key +5. Note your environment (e.g., `us-west1-gcp`, `us-east-1-aws`) +6. Store as `PINECONE_API_KEY` and `PINECONE_ENVIRONMENT` + +## Index Types + +### Serverless (Recommended) +- Pay per usage +- Auto-scaling +- No infrastructure management +- Available regions: AWS (us-east-1, us-west-2), GCP (us-central1, us-west1), Azure (eastus) + +### Pod-based +- Fixed capacity +- Dedicated resources +- More control over performance +- Higher cost + +## Vector Dimensions + +Match your embedding model: +- **OpenAI text-embedding-ada-002**: 1536 dimensions +- **OpenAI text-embedding-3-small**: 1536 dimensions +- **OpenAI text-embedding-3-large**: 3072 dimensions +- **sentence-transformers/all-MiniLM-L6-v2**: 384 dimensions +- **sentence-transformers/all-mpnet-base-v2**: 768 dimensions + +## Distance Metrics + +- **cosine** - Cosine similarity (recommended for most use cases) +- **euclidean** - Euclidean distance +- **dotproduct** - Dot product similarity + +## Available Tools + +### Index Management + +#### `list_indexes` +List all indexes in the project. + +**Example:** +```python +indexes = await list_indexes() +``` + +#### `create_index` +Create a new vector index. + +**Parameters:** +- `name` (string, required): Index name +- `dimension` (int, required): Vector dimension +- `metric` (string, optional): Distance metric (default: 'cosine') +- `spec_type` (string, optional): 'serverless' or 'pod' (default: 'serverless') +- `cloud` (string, optional): 'aws', 'gcp', or 'azure' (default: 'aws') +- `region` (string, optional): Region (default: 'us-east-1') + +**Example:** +```python +index = await create_index( + name="my-index", + dimension=1536, # OpenAI embeddings + metric="cosine", + spec_type="serverless", + cloud="aws", + region="us-east-1" +) +``` + +#### `describe_index` +Get index configuration and status. + +**Example:** +```python +info = await describe_index(index_name="my-index") +``` + +#### `delete_index` +Delete an index. + +**Example:** +```python +result = await delete_index(index_name="my-index") +``` + +### Vector Operations + +#### `upsert_vectors` +Insert or update vectors with metadata. + +**Parameters:** +- `index_name` (string, required): Index name +- `vectors` (list, required): List of vector objects +- `namespace` (string, optional): Namespace (default: "") + +**Vector format:** +```python +{ + "id": "vec1", + "values": [0.1, 0.2, 0.3, ...], # Must match index dimension + "metadata": {"key": "value"} # Optional +} +``` + +**Example:** +```python +result = await upsert_vectors( + index_name="my-index", + vectors=[ + { + "id": "doc1", + "values": [0.1, 0.2, ...], # 1536 dimensions + "metadata": { + "title": "Document 1", + "category": "tech", + "year": 2024 + } + }, + { + "id": "doc2", + "values": [0.3, 0.4, ...], + "metadata": { + "title": "Document 2", + "category": "science" + } + } + ], + namespace="production" +) +``` + +#### `query_vectors` +Query similar vectors. + +**Parameters:** +- `index_name` (string, required): Index name +- `vector` (list, optional): Query vector (use this OR id) +- `id` (string, optional): Vector ID to use as query (use this OR vector) +- `top_k` (int, optional): Number of results (default: 10) +- `namespace` (string, optional): Namespace (default: "") +- `include_values` (bool, optional): Include vectors (default: False) +- `include_metadata` (bool, optional): Include metadata (default: True) +- `filter` (dict, optional): Metadata filter + +**Example:** +```python +# Query by vector +results = await query_vectors( + index_name="my-index", + vector=[0.1, 0.2, ...], # Your query embedding + top_k=5, + filter={"category": {"$eq": "tech"}, "year": {"$gte": 2023}}, + include_metadata=True +) + +# Query by existing vector ID +results = await query_vectors( + index_name="my-index", + id="doc1", + top_k=5 +) +``` + +**Response:** +```json +{ + "matches": [ + { + "id": "doc1", + "score": 0.95, + "metadata": {"title": "Document 1", "category": "tech"} + } + ] +} +``` + +#### `fetch_vectors` +Fetch vectors by IDs. + +**Example:** +```python +vectors = await fetch_vectors( + index_name="my-index", + ids=["doc1", "doc2", "doc3"], + namespace="production" +) +``` + +#### `update_vector` +Update vector values or metadata. + +**Example:** +```python +# Update values +result = await update_vector( + index_name="my-index", + id="doc1", + values=[0.5, 0.6, ...] +) + +# Update metadata +result = await update_vector( + index_name="my-index", + id="doc1", + set_metadata={"category": "updated", "year": 2025} +) +``` + +#### `delete_vectors` +Delete vectors. + +**Example:** +```python +# Delete by IDs +result = await delete_vectors( + index_name="my-index", + ids=["doc1", "doc2"] +) + +# Delete by filter +result = await delete_vectors( + index_name="my-index", + filter={"year": {"$lt": 2020}} +) + +# Delete all in namespace +result = await delete_vectors( + index_name="my-index", + delete_all=True, + namespace="test" +) +``` + +### Statistics & Utility + +#### `describe_index_stats` +Get index statistics. + +**Example:** +```python +stats = await describe_index_stats(index_name="my-index") +# Returns: dimension, totalVectorCount, indexFullness, namespaces +``` + +#### `list_vector_ids` +List all vector IDs. + +**Example:** +```python +ids = await list_vector_ids( + index_name="my-index", + namespace="production", + prefix="doc", + limit=100 +) +``` + +#### `create_collection` +Create a collection (backup) from an index. + +**Example:** +```python +collection = await create_collection( + name="my-backup", + source_index="my-index" +) +``` + +## Namespaces + +Namespaces provide data isolation within an index: + +```python +# Production data +await upsert_vectors(index_name="my-index", vectors=[...], namespace="prod") + +# Test data +await upsert_vectors(index_name="my-index", vectors=[...], namespace="test") + +# Query only production +results = await query_vectors(index_name="my-index", vector=[...], namespace="prod") +``` + +## Metadata Filtering + +Filter vectors during queries using metadata: + +**Operators:** +- `$eq` - Equal +- `$ne` - Not equal +- `$gt` - Greater than +- `$gte` - Greater than or equal +- `$lt` - Less than +- `$lte` - Less than or equal +- `$in` - In array +- `$nin` - Not in array + +**Examples:** +```python +# Simple filter +filter={"category": {"$eq": "tech"}} + +# Range filter +filter={"year": {"$gte": 2020, "$lte": 2024}} + +# Multiple conditions +filter={ + "$and": [ + {"category": {"$eq": "tech"}}, + {"year": {"$gte": 2020}} + ] +} + +# OR condition +filter={ + "$or": [ + {"category": {"$eq": "tech"}}, + {"category": {"$eq": "science"}} + ] +} + +# In array +filter={"category": {"$in": ["tech", "science", "engineering"]}} +``` + +## RAG Example with OpenAI + +```python +import openai + +# 1. Generate embedding +response = openai.Embedding.create( + input="What is machine learning?", + model="text-embedding-ada-002" +) +query_embedding = response['data'][0]['embedding'] + +# 2. Query Pinecone +results = await query_vectors( + index_name="knowledge-base", + vector=query_embedding, + top_k=3, + include_metadata=True +) + +# 3. Get context from results +context = "\n".join([match['metadata']['text'] for match in results['matches']]) + +# 4. Generate answer with context +answer = openai.ChatCompletion.create( + model="gpt-4", + messages=[ + {"role": "system", "content": f"Answer based on this context:\n{context}"}, + {"role": "user", "content": "What is machine learning?"} + ] +) +``` + +## Rate Limits + +### Free Tier (Starter) +- **100,000 operations/month** +- 1 pod/index +- 100 indexes max + +### Paid Tiers +- **Standard**: $70/month, unlimited operations +- **Enterprise**: Custom pricing, dedicated support + +## Best Practices + +1. **Match dimensions**: Ensure vector dimensions match index +2. **Use namespaces**: Separate prod/test/dev data +3. **Add metadata**: Enable hybrid search and filtering +4. **Batch upserts**: Insert multiple vectors per request +5. **Use serverless**: For most applications (cost-effective) +6. **Monitor usage**: Track vector count and operations +7. **Create backups**: Use collections for important data +8. **Optimize queries**: Use appropriate top_k values + +## Common Use Cases + +- **Semantic Search**: Find similar documents or products +- **RAG**: Retrieval for LLM context +- **Recommendation Systems**: Similar item recommendations +- **Duplicate Detection**: Find near-duplicate content +- **Anomaly Detection**: Identify outliers +- **Image Search**: Visual similarity search +- **Chatbot Memory**: Store conversation context + +## Error Handling + +Common errors: + +- **401 Unauthorized**: Invalid API key +- **404 Not Found**: Index or vector not found +- **400 Bad Request**: Invalid dimensions or parameters +- **429 Too Many Requests**: Rate limit exceeded +- **503 Service Unavailable**: Pinecone service issue + +## API Documentation + +- [Pinecone Documentation](https://docs.pinecone.io/) +- [API Reference](https://docs.pinecone.io/reference/api/introduction) +- [Python SDK](https://docs.pinecone.io/docs/python-client) +- [Serverless Indexes](https://docs.pinecone.io/docs/serverless-indexes) +- [Metadata Filtering](https://docs.pinecone.io/docs/metadata-filtering) + +## Support + +- [Pinecone Community](https://community.pinecone.io/) +- [Discord](https://discord.gg/pinecone) +- [Support](https://support.pinecone.io/) +- [Status Page](https://status.pinecone.io/) diff --git a/servers/pinecone/requirements.txt b/servers/pinecone/requirements.txt new file mode 100644 index 0000000..42f0167 --- /dev/null +++ b/servers/pinecone/requirements.txt @@ -0,0 +1,4 @@ +fastmcp>=0.2.0 +httpx>=0.27.0 +python-dotenv>=1.0.0 +uvicorn>=0.30.0 diff --git a/servers/pinecone/server.json b/servers/pinecone/server.json new file mode 100644 index 0000000..e96de42 --- /dev/null +++ b/servers/pinecone/server.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://registry.nimbletools.ai/schemas/2025-09-22/nimbletools-server.schema.json", + "name": "ai.nimbletools/pinecone", + "version": "1.0.0", + "description": "Pinecone API: vector database for embeddings, similarity search, and RAG applications", + "status": "active", + "repository": { + "url": "https://github.com/NimbleBrainInc/mcp-pinecone", + "source": "github", + "branch": "main" + }, + "websiteUrl": "https://www.pinecone.io/", + "packages": [ + { + "registryType": "oci", + "registryBaseUrl": "https://docker.io", + "identifier": "nimbletools/mcp-pinecone", + "version": "1.0.0", + "transport": { + "type": "streamable-http", + "url": "https://mcp.nimbletools.ai/mcp" + }, + "environmentVariables": [ + { + "name": "PINECONE_API_KEY", + "description": "Pinecone API key (get from https://app.pinecone.io)", + "isRequired": true, + "isSecret": true, + "example": "your_pinecone_api_key" + }, + { + "name": "PINECONE_ENVIRONMENT", + "description": "Pinecone environment (e.g., us-west1-gcp, us-east-1-aws)", + "isRequired": true, + "isSecret": false, + "example": "us-west1-gcp" + } + ] + } + ], + "_meta": { + "ai.nimbletools.mcp/v1": { + "container": { + "healthCheck": { + "path": "/health", + "port": 8000 + } + }, + "capabilities": { + "tools": true, + "resources": false, + "prompts": false + }, + "resources": { + "limits": { + "memory": "256Mi", + "cpu": "250m" + }, + "requests": { + "memory": "128Mi", + "cpu": "100m" + } + }, + "deployment": { + "protocol": "http", + "port": 8000, + "mcpPath": "/mcp" + }, + "display": { + "name": "Pinecone", + "category": "infrastructure-data", + "tags": [ + "pinecone", + "vector-database", + "embeddings", + "similarity-search", + "rag", + "semantic-search", + "ai", + "machine-learning", + "requires-api-key" + ], + "branding": { + "logoUrl": "https://static.nimbletools.ai/logos/pinecone.png", + "iconUrl": "https://static.nimbletools.ai/icons/pinecone.png" + }, + "documentation": { + "readmeUrl": "https://raw.githubusercontent.com/NimbleBrainInc/mcp-pinecone/main/README.md" + } + } + } + } +} diff --git a/servers/pinecone/server.py b/servers/pinecone/server.py new file mode 100644 index 0000000..1d7c2b2 --- /dev/null +++ b/servers/pinecone/server.py @@ -0,0 +1,454 @@ +""" +Pinecone MCP Server +Provides tools for managing vector databases with Pinecone. +""" + +import os +from typing import Optional, List, Dict, Any +import httpx +from fastmcp import FastMCP + +# Initialize FastMCP server +mcp = FastMCP("Pinecone MCP Server") + +# Get API credentials from environment +PINECONE_API_KEY = os.getenv("PINECONE_API_KEY") +PINECONE_ENVIRONMENT = os.getenv("PINECONE_ENVIRONMENT") +CONTROL_PLANE_URL = "https://api.pinecone.io" + + +def get_headers() -> dict: + """Get headers for Pinecone API requests.""" + if not PINECONE_API_KEY: + raise ValueError("PINECONE_API_KEY environment variable is required") + return { + "Api-Key": PINECONE_API_KEY, + "Content-Type": "application/json", + } + + +def get_index_host(index_name: str) -> str: + """Construct index host URL.""" + if not PINECONE_ENVIRONMENT: + raise ValueError("PINECONE_ENVIRONMENT environment variable is required") + return f"https://{index_name}-{PINECONE_ENVIRONMENT}.svc.pinecone.io" + + +@mcp.tool() +async def list_indexes() -> dict: + """ + List all indexes in the project. + + Returns: + Dictionary with list of indexes and their configurations + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + f"{CONTROL_PLANE_URL}/indexes", + headers=get_headers(), + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def create_index( + name: str, + dimension: int, + metric: str = "cosine", + spec_type: str = "serverless", + cloud: str = "aws", + region: str = "us-east-1" +) -> dict: + """ + Create a new vector index. + + Args: + name: Index name + dimension: Vector dimension (e.g., 1536 for OpenAI, 768 for sentence-transformers) + metric: Distance metric ('cosine', 'euclidean', 'dotproduct', default: 'cosine') + spec_type: Index type ('serverless' or 'pod', default: 'serverless') + cloud: Cloud provider ('aws', 'gcp', 'azure', default: 'aws') + region: Region (e.g., 'us-east-1', 'us-west-2', default: 'us-east-1') + + Returns: + Dictionary with index creation status + """ + async with httpx.AsyncClient(timeout=60.0) as client: + payload = { + "name": name, + "dimension": dimension, + "metric": metric, + "spec": { + spec_type: { + "cloud": cloud, + "region": region + } + } + } + + response = await client.post( + f"{CONTROL_PLANE_URL}/indexes", + headers=get_headers(), + json=payload, + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def describe_index(index_name: str) -> dict: + """ + Get index configuration and stats. + + Args: + index_name: Name of the index + + Returns: + Dictionary with index details including dimension, metric, and status + """ + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.get( + f"{CONTROL_PLANE_URL}/indexes/{index_name}", + headers=get_headers(), + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def delete_index(index_name: str) -> dict: + """ + Delete an index. + + Args: + index_name: Name of the index to delete + + Returns: + Dictionary with deletion status + """ + async with httpx.AsyncClient(timeout=60.0) as client: + response = await client.delete( + f"{CONTROL_PLANE_URL}/indexes/{index_name}", + headers=get_headers(), + ) + response.raise_for_status() + return {"status": "deleted", "index": index_name} + + +@mcp.tool() +async def upsert_vectors( + index_name: str, + vectors: List[Dict[str, Any]], + namespace: str = "" +) -> dict: + """ + Insert or update vectors with metadata. + + Args: + index_name: Name of the index + vectors: List of vector objects with 'id', 'values', and optional 'metadata' + namespace: Namespace for data isolation (default: "" for default namespace) + + Returns: + Dictionary with upsert count + + Example vector: {"id": "vec1", "values": [0.1, 0.2, ...], "metadata": {"key": "value"}} + """ + async with httpx.AsyncClient(timeout=60.0) as client: + index_host = get_index_host(index_name) + payload = { + "vectors": vectors, + "namespace": namespace + } + + response = await client.post( + f"{index_host}/vectors/upsert", + headers=get_headers(), + json=payload, + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def query_vectors( + index_name: str, + vector: Optional[List[float]] = None, + id: Optional[str] = None, + top_k: int = 10, + namespace: str = "", + include_values: bool = False, + include_metadata: bool = True, + filter: Optional[Dict[str, Any]] = None +) -> dict: + """ + Query similar vectors by vector or ID. + + Args: + index_name: Name of the index + vector: Query vector values (use this OR id, not both) + id: ID of vector to use as query (use this OR vector, not both) + top_k: Number of results to return (default: 10) + namespace: Namespace to query (default: "") + include_values: Include vector values in response (default: False) + include_metadata: Include metadata in response (default: True) + filter: Metadata filter for hybrid search (optional) + + Returns: + Dictionary with matches including ids, scores, and optional metadata + + Filter example: {"genre": {"$eq": "drama"}, "year": {"$gte": 2020}} + """ + async with httpx.AsyncClient(timeout=60.0) as client: + index_host = get_index_host(index_name) + payload = { + "topK": top_k, + "namespace": namespace, + "includeValues": include_values, + "includeMetadata": include_metadata + } + + if vector: + payload["vector"] = vector + elif id: + payload["id"] = id + else: + raise ValueError("Must provide either 'vector' or 'id' parameter") + + if filter: + payload["filter"] = filter + + response = await client.post( + f"{index_host}/query", + headers=get_headers(), + json=payload, + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def fetch_vectors( + index_name: str, + ids: List[str], + namespace: str = "" +) -> dict: + """ + Fetch vectors by IDs. + + Args: + index_name: Name of the index + ids: List of vector IDs to fetch + namespace: Namespace (default: "") + + Returns: + Dictionary with vectors including values and metadata + """ + async with httpx.AsyncClient(timeout=60.0) as client: + index_host = get_index_host(index_name) + params = { + "ids": ids, + "namespace": namespace + } + + response = await client.get( + f"{index_host}/vectors/fetch", + headers=get_headers(), + params=params, + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def delete_vectors( + index_name: str, + ids: Optional[List[str]] = None, + delete_all: bool = False, + namespace: str = "", + filter: Optional[Dict[str, Any]] = None +) -> dict: + """ + Delete vectors by IDs or metadata filter. + + Args: + index_name: Name of the index + ids: List of vector IDs to delete (optional) + delete_all: Delete all vectors in namespace (default: False) + namespace: Namespace (default: "") + filter: Metadata filter for deletion (optional) + + Returns: + Dictionary with deletion status + + Note: Provide either ids, delete_all=True, or filter + """ + async with httpx.AsyncClient(timeout=60.0) as client: + index_host = get_index_host(index_name) + payload = {"namespace": namespace} + + if ids: + payload["ids"] = ids + elif delete_all: + payload["deleteAll"] = True + elif filter: + payload["filter"] = filter + else: + raise ValueError("Must provide 'ids', 'delete_all=True', or 'filter'") + + response = await client.post( + f"{index_host}/vectors/delete", + headers=get_headers(), + json=payload, + ) + response.raise_for_status() + return {"status": "deleted"} + + +@mcp.tool() +async def update_vector( + index_name: str, + id: str, + values: Optional[List[float]] = None, + set_metadata: Optional[Dict[str, Any]] = None, + namespace: str = "" +) -> dict: + """ + Update vector values or metadata. + + Args: + index_name: Name of the index + id: Vector ID to update + values: New vector values (optional) + set_metadata: Metadata to set (optional) + namespace: Namespace (default: "") + + Returns: + Dictionary with update status + + Note: Provide either values, set_metadata, or both + """ + async with httpx.AsyncClient(timeout=60.0) as client: + index_host = get_index_host(index_name) + payload = { + "id": id, + "namespace": namespace + } + + if values: + payload["values"] = values + if set_metadata: + payload["setMetadata"] = set_metadata + + if not values and not set_metadata: + raise ValueError("Must provide 'values' or 'set_metadata'") + + response = await client.post( + f"{index_host}/vectors/update", + headers=get_headers(), + json=payload, + ) + response.raise_for_status() + return {"status": "updated", "id": id} + + +@mcp.tool() +async def describe_index_stats( + index_name: str, + filter: Optional[Dict[str, Any]] = None +) -> dict: + """ + Get index statistics including vector count and dimension. + + Args: + index_name: Name of the index + filter: Optional metadata filter to get stats for subset + + Returns: + Dictionary with total vector count, dimension, index fullness, and namespace stats + """ + async with httpx.AsyncClient(timeout=30.0) as client: + index_host = get_index_host(index_name) + payload = {} + if filter: + payload["filter"] = filter + + response = await client.post( + f"{index_host}/describe_index_stats", + headers=get_headers(), + json=payload if filter else None, + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def list_vector_ids( + index_name: str, + namespace: str = "", + prefix: Optional[str] = None, + limit: Optional[int] = None +) -> dict: + """ + List all vector IDs in a namespace. + + Args: + index_name: Name of the index + namespace: Namespace (default: "") + prefix: Filter IDs by prefix (optional) + limit: Maximum number of IDs to return (optional) + + Returns: + Dictionary with list of vector IDs + """ + async with httpx.AsyncClient(timeout=60.0) as client: + index_host = get_index_host(index_name) + payload = {"namespace": namespace} + + if prefix: + payload["prefix"] = prefix + if limit: + payload["limit"] = limit + + response = await client.post( + f"{index_host}/vectors/list", + headers=get_headers(), + json=payload, + ) + response.raise_for_status() + return response.json() + + +@mcp.tool() +async def create_collection( + name: str, + source_index: str +) -> dict: + """ + Create a collection from an index (for backups). + + Args: + name: Collection name + source_index: Source index name + + Returns: + Dictionary with collection creation status + """ + async with httpx.AsyncClient(timeout=60.0) as client: + payload = { + "name": name, + "source": source_index + } + + response = await client.post( + f"{CONTROL_PLANE_URL}/collections", + headers=get_headers(), + json=payload, + ) + response.raise_for_status() + return response.json() + + +if __name__ == "__main__": + mcp.run() diff --git a/servers/pinecone/test.json b/servers/pinecone/test.json new file mode 100644 index 0000000..6ea9e2b --- /dev/null +++ b/servers/pinecone/test.json @@ -0,0 +1,59 @@ +{ + "tests": [ + { + "name": "List Indexes", + "tool": "list_indexes", + "params": {}, + "expectedFields": [ + "indexes" + ], + "assertions": [ + { + "type": "exists", + "path": "indexes" + } + ] + }, + { + "name": "Describe Index Stats", + "tool": "describe_index_stats", + "params": { + "index_name": "test-index" + }, + "expectedFields": [ + "dimension", + "indexFullness", + "totalVectorCount" + ], + "assertions": [ + { + "type": "exists", + "path": "dimension" + }, + { + "type": "exists", + "path": "totalVectorCount" + } + ] + }, + { + "name": "Query Vectors", + "tool": "query_vectors", + "params": { + "index_name": "test-index", + "vector": [0.1, 0.2, 0.3], + "top_k": 5, + "include_metadata": true + }, + "expectedFields": [ + "matches" + ], + "assertions": [ + { + "type": "exists", + "path": "matches" + } + ] + } + ] +}