ryoshimu commited on
Commit
025d91f
·
1 Parent(s): cf73d8b
Files changed (5) hide show
  1. Dockerfile +15 -0
  2. README.md +39 -10
  3. app.py +96 -0
  4. client.py +55 -0
  5. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1 \
4
+ PYTHONUNBUFFERED=1 \
5
+ PIP_NO_CACHE_DIR=1
6
+
7
+ WORKDIR /code
8
+
9
+ COPY requirements.txt .
10
+ RUN pip install --upgrade pip \
11
+ && pip install --no-cache-dir -r requirements.txt
12
+
13
+ COPY . .
14
+
15
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,39 @@
1
- ---
2
- title: MCP Sample
3
- emoji: 👀
4
- colorFrom: pink
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## サンプル MCP サーバー (Hugging Face Spaces デプロイ用)
2
+
3
+ このリポジトリは、Hugging Face Spaces にデプロイできるシンプルな MCP 互換 WebSocket サーバーのサンプルです。`/mcp` エンドポイントへ接続すると、以下の 3 種類のアクションで動作確認ができます。
4
+
5
+ - `ping` : サーバーが `pong` を返却します。
6
+ - `tools.list` : 利用可能なツール(ここでは `echo`)の一覧を返却します。
7
+ - `tool.call` : `echo` ツールに文字列を渡すと、そのまま返却します。
8
+
9
+ ### 1. ローカルでの動作確認
10
+
11
+ ```bash
12
+ python -m venv .venv
13
+ source .venv/bin/activate # Windows の場合は .venv\Scripts\activate
14
+ pip install -r requirements.txt
15
+ uvicorn app:app --host 0.0.0.0 --port 7860
16
+ ```
17
+
18
+ 別ターミナルで以下を実行すると、WebSocket プロトコルのやり取りを確認できます。
19
+
20
+ ```bash
21
+ python client.py --url ws://localhost:7860/mcp
22
+ ```
23
+
24
+ ### 2. Hugging Face Spaces へのデプロイ手順
25
+
26
+ 1. Hugging Face で新しい Space を作成し、**Space SDK** として **Docker** を選択します。
27
+ 2. このリポジトリのファイルをそのままアップロード(または `git push`)します。
28
+ 3. ビルドが完了すると自動で `uvicorn` が起動し、`https://<your-space>.hf.space/mcp` で WebSocket が待ち受けます。
29
+ 4. ローカルまたは別サービスから WebSocket クライアントで接続すれば、手元と同じように動作確認できます。
30
+
31
+ ### 3. サーバー仕様メモ
32
+
33
+ - HTTP `GET /` : ヘルスチェック用。`{"status": "ok"}` を返します。
34
+ - WebSocket `/mcp` :
35
+ - 接続直後に `server_hello` を送信。
36
+ - `ping` / `tools.list` / `tool.call` のシンプルな 3 種類を実装。
37
+ - `tool.call` (`echo`) は `{"text": "..."} ` をそのまま返します。
38
+
39
+ 必要に応じて `mcp_endpoint` 内にツールを追加することで、独自の MCP サーバーとして発展させられます。
app.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hugging Face Space friendly FastAPI application that exposes a minimal MCP-style
3
+ WebSocket endpoint. It can be used to verify deployment and basic tool calls.
4
+ """
5
+
6
+ import json
7
+ from typing import Any, Dict
8
+
9
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
10
+ from fastapi.responses import JSONResponse
11
+ import uvicorn
12
+
13
+
14
+ app = FastAPI(title="Sample MCP Server", version="0.1.0")
15
+
16
+
17
+ @app.get("/")
18
+ async def healthcheck() -> JSONResponse:
19
+ """Simple HTTP healthcheck so Spaces can mark the app as ready."""
20
+ return JSONResponse({"status": "ok", "message": "Sample MCP server is running."})
21
+
22
+
23
+ @app.websocket("/mcp")
24
+ async def mcp_endpoint(socket: WebSocket) -> None:
25
+ """
26
+ Minimal MCP-compatible WebSocket endpoint.
27
+
28
+ Protocol highlights:
29
+ - Sends a server hello message immediately after accepting a connection.
30
+ - Supports `ping` messages and a toy `echo` tool for demonstration.
31
+ """
32
+
33
+ await socket.accept()
34
+ await socket.send_json(
35
+ {
36
+ "type": "server_hello",
37
+ "version": "0.1.0",
38
+ "capabilities": ["ping", "tools.list", "tool.call"],
39
+ }
40
+ )
41
+
42
+ try:
43
+ while True:
44
+ raw = await socket.receive_text()
45
+ message: Dict[str, Any] = json.loads(raw)
46
+ kind = message.get("type")
47
+
48
+ if kind == "ping":
49
+ await socket.send_json({"type": "pong", "echo": message.get("payload")})
50
+ elif kind == "tools.list":
51
+ await socket.send_json(
52
+ {
53
+ "type": "tools",
54
+ "tools": [
55
+ {
56
+ "name": "echo",
57
+ "description": "Echoes back the provided text payload.",
58
+ }
59
+ ],
60
+ }
61
+ )
62
+ elif kind == "tool.call":
63
+ tool_name = message.get("name")
64
+ request_id = message.get("id")
65
+ arguments = message.get("arguments", {})
66
+
67
+ if tool_name == "echo":
68
+ await socket.send_json(
69
+ {
70
+ "type": "tool.result",
71
+ "id": request_id,
72
+ "result": {
73
+ "text": arguments.get("text", ""),
74
+ },
75
+ }
76
+ )
77
+ else:
78
+ await socket.send_json(
79
+ {
80
+ "type": "error",
81
+ "error": f"Unknown tool '{tool_name}'.",
82
+ "id": request_id,
83
+ }
84
+ )
85
+ else:
86
+ await socket.send_json(
87
+ {"type": "error", "error": f"Unsupported message type '{kind}'."}
88
+ )
89
+ except WebSocketDisconnect:
90
+ # Client disconnected; nothing else to do.
91
+ return
92
+
93
+
94
+ if __name__ == "__main__":
95
+ uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=False)
96
+
client.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simple asyncio client to verify the sample MCP server.
3
+
4
+ Usage:
5
+ python client.py --url ws://localhost:7860/mcp
6
+ """
7
+
8
+ import argparse
9
+ import asyncio
10
+ import json
11
+ from typing import Any, Dict
12
+
13
+ import websockets
14
+
15
+
16
+ async def run_client(url: str) -> None:
17
+ async with websockets.connect(url) as websocket:
18
+ hello_raw = await websocket.recv()
19
+ hello_message: Dict[str, Any] = json.loads(hello_raw)
20
+ print("hello:", hello_message)
21
+
22
+ await websocket.send(json.dumps({"type": "ping", "payload": "test"}))
23
+ print("pong :", await websocket.recv())
24
+
25
+ await websocket.send(json.dumps({"type": "tools.list"}))
26
+ print("tools:", await websocket.recv())
27
+
28
+ await websocket.send(
29
+ json.dumps(
30
+ {
31
+ "type": "tool.call",
32
+ "id": "demo-1",
33
+ "name": "echo",
34
+ "arguments": {"text": "Hello MCP"},
35
+ }
36
+ )
37
+ )
38
+ print("tool :", await websocket.recv())
39
+
40
+
41
+ def main() -> None:
42
+ parser = argparse.ArgumentParser(description="Sample MCP client")
43
+ parser.add_argument(
44
+ "--url",
45
+ default="ws://localhost:7860/mcp",
46
+ help="WebSocket endpoint of the MCP server.",
47
+ )
48
+ args = parser.parse_args()
49
+
50
+ asyncio.run(run_client(args.url))
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
55
+
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi==0.110.3
2
+ uvicorn[standard]==0.30.1
3
+ websockets==12.0