-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #313 from DefangLabs/mcp-with-ui-proxy
MCP Sample
- Loading branch information
Showing
29 changed files
with
8,979 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
FROM mcr.microsoft.com/devcontainers/typescript-node:22-bookworm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"build": { | ||
"dockerfile": "Dockerfile", | ||
"context": ".." | ||
}, | ||
"features": { | ||
"ghcr.io/defanglabs/devcontainer-feature/defang-cli:1.0.4": {}, | ||
"ghcr.io/devcontainers/features/docker-in-docker:2": {}, | ||
"ghcr.io/devcontainers/features/aws-cli:1": {} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: Deploy | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
deploy: | ||
environment: playground | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: read | ||
id-token: write | ||
|
||
steps: | ||
- name: Checkout Repo | ||
uses: actions/checkout@v4 | ||
|
||
- name: Deploy | ||
uses: DefangLabs/[email protected] | ||
with: | ||
config-env-vars: ANTHROPIC_API_KEY | ||
env: | ||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# MCP | ||
|
||
[![1-click-deploy](https://defang.io/deploy-with-defang.png)](https://portal.defang.dev/redirect?url=https%3A%2F%2Fgithub.com%2Fnew%3Ftemplate_name%3Dsample-mcp-template%26template_owner%3DDefangSamples) | ||
|
||
This is a sample of an MCP (Model Context Protocol) chatbot application built with Next.js, Python, and Anthropic Claude, deployed using Defang. | ||
|
||
This example uses Docker's [`mcp/time`](https://hub.docker.com/r/mcp/time) image as a base for the MCP server (with MCP tools included), but it can be adapted to use any of the Docker [MCP images](https://hub.docker.com/u/mcp). | ||
|
||
### How It Works | ||
|
||
The [MCP client](https://modelcontextprotocol.io/quickstart/client) is written in Python and ran in a `venv` virtual environment. The MCP server is provided by the Docker `mcp/time` image. The MCP server communicates with the MCP client in a Quart app (i.e. Asynchronous Server Gateway Interface (ASGI) version of Flask) through the `stdio` transport method, as seen in `/mcp-server/main.py`. For more on MCP transport methods, see [here](https://modelcontextprotocol.io/docs/concepts/transports). | ||
|
||
The UI (User Interface) and web server are built in Next.js (see `/ui/src/app`). The web server includes a forwarding action to connect to the MCP client. | ||
|
||
Here's a breakdown of what happens when you interact with the UI: | ||
1. When a user submits a query to the chatbot, the browser sends a request to the Next.js web server. | ||
2. The Next.js web server will forward this request to the MCP client via a REST endpoint. | ||
3. The MCP client processes the request by interacting with the Anthropic (Claude) API and tools available through the MCP server. | ||
4. Once the response is generated, it is sent back to the Next.js web server and displayed to the user in the UI. | ||
|
||
## Prerequisites | ||
|
||
1. Download [Defang CLI](https://github.com/DefangLabs/defang) | ||
2. (Optional) If you are using [Defang BYOC](https://docs.defang.io/docs/concepts/defang-byoc) authenticate with your cloud provider account | ||
3. (Optional for local development) [Docker CLI](https://docs.docker.com/engine/install/) | ||
|
||
## Development | ||
|
||
To run the application locally, you can use the following command: | ||
|
||
```bash | ||
docker compose up --build | ||
``` | ||
|
||
## Configuration | ||
For this sample, you will need to provide the following [configuration](https://docs.defang.io/docs/concepts/configuration): | ||
|
||
> Note that if you are using the 1-click deploy option, you can set these values as secrets in your GitHub repository and the action will automatically deploy them for you. | ||
### `ANTHROPIC_API_KEY` | ||
An API key for accessing the [Anthropic Claude API](https://docs.anthropic.com/en/api/getting-started). | ||
```bash | ||
defang config set ANTHROPIC_API_KEY | ||
``` | ||
|
||
## Deployment | ||
|
||
> [!NOTE] | ||
> Download [Defang CLI](https://github.com/DefangLabs/defang) | ||
### Defang Playground | ||
|
||
Deploy your application to the Defang Playground by opening up your terminal and typing: | ||
```bash | ||
defang compose up | ||
``` | ||
|
||
### BYOC | ||
|
||
If you want to deploy to your own cloud account, you can [use Defang BYOC](https://docs.defang.io/docs/tutorials/deploy-to-your-cloud). | ||
|
||
--- | ||
|
||
Title: MCP | ||
|
||
Short Description: An MCP (Model Context Protocol) chatbot assistant built with Next.js, Python, and Anthropic Claude. | ||
|
||
Tags: MCP, Next.js, Python, Quart, Claude, AI, Anthropic, TypeScript, React, JavaScript | ||
|
||
Languages: nodejs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
services: | ||
ui: | ||
# uncomment to add your own domain | ||
# domainname: example.com | ||
build: | ||
context: ./ui | ||
dockerfile: Dockerfile | ||
ports: | ||
- target: 3000 | ||
published: 3000 | ||
mode: ingress | ||
deploy: | ||
resources: | ||
reservations: | ||
memory: 256M | ||
environment: | ||
- MCP_SERVER_URL=http://mcp-server:8000 | ||
healthcheck: | ||
test: ["CMD", "curl", "-f", "http://localhost:3000/"] | ||
|
||
mcp-server: | ||
build: | ||
context: ./mcp-server | ||
dockerfile: Dockerfile | ||
ports: | ||
- target: 8000 | ||
published: 8000 | ||
mode: host | ||
environment: | ||
- ANTHROPIC_API_KEY |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
venv | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.env | ||
venv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
FROM mcp/time:latest | ||
|
||
WORKDIR /wrapper | ||
|
||
COPY requirements.txt ./requirements.txt | ||
|
||
# create a virtual environment | ||
RUN python3 -m venv venv && \ | ||
# activate the virtual environment | ||
. venv/bin/activate && \ | ||
pip3 install --upgrade pip && \ | ||
pip3 install -r requirements.txt && \ | ||
# deactivate the virtual environment | ||
deactivate | ||
|
||
COPY . . | ||
|
||
ENTRYPOINT ["./run.sh"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import asyncio | ||
import os | ||
from typing import Optional | ||
from contextlib import AsyncExitStack | ||
|
||
from mcp import ClientSession, StdioServerParameters | ||
from mcp.client.stdio import stdio_client | ||
|
||
from anthropic import Anthropic | ||
import logging | ||
from quart import Quart, request, jsonify | ||
from quart_cors import cors | ||
|
||
# Configure logging | ||
logging.basicConfig(level=logging.INFO) | ||
logger = logging.getLogger(__name__) | ||
|
||
# Define an MCP client | ||
class MCPClient: | ||
def __init__(self): | ||
# Initialize session and client objects | ||
self.session: Optional[ClientSession] = None | ||
self.exit_stack = AsyncExitStack() | ||
self.anthropic = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) | ||
self.tools = [] | ||
|
||
|
||
async def connect_to_server(self, server_script_path: str): | ||
"""Connect to an MCP server | ||
Args: | ||
server_script_path: Path to the server script | ||
""" | ||
|
||
# run the command to start the server | ||
server_params = StdioServerParameters( | ||
command="/app/.venv/bin/python", | ||
args=[server_script_path, "--local-timezone=America/Los_Angeles"], | ||
env=None | ||
) | ||
try: | ||
logger.info("Starting async context") | ||
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() | ||
self.tools = response.tools | ||
|
||
logger.info("\nConnected to server with tools:", [tool.name for tool in self.tools]) | ||
|
||
except Exception as e: | ||
logger.error(f"Failed to connect to server: {e}") | ||
await self.cleanup() | ||
raise | ||
|
||
async def process_query(self, query: str) -> str: | ||
"""Process a query using Claude and available tools""" | ||
messages = [ | ||
{ | ||
"role": "user", | ||
"content": query | ||
} | ||
] | ||
|
||
available_tools = [{ | ||
"name": tool.name, | ||
"description": tool.description, | ||
"input_schema": tool.inputSchema | ||
} for tool in self.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 = [] | ||
|
||
# loops through content, and if content.type is a tool_use, calls the tool | ||
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 | ||
logger.info(f"Noticed tool {tool_name} with args {tool_args}") | ||
|
||
if self.session is None: | ||
logger.error("Session not initialized. Exiting.") | ||
return jsonify({"response": "Session not initialized. Exiting."}) | ||
|
||
try: | ||
# Execute tool call | ||
await self.session.initialize() | ||
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 | ||
}) | ||
except Exception as e: | ||
logger.error(f"Failed to call tool {tool_name}: {(e)}") | ||
final_text.append(f"Failed to call tool {tool_name}: {(e)}") | ||
return jsonify({"response": "\n".join(final_text)}) | ||
|
||
# Get completed response from Claude with tool results | ||
response = self.anthropic.messages.create( | ||
model="claude-3-5-sonnet-20241022", | ||
max_tokens=1000, | ||
messages=messages, | ||
) | ||
|
||
final_text.append(response.content[0].text) | ||
|
||
logger.info(f"Final text: {final_text}") | ||
logger.info(f"Tool Results: {tool_results}") | ||
return jsonify({"response": response.content[0].text}) | ||
|
||
async def cleanup(self): | ||
"""Clean up resources""" | ||
await self.exit_stack.aclose() | ||
|
||
# let's start a Quart server | ||
app = Quart(__name__) | ||
app = cors(app, allow_origin="*") | ||
|
||
@app.route('/', methods=['POST']) | ||
async def chat(): | ||
client = MCPClient() | ||
try: | ||
data = await request.get_json() | ||
query = data.get('messages', [None])[0] | ||
logger.info(f"Received query: {query}") | ||
await client.connect_to_server("/app/.venv/bin/mcp-server-time") | ||
return await client.process_query(query) | ||
finally: | ||
await client.cleanup() | ||
|
||
async def main(): | ||
print("app OK") | ||
|
||
if __name__ == "__main__": | ||
app.run(port=8000, host='0.0.0.0') | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[project] | ||
name = "mcp-client" | ||
version = "0.1.0" | ||
description = "MCP client to query Anthropic's Claude AI and utilize MCP tools to augment response." | ||
readme = "README.md" | ||
requires-python = ">=3.10" | ||
dependencies = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
annotated-types==0.7.0 | ||
anthropic==0.45.2 | ||
anyio==4.8.0 | ||
certifi==2025.1.31 | ||
click==8.1.8 | ||
distro==1.9.0 | ||
exceptiongroup==1.2.2 | ||
httpcore==1.0.7 | ||
httpx==0.28.1 | ||
httpx-sse==0.4.0 | ||
idna==3.10 | ||
itsdangerous==2.2.0 | ||
Jinja2==3.1.5 | ||
jiter==0.8.2 | ||
MarkupSafe==3.0.2 | ||
mcp==1.2.1 | ||
pydantic==2.10.6 | ||
pydantic-settings==2.7.1 | ||
pydantic_core==2.27.2 | ||
python-dotenv==1.0.1 | ||
quart==0.18.3 | ||
quart-cors==0.6.0 | ||
sniffio==1.3.1 | ||
sse-starlette==2.2.1 | ||
starlette==0.45.3 | ||
typing_extensions==4.12.2 | ||
uvicorn==0.34.0 | ||
Werkzeug==2.3.7 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/bin/bash | ||
|
||
echo "Starting MCP Server" | ||
|
||
# activate the venv | ||
source /wrapper/venv/bin/activate | ||
|
||
# run python main.py using the venv | ||
python3 main.py | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
.next | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "next/core-web-vitals" | ||
} |
Oops, something went wrong.