Added template mcp server and client, env values, requirements and compose objects
This commit is contained in:
parent
08fb386b14
commit
69458b7fed
7 changed files with 205 additions and 1 deletions
|
|
@ -35,3 +35,7 @@ POSTGRES_USER=postgres_user
|
|||
POSTGRES_PASSWORD=postgres_password
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
|
||||
# MCP Server
|
||||
MCP_SERVER_HOST=localhost
|
||||
MCP_SERVER_PORT=8001
|
||||
|
|
@ -82,6 +82,24 @@ services:
|
|||
fyp-postgres-dev:
|
||||
condition: service_healthy
|
||||
|
||||
fyp-mcp-dev:
|
||||
container_name: fyp-mcp-dev
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: compose/dev/mcp/Dockerfile
|
||||
env_file:
|
||||
- ../../.env
|
||||
volumes:
|
||||
- ../../:/app
|
||||
ports:
|
||||
- "0.0.0.0:8001:8001"
|
||||
depends_on:
|
||||
fyp-redis-dev:
|
||||
condition: service_healthy
|
||||
fyp-postgres-dev:
|
||||
condition: service_healthy
|
||||
|
||||
|
||||
volumes:
|
||||
fyp_postgres_data:
|
||||
fyp_redis_data:
|
||||
|
|
|
|||
39
compose/dev/mcp/Dockerfile
Normal file
39
compose/dev/mcp/Dockerfile
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
FROM nvidia/cuda:11.8.0-runtime-ubuntu22.04
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
python3 \
|
||||
python3-pip \
|
||||
build-essential \
|
||||
git \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& ln -sf /usr/bin/python3 /usr/bin/python \
|
||||
&& ln -sf /usr/bin/pip3 /usr/bin/pip
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
|
||||
apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
python3-dev \
|
||||
libffi-dev \
|
||||
libssl-dev \
|
||||
cmake \
|
||||
pkg-config \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN if [ ! -e /usr/lib/x86_64-linux-gnu/libcudart.so.11.0 ]; then \
|
||||
found=$(ls /usr/local/cuda/lib64/libcudart.so* 2>/dev/null | head -n1 || true); \
|
||||
if [ -n "$found" ]; then \
|
||||
mkdir -p /usr/lib/x86_64-linux-gnu || true; \
|
||||
ln -sf "$found" /usr/lib/x86_64-linux-gnu/libcudart.so.11.0 || true; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
COPY requirements/mcp.txt .
|
||||
RUN pip install --no-cache-dir --requirement mcp.txt
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV DJANGO_SETTINGS_MODULE=config.settings
|
||||
EXPOSE 8001
|
||||
|
||||
CMD ["python", "-m", "mcp_agent.mcp_server"]
|
||||
0
mcp_agent/__init__.py
Normal file
0
mcp_agent/__init__.py
Normal file
42
mcp_agent/mcp_client.py
Normal file
42
mcp_agent/mcp_client.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import httpx
|
||||
import asyncio
|
||||
|
||||
class MCPClient:
|
||||
def __init__(self, server_url: str):
|
||||
self.server_url = server_url
|
||||
self.client = httpx.AsyncClient(timeout=60)
|
||||
|
||||
async def send(self, tool: str, arguments: dict):
|
||||
response = await self.client.post(
|
||||
f"{self.server_url}/execute",
|
||||
json={
|
||||
"tool": tool,
|
||||
"arguments": arguments,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def health(self):
|
||||
response = await self.client.get(f"{self.server_url}/health")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def close(self):
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
async def main():
|
||||
client = MCPClient("http://localhost:8001")
|
||||
|
||||
result = await client.send(
|
||||
tool="echo",
|
||||
arguments={"message": "hello from client"},
|
||||
)
|
||||
|
||||
print(result)
|
||||
await client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
95
mcp_agent/mcp_server.py
Normal file
95
mcp_agent/mcp_server.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from aiohttp import web
|
||||
from mcp.server import Server
|
||||
from mcp.types import Tool, TextContent
|
||||
|
||||
app = Server("minimal-mcp-server")
|
||||
|
||||
|
||||
@app.list_tools()
|
||||
async def list_tools():
|
||||
return [
|
||||
Tool(
|
||||
name="echo",
|
||||
description="Echo back the provided input",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {"type": "string"}
|
||||
},
|
||||
"required": ["message"]
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@app.call_tool()
|
||||
async def call_tool(name: str, arguments: dict):
|
||||
if name != "echo":
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
return [
|
||||
TextContent(
|
||||
type="text",
|
||||
text=json.dumps(
|
||||
{
|
||||
"received": arguments,
|
||||
"status": "ok",
|
||||
},
|
||||
indent=2,
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
async def handle_execute(request: web.Request) -> web.Response:
|
||||
try:
|
||||
payload = await request.json()
|
||||
tool = payload.get("tool")
|
||||
arguments = payload.get("arguments", {})
|
||||
|
||||
if not tool:
|
||||
return web.json_response(
|
||||
{"error": "Missing 'tool' field"}, status=400
|
||||
)
|
||||
|
||||
result = await call_tool(tool, arguments)
|
||||
return web.json_response(
|
||||
{
|
||||
"tool": tool,
|
||||
"result": [c.text for c in result],
|
||||
}
|
||||
)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return web.json_response({"error": "Invalid JSON"}, status=400)
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
async def handle_health(request: web.Request) -> web.Response:
|
||||
return web.json_response({"status": "healthy"})
|
||||
|
||||
|
||||
async def run_http_server():
|
||||
host = os.getenv("MCP_HTTP_HOST", "0.0.0.0")
|
||||
port = int(os.getenv("MCP_HTTP_PORT", "8001"))
|
||||
|
||||
app_http = web.Application()
|
||||
app_http.router.add_post("/execute", handle_execute)
|
||||
app_http.router.add_get("/health", handle_health)
|
||||
|
||||
runner = web.AppRunner(app_http)
|
||||
await runner.setup()
|
||||
site = web.TCPSite(runner, host, port)
|
||||
await site.start()
|
||||
|
||||
print(f"HTTP server running on {host}:{port}", file=sys.stderr)
|
||||
await asyncio.Event().wait()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_http_server())
|
||||
6
requirements/mcp.txt
Normal file
6
requirements/mcp.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
aiohttp==3.8.4
|
||||
mcp==1.25.0
|
||||
pyjwt==2.10.1
|
||||
python-multipart==0.0.21
|
||||
sse-starlette==3.2.0
|
||||
starlette==0.52.1
|
||||
Loading…
Reference in a new issue