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_PASSWORD=postgres_password
|
||||||
POSTGRES_HOST=localhost
|
POSTGRES_HOST=localhost
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
|
|
||||||
|
# MCP Server
|
||||||
|
MCP_SERVER_HOST=localhost
|
||||||
|
MCP_SERVER_PORT=8001
|
||||||
|
|
@ -82,6 +82,24 @@ services:
|
||||||
fyp-postgres-dev:
|
fyp-postgres-dev:
|
||||||
condition: service_healthy
|
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:
|
volumes:
|
||||||
fyp_postgres_data:
|
fyp_postgres_data:
|
||||||
fyp_redis_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