Build a Knowledge Base MCP Server
Build an MCP server that lets Claude search your ChromaDB knowledge base.
Introduction
30 Second Summary
Every AI assistant gives better answers when it can search through your actual documents instead of relying on general knowledge. The same technique powers ChatGPT's file search, Cursor's codebase understanding, and enterprise AI tools.
In this project, you will build a Python MCP server that connects Claude Desktop to your own personal knowledge base powered by semantic search.
What You'll Build
A Python MCP server that exposes a ChromaDB vector database to Claude Desktop, letting you ask natural language questions and get answers grounded in your own documents.
By the end of this project, you'll have:
- A persistent ChromaDB database storing 8 documents as vector embeddings that survive between sessions.
- A working MCP server exposing three tools: semantic search, document listing, and document retrieval.
- A Claude Desktop integration that searches your knowledge base and uses the results to ground its responses.
- Secret Mission: Add a document ingestion tool so Claude can grow your knowledge base through conversation.
Are there any prerequisites?
You should be comfortable with intermediate Python, including decorators, type hints, and functions. You also need Claude Desktop installed on your machine.
Set Up Your Project
Before you can build an MCP server, you need a project directory with the right dependencies installed.
In this step, you'll scaffold a new Python project using uv and install the two libraries that power your knowledge base: the MCP Python SDK and ChromaDB.
In this step, get ready to:
- Create a new Python project with uv.
- Install the MCP SDK and ChromaDB packages.
- Verify both packages are importable.
Create the project
Every Python project needs its own directory and virtual environment to keep dependencies isolated.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder by running this command:
uv init mcp-knowledge-base
What does this command do?
The uv init command scaffolds a new Python project.
It creates a pyproject.toml (your project's configuration file), a main.py starter file, and a .python-version file that pins the Python version.
- Navigate into the new project folder by running:
cd mcp-knowledge-base
- Confirm the project was created by running:
ls
You should see pyproject.toml, main.py, and .python-version listed in the output.
Don't see the files?
Make sure you ran cd mcp-knowledge-base before running ls. If the folder doesn't exist, re-run the uv init command from your Desktop.
help me troubleshoot why my uv init project folder is missing files
Add dependencies
Your MCP server needs two packages: the MCP Python SDK (which provides the FastMCP framework for building servers) and ChromaDB (the vector database that stores and searches your documents).
- Install both packages by running this command:
uv add "mcp[cli]>=1.27.0,<2" "chromadb>=1.5.0"
What does this command do?
The uv add command installs packages and records them in your pyproject.toml. It also creates a virtual environment automatically on first run.
The version constraint >=1.27.0,<2 on the MCP SDK ensures you get a stable v1 release. The SDK's docs recommend adding the <2 upper bound because v2 is still in pre-alpha.
The [cli] extra installs the MCP command-line tools, which you'll use later to test your server.
You should see output showing packages being resolved and installed. This may take a minute on first run as uv downloads and caches all the dependencies.
Seeing a resolution error?
If you see an error about unsatisfiable requirements, make sure you're using Python 3.10 or higher. Check with python3 --version.
Also confirm you're inside the mcp-knowledge-base directory. The uv add command looks for a pyproject.toml in the current directory.
help me fix a uv add resolution error
Verify installation
Before moving on, confirm that both packages are installed correctly and can be imported by Python.
- Run this verification command:
uv run python -c "from mcp.server.fastmcp import FastMCP; import chromadb; print('MCP SDK:', __import__('mcp').__version__); print('ChromaDB:', chromadb.__version__)"
What should I see?
You should see version numbers printed for both packages. The MCP SDK version will be 1.27.0 or higher, and ChromaDB will be 1.5.0 or higher.
The uv run prefix ensures the command runs inside the project's virtual environment where the packages are installed.
Seeing a ModuleNotFoundError?
This means the packages didn't install correctly. Re-run the uv add command from the previous substep. Make sure you're in the mcp-knowledge-base directory.
If you run python directly instead of uv run python, you'll use the system Python which doesn't have access to project packages.
help me fix my MCP SDK or ChromaDB import error
Finally, confirm your pyproject.toml lists both dependencies.
- View your project config by running:
cat pyproject.toml
✔️ Awesome, I've got everything!
Great. Your project is set up and both packages are ready to use.
ⓧ I'd like to double check the full code
Your pyproject.toml should contain at least these entries in the [project] section:
[project]
name = "mcp-knowledge-base"
version = "0.1.0"
dependencies = [
"mcp[cli]>=1.27.0,<2",
"chromadb>=1.5.0",
]
Your file may include additional fields like requires-python or description. That's fine. The important part is that both dependencies appear in the dependencies list.
Your project skeleton is ready with both dependencies locked in. Next up, you'll populate your ChromaDB knowledge base with real documents that Claude can search through.
Populate the Knowledge Base
Your MCP server needs data to search through. Before building the server itself, you need a knowledge base filled with documents that ChromaDB can store as vector embeddings and query semantically.
In this step, you'll create a Python script that loads 8 documents about Python and AI topics into a persistent ChromaDB database. ChromaDB handles embedding generation automatically, so you won't need an external API key or a separate embedding model.
In this step, get ready to:
- Create a populate script with sample documents about Python and AI.
- Store documents in a persistent ChromaDB database with metadata.
- Verify semantic search returns relevant results.
Create the populate script
ChromaDB's PersistentClient saves data to a directory on disk so it survives between script runs. You'll point it at a chroma_data folder using an absolute path. This is important because MCP servers launched by Claude Desktop have an undefined working directory.
- Create a new file called populate_db.py in your mcp-knowledge-base project folder.
- Add the following code to define your imports, storage path, and document data:
import os
import chromadb
# Resolve the absolute path for the chroma_data directory
CHROMA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "chroma_data")
# Each document has an ID, text content, and metadata for filtering/display
documents = [
{
"id": "doc1",
"content": "Python decorators are functions that modify the behavior of other functions. They use the @syntax and are commonly used for logging, authentication, and caching. A decorator takes a function as input and returns a new function that usually extends the original function's behavior.",
"metadata": {"title": "Python Decorators", "source": "python-notes", "topic": "python"},
},
{
"id": "doc2",
"content": "Vector databases store data as high-dimensional vectors (embeddings) and enable similarity search. Unlike traditional databases that match exact values, vector databases find items that are semantically similar. They use algorithms like HNSW for approximate nearest neighbor search.",
"metadata": {"title": "What are Vector Databases", "source": "ai-notes", "topic": "databases"},
},
{
"id": "doc3",
"content": "The Model Context Protocol (MCP) is an open protocol that standardizes how AI applications connect to external data sources and tools. It uses a client-server architecture where MCP servers expose tools, resources, and prompts that AI models can use.",
"metadata": {"title": "Introduction to MCP", "source": "ai-notes", "topic": "ai"},
},
{
"id": "doc4",
"content": "Async/await in Python allows writing concurrent code that is more readable than callbacks or threads. The asyncio library provides the event loop, and coroutines defined with async def can use await to pause execution while waiting for I/O operations without blocking other tasks.",
"metadata": {"title": "Python Async Programming", "source": "python-notes", "topic": "python"},
},
{
"id": "doc5",
"content": "RAG (Retrieval-Augmented Generation) is a technique that enhances LLM responses by first retrieving relevant documents from a knowledge base, then providing those documents as context to the model. This reduces hallucinations and keeps responses grounded in actual data.",
"metadata": {"title": "RAG Explained", "source": "ai-notes", "topic": "ai"},
},
{
"id": "doc6",
"content": "FastMCP is the high-level Python framework for building MCP servers. It uses decorators like @mcp.tool() to expose functions as tools, @mcp.resource() for read-only data, and @mcp.prompt() for reusable prompt templates. It handles JSON-RPC, input validation, and transport negotiation automatically.",
"metadata": {"title": "Building with FastMCP", "source": "ai-notes", "topic": "mcp"},
},
{
"id": "doc7",
"content": "Embeddings are numerical representations of text in high-dimensional space where semantically similar texts are positioned closer together. Models like sentence-transformers convert text into vectors of fixed dimensions. These vectors capture meaning, allowing mathematical comparison of text similarity.",
"metadata": {"title": "Understanding Embeddings", "source": "ai-notes", "topic": "ai"},
},
{
"id": "doc8",
"content": "Python virtual environments isolate project dependencies from the system Python installation. Tools like venv, virtualenv, and uv create isolated environments. This prevents version conflicts between projects and ensures reproducible installations across different machines.",
"metadata": {"title": "Python Virtual Environments", "source": "python-notes", "topic": "python"},
},
]
What does this code do?
CHROMA_PATH uses os.path.abspath(__file__) to build an absolute path to the chroma_data directory. This ensures the database is always found regardless of where the script is launched from.
Each document in the list has three fields: an id for retrieval, content that ChromaDB will embed into a vector, and metadata (title, source, topic) for filtering and display.
Why an absolute path?
When Claude Desktop launches your MCP server later, the working directory could be anything (even / on macOS). An absolute path guarantees the database is always found next to server.py and populate_db.py.
Add the population logic
The main() function connects to ChromaDB, clears any existing data, adds all 8 documents, and runs a test query to confirm everything works.
- Add the following code below your documents list in populate_db.py:
def main():
# Connect to ChromaDB with persistent storage
client = chromadb.PersistentClient(path=CHROMA_PATH)
collection = client.get_or_create_collection(name="knowledge_base")
# Clear existing documents to avoid duplicates on re-run
existing = collection.count()
if existing > 0:
print(f"Collection already has {existing} documents. Clearing and re-populating...")
client.delete_collection(name="knowledge_base")
collection = client.create_collection(name="knowledge_base")
# Add all documents - ChromaDB handles embedding automatically
collection.add(
ids=[doc["id"] for doc in documents],
documents=[doc["content"] for doc in documents],
metadatas=[doc["metadata"] for doc in documents],
)
print(f"Added {len(documents)} documents to the knowledge base.")
print("\nDocuments added:")
for doc in documents:
print(f" - [{doc['id']}] {doc['metadata']['title']}")
# Run a test search to verify embeddings work
print("\nTesting search with query: 'How do vector databases work?'")
results = collection.query(query_texts=["How do vector databases work?"], n_results=2)
print("Top results:")
for i, (doc, metadata) in enumerate(
zip(results["documents"][0], results["metadatas"][0])
):
print(f" {i + 1}. {metadata['title']}: {doc[:80]}...")
if __name__ == "__main__":
main()
What does this code do?
- PersistentClient(path=CHROMA_PATH) opens (or creates) a database that saves to disk instantly. No manual .persist() call needed.
- get_or_create_collection retrieves the collection if it exists, or creates it fresh. The script also clears existing data on re-runs so you always start clean.
- collection.add() takes your documents and ChromaDB automatically converts the text into vector embeddings using its built-in model.
- collection.query() performs a semantic search. It returns results as nested lists (one list per query) sorted by distance, where lower distance means higher similarity.
- Save populate_db.py.
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
import os
import chromadb
CHROMA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "chroma_data")
documents = [
{
"id": "doc1",
"content": "Python decorators are functions that modify the behavior of other functions. They use the @syntax and are commonly used for logging, authentication, and caching. A decorator takes a function as input and returns a new function that usually extends the original function's behavior.",
"metadata": {"title": "Python Decorators", "source": "python-notes", "topic": "python"},
},
{
"id": "doc2",
"content": "Vector databases store data as high-dimensional vectors (embeddings) and enable similarity search. Unlike traditional databases that match exact values, vector databases find items that are semantically similar. They use algorithms like HNSW for approximate nearest neighbor search.",
"metadata": {"title": "What are Vector Databases", "source": "ai-notes", "topic": "databases"},
},
{
"id": "doc3",
"content": "The Model Context Protocol (MCP) is an open protocol that standardizes how AI applications connect to external data sources and tools. It uses a client-server architecture where MCP servers expose tools, resources, and prompts that AI models can use.",
"metadata": {"title": "Introduction to MCP", "source": "ai-notes", "topic": "ai"},
},
{
"id": "doc4",
"content": "Async/await in Python allows writing concurrent code that is more readable than callbacks or threads. The asyncio library provides the event loop, and coroutines defined with async def can use await to pause execution while waiting for I/O operations without blocking other tasks.",
"metadata": {"title": "Python Async Programming", "source": "python-notes", "topic": "python"},
},
{
"id": "doc5",
"content": "RAG (Retrieval-Augmented Generation) is a technique that enhances LLM responses by first retrieving relevant documents from a knowledge base, then providing those documents as context to the model. This reduces hallucinations and keeps responses grounded in actual data.",
"metadata": {"title": "RAG Explained", "source": "ai-notes", "topic": "ai"},
},
{
"id": "doc6",
"content": "FastMCP is the high-level Python framework for building MCP servers. It uses decorators like @mcp.tool() to expose functions as tools, @mcp.resource() for read-only data, and @mcp.prompt() for reusable prompt templates. It handles JSON-RPC, input validation, and transport negotiation automatically.",
"metadata": {"title": "Building with FastMCP", "source": "ai-notes", "topic": "mcp"},
},
{
"id": "doc7",
"content": "Embeddings are numerical representations of text in high-dimensional space where semantically similar texts are positioned closer together. Models like sentence-transformers convert text into vectors of fixed dimensions. These vectors capture meaning, allowing mathematical comparison of text similarity.",
"metadata": {"title": "Understanding Embeddings", "source": "ai-notes", "topic": "ai"},
},
{
"id": "doc8",
"content": "Python virtual environments isolate project dependencies from the system Python installation. Tools like venv, virtualenv, and uv create isolated environments. This prevents version conflicts between projects and ensures reproducible installations across different machines.",
"metadata": {"title": "Python Virtual Environments", "source": "python-notes", "topic": "python"},
},
]
def main():
client = chromadb.PersistentClient(path=CHROMA_PATH)
collection = client.get_or_create_collection(name="knowledge_base")
existing = collection.count()
if existing > 0:
print(f"Collection already has {existing} documents. Clearing and re-populating...")
client.delete_collection(name="knowledge_base")
collection = client.create_collection(name="knowledge_base")
collection.add(
ids=[doc["id"] for doc in documents],
documents=[doc["content"] for doc in documents],
metadatas=[doc["metadata"] for doc in documents],
)
print(f"Added {len(documents)} documents to the knowledge base.")
print("\nDocuments added:")
for doc in documents:
print(f" - [{doc['id']}] {doc['metadata']['title']}")
print("\nTesting search with query: 'How do vector databases work?'")
results = collection.query(query_texts=["How do vector databases work?"], n_results=2)
print("Top results:")
for i, (doc, metadata) in enumerate(
zip(results["documents"][0], results["metadatas"][0])
):
print(f" {i + 1}. {metadata['title']}: {doc[:80]}...")
if __name__ == "__main__":
main()
Run the script and verify
Now you'll run the script to populate the database and confirm semantic search is working.
- Run the populate script by executing this command:
uv run populate_db.py
What should I see?
The first run may take a few seconds while ChromaDB loads its built-in embedding model. You should see output listing all 8 documents added, followed by test search results showing "What are Vector Databases" as the top result for the query "How do vector databases work?".
- Confirm the chroma_data directory was created by running:
ls chroma_data
You should see database files inside the directory. This confirms ChromaDB persisted your documents to disk.
Seeing an error when running the script?
- If you see an import error for chromadb, make sure you're running with uv run (not bare python). The uv run command activates the virtual environment automatically.
- If you see a syntax error, double check that all the dictionary brackets and commas in your documents list match the full code above.
- If the test search returns unexpected results, that's OK. Semantic similarity depends on the embedding model, and the top result should still be relevant to the query topic.
Still stuck? Help me debug my populate_db.py script
Your knowledge base is populated and semantic search is working. Next up, you'll build the MCP server that exposes this database to Claude Desktop as searchable tools.
Build the MCP Server
Your knowledge base is populated with 8 documents stored as vector embeddings. Now you need to expose that data through an interface that Claude can actually call.
The Model Context Protocol lets you define tools that an AI assistant can invoke on its own. In this step, you'll build a Python MCP server that wraps your ChromaDB collection in three tools: semantic search, document listing, and single-document retrieval.
In this step, get ready to:
- Set up the MCP server with FastMCP and connect it to your ChromaDB collection.
- Implement three tools: semantic search, document listing, and document retrieval.
- Test the server locally using the MCP Inspector.
Create the server with FastMCP
Every MCP server needs a name, a connection to its data source, and a transport layer. You'll set all of this up in a new file called server.py.
- Create server.py in the root of your mcp-knowledge-base folder and add the following code:
import os
import chromadb
from mcp.server.fastmcp import FastMCP
# Initialize the MCP server with a name Claude will see
mcp = FastMCP("Knowledge Base")
# Resolve the absolute path to the ChromaDB data directory
CHROMA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "chroma_data")
def get_collection():
# Connect to the persistent database and return the collection
client = chromadb.PersistentClient(path=CHROMA_PATH)
return client.get_or_create_collection(name="knowledge_base")
What does this code do?
FastMCP("Knowledge Base") creates your MCP server with a human-readable name. Claude Desktop displays this name when listing connected servers.
CHROMA_PATH uses os.path.abspath(__file__) to build an absolute path to the chroma_data directory. This is critical because Claude Desktop launches MCP servers with an undefined working directory. Without an absolute path, the server would fail to find your database.
get_collection() is a helper that reconnects to your persistent ChromaDB database each time a tool is called. This pattern keeps each tool invocation independent.
- Save server.py.
- Confirm the file exists by running:
ls server.py
You should see server.py listed in the output.
Don't see server.py?
Make sure you're inside the mcp-knowledge-base directory. Run pwd to check your current location.
help me figure out why my server.py file wasn't created
Implement the tools
MCP tools are Python functions decorated with @mcp.tool(). The decorator reads each function's type hints and docstring to generate a schema that Claude uses to decide when and how to call it.
- Add the search_knowledge_base tool below get_collection() in server.py:
@mcp.tool()
def search_knowledge_base(query: str, n_results: int = 3) -> str:
"""Search the knowledge base for documents semantically related to the query.
Args:
query: The search query to find relevant documents.
n_results: Number of results to return (default 3).
"""
collection = get_collection()
results = collection.query(query_texts=[query], n_results=n_results)
if not results["documents"][0]:
return "No results found."
# Format each result with its relevance score, title, and content
output = []
for i, (doc, metadata, distance) in enumerate(
zip(results["documents"][0], results["metadatas"][0], results["distances"][0])
):
source = metadata.get("source", "Unknown")
title = metadata.get("title", "Untitled")
output.append(
f"Result {i + 1} (distance: {distance:.4f}):\n"
f"Title: {title}\n"
f"Source: {source}\n"
f"Content: {doc}\n"
)
return "\n---\n".join(output)
What does this tool do?
This is the core search tool. When Claude needs to answer a question, it calls this function with a natural language query. ChromaDB converts the query into an embedding and finds the closest matching documents.
The distance value in the results represents how similar each document is to the query. Lower distance means higher similarity.
- Save server.py.
- Now add the list_documents tool below search_knowledge_base:
@mcp.tool()
def list_documents() -> str:
"""List all documents in the knowledge base with their IDs and titles."""
collection = get_collection()
results = collection.get()
if not results["ids"]:
return "The knowledge base is empty."
# Format each document's ID, title, and source
output = []
for doc_id, metadata in zip(results["ids"], results["metadatas"]):
title = metadata.get("title", "Untitled")
source = metadata.get("source", "Unknown")
output.append(f"- [{doc_id}] {title} (source: {source})")
return "\n".join(output)
What does this tool do?
This tool gives Claude a birds-eye view of everything in your knowledge base. It uses collection.get() with no arguments, which returns all documents in the collection. Claude will call this when you ask something like "What documents are in my knowledge base?"
- Save server.py.
- Add the final tool, get_document, below list_documents:
@mcp.tool()
def get_document(document_id: str) -> str:
"""Retrieve a specific document by its ID.
Args:
document_id: The unique ID of the document to retrieve.
"""
collection = get_collection()
results = collection.get(ids=[document_id])
if not results["ids"]:
return f"Document with ID '{document_id}' not found."
doc = results["documents"][0]
metadata = results["metadatas"][0]
title = metadata.get("title", "Untitled")
source = metadata.get("source", "Unknown")
return f"Title: {title}\nSource: {source}\n\nContent:\n{doc}"
What does this tool do?
This tool retrieves a single document by its exact ID (like doc1 or doc5). Claude uses this after listing or searching, when it wants to read the full content of a specific document.
- Save server.py.
Add the entry point and test the server
The server needs an entry point that tells it how to communicate. For local use with Claude Desktop, the standard transport is stdio (standard input/output).
- Add this at the very bottom of server.py:
if __name__ == "__main__":
mcp.run(transport="stdio")
Why stdio?
Claude Desktop spawns MCP servers as child processes and communicates with them over standard input/output. This requires zero network configuration. The alternative (Streamable HTTP) is used for remote or multi-tenant deployments.
- Save server.py.
- Test the server locally by launching the MCP Inspector:
uv run mcp dev server.py
What does this command do?
The mcp dev command launches the MCP Inspector, a browser-based UI that lets you call your server's tools manually. It requires Node.js to be installed because it runs npx under the hood.
The MCP Inspector opens in your browser. You should see your three tools listed: search_knowledge_base, list_documents, and get_document.
- Click on search_knowledge_base in the Inspector.
- Enter a query like How do vector databases work? and run it.
- Verify you see formatted results with titles, sources, and distance scores.
- Click on list_documents and run it. You should see all 8 documents listed.
- Press Ctrl+C in your terminal to stop the Inspector when you're done testing.
Inspector not opening or showing errors?
If you see an error about npx not being found, you need Node.js installed. The mcp dev command uses npx to launch the Inspector.
If tools don't appear, double-check that server.py has no syntax errors. Run uv run python server.py to check for import or indentation errors.
help me troubleshoot why my MCP Inspector isn't showing my tools
✔️ Awesome, I've got everything!
Great. Double check you've saved server.py and all three tools appeared in the MCP Inspector.
ⓧ I'd like to double check the full code
Here's the complete server.py file for comparison:
import os
import chromadb
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Knowledge Base")
CHROMA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "chroma_data")
def get_collection():
client = chromadb.PersistentClient(path=CHROMA_PATH)
return client.get_or_create_collection(name="knowledge_base")
@mcp.tool()
def search_knowledge_base(query: str, n_results: int = 3) -> str:
"""Search the knowledge base for documents semantically related to the query.
Args:
query: The search query to find relevant documents.
n_results: Number of results to return (default 3).
"""
collection = get_collection()
results = collection.query(query_texts=[query], n_results=n_results)
if not results["documents"][0]:
return "No results found."
output = []
for i, (doc, metadata, distance) in enumerate(
zip(results["documents"][0], results["metadatas"][0], results["distances"][0])
):
source = metadata.get("source", "Unknown")
title = metadata.get("title", "Untitled")
output.append(
f"Result {i + 1} (distance: {distance:.4f}):\n"
f"Title: {title}\n"
f"Source: {source}\n"
f"Content: {doc}\n"
)
return "\n---\n".join(output)
@mcp.tool()
def list_documents() -> str:
"""List all documents in the knowledge base with their IDs and titles."""
collection = get_collection()
results = collection.get()
if not results["ids"]:
return "The knowledge base is empty."
output = []
for doc_id, metadata in zip(results["ids"], results["metadatas"]):
title = metadata.get("title", "Untitled")
source = metadata.get("source", "Unknown")
output.append(f"- [{doc_id}] {title} (source: {source})")
return "\n".join(output)
@mcp.tool()
def get_document(document_id: str) -> str:
"""Retrieve a specific document by its ID.
Args:
document_id: The unique ID of the document to retrieve.
"""
collection = get_collection()
results = collection.get(ids=[document_id])
if not results["ids"]:
return f"Document with ID '{document_id}' not found."
doc = results["documents"][0]
metadata = results["metadatas"][0]
title = metadata.get("title", "Untitled")
source = metadata.get("source", "Unknown")
return f"Title: {title}\nSource: {source}\n\nContent:\n{doc}"
if __name__ == "__main__":
mcp.run(transport="stdio")
Your MCP server is built and tested locally. Next up, you'll connect it to Claude Desktop so you can search your knowledge base through natural conversation.
Connect to Claude Desktop
Your MCP server is tested and working in the Inspector. The final step is connecting it to Claude Desktop so you can query your knowledge base through natural conversation.
Claude Desktop launches MCP servers as subprocesses using a JSON config file. You'll point it to your project's absolute path, restart the app, and test the full loop of asking questions that trigger your tools.
In this step, get ready to:
- Configure Claude Desktop's MCP server settings.
- Verify the Knowledge Base server appears as a connector.
- Test natural language queries that trigger your MCP tools.
Configure Claude Desktop
Claude Desktop needs to know where your server lives on disk so it can launch it as a subprocess. The config file maps a server name to a command that starts it.
- Get your project's absolute path by running this command in your terminal:
pwd
You should see something like /Users/yourname/Desktop/mcp-knowledge-base. Copy this path. You'll need it in a moment.
Why does Claude Desktop need the absolute path?
When Claude Desktop spawns your MCP server, the working directory can be undefined (sometimes / on macOS). Using an absolute path with the --directory flag ensures uv always finds your project files regardless of where the subprocess starts.
- Open Claude Desktop.
- Click the Claude menu in the macOS menu bar.
- Select Settings....
- Navigate to the Developer tab.
- Click Edit Config.
This opens ~/Library/Application Support/Claude/claude_desktop_config.json in your default text editor.
- Replace the contents of the file with the following configuration:
{
"mcpServers": {
"knowledge-base": {
"command": "uv",
"args": [
"--directory",
"[[PROJECT_PATH=\"/Users/yourname/Desktop/mcp-knowledge-base\"]]",
"run",
"server.py"
]
}
}
}
What does this config do?
- "knowledge-base" is the name Claude Desktop uses to identify your server.
- "command": "uv" tells Claude Desktop to launch the server using uv.
- "--directory" points uv to your project folder so it can find the virtual environment and dependencies.
- "run", "server.py" tells uv to execute your server script.
- Save the file and close your text editor.
Already have other MCP servers?
If your config file already has entries inside mcpServers, add the "knowledge-base": {...} block alongside them rather than replacing the entire file. Each server gets its own key inside the mcpServers object.
Seeing a JSON syntax error?
Make sure every opening brace { has a matching closing brace }. Check that there are no trailing commas after the last item in any array or object. Commas between items, but not after the final one.
help me fix a JSON syntax error in my Claude Desktop config file
Test the integration
Claude Desktop only reads the config file at launch. You need to fully quit and relaunch for changes to take effect.
- Completely quit Claude Desktop by pressing Cmd+Q (not just closing the window).
- Relaunch Claude Desktop.
- In the chat input, click the Add files, connectors, and more plus icon.
- Hover over the Connectors menu to verify your Knowledge Base server appears in the list.
If you see your server listed, the connection is working. Now test it with a real query.
- Type this message to Claude: "What documents are in my knowledge base?"
- Watch Claude call the list_documents tool and return a list of all 8 documents.
- Ask Claude: "How do vector databases work?"
- Observe Claude calling the search_knowledge_base tool and using the results to ground its answer.
What should I see?
Claude will show a tool-use indicator before responding. You can expand it to see the exact query sent to your server and the results returned.
When you ask about vector databases, Claude's response should reference content from your doc2 ("What are Vector Databases") document. This is the RAG pattern in action: retrieve first, then generate.
Troubleshoot if needed
If your server does not appear in the Connectors menu after relaunching, check the MCP logs for error messages.
- View the MCP server logs by running this command:
tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
Server not appearing in Claude Desktop?
Check these common issues:
- Relative paths: The --directory value must be an absolute path (starting with /). Use the output from pwd exactly as it appeared.
- Missing uv in PATH: Run which uv in your terminal and use the full path (e.g. /Users/yourname/.local/bin/uv) as the "command" value in your config instead of just "uv".
- JSON syntax errors: Validate your config file has matching braces and no trailing commas.
- Didn't fully quit: Closing the window is not enough. Use Cmd+Q to quit, then relaunch.
help me debug why my MCP Knowledge Base server is not connecting to Claude Desktop
Secret mission
Add Document Ingestion
Right now your knowledge base is read-only. Claude can search it, list it, and retrieve from it, but it can't add anything new. In this secret mission, you'll give Claude the power to write to your knowledge base through conversation, turning it into a living collection that grows every time you chat.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- The mcp-knowledge-base project folder on your Desktop (contains server.py, populate_db.py, and pyproject.toml).
- The chroma_data directory (your persistent ChromaDB vector database).
- The Claude Desktop MCP server configuration in claude_desktop_config.json.
- The uv package manager (installed to ~/.local/bin).
Keep everything running
No action needed. Choose this if you plan to keep using your MCP knowledge base server with Claude Desktop.
- Your MCP server will continue to work whenever Claude Desktop is open.
- You can add more documents to your knowledge base at any time by running populate_db.py again or by asking Claude to add them.
- The uv package manager stays installed on your machine for future Python projects.
Pause - I'll come back to this later
Remove the MCP server from Claude Desktop so it stops trying to connect. Your project files stay on disk for later.
- Open Claude Desktop, click the **Claude** menu in the macOS menu bar, then select **Settings...**.
- Navigate to the **Developer** tab and click **Edit Config**.
- Remove the knowledge-base entry from the mcpServers object, then save the file.
- Quit and relaunch Claude Desktop to apply the change.
- Your mcp-knowledge-base folder and chroma_data database remain on disk. To resume, re-add the config entry and restart Claude Desktop.
Delete - I don't want to use this again
Remove all project resources and start fresh. This deletes your knowledge base data permanently.
- Remove the MCP server config from Claude Desktop by following the Pause steps above (edit the config, remove the knowledge-base entry, save, quit, and relaunch).
- Delete the project folder by running this command in your terminal:
rm -rf ~/Desktop/mcp-knowledge-base
- This removes the project files, the virtual environment, and the chroma_data database in one step.
- To uninstall uv from your system, run:
rm ~/.local/bin/uv ~/.local/bin/uvx
- Optionally, clear the uv cache and stored data before removing the binaries by running:
uv cache clean
- Confirm your Desktop is clean by running:
ls ~/Desktop
- You should no longer see mcp-knowledge-base in the output.
Nice Work!
Nice Work!
You just built a fully functional MCP server that connects Claude Desktop to your own personal knowledge base. This is the same RAG architecture that powers production AI assistants across the industry.
You've learned how to:
- Build a ChromaDB vector database that stores documents as embeddings and persists them to disk.
- Create a Python MCP server using FastMCP with three tools: semantic search, document listing, and document retrieval.
- Connect the server to Claude Desktop via stdio transport, enabling natural language interaction with your knowledge base through the RAG pattern.
- Secret Mission: Added a document ingestion tool, turning Claude into both a reader and writer of your knowledge base.
Ready to quiz yourself?