Revamped quickstart again
This commit is contained in:
parent
9b4fe75a99
commit
9870471072
6 changed files with 637 additions and 926 deletions
BIN
images/quickstart-weather.png
Normal file
BIN
images/quickstart-weather.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
|
|
@ -3,12 +3,17 @@ title: Introduction
|
|||
description: 'Get started with the Model Context Protocol (MCP)'
|
||||
---
|
||||
|
||||
Model Context Protocol (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools.
|
||||
MCP is an open protocol that standardizes how applications provide context to LLMs. Think of it as the "Language Server Protocol for AI" - it separates the concerns of providing context from the LLM interaction itself. This separation enables:
|
||||
|
||||
## Why MCP
|
||||
- **Modularity**: Each MCP server focuses on one specific integration or capability
|
||||
- **Reusability**: The same server can be used with any MCP-compatible client
|
||||
- **Composability**: Multiple servers can be combined to create powerful workflows
|
||||
- **Standardization**: A common protocol for all integrations reduces complexity
|
||||
|
||||
MCP is a great choice for anyone building agents or complex integrations on top of LLMs. LLMs frequently need to integrate with data and tools, and MCP provides:
|
||||
- A growing list of pre-built integrations that your LLM can directly plug into.
|
||||
## Why MCP?
|
||||
|
||||
MCP helps you build agents and complex workflows on top of LLMs. LLMs frequently need to integrate with data and tools, and MCP provides:
|
||||
- A growing list of pre-built integrations that your LLM can directly plug into
|
||||
- The flexibility to switch between LLM providers and vendors
|
||||
- Best practices for securing your data within your infrastructure
|
||||
|
||||
|
|
@ -40,7 +45,7 @@ flowchart LR
|
|||
- **Local Data Sources**: Your computer's files, databases, and services that MCP servers can securely access
|
||||
- **Remote Services**: External systems available over the internet (e.g., through APIs) that MCP servers can connect to
|
||||
|
||||
## Get started with MCP
|
||||
## Get started
|
||||
|
||||
Choose the path that best fits your needs:
|
||||
|
||||
|
|
@ -50,28 +55,19 @@ Choose the path that best fits your needs:
|
|||
icon="bolt"
|
||||
href="/quickstart"
|
||||
>
|
||||
Build your first MCP client and server in less than 5 minutes
|
||||
Build and connect to your first MCP server
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Tutorials
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Take MCP servers for a test run in Claude Desktop"
|
||||
icon="desktop"
|
||||
href="/tutorials/claude-desktop"
|
||||
>
|
||||
Give Claude Desktop the ability to query SQLite data using MCP
|
||||
</Card>
|
||||
<Card
|
||||
title="MCP clients"
|
||||
icon="plug"
|
||||
href="/clients"
|
||||
>
|
||||
See a detailed list of MCP clients
|
||||
</Card>
|
||||
<Card
|
||||
title="MCP servers"
|
||||
title="Building a MCP client"
|
||||
icon="outlet"
|
||||
href="/servers"
|
||||
href="/building-a-client"
|
||||
>
|
||||
See a detailed list of MCP servers
|
||||
Learn how to build your first MCP client
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
|
|
@ -81,7 +77,7 @@ Dive deeper into MCP's core concepts and capabilities:
|
|||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Core Architecture"
|
||||
title="Core architecture"
|
||||
icon="sitemap"
|
||||
href="/docs/concepts/architecture"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@
|
|||
"navigation": [
|
||||
{
|
||||
"group": "Get Started",
|
||||
"pages": ["introduction", "quickstart", "clients", "servers"]
|
||||
"pages": ["introduction", "quickstart"]
|
||||
},
|
||||
{
|
||||
"group": "Development Tools",
|
||||
"pages": ["docs/tools/debugging", "docs/tools/inspector"]
|
||||
"group": "Tutorials",
|
||||
"pages": ["tutorials/building-a-client"]
|
||||
},
|
||||
{
|
||||
"group": "Concepts",
|
||||
|
|
|
|||
890
quickstart.mdx
890
quickstart.mdx
|
|
@ -1,598 +1,426 @@
|
|||
---
|
||||
title: "Quickstart"
|
||||
description: "Get started with building your first MCP server and client in just a few minutes."
|
||||
description: "Get started with building your first MCP server and connecting it to an MCP host"
|
||||
---
|
||||
|
||||
In this quickstart, you'll learn the ins and outs of MCP by building your first client and server. We'll start with a basic setup, and then progress to more complex use cases.
|
||||
In this tutorial, we'll build a simple MCP server with a `get_weather` tool and connect it to a popular MCP host, Claude for Desktop. We'll start with a basic setup, and then progress to more complex use cases.
|
||||
|
||||
You can think of MCP as a bridge between your LLMs and your data and tools. Model Context Protocol was developed by Anthropic (the makers of [Claude](https://claude.ai)), but MCP can work with any Large Language Model provider that supports tool calling.
|
||||
|
||||
### What is MCP?
|
||||
|
||||
MCP is an open protocol that standardizes how applications provide context to LLMs. Think of it as the "Language Server Protocol for AI" - it separates the concerns of providing context from the LLM interaction itself. This separation enables:
|
||||
|
||||
1. **Modularity**: Each MCP server focuses on one specific integration or capability
|
||||
2. **Reusability**: The same server can be used with any MCP-compatible client
|
||||
3. **Composability**: Multiple servers can be combined to create powerful workflows
|
||||
4. **Standardization**: A common protocol for all integrations reduces complexity
|
||||
|
||||
### What we'll be building
|
||||
|
||||
Many LLMs (including Claude) do not currently have the ability to know the current time and date. Let's use MCP to fix that!
|
||||
Many LLMs (including Claude) do not currently have the ability to fetch the current temperature and weather conditions. Let's use MCP to solve that!
|
||||
|
||||
In this quickstart, we'll start by building a Claude CLI chatbot which connects to a simple server that exposes two tools: `get_current_time` and `convert_time`.
|
||||
<Frame>
|
||||
<img src="/images/quickstart-weather.png" />
|
||||
</Frame>
|
||||
|
||||
This will allow us to ask Claude questions like:
|
||||
Servers can connect to any host. We'll walk through using Claude Desktop as the host and eventually show you how to build your own.
|
||||
|
||||
"What time is it in NYC?"
|
||||
"When it's 4 PM in New York, what time is it in London?"
|
||||
<Accordion title="Why Claude Desktop and not Claude.ai?">
|
||||
Because servers are locally run, MCP currently only supports desktop hosts. Remote hosts are in active development.
|
||||
</Accordion>
|
||||
|
||||
### Core MCP Concepts
|
||||
|
||||
MCP servers can provide three main types of capabilities:
|
||||
|
||||
1. **Resources**: File-like data that can be read by clients (like API responses or file contents)
|
||||
2. **Tools**: Functions that can be called by the LLM (with user approval)
|
||||
3. **Prompts**: Pre-written templates that help users accomplish specific tasks
|
||||
|
||||
This tutorial focuses on tools, but we have intermediate tutorials if you'd like to learn more about Resources and Prompts.
|
||||
|
||||
### Prerequisite knowledge
|
||||
|
||||
This quickstart assumes you have familiarity with:
|
||||
- Python
|
||||
- Python or TypeScript
|
||||
- LLMs like Claude
|
||||
|
||||
It also helps to be familiar with [tool use (aka function calling)](https://docs.anthropic.com/en/docs/build-with-claude/tool-use), but it's not required.
|
||||
|
||||
|
||||
### System requirements
|
||||
|
||||
Before we begin, make sure your system meets the following requirements for best results:
|
||||
|
||||
- You're on a Mac or Windows computer
|
||||
- You have the latest Python version installed
|
||||
For Python, make sure you have the latest version of Python installed. For TypeScript, make sure you have the latest version of Node installed.
|
||||
|
||||
### Set up your environment
|
||||
|
||||
First, let's create and activate a virtual environment in a new Python project.
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
First, let's install `uv` and set up our Python project and environment:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash MacOS/Linux
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
```bash Windows
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</Tab>
|
||||
<Tab title="Node">
|
||||
First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
|
||||
Verify your Node.js installation:
|
||||
```bash
|
||||
# Install virtualenv if you don't have it
|
||||
pip install virtualenv
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
For this tutorial, you'll need Node.js version 16 or higher.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Now, let's create and set up our project:
|
||||
|
||||
<Tabs>
|
||||
<Tab title='Python'>
|
||||
<CodeGroup>
|
||||
```bash MacOS/Linux
|
||||
# Create a new directory for our project
|
||||
uv init mcp-quickstart
|
||||
cd mcp-quickstart
|
||||
|
||||
# Create virtual environment and activate it
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
uv pip install mcp httpx
|
||||
|
||||
# Clean up boilerplate code
|
||||
rm -rf hello.py
|
||||
|
||||
# Create our files
|
||||
mkdir src
|
||||
mkdir src/weather
|
||||
touch src/__init__.py
|
||||
touch src/server.py
|
||||
```
|
||||
|
||||
```bash Windows
|
||||
# Create a new directory for our project
|
||||
uv init mcp-quickstart
|
||||
cd mcp-quickstart
|
||||
|
||||
# Create virtual environment and activate it
|
||||
uv venv
|
||||
.venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
uv pip install mcp httpx
|
||||
|
||||
# Clean up boilerplate code
|
||||
del /f /q hello.py
|
||||
|
||||
# Create our files
|
||||
md src
|
||||
md src\weather
|
||||
echo.> src\__init__.py
|
||||
echo.> src\server.py
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Add this code to __init__.py:
|
||||
|
||||
```python src/weather/__init__.py
|
||||
from . import server
|
||||
import asyncio
|
||||
|
||||
def main():
|
||||
"""Main entry point for the package."""
|
||||
asyncio.run(server.main())
|
||||
|
||||
# Optionally expose other important items at package level
|
||||
__all__ = ['main', 'server']
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="Node">
|
||||
```bash
|
||||
# Create a new directory for our project
|
||||
mkdir mcp-quickstart
|
||||
cd mcp-quickstart
|
||||
|
||||
# Create virtual environment
|
||||
python -m virtualenv venv
|
||||
# Initialize a new npm project
|
||||
npm init -y
|
||||
|
||||
# Activate virtual environment
|
||||
# On Windows:
|
||||
venv\Scripts\activate
|
||||
# On Unix or MacOS:
|
||||
source venv/bin/activate
|
||||
|
||||
# Install mcp, anthropic, and python-dotenv
|
||||
pip install mcp anthropic python-dotenv
|
||||
# Install dependencies
|
||||
npm install @modelcontextprotocol/sdk node-fetch
|
||||
npm install -D @types/node typescript
|
||||
```
|
||||
|
||||
### Set up Anthropic API key
|
||||
|
||||
You'll need an Anthropic API key, which you can get from the [Anthropic Console](https://console.anthropic.com/settings/keys).
|
||||
|
||||
After you've gotten your key, create a `.env` file to store it securely.
|
||||
Then create a new file called `src/index.ts` in your project directory:
|
||||
|
||||
```bash
|
||||
# Creates the .env file
|
||||
touch .env
|
||||
mkdir src
|
||||
touch src/index.ts
|
||||
```
|
||||
Update your package.json to add type: "module" and a build script:
|
||||
|
||||
```json package.json
|
||||
{
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"mcp-quickstart": "./build/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
|
||||
},
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
```bash .env
|
||||
ANTHROPIC_API_KEY=<your key here>
|
||||
Create a tsconfig.json:
|
||||
|
||||
```json tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Now let's dive into building your server.
|
||||
|
||||
## Building your server
|
||||
|
||||
### Importing packages
|
||||
|
||||
Add these to your server.py:
|
||||
```python
|
||||
from typing import Any
|
||||
import httpx
|
||||
from mcp.server.models import InitializationOptions
|
||||
import mcp.types as types
|
||||
from mcp.server import NotificationOptions, Server
|
||||
import mcp.server.stdio
|
||||
```
|
||||
|
||||
```bash .gitignore
|
||||
# Make sure to add your .env key to your .gitignore to exclude it from source control!
|
||||
### Setting up the instance
|
||||
|
||||
.env
|
||||
```
|
||||
|
||||
|
||||
### Creating the client (client.py)
|
||||
|
||||
Next, let's create a Python file called `client.py` in your `mcp-quickstart` directory.
|
||||
|
||||
```bash
|
||||
touch client.py
|
||||
```
|
||||
|
||||
Let's start by importing our libraries at the top of the file:
|
||||
|
||||
```python client.py
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
from contextlib import AsyncExitStack
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
from anthropic import Anthropic
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv() # load environment variables from .env.
|
||||
```
|
||||
|
||||
Next, we'll create a `class` called `ClaudeCLIChatbot` with a single `__init__` method for now:
|
||||
|
||||
```python client.py
|
||||
# continued from above
|
||||
|
||||
class ClaudeCLIChatbot:
|
||||
def __init__(self):
|
||||
# This is where we'll keep the MCP client session
|
||||
self.session: Optional[ClientSession] = None
|
||||
self.exit_stack = AsyncExitStack()
|
||||
# Initialize Anthropic client
|
||||
self.anthropic = Anthropic()
|
||||
|
||||
# rest of the class methods will go here
|
||||
```
|
||||
|
||||
Now, lets add a `connect_to_time_server` method. We'll call this method when we first start the main process to actually initialize the connection between our client and our time server.
|
||||
|
||||
```python client.py
|
||||
class ClaudeCLIChatbot:
|
||||
# continued from above
|
||||
|
||||
async def connect_to_time_server(self, server_script_path: str):
|
||||
"""Connect to the MCP time server"""
|
||||
server_params = StdioServerParameters(
|
||||
command="python",
|
||||
args=[server_script_path],
|
||||
env=None
|
||||
)
|
||||
|
||||
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
||||
self.stdio, self.write = stdio_transport
|
||||
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
|
||||
|
||||
await self.session.initialize()
|
||||
|
||||
response = await self.session.list_tools()
|
||||
tools = response.tools
|
||||
print("\nConnected to time server with tools:", [tool.name for tool in tools])
|
||||
|
||||
# more methods will go here
|
||||
```
|
||||
|
||||
Let's add a `process_query` method. This is where the following happens:
|
||||
- It sends the user's query to Claude
|
||||
- Claude decides which tools to use based on the query
|
||||
- The client executes the tools and returns the results to Claude
|
||||
- Claude provides a natural language response
|
||||
|
||||
```python client.py
|
||||
class ClaudeCLIChatbot:
|
||||
# continued from above
|
||||
|
||||
async def process_query(self, query: str) -> str:
|
||||
"""Use Claude to understand the query and call appropriate tools"""
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": query
|
||||
}
|
||||
]
|
||||
|
||||
response = await self.session.list_tools()
|
||||
available_tools = [{
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"input_schema": tool.inputSchema
|
||||
} for tool in response.tools]
|
||||
|
||||
# Make the initial call to the Anthropic API
|
||||
response = self.anthropic.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
messages=messages,
|
||||
tools=available_tools
|
||||
)
|
||||
|
||||
# Process each content block in the response
|
||||
tool_results = []
|
||||
final_text = []
|
||||
|
||||
for content in response.content:
|
||||
if content.type == 'text':
|
||||
final_text.append(content.text)
|
||||
elif content.type == 'tool_use':
|
||||
tool_name = content.name
|
||||
tool_args = content.input
|
||||
|
||||
# Call the tool and get result
|
||||
result = await self.session.call_tool(tool_name, tool_args)
|
||||
tool_results.append({"call": tool_name, "result": result})
|
||||
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
|
||||
|
||||
# Continue conversation with tool results
|
||||
if hasattr(content, 'text') and content.text:
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": content.text
|
||||
})
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": result.content
|
||||
})
|
||||
|
||||
# Get next response from Claude
|
||||
response = self.anthropic.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
final_text.append(response.content[0].text)
|
||||
|
||||
# Return combined output
|
||||
return "\n".join(final_text)
|
||||
|
||||
# more methods will go here
|
||||
```
|
||||
|
||||
Now we'll add the last two methods to our class: `chat_loop` and `cleanup`. These methods are responsible for the continuous running of our chatbot and the cleanup afterwards.
|
||||
|
||||
```python client.py
|
||||
class ClaudeCLIChatbot:
|
||||
# continued from above
|
||||
|
||||
async def chat_loop(self):
|
||||
"""Main chat loop"""
|
||||
print("\nWelcome to the Time Assistant!")
|
||||
print("Ask me anything about times and timezones. For example:")
|
||||
print("- What time is it now?")
|
||||
print("- What time is it in Tokyo?")
|
||||
print("- When it's 4 PM in New York, what time is it in London?")
|
||||
print("- Convert 9:30 AM Tokyo time to New York time")
|
||||
print("\nType 'quit' to exit.")
|
||||
|
||||
while True:
|
||||
try:
|
||||
query = input("\nWhat would you like to know? ").strip()
|
||||
|
||||
if query.lower() == 'quit':
|
||||
break
|
||||
|
||||
response = await self.process_query(query)
|
||||
print("\n" + response)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError: {str(e)}")
|
||||
|
||||
async def cleanup(self):
|
||||
"""Cleanup resources"""
|
||||
await self.exit_stack.aclose()
|
||||
```
|
||||
|
||||
The final step for our `client.py` file is to add and call the `main` function that will actually initialize our connection to the MCP time server (which we'll create in the next step) and begin the chat loop.
|
||||
|
||||
```python client.py
|
||||
class ClaudeCLIChatbot:
|
||||
# methods from above
|
||||
|
||||
async def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python chatbot.py <path_to_server_script>")
|
||||
sys.exit(1)
|
||||
|
||||
chatbot = ClaudeCLIChatbot()
|
||||
try:
|
||||
await chatbot.connect_to_time_server(sys.argv[1])
|
||||
await chatbot.chat_loop()
|
||||
finally:
|
||||
await chatbot.cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
Putting it all together, the `client.py` file should look like this:
|
||||
|
||||
```python client.py
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
from contextlib import AsyncExitStack
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
from anthropic import Anthropic
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv() # load environment variables from .env.
|
||||
|
||||
class ClaudeCLIChatbot:
|
||||
def __init__(self):
|
||||
self.session: Optional[ClientSession] = None
|
||||
self.exit_stack = AsyncExitStack()
|
||||
# Initialize Anthropic client
|
||||
self.anthropic = Anthropic()
|
||||
|
||||
async def connect_to_time_server(self, server_script_path: str):
|
||||
"""Connect to the MCP time server"""
|
||||
server_params = StdioServerParameters(
|
||||
command="python",
|
||||
args=[server_script_path],
|
||||
env=None
|
||||
)
|
||||
|
||||
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
||||
self.stdio, self.write = stdio_transport
|
||||
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
|
||||
|
||||
await self.session.initialize()
|
||||
|
||||
response = await self.session.list_tools()
|
||||
tools = response.tools
|
||||
print("\nConnected to time server with tools:", [tool.name for tool in tools])
|
||||
|
||||
async def process_query(self, query: str) -> str:
|
||||
"""Use Claude to understand the query and call appropriate tools"""
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": query
|
||||
}
|
||||
]
|
||||
|
||||
response = await self.session.list_tools()
|
||||
available_tools = [{
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"input_schema": tool.inputSchema
|
||||
} for tool in response.tools]
|
||||
|
||||
# Make the initial call to the Anthropic API
|
||||
response = self.anthropic.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
messages=messages,
|
||||
tools=available_tools
|
||||
)
|
||||
|
||||
# Process each content block in the response
|
||||
tool_results = []
|
||||
final_text = []
|
||||
|
||||
for content in response.content:
|
||||
if content.type == 'text':
|
||||
final_text.append(content.text)
|
||||
elif content.type == 'tool_use':
|
||||
tool_name = content.name
|
||||
tool_args = content.input
|
||||
|
||||
# Call the tool and get result
|
||||
result = await self.session.call_tool(tool_name, tool_args)
|
||||
tool_results.append({"call": tool_name, "result": result})
|
||||
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
|
||||
|
||||
# Continue conversation with tool results
|
||||
if hasattr(content, 'text') and content.text:
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": content.text
|
||||
})
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": result.content
|
||||
})
|
||||
|
||||
# Get next response from Claude
|
||||
response = self.anthropic.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
final_text.append(response.content[0].text)
|
||||
|
||||
# Return combined output
|
||||
return "\n".join(final_text)
|
||||
|
||||
async def chat_loop(self):
|
||||
"""Main chat loop"""
|
||||
print("\nWelcome to the Time Assistant!")
|
||||
print("Ask me anything about times and timezones. For example:")
|
||||
print("- What time is it now?")
|
||||
print("- What time is it in Tokyo?")
|
||||
print("- When it's 4 PM in New York, what time is it in London?")
|
||||
print("- Convert 9:30 AM Tokyo time to New York time")
|
||||
print("\nType 'quit' to exit.")
|
||||
|
||||
while True:
|
||||
try:
|
||||
query = input("\nWhat would you like to know? ").strip()
|
||||
|
||||
if query.lower() == 'quit':
|
||||
break
|
||||
|
||||
response = await self.process_query(query)
|
||||
print("\n" + response)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError: {str(e)}")
|
||||
|
||||
async def cleanup(self):
|
||||
"""Cleanup resources"""
|
||||
await self.exit_stack.aclose()
|
||||
|
||||
async def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python chatbot.py <path_to_server_script>")
|
||||
sys.exit(1)
|
||||
|
||||
chatbot = ClaudeCLIChatbot()
|
||||
try:
|
||||
await chatbot.connect_to_time_server(sys.argv[1])
|
||||
await chatbot.chat_loop()
|
||||
finally:
|
||||
await chatbot.cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Creating the server (server.py)
|
||||
|
||||
Next, let's create our server file that will provide time-related tools to our client. The original source code for the MCP time server can be [found here](https://github.com/modelcontextprotocol/servers/tree/main/src/time).
|
||||
|
||||
Create a new file called `server.py` in your project directory.
|
||||
Then add the cache, server instance and the base URL for the NWS API:
|
||||
|
||||
```python
|
||||
touch server.py
|
||||
# Cache weather data
|
||||
weather_cache: dict[str, Any] = {}
|
||||
NWS_API_BASE = "https://api.weather.gov"
|
||||
|
||||
server = Server("weather")
|
||||
```
|
||||
|
||||
The server will expose two tools:
|
||||
- `get_current_time`: Gets the current time in a specified timezone
|
||||
- `convert_time`: Converts times between different timezones
|
||||
### Implementing tool listing
|
||||
|
||||
Here's the complete server code with explanations:
|
||||
We need to tell clients what tools are available. The `list_tools()` decorator registers this handler:
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
import json
|
||||
from mcp.server import Server
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.types import Tool, TextContent
|
||||
@server.list_tools()
|
||||
async def handle_list_tools() -> list[types.Tool]:
|
||||
return [
|
||||
types.Tool(
|
||||
name="get-weather",
|
||||
description="Get weather data for a location",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"latitude": {"type": "number"},
|
||||
"longitude": {"type": "number"},
|
||||
},
|
||||
"required": ["latitude", "longitude"],
|
||||
},
|
||||
)
|
||||
]
|
||||
```
|
||||
|
||||
async def run_server():
|
||||
# Create server instance
|
||||
server = Server("time-server")
|
||||
This defines a single tool that requires latitude and longitude parameters.
|
||||
|
||||
# Define our tools
|
||||
@server.list_tools()
|
||||
async def list_tools() -> list[Tool]:
|
||||
### Implementing tool execution
|
||||
|
||||
The tool execution handler processes requests to get weather data:
|
||||
|
||||
```python
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(
|
||||
name: str,
|
||||
arguments: dict | None
|
||||
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
||||
# Validate tool name and arguments
|
||||
if name != "get-weather":
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
if not arguments:
|
||||
raise ValueError("Missing arguments")
|
||||
|
||||
lat = arguments.get("latitude")
|
||||
lon = arguments.get("longitude")
|
||||
if lat is None or lon is None:
|
||||
raise ValueError("Missing latitude or longitude")
|
||||
|
||||
# Fetch weather data
|
||||
async with httpx.AsyncClient() as client:
|
||||
# Get grid point
|
||||
points_url = f"{NWS_API_BASE}/points/{lat},{lon}"
|
||||
points_response = await client.get(
|
||||
points_url,
|
||||
headers={"Accept": "application/json"}
|
||||
)
|
||||
points_data = points_response.json()
|
||||
|
||||
# Get forecast
|
||||
forecast_url = points_data["properties"]["forecast"]
|
||||
forecast_response = await client.get(
|
||||
forecast_url,
|
||||
headers={"Accept": "application/json"}
|
||||
)
|
||||
forecast_data = forecast_response.json()
|
||||
|
||||
# Cache the forecast data
|
||||
location_key = f"{lat},{lon}"
|
||||
weather_cache[location_key] = forecast_data
|
||||
|
||||
# Notify clients of resource changes
|
||||
await server.request_context.session.send_resource_list_changed()
|
||||
|
||||
return [
|
||||
Tool(
|
||||
name="get_current_time",
|
||||
description="Get current time in a specific timezone",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"timezone": {
|
||||
"type": "string",
|
||||
"description": "IANA timezone name (e.g., 'UTC', 'America/New_York')"
|
||||
}
|
||||
},
|
||||
"required": ["timezone"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="convert_time",
|
||||
description="Convert time between timezones",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source_timezone": {
|
||||
"type": "string",
|
||||
"description": "Source timezone"
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"description": "Time in HH:MM format"
|
||||
},
|
||||
"target_timezone": {
|
||||
"type": "string",
|
||||
"description": "Target timezone"
|
||||
}
|
||||
},
|
||||
"required": ["source_timezone", "time", "target_timezone"]
|
||||
}
|
||||
types.TextContent(
|
||||
type="text",
|
||||
text=f"Retrieved weather data for location ({lat}, {lon})",
|
||||
)
|
||||
]
|
||||
|
||||
@server.call_tool()
|
||||
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
||||
try:
|
||||
if name == "get_current_time":
|
||||
timezone = ZoneInfo(arguments["timezone"])
|
||||
current_time = datetime.now(timezone)
|
||||
result = {
|
||||
"time": current_time.isoformat(),
|
||||
"timezone": str(timezone)
|
||||
}
|
||||
|
||||
elif name == "convert_time":
|
||||
source_tz = ZoneInfo(arguments["source_timezone"])
|
||||
target_tz = ZoneInfo(arguments["target_timezone"])
|
||||
|
||||
# Parse time
|
||||
hour, minute = map(int, arguments["time"].split(":"))
|
||||
source_time = datetime.now(source_tz).replace(
|
||||
hour=hour,
|
||||
minute=minute
|
||||
)
|
||||
|
||||
# Convert to target timezone
|
||||
target_time = source_time.astimezone(target_tz)
|
||||
|
||||
result = {
|
||||
"source_time": source_time.isoformat(),
|
||||
"target_time": target_time.isoformat(),
|
||||
"source_timezone": str(source_tz),
|
||||
"target_timezone": str(target_tz)
|
||||
}
|
||||
else:
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=json.dumps(result, indent=2)
|
||||
)]
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error processing request: {str(e)}")
|
||||
|
||||
# Initialize and run the server
|
||||
options = server.create_initialization_options()
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await server.run(read_stream, write_stream, options)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
asyncio.run(run_server())
|
||||
```
|
||||
|
||||
### Understanding the server code
|
||||
### Running the server
|
||||
|
||||
The server code consists of three main parts:
|
||||
Finally, implement the main function to run the server:
|
||||
|
||||
1. **Tool Definitions**: The `list_tools()` function defines what tools are available and their input schemas. Each tool has:
|
||||
- A name
|
||||
- A description
|
||||
- An input schema that defines what parameters it accepts
|
||||
|
||||
2. **Tool Implementation**: The `call_tool()` function contains the actual logic for each tool:
|
||||
- `get_current_time`: Returns the current time in a specified timezone
|
||||
- `convert_time`: Converts a time from one timezone to another
|
||||
|
||||
3. **Server Setup**: The main server setup code that initializes the MCP server and starts listening for requests
|
||||
|
||||
### Running the application
|
||||
|
||||
1. Make sure both files are in your project directory:
|
||||
```
|
||||
mcp-quickstart/
|
||||
├── /venv
|
||||
├── .env
|
||||
├── client.py
|
||||
└── server.py
|
||||
```python
|
||||
async def main():
|
||||
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
InitializationOptions(
|
||||
server_name="weather",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
2. Run the client, passing the server script as an argument:
|
||||
Your server is complete! Let's now test your server from an existing MCP host, Claude for Desktop.
|
||||
|
||||
## Testing your server with Claude for Desktop
|
||||
|
||||
<Note>
|
||||
Unfortunately, Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/tutorials/building-a-client) tutorial for a workaround.
|
||||
</Note>
|
||||
|
||||
First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download)
|
||||
|
||||
Open your Claude Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
|
||||
|
||||
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="MacOS/Linux">
|
||||
```bash
|
||||
python client.py server.py
|
||||
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
```powershell
|
||||
code $env:AppData\Claude\claude_desktop_config.json
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
3. You can now ask questions like:
|
||||
|
||||
Add this configuration (replace the parent folder path):
|
||||
|
||||
<Tabs>
|
||||
<Tab title="MacOS/Linux">
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"--directory",
|
||||
"/PATH/TO/PARENT/FOLDER/mcp-quickstart",
|
||||
"run",
|
||||
"weather"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
What time is it in New York?
|
||||
What time was it an hour ago in Sydney?
|
||||
Convert 3:30 PM PST to EST
|
||||
When it's 9 AM in London, what time is it in Sydney?
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"--directory",
|
||||
"C:\\PATH\TO\PARENT\FOLDER\mcp-quickstart",
|
||||
"run",
|
||||
"weather"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
This tells Claude Desktop:
|
||||
1. There's an MCP server named "weather"
|
||||
2. Launch it by running `uv --directory /PATH/TO/PARENT/FOLDER/mcp-quickstart run weather`
|
||||
|
||||
Save the file, and restart **Claude Desktop**.
|
||||
- Test with commands
|
||||
- You can now test your server by running the following commands in Claude Desktop:
|
||||
|
||||
What's the weather in Brooklyn right now?
|
||||
|
||||
What's the weather in San Francisco?
|
||||
|
||||
<Frame>
|
||||
<img src="/images/time-server-quickstart.png" />
|
||||
<img src="/images/quickstart-weather.png" />
|
||||
</Frame>
|
||||
|
||||
### What's happening under the hood
|
||||
<Note>
|
||||
Since this is the US National Weather service, the queries will only work for US locations.
|
||||
</Note>
|
||||
|
||||
## What's happening under the hood
|
||||
|
||||
When you ask a question:
|
||||
|
||||
|
|
@ -603,12 +431,4 @@ When you ask a question:
|
|||
5. Claude formulates a natural language response
|
||||
6. The response is displayed to you!
|
||||
|
||||
### Next steps
|
||||
|
||||
Now that you have a basic MCP application working, you can:
|
||||
1. Add more tools to the server
|
||||
2. Enhance the error handling
|
||||
3. Add support for more time formats
|
||||
4. Expand the functionality to handle more complex queries
|
||||
|
||||
This example demonstrates the power of MCP in connecting LLMs with external tools and data sources. You can use this pattern to build all sorts of applications that combine Claude's natural language understanding with custom tools and services.
|
||||
## Next steps
|
||||
289
tutorials/building-a-client.mdx
Normal file
289
tutorials/building-a-client.mdx
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
---
|
||||
title: "Building a MCP client"
|
||||
description: "Learn how to build your first client in MCP"
|
||||
---
|
||||
|
||||
In this tutorial, you'll learn how to build a client application that connects to MCP servers.
|
||||
|
||||
## Prerequisite Knowledge
|
||||
|
||||
This tutorial assumes familiarity with:
|
||||
- Python
|
||||
- LLMs like Claude
|
||||
|
||||
It helps to understand [tool use (aka function calling)](https://docs.anthropic.com/en/docs/build-with-claude/tool-use), but it's not required.
|
||||
|
||||
## System Requirements
|
||||
|
||||
Before starting, ensure your system meets these requirements:
|
||||
- Mac or Windows computer
|
||||
- Latest Python version installed
|
||||
|
||||
## Setting Up Your Environment
|
||||
|
||||
First, create and activate a virtual environment:
|
||||
|
||||
```bash
|
||||
# Install virtualenv if needed
|
||||
pip install virtualenv
|
||||
|
||||
# Create project directory
|
||||
mkdir mcp-client
|
||||
cd mcp-client
|
||||
|
||||
# Create virtual environment
|
||||
python -m virtualenv venv
|
||||
|
||||
# Activate virtual environment
|
||||
# On Windows:
|
||||
venv\Scripts\activate
|
||||
# On Unix or MacOS:
|
||||
source venv/bin/activate
|
||||
|
||||
# Install required packages
|
||||
pip install mcp anthropic python-dotenv
|
||||
```
|
||||
|
||||
## Setting Up Your API Key
|
||||
|
||||
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
|
||||
|
||||
Create a `.env` file to store it:
|
||||
|
||||
```bash
|
||||
# Create .env file
|
||||
touch .env
|
||||
```
|
||||
|
||||
Add your key to the `.env` file:
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=<your key here>
|
||||
```
|
||||
|
||||
Add `.env` to your `.gitignore`:
|
||||
```bash
|
||||
echo ".env" >> .gitignore
|
||||
```
|
||||
|
||||
## Creating the Client
|
||||
|
||||
Let's create a Python client that can connect to any MCP server. We'll call it `client.py`:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
from contextlib import AsyncExitStack
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
from anthropic import Anthropic
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv() # load environment variables from .env
|
||||
|
||||
class MCPClient:
|
||||
def __init__(self):
|
||||
# Initialize session and client objects
|
||||
self.session: Optional[ClientSession] = None
|
||||
self.exit_stack = AsyncExitStack()
|
||||
self.anthropic = Anthropic()
|
||||
|
||||
async def connect_to_server(self, server_script_path: str):
|
||||
"""Connect to an MCP server"""
|
||||
server_params = StdioServerParameters(
|
||||
command="python",
|
||||
args=[server_script_path],
|
||||
env=None
|
||||
)
|
||||
|
||||
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
||||
self.stdio, self.write = stdio_transport
|
||||
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
|
||||
|
||||
await self.session.initialize()
|
||||
|
||||
# List available tools
|
||||
response = await self.session.list_tools()
|
||||
tools = response.tools
|
||||
print("\nConnected to server with tools:", [tool.name for tool in tools])
|
||||
|
||||
async def process_query(self, query: str) -> str:
|
||||
"""Process a query using Claude and available tools"""
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": query
|
||||
}
|
||||
]
|
||||
|
||||
response = await self.session.list_tools()
|
||||
available_tools = [{
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"input_schema": tool.inputSchema
|
||||
} for tool in response.tools]
|
||||
|
||||
# Initial Claude API call
|
||||
response = self.anthropic.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
messages=messages,
|
||||
tools=available_tools
|
||||
)
|
||||
|
||||
# Process response and handle tool calls
|
||||
tool_results = []
|
||||
final_text = []
|
||||
|
||||
for content in response.content:
|
||||
if content.type == 'text':
|
||||
final_text.append(content.text)
|
||||
elif content.type == 'tool_use':
|
||||
tool_name = content.name
|
||||
tool_args = content.input
|
||||
|
||||
# Execute tool call
|
||||
result = await self.session.call_tool(tool_name, tool_args)
|
||||
tool_results.append({"call": tool_name, "result": result})
|
||||
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
|
||||
|
||||
# Continue conversation with tool results
|
||||
if hasattr(content, 'text') and content.text:
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": content.text
|
||||
})
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": result.content
|
||||
})
|
||||
|
||||
# Get next response from Claude
|
||||
response = self.anthropic.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
final_text.append(response.content[0].text)
|
||||
|
||||
return "\n".join(final_text)
|
||||
|
||||
async def chat_loop(self):
|
||||
"""Run an interactive chat loop"""
|
||||
print("\nMCP Client Started!")
|
||||
print("Type your queries or 'quit' to exit.")
|
||||
|
||||
while True:
|
||||
try:
|
||||
query = input("\nQuery: ").strip()
|
||||
|
||||
if query.lower() == 'quit':
|
||||
break
|
||||
|
||||
response = await self.process_query(query)
|
||||
print("\n" + response)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError: {str(e)}")
|
||||
|
||||
async def cleanup(self):
|
||||
"""Clean up resources"""
|
||||
await self.exit_stack.aclose()
|
||||
|
||||
async def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python client.py <path_to_server_script>")
|
||||
sys.exit(1)
|
||||
|
||||
client = MCPClient()
|
||||
try:
|
||||
await client.connect_to_server(sys.argv[1])
|
||||
await client.chat_loop()
|
||||
finally:
|
||||
await client.cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Understanding the Client Code
|
||||
|
||||
Let's break down the key components of our MCP client:
|
||||
|
||||
1. **MCPClient Class**
|
||||
- Manages the connection to the MCP server
|
||||
- Handles communication between Claude and the server's tools
|
||||
- Maintains the chat session
|
||||
|
||||
2. **Key Methods**
|
||||
- `connect_to_server()`: Establishes connection with an MCP server
|
||||
- `process_query()`: Handles user queries by:
|
||||
- Getting available tools from the server
|
||||
- Sending the query to Claude with tool information
|
||||
- Executing any tool calls Claude requests
|
||||
- Processing Claude's responses
|
||||
- `chat_loop()`: Runs the interactive chat interface
|
||||
- `cleanup()`: Properly closes connections and resources
|
||||
|
||||
## Running the Client
|
||||
|
||||
To run your client with any MCP server:
|
||||
|
||||
```bash
|
||||
python client.py path/to/server.py
|
||||
```
|
||||
|
||||
The client will:
|
||||
1. Connect to the specified server
|
||||
2. List available tools
|
||||
3. Start an interactive chat session where you can:
|
||||
- Enter queries
|
||||
- See tool executions
|
||||
- Get responses from Claude
|
||||
|
||||
## How It Works
|
||||
|
||||
When you submit a query:
|
||||
|
||||
1. The client gets the list of available tools from the server
|
||||
2. Your query is sent to Claude along with tool descriptions
|
||||
3. Claude decides which tools (if any) to use
|
||||
4. The client executes any requested tool calls through the server
|
||||
5. Results are sent back to Claude
|
||||
6. Claude provides a natural language response
|
||||
7. The response is displayed to you
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Error Handling**
|
||||
- Always wrap tool calls in try-catch blocks
|
||||
- Provide meaningful error messages
|
||||
- Gracefully handle connection issues
|
||||
|
||||
2. **Resource Management**
|
||||
- Use `AsyncExitStack` for proper cleanup
|
||||
- Close connections when done
|
||||
- Handle server disconnections
|
||||
|
||||
3. **Security**
|
||||
- Store API keys securely in `.env`
|
||||
- Validate server responses
|
||||
- Be cautious with tool permissions
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a basic MCP client, you can:
|
||||
1. Add support for different server types
|
||||
2. Enhance error handling and recovery
|
||||
3. Implement more sophisticated conversation handling
|
||||
4. Add authentication and security features
|
||||
5. Create a better user interface
|
||||
|
||||
## Resources
|
||||
|
||||
- [MCP Documentation](https://modelcontextprotocol.github.io/)
|
||||
- [Claude API Documentation](https://docs.anthropic.com/claude/reference)
|
||||
- [Tool Use Guide](https://docs.anthropic.com/claude/docs/function-calling)
|
||||
|
|
@ -1,394 +0,0 @@
|
|||
---
|
||||
title: "Set up MCP on Claude Desktop"
|
||||
description: "How to set up MCP servers on Claude Desktop"
|
||||
---
|
||||
|
||||
MCP is a protocol that enables secure connections between host applications, such as [Claude Desktop](https://claude.ai/download), and local services. In this short tutorial, you'll learn how to:
|
||||
|
||||
<Note>
|
||||
While this tutorial focuses on using Claude Desktop as an example MCP client, the protocol is open and can be integrated by any application. IDEs, AI tools, and other software can all use MCP to connect to local integrations in a standardized way.
|
||||
</Note>
|
||||
|
||||
<Warning>
|
||||
Claude Desktop's MCP support is currently in developer preview and only supports connecting to local MCP servers running on your machine. Remote MCP connections are not yet supported. This integration is only available in the Claude Desktop app, not the Claude web interface (claude.ai).
|
||||
</Warning>
|
||||
|
||||
### In This Guide
|
||||
|
||||
For this tutorial, we'll implement a focused example using SQLite:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Your Computer"
|
||||
direction LR
|
||||
Claude["Claude Desktop"]
|
||||
MCP["SQLite MCP Server"]
|
||||
DB[(SQLite Database\n~/test.db)]
|
||||
|
||||
Claude <-->|"MCP Protocol\n(Queries & Results)"| MCP
|
||||
MCP <-->|"Local Access\n(SQL Operations)"| DB
|
||||
end
|
||||
```
|
||||
|
||||
1. Claude Desktop acts as our MCP client
|
||||
2. A SQLite MCP Server provides secure database access
|
||||
3. Your local SQLite database stores the actual data
|
||||
|
||||
The communication between the SQLite MCP server and your local SQLite database happens entirely on your machine—your SQLite database is not exposed to the internet. The Model Context Protocol ensures that Claude Desktop can only perform approved database operations through well-defined interfaces. This gives you a secure way to let Claude analyze and interact with your local data while maintaining complete control over what it can access.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- macOS or Windows
|
||||
- The latest version of [Claude Desktop](https://claude.ai/download) installed
|
||||
- [uv](https://docs.astral.sh/uv/) 0.4.18 or higher (`uv --version` to check)
|
||||
- Git (`git --version` to check)
|
||||
- SQLite (`sqlite3 --version` to check)
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Installing prerequisites (macOS)">
|
||||
```bash
|
||||
# Using Homebrew
|
||||
brew install uv git sqlite3
|
||||
|
||||
# Or download directly:
|
||||
# uv: https://docs.astral.sh/uv/
|
||||
# Git: https://git-scm.com
|
||||
# SQLite: https://www.sqlite.org/download.html
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Installing prerequisites (Windows)">
|
||||
```powershell
|
||||
# Using winget
|
||||
winget install --id=astral-sh.uv -e
|
||||
winget install git.git sqlite.sqlite
|
||||
|
||||
# Or download directly:
|
||||
# uv: https://docs.astral.sh/uv/
|
||||
# Git: https://git-scm.com
|
||||
# SQLite: https://www.sqlite.org/download.html
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
<Steps>
|
||||
<Step title="Create a sample database">
|
||||
Let's create a simple SQLite database for testing:
|
||||
|
||||
```bash
|
||||
# Create a new SQLite database
|
||||
sqlite3 ~/test.db <<EOF
|
||||
CREATE TABLE products (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
price REAL
|
||||
);
|
||||
|
||||
INSERT INTO products (name, price) VALUES
|
||||
('Widget', 19.99),
|
||||
('Gadget', 29.99),
|
||||
('Gizmo', 39.99),
|
||||
('Smart Watch', 199.99),
|
||||
('Wireless Earbuds', 89.99),
|
||||
('Portable Charger', 24.99),
|
||||
('Bluetooth Speaker', 79.99),
|
||||
('Phone Stand', 15.99),
|
||||
('Laptop Sleeve', 34.99),
|
||||
('Mini Drone', 299.99),
|
||||
('LED Desk Lamp', 45.99),
|
||||
('Keyboard', 129.99),
|
||||
('Mouse Pad', 12.99),
|
||||
('USB Hub', 49.99),
|
||||
('Webcam', 69.99),
|
||||
('Screen Protector', 9.99),
|
||||
('Travel Adapter', 27.99),
|
||||
('Gaming Headset', 159.99),
|
||||
('Fitness Tracker', 119.99),
|
||||
('Portable SSD', 179.99);
|
||||
EOF
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Claude Desktop">
|
||||
Open your Claude Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
|
||||
|
||||
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
|
||||
|
||||
```bash
|
||||
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
||||
```
|
||||
|
||||
Add this configuration (replace YOUR_USERNAME with your actual username):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"sqlite": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-server-sqlite", "--db-path", "/Users/YOUR_USERNAME/test.db"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This tells Claude Desktop:
|
||||
1. There's an MCP server named "sqlite"
|
||||
2. Launch it by running `uvx mcp-server-sqlite`
|
||||
3. Connect it to your test database
|
||||
|
||||
Save the file, and restart **Claude Desktop**.
|
||||
</Step>
|
||||
</Steps>
|
||||
</Tab>
|
||||
<Tab title="Windows">
|
||||
<Steps>
|
||||
<Step title="Create a sample database">
|
||||
Let's create a simple SQLite database for testing:
|
||||
|
||||
```powershell
|
||||
# Create a new SQLite database
|
||||
$sql = @'
|
||||
CREATE TABLE products (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
price REAL
|
||||
);
|
||||
|
||||
INSERT INTO products (name, price) VALUES
|
||||
('Widget', 19.99),
|
||||
('Gadget', 29.99),
|
||||
('Gizmo', 39.99),
|
||||
('Smart Watch', 199.99),
|
||||
('Wireless Earbuds', 89.99),
|
||||
('Portable Charger', 24.99),
|
||||
('Bluetooth Speaker', 79.99),
|
||||
('Phone Stand', 15.99),
|
||||
('Laptop Sleeve', 34.99),
|
||||
('Mini Drone', 299.99),
|
||||
('LED Desk Lamp', 45.99),
|
||||
('Keyboard', 129.99),
|
||||
('Mouse Pad', 12.99),
|
||||
('USB Hub', 49.99),
|
||||
('Webcam', 69.99),
|
||||
('Screen Protector', 9.99),
|
||||
('Travel Adapter', 27.99),
|
||||
('Gaming Headset', 159.99),
|
||||
('Fitness Tracker', 119.99),
|
||||
('Portable SSD', 179.99);
|
||||
'@
|
||||
|
||||
cd ~
|
||||
& sqlite3 test.db $sql
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Claude Desktop">
|
||||
Open your Claude Desktop App configuration at `%APPDATA%\Claude\claude_desktop_config.json` in a text editor.
|
||||
|
||||
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
|
||||
|
||||
```powershell
|
||||
code $env:AppData\Claude\claude_desktop_config.json
|
||||
```
|
||||
|
||||
Add this configuration (replace YOUR_USERNAME with your actual username):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"sqlite": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"mcp-server-sqlite",
|
||||
"--db-path",
|
||||
"C:\\Users\\YOUR_USERNAME\\test.db"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This tells Claude Desktop:
|
||||
1. There's an MCP server named "sqlite"
|
||||
2. Launch it by running `uvx mcp-server-sqlite`
|
||||
3. Connect it to your test database
|
||||
|
||||
Save the file, and restart **Claude Desktop**.
|
||||
</Step>
|
||||
</Steps>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Test it out
|
||||
|
||||
Let's verify everything is working. Try sending this prompt to Claude Desktop:
|
||||
|
||||
```
|
||||
Can you connect to my SQLite database and tell me what products are available, and their prices?
|
||||
```
|
||||
|
||||
Claude Desktop will:
|
||||
1. Connect to the SQLite MCP server
|
||||
2. Query your local database
|
||||
3. Format and present the results
|
||||
|
||||
<Frame caption="Claude Desktop successfully queries our SQLite database 🎉">
|
||||
<img src="/images/quickstart-screenshot.png" alt="Example Claude Desktop conversation showing database query results" />
|
||||
</Frame>
|
||||
|
||||
## What's happening under the hood?
|
||||
|
||||
When you interact with Claude Desktop using MCP:
|
||||
|
||||
1. **Server Discovery**: Claude Desktop connects to your configured MCP servers on startup
|
||||
2. **Protocol Handshake**: When you ask about data, Claude Desktop:
|
||||
- Identifies which MCP server can help (sqlite in this case)
|
||||
- Negotiates capabilities through the protocol
|
||||
- Requests data or actions from the MCP server
|
||||
|
||||
3. **Interaction Flow**:
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Claude Desktop
|
||||
participant M as MCP Server
|
||||
participant D as SQLite DB
|
||||
|
||||
C->>M: Initialize connection
|
||||
M-->>C: Available capabilities
|
||||
|
||||
C->>M: Query request
|
||||
M->>D: SQL query
|
||||
D-->>M: Results
|
||||
M-->>C: Formatted results
|
||||
```
|
||||
|
||||
4. **Security**:
|
||||
- MCP servers only expose specific, controlled capabilities
|
||||
- MCP servers run locally on your machine, and the resources they access are not exposed to the internet
|
||||
- Claude Desktop requires user confirmation for sensitive operations
|
||||
|
||||
## Try these examples
|
||||
|
||||
Now that MCP is working, try these increasingly powerful examples:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Basic Queries" active>
|
||||
```
|
||||
What's the average price of all products in the database?
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Data Analysis">
|
||||
```
|
||||
Can you analyze the price distribution and suggest any pricing optimizations?
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Complex Operations">
|
||||
```
|
||||
Could you help me design and create a new table for storing customer orders?
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Add more capabilities
|
||||
|
||||
Want to give Claude Desktop more local integration capabilities? Add these servers to your configuration:
|
||||
|
||||
<Note>
|
||||
Note that these MCP servers will require [Node.js](https://nodejs.org/en) to be installed on your machine.
|
||||
</Note>
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="File System Access" icon="folder-open">
|
||||
Add this to your config to let Claude Desktop read and analyze files:
|
||||
```json
|
||||
"filesystem": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/YOUR_USERNAME/Desktop"]
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="PostgreSQL Connection" icon="database">
|
||||
Connect Claude Desktop to your PostgreSQL database:
|
||||
```json
|
||||
"postgres": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"]
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## More MCP Clients
|
||||
|
||||
While this guide demonstrates MCP using Claude Desktop as a client, several other applications support MCP integration:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Zed Editor"
|
||||
icon="pen-to-square"
|
||||
href="https://zed.dev">
|
||||
A high-performance, multiplayer code editor with built-in MCP support for AI-powered coding assistance
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="Cody"
|
||||
icon="magnifying-glass"
|
||||
href="https://sourcegraph.com/cody">
|
||||
Code intelligence platform featuring MCP integration for enhanced code search and analysis capabilities
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
Each host application may implement MCP features differently or support different capabilities. Check out our [clients](/clients) documentation for more information.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Nothing showing up in Claude Desktop?">
|
||||
1. Check if MCP is enabled:
|
||||
- Click the 🔌 icon in Claude Desktop, next to the chat box
|
||||
- Expand "Installed MCP Servers"
|
||||
- You should see your configured servers
|
||||
|
||||
2. Verify your config:
|
||||
- From Claude Desktop, go to Claude > Settings…
|
||||
- Open the "Developer" tab to see your configuration
|
||||
|
||||
3. Restart Claude Desktop completely:
|
||||
- Quit the app (not just close the window)
|
||||
- Start it again
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="MCP or database errors?">
|
||||
1. Check Claude Desktop's logs:
|
||||
```bash
|
||||
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
|
||||
```
|
||||
|
||||
2. Verify database access:
|
||||
```bash
|
||||
# Test database connection
|
||||
sqlite3 ~/test.db ".tables"
|
||||
```
|
||||
|
||||
3. Common fixes:
|
||||
- Check file paths in your config
|
||||
- Verify database file permissions
|
||||
- Ensure SQLite is installed properly
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Next steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Build your first MCP client and server" icon="code" href="/quickstart">
|
||||
Build your first MCP client and server from scratch in less than 5 minutes.
|
||||
</Card>
|
||||
|
||||
<Card title="Explore examples" icon="github" href="https://github.com/modelcontextprotocol/servers">
|
||||
Browse our collection of example servers to see what's possible.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
Loading…
Reference in a new issue