3morixd commited on
Commit
5233b57
·
verified ·
1 Parent(s): 0ad01db

Upload api/mcp_server.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. api/mcp_server.py +180 -0
api/mcp_server.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ dispatchAI MCP Server - gives the agent tools to interact with the phone farm API.
3
+ This is a simple MCP server that exposes the dispatchAI API as tools.
4
+ """
5
+ import json
6
+ import httpx
7
+ import asyncio
8
+ from typing import Any
9
+
10
+ # MCP server using the simple JSON-RPC protocol
11
+ # This runs as a subprocess that Hermes communicates with via stdio
12
+
13
+ BASE_URL = "http://127.0.0.1:8081"
14
+ API_KEY = "da-demo-key-0001"
15
+
16
+ TOOLS = [
17
+ {
18
+ "name": "chat_completion",
19
+ "description": "Send a chat completion request to a dispatchAI model running on a phone. Returns the model's response.",
20
+ "inputSchema": {
21
+ "type": "object",
22
+ "properties": {
23
+ "message": {"type": "string", "description": "The user message to send"},
24
+ "model": {"type": "string", "description": "Model name (default: SmolLM2-135M)", "default": "dispatchAI/SmolLM2-135M-Instruct-mobile"},
25
+ "max_tokens": {"type": "integer", "description": "Max tokens to generate", "default": 100},
26
+ },
27
+ "required": ["message"],
28
+ },
29
+ },
30
+ {
31
+ "name": "list_models",
32
+ "description": "List all available dispatchAI models",
33
+ "inputSchema": {"type": "object", "properties": {}},
34
+ },
35
+ {
36
+ "name": "get_phone_status",
37
+ "description": "Get the status of the phone farm - how many phones are connected and their load",
38
+ "inputSchema": {"type": "object", "properties": {}},
39
+ },
40
+ {
41
+ "name": "get_api_usage",
42
+ "description": "Get API usage statistics for the current API key",
43
+ "inputSchema": {"type": "object", "properties": {}},
44
+ },
45
+ ]
46
+
47
+
48
+ async def call_tool(name: str, arguments: dict) -> str:
49
+ """Call a tool and return the result."""
50
+ async with httpx.AsyncClient(timeout=120.0) as client:
51
+ if name == "chat_completion":
52
+ model = arguments.get("model", "dispatchAI/SmolLM2-135M-Instruct-mobile")
53
+ message = arguments.get("message", "")
54
+ max_tokens = arguments.get("max_tokens", 100)
55
+
56
+ r = await client.post(
57
+ f"{BASE_URL}/v1/chat/completions",
58
+ headers={
59
+ "Content-Type": "application/json",
60
+ "Authorization": f"Bearer {API_KEY}",
61
+ },
62
+ json={
63
+ "model": model,
64
+ "messages": [{"role": "user", "content": message}],
65
+ "max_tokens": max_tokens,
66
+ },
67
+ )
68
+ if r.status_code == 200:
69
+ data = r.json()
70
+ content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
71
+ phone = data.get("phone_info", {})
72
+ usage = data.get("usage", {})
73
+ return json.dumps({
74
+ "response": content,
75
+ "phone": phone.get("serial", "unknown"),
76
+ "speed_tps": phone.get("generation_tps", 0),
77
+ "tokens_used": usage.get("total_tokens", 0),
78
+ })
79
+ else:
80
+ return f"Error: {r.status_code} - {r.text[:200]}"
81
+
82
+ elif name == "list_models":
83
+ r = await client.get(
84
+ f"{BASE_URL}/v1/models",
85
+ headers={"Authorization": f"Bearer {API_KEY}"},
86
+ )
87
+ if r.status_code == 200:
88
+ data = r.json()
89
+ models = [m["id"] for m in data.get("data", [])]
90
+ return json.dumps({"models": models})
91
+ return f"Error: {r.status_code}"
92
+
93
+ elif name == "get_phone_status":
94
+ r = await client.get(
95
+ f"{BASE_URL}/admin/phones",
96
+ headers={"Authorization": f"Bearer {API_KEY}"},
97
+ )
98
+ if r.status_code == 200:
99
+ return json.dumps(r.json())
100
+ return f"Error: {r.status_code}"
101
+
102
+ elif name == "get_api_usage":
103
+ r = await client.get(
104
+ f"{BASE_URL}/v1/usage",
105
+ headers={"Authorization": f"Bearer {API_KEY}"},
106
+ )
107
+ if r.status_code == 200:
108
+ return json.dumps(r.json())
109
+ return f"Error: {r.status_code}"
110
+
111
+ return f"Unknown tool: {name}"
112
+
113
+
114
+ # Simple MCP JSON-RPC server over stdio
115
+ import sys
116
+
117
+ def main():
118
+ """Main MCP server loop - reads JSON-RPC from stdin, writes to stdout."""
119
+ while True:
120
+ try:
121
+ line = sys.stdin.readline()
122
+ if not line:
123
+ break
124
+
125
+ req = json.loads(line)
126
+ method = req.get("method", "")
127
+ req_id = req.get("id")
128
+
129
+ if method == "initialize":
130
+ resp = {
131
+ "jsonrpc": "2.0",
132
+ "id": req_id,
133
+ "result": {
134
+ "protocolVersion": "2024-11-05",
135
+ "capabilities": {"tools": {}},
136
+ "serverInfo": {"name": "dispatchai-mcp", "version": "1.0.0"},
137
+ },
138
+ }
139
+ sys.stdout.write(json.dumps(resp) + "\n")
140
+ sys.stdout.flush()
141
+
142
+ elif method == "tools/list":
143
+ resp = {
144
+ "jsonrpc": "2.0",
145
+ "id": req_id,
146
+ "result": {"tools": TOOLS},
147
+ }
148
+ sys.stdout.write(json.dumps(resp) + "\n")
149
+ sys.stdout.flush()
150
+
151
+ elif method == "tools/call":
152
+ params = req.get("params", {})
153
+ tool_name = params.get("name", "")
154
+ arguments = params.get("arguments", {})
155
+
156
+ result = asyncio.run(call_tool(tool_name, arguments))
157
+
158
+ resp = {
159
+ "jsonrpc": "2.0",
160
+ "id": req_id,
161
+ "result": {
162
+ "content": [{"type": "text", "text": result}]
163
+ },
164
+ }
165
+ sys.stdout.write(json.dumps(resp) + "\n")
166
+ sys.stdout.flush()
167
+
168
+ except Exception as e:
169
+ if req_id:
170
+ resp = {
171
+ "jsonrpc": "2.0",
172
+ "id": req_id,
173
+ "error": {"code": -32603, "message": str(e)},
174
+ }
175
+ sys.stdout.write(json.dumps(resp) + "\n")
176
+ sys.stdout.flush()
177
+
178
+
179
+ if __name__ == "__main__":
180
+ main()