petergits commited on
Commit
6a37131
Β·
1 Parent(s): 7e8a36b

first checkin for mcp server running in a docker container hosted on hf

Browse files
Files changed (6) hide show
  1. Dockerfile +32 -0
  2. app.py +16 -0
  3. http_wrapper.py +177 -0
  4. mcp_server.py +395 -0
  5. requirements.txt +8 -0
  6. setup_script.sh +781 -0
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ ENV PYTHONUNBUFFERED=1
6
+ ENV PYTHONDONTWRITEBYTECODE=1
7
+ ENV PORT=7860
8
+ ENV HOST=0.0.0.0
9
+
10
+ RUN apt-get update && apt-get install -y \
11
+ build-essential \
12
+ curl \
13
+ git \
14
+ && rm -rf /var/lib/apt/lists/* \
15
+ && apt-get clean
16
+
17
+ COPY requirements.txt .
18
+ RUN pip install --no-cache-dir --upgrade pip && \
19
+ pip install --no-cache-dir -r requirements.txt
20
+
21
+ COPY . .
22
+
23
+ RUN useradd --create-home --shell /bin/bash app && \
24
+ chown -R app:app /app
25
+ USER app
26
+
27
+ EXPOSE 7860
28
+
29
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
30
+ CMD curl -f http://localhost:7860/health || exit 1
31
+
32
+ CMD ["python", "http_wrapper.py"]
app.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from transformers import pipeline
3
+
4
+ app = FastAPI()
5
+ pipe_flan = pipeline("text2text-generation", model="google/flan-t5-small")
6
+
7
+ @app.get("/")
8
+ def greet_json():
9
+ return {"Hello": "World!"}
10
+
11
+
12
+
13
+ @app.get("/infer_t5")
14
+ def t5(input):
15
+ output = pipe_flan(input)
16
+ return {"output": output[0]["generated_text"]}
http_wrapper.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ HTTP wrapper for the MCP server to run on Hugging Face Spaces
4
+ """
5
+
6
+ from fastapi import FastAPI, HTTPException, Request
7
+ from fastapi.responses import HTMLResponse
8
+ from pydantic import BaseModel
9
+ import asyncio
10
+ import json
11
+ import logging
12
+ import os
13
+ from typing import Any, Dict, Optional
14
+ from datetime import datetime
15
+ import uvicorn
16
+
17
+ # Import our MCP server
18
+ from mcp_server import server
19
+
20
+ # Setup logging
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # FastAPI app
25
+ app = FastAPI(
26
+ title="MCP Server HTTP Wrapper",
27
+ description="HTTP interface for Model Context Protocol server",
28
+ version="1.0.0",
29
+ docs_url="/docs",
30
+ redoc_url="/redoc"
31
+ )
32
+
33
+ # Request/Response models
34
+ class MCPRequest(BaseModel):
35
+ method: str
36
+ params: Dict[str, Any] = {}
37
+ id: Optional[int] = 1
38
+
39
+ class MCPResponse(BaseModel):
40
+ jsonrpc: str = "2.0"
41
+ id: Optional[int] = None
42
+ result: Optional[Any] = None
43
+ error: Optional[Dict[str, Any]] = None
44
+
45
+ class HealthResponse(BaseModel):
46
+ status: str
47
+ timestamp: str
48
+ server_name: str
49
+ version: str
50
+ tools_count: int
51
+ resources_count: int
52
+
53
+ # Root endpoint with basic information
54
+ @app.get("/", response_class=HTMLResponse)
55
+ async def root():
56
+ """Root endpoint with server information"""
57
+ return f"""
58
+ <!DOCTYPE html>
59
+ <html>
60
+ <head>
61
+ <title>MCP Server - Hugging Face Spaces</title>
62
+ <style>
63
+ body {{ font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }}
64
+ .container {{ max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; }}
65
+ h1 {{ color: #333; border-bottom: 2px solid #007acc; padding-bottom: 10px; }}
66
+ </style>
67
+ </head>
68
+ <body>
69
+ <div class="container">
70
+ <h1>πŸ€– MCP Server</h1>
71
+ <p>Welcome to the Model Context Protocol server!</p>
72
+ <p><strong>Tools Available:</strong> {len(server.tools)}</p>
73
+ <p><strong>Status:</strong> βœ… Running</p>
74
+ <p><a href="/docs">πŸ“š View API Documentation</a></p>
75
+ </div>
76
+ </body>
77
+ </html>
78
+ """
79
+
80
+ # Health check endpoint
81
+ @app.get("/health", response_model=HealthResponse)
82
+ async def health_check():
83
+ """Health check endpoint"""
84
+ return HealthResponse(
85
+ status="healthy",
86
+ timestamp=datetime.now().isoformat(),
87
+ server_name=server.name,
88
+ version=server.version,
89
+ tools_count=len(server.tools),
90
+ resources_count=len(server.resources)
91
+ )
92
+
93
+ # Main MCP endpoint
94
+ @app.post("/mcp", response_model=MCPResponse)
95
+ async def mcp_endpoint(request: MCPRequest):
96
+ """Main MCP endpoint for handling requests"""
97
+ try:
98
+ logger.info(f"Received MCP request: {request.method}")
99
+
100
+ # Convert Pydantic model to dict for the MCP server
101
+ mcp_request = {
102
+ "jsonrpc": "2.0",
103
+ "id": request.id,
104
+ "method": request.method,
105
+ "params": request.params
106
+ }
107
+
108
+ # Process the request through our MCP server
109
+ response = await server.handle_request(mcp_request)
110
+
111
+ # Convert response to our response model
112
+ return MCPResponse(
113
+ jsonrpc=response.get("jsonrpc", "2.0"),
114
+ id=response.get("id"),
115
+ result=response.get("result"),
116
+ error=response.get("error")
117
+ )
118
+
119
+ except Exception as e:
120
+ logger.error(f"Error processing MCP request: {e}")
121
+ return MCPResponse(
122
+ jsonrpc="2.0",
123
+ id=request.id,
124
+ error={
125
+ "code": -32603,
126
+ "message": f"Internal error: {str(e)}"
127
+ }
128
+ )
129
+
130
+ # Convenience endpoints
131
+ @app.get("/tools")
132
+ async def list_tools():
133
+ """List all available tools"""
134
+ request = MCPRequest(method="tools/list")
135
+ response = await mcp_endpoint(request)
136
+ return response.result if response.result else response.error
137
+
138
+ @app.get("/resources")
139
+ async def list_resources():
140
+ """List all available resources"""
141
+ request = MCPRequest(method="resources/list")
142
+ response = await mcp_endpoint(request)
143
+ return response.result if response.result else response.error
144
+
145
+ @app.post("/tools/{tool_name}")
146
+ async def execute_tool(tool_name: str, arguments: Dict[str, Any] = None):
147
+ """Execute a specific tool with arguments"""
148
+ if arguments is None:
149
+ arguments = {}
150
+
151
+ request = MCPRequest(
152
+ method="tools/call",
153
+ params={
154
+ "name": tool_name,
155
+ "arguments": arguments
156
+ }
157
+ )
158
+ return await mcp_endpoint(request)
159
+
160
+ # Main function
161
+ def main():
162
+ """Main function to run the HTTP server"""
163
+ port = int(os.getenv("PORT", 7860))
164
+ host = os.getenv("HOST", "0.0.0.0")
165
+
166
+ logger.info(f"Starting HTTP server on {host}:{port}")
167
+
168
+ uvicorn.run(
169
+ app,
170
+ host=host,
171
+ port=port,
172
+ log_level="info",
173
+ access_log=True
174
+ )
175
+
176
+ if __name__ == "__main__":
177
+ main()
mcp_server.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ MCP Server with sample tools for Hugging Face Spaces deployment
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import logging
9
+ import sys
10
+ from typing import Any, Dict, List, Optional, Union
11
+ from datetime import datetime
12
+ import os
13
+
14
+ # MCP Server implementation
15
+ class MCPServer:
16
+ def __init__(self, name: str = "hf-mcp-server", version: str = "1.0.0"):
17
+ self.name = name
18
+ self.version = version
19
+ self.tools = {}
20
+ self.resources = {}
21
+ self.setup_logging()
22
+
23
+ def setup_logging(self):
24
+ logging.basicConfig(
25
+ level=logging.INFO,
26
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
27
+ )
28
+ self.logger = logging.getLogger(self.name)
29
+
30
+ def tool(self, name: str, description: str = "", parameters: Dict = None):
31
+ """Decorator to register tools"""
32
+ def decorator(func):
33
+ self.tools[name] = {
34
+ "function": func,
35
+ "description": description,
36
+ "parameters": parameters or {}
37
+ }
38
+ return func
39
+ return decorator
40
+
41
+ def resource(self, uri: str, name: str = "", description: str = ""):
42
+ """Decorator to register resources"""
43
+ def decorator(func):
44
+ self.resources[uri] = {
45
+ "function": func,
46
+ "name": name,
47
+ "description": description
48
+ }
49
+ return func
50
+ return decorator
51
+
52
+ async def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
53
+ """Handle incoming MCP requests"""
54
+ try:
55
+ method = request.get("method")
56
+ params = request.get("params", {})
57
+ request_id = request.get("id", 1)
58
+
59
+ if method == "initialize":
60
+ return await self.handle_initialize(request_id, params)
61
+ elif method == "tools/list":
62
+ return await self.handle_list_tools(request_id)
63
+ elif method == "tools/call":
64
+ return await self.handle_call_tool(request_id, params)
65
+ elif method == "resources/list":
66
+ return await self.handle_list_resources(request_id)
67
+ elif method == "resources/read":
68
+ return await self.handle_read_resource(request_id, params)
69
+ else:
70
+ return {
71
+ "jsonrpc": "2.0",
72
+ "id": request_id,
73
+ "error": {
74
+ "code": -32601,
75
+ "message": f"Method not found: {method}"
76
+ }
77
+ }
78
+ except Exception as e:
79
+ self.logger.error(f"Error handling request: {e}")
80
+ return {
81
+ "jsonrpc": "2.0",
82
+ "id": request.get("id", 1),
83
+ "error": {
84
+ "code": -32603,
85
+ "message": f"Internal error: {str(e)}"
86
+ }
87
+ }
88
+
89
+ async def handle_initialize(self, request_id: int, params: Dict) -> Dict:
90
+ """Handle initialization request"""
91
+ return {
92
+ "jsonrpc": "2.0",
93
+ "id": request_id,
94
+ "result": {
95
+ "protocolVersion": "2024-11-05",
96
+ "capabilities": {
97
+ "tools": {},
98
+ "resources": {}
99
+ },
100
+ "serverInfo": {
101
+ "name": self.name,
102
+ "version": self.version
103
+ }
104
+ }
105
+ }
106
+
107
+ async def handle_list_tools(self, request_id: int) -> Dict:
108
+ """Handle tools list request"""
109
+ tools_list = []
110
+ for name, tool_info in self.tools.items():
111
+ tools_list.append({
112
+ "name": name,
113
+ "description": tool_info["description"],
114
+ "inputSchema": {
115
+ "type": "object",
116
+ "properties": tool_info["parameters"],
117
+ "required": list(tool_info["parameters"].keys()) if tool_info["parameters"] else []
118
+ }
119
+ })
120
+
121
+ return {
122
+ "jsonrpc": "2.0",
123
+ "id": request_id,
124
+ "result": {
125
+ "tools": tools_list
126
+ }
127
+ }
128
+
129
+ async def handle_call_tool(self, request_id: int, params: Dict) -> Dict:
130
+ """Handle tool call request"""
131
+ tool_name = params.get("name")
132
+ arguments = params.get("arguments", {})
133
+
134
+ if tool_name not in self.tools:
135
+ return {
136
+ "jsonrpc": "2.0",
137
+ "id": request_id,
138
+ "error": {
139
+ "code": -32602,
140
+ "message": f"Tool not found: {tool_name}"
141
+ }
142
+ }
143
+
144
+ try:
145
+ tool_func = self.tools[tool_name]["function"]
146
+ result = await tool_func(**arguments)
147
+
148
+ return {
149
+ "jsonrpc": "2.0",
150
+ "id": request_id,
151
+ "result": {
152
+ "content": [
153
+ {
154
+ "type": "text",
155
+ "text": str(result)
156
+ }
157
+ ]
158
+ }
159
+ }
160
+ except Exception as e:
161
+ return {
162
+ "jsonrpc": "2.0",
163
+ "id": request_id,
164
+ "error": {
165
+ "code": -32603,
166
+ "message": f"Tool execution error: {str(e)}"
167
+ }
168
+ }
169
+
170
+ async def handle_list_resources(self, request_id: int) -> Dict:
171
+ """Handle resources list request"""
172
+ resources_list = []
173
+ for uri, resource_info in self.resources.items():
174
+ resources_list.append({
175
+ "uri": uri,
176
+ "name": resource_info["name"],
177
+ "description": resource_info["description"],
178
+ "mimeType": "text/plain"
179
+ })
180
+
181
+ return {
182
+ "jsonrpc": "2.0",
183
+ "id": request_id,
184
+ "result": {
185
+ "resources": resources_list
186
+ }
187
+ }
188
+
189
+ async def handle_read_resource(self, request_id: int, params: Dict) -> Dict:
190
+ """Handle resource read request"""
191
+ uri = params.get("uri")
192
+
193
+ if uri not in self.resources:
194
+ return {
195
+ "jsonrpc": "2.0",
196
+ "id": request_id,
197
+ "error": {
198
+ "code": -32602,
199
+ "message": f"Resource not found: {uri}"
200
+ }
201
+ }
202
+
203
+ try:
204
+ resource_func = self.resources[uri]["function"]
205
+ content = await resource_func()
206
+
207
+ return {
208
+ "jsonrpc": "2.0",
209
+ "id": request_id,
210
+ "result": {
211
+ "contents": [
212
+ {
213
+ "uri": uri,
214
+ "mimeType": "text/plain",
215
+ "text": str(content)
216
+ }
217
+ ]
218
+ }
219
+ }
220
+ except Exception as e:
221
+ return {
222
+ "jsonrpc": "2.0",
223
+ "id": request_id,
224
+ "error": {
225
+ "code": -32603,
226
+ "message": f"Resource read error: {str(e)}"
227
+ }
228
+ }
229
+
230
+
231
+ # Create server instance
232
+ server = MCPServer("hf-mcp-server", "1.0.0")
233
+
234
+
235
+ # Sample Tools
236
+ @server.tool(
237
+ name="echo",
238
+ description="Echo back the input text",
239
+ parameters={
240
+ "text": {
241
+ "type": "string",
242
+ "description": "Text to echo back"
243
+ }
244
+ }
245
+ )
246
+ async def echo_tool(text: str) -> str:
247
+ """Simple echo tool"""
248
+ return f"Echo: {text}"
249
+
250
+
251
+ @server.tool(
252
+ name="current_time",
253
+ description="Get the current timestamp",
254
+ parameters={}
255
+ )
256
+ async def current_time_tool() -> str:
257
+ """Get current time"""
258
+ return f"Current time: {datetime.now().isoformat()}"
259
+
260
+
261
+ @server.tool(
262
+ name="calculate",
263
+ description="Perform basic mathematical calculations",
264
+ parameters={
265
+ "expression": {
266
+ "type": "string",
267
+ "description": "Mathematical expression to evaluate (e.g., '2 + 2')"
268
+ }
269
+ }
270
+ )
271
+ async def calculate_tool(expression: str) -> str:
272
+ """Simple calculator tool"""
273
+ try:
274
+ # Basic safety check - only allow certain characters
275
+ allowed_chars = set("0123456789+-*/()%. ")
276
+ if not all(c in allowed_chars for c in expression):
277
+ return "Error: Invalid characters in expression"
278
+
279
+ result = eval(expression)
280
+ return f"Result: {expression} = {result}"
281
+ except Exception as e:
282
+ return f"Error: {str(e)}"
283
+
284
+
285
+ @server.tool(
286
+ name="word_count",
287
+ description="Count words in the given text",
288
+ parameters={
289
+ "text": {
290
+ "type": "string",
291
+ "description": "Text to count words in"
292
+ }
293
+ }
294
+ )
295
+ async def word_count_tool(text: str) -> str:
296
+ """Count words in text"""
297
+ words = text.split()
298
+ chars = len(text)
299
+ chars_no_spaces = len(text.replace(" ", ""))
300
+
301
+ return f"Text analysis:\n- Words: {len(words)}\n- Characters: {chars}\n- Characters (no spaces): {chars_no_spaces}"
302
+
303
+
304
+ @server.tool(
305
+ name="reverse_text",
306
+ description="Reverse the given text",
307
+ parameters={
308
+ "text": {
309
+ "type": "string",
310
+ "description": "Text to reverse"
311
+ }
312
+ }
313
+ )
314
+ async def reverse_text_tool(text: str) -> str:
315
+ """Reverse text"""
316
+ return f"Reversed: {text[::-1]}"
317
+
318
+
319
+ # Sample Resources
320
+ @server.resource(
321
+ uri="server://info",
322
+ name="Server Information",
323
+ description="Information about this MCP server"
324
+ )
325
+ async def server_info_resource() -> str:
326
+ """Server information resource"""
327
+ return f"""
328
+ MCP Server Information:
329
+ - Name: {server.name}
330
+ - Version: {server.version}
331
+ - Tools available: {len(server.tools)}
332
+ - Resources available: {len(server.resources)}
333
+ - Started at: {datetime.now().isoformat()}
334
+ - Environment: Hugging Face Spaces
335
+ """
336
+
337
+
338
+ @server.resource(
339
+ uri="server://capabilities",
340
+ name="Server Capabilities",
341
+ description="List of available tools and their descriptions"
342
+ )
343
+ async def capabilities_resource() -> str:
344
+ """Server capabilities resource"""
345
+ capabilities = "Available Tools:\n\n"
346
+ for name, tool_info in server.tools.items():
347
+ capabilities += f"- {name}: {tool_info['description']}\n"
348
+
349
+ capabilities += "\nAvailable Resources:\n\n"
350
+ for uri, resource_info in server.resources.items():
351
+ capabilities += f"- {uri}: {resource_info['description']}\n"
352
+
353
+ return capabilities
354
+
355
+
356
+ # Main execution function for stdio mode
357
+ async def main():
358
+ """Main function for stdio-based MCP server"""
359
+ server.logger.info("Starting MCP server in stdio mode")
360
+
361
+ try:
362
+ while True:
363
+ # Read from stdin
364
+ line = await asyncio.get_event_loop().run_in_executor(
365
+ None, sys.stdin.readline
366
+ )
367
+
368
+ if not line:
369
+ break
370
+
371
+ try:
372
+ request = json.loads(line.strip())
373
+ response = await server.handle_request(request)
374
+ print(json.dumps(response))
375
+ sys.stdout.flush()
376
+ except json.JSONDecodeError:
377
+ error_response = {
378
+ "jsonrpc": "2.0",
379
+ "id": None,
380
+ "error": {
381
+ "code": -32700,
382
+ "message": "Parse error"
383
+ }
384
+ }
385
+ print(json.dumps(error_response))
386
+ sys.stdout.flush()
387
+
388
+ except KeyboardInterrupt:
389
+ server.logger.info("Server shutting down")
390
+ except Exception as e:
391
+ server.logger.error(f"Server error: {e}")
392
+
393
+
394
+ if __name__ == "__main__":
395
+ asyncio.run(main())
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi>=0.104.1
2
+ uvicorn[standard]>=0.24.0
3
+ pydantic>=2.5.0
4
+ httpx>=0.25.0
5
+ requests>=2.31.0
6
+ python-json-logger>=2.0.7
7
+ python-multipart>=0.0.6
8
+ python-dotenv>=1.0.0
setup_script.sh ADDED
@@ -0,0 +1,781 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Setup script for HF MCP Server project
4
+ # Run this script to create the project structure on your local machine
5
+
6
+ set -e # Exit on any error
7
+
8
+ # Configuration
9
+ PROJECT_DIR="/Volumes/TOSHIBA_EXT/huggingface/hf-mcp-server"
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+
12
+ echo "πŸš€ Setting up HF MCP Server project..."
13
+ echo "Target directory: $PROJECT_DIR"
14
+
15
+ # Create project directory
16
+ echo "πŸ“ Creating project directory..."
17
+ mkdir -p "$PROJECT_DIR"
18
+ cd "$PROJECT_DIR"
19
+
20
+ # Create main Python files
21
+ echo "πŸ“ Creating mcp_server.py..."
22
+ cat > mcp_server.py << 'EOF'
23
+ #!/usr/bin/env python3
24
+ """
25
+ MCP Server with sample tools for Hugging Face Spaces deployment
26
+ """
27
+
28
+ import asyncio
29
+ import json
30
+ import logging
31
+ import sys
32
+ from typing import Any, Dict, List, Optional, Union
33
+ from datetime import datetime
34
+ import os
35
+
36
+ # MCP Server implementation
37
+ class MCPServer:
38
+ def __init__(self, name: str = "hf-mcp-server", version: str = "1.0.0"):
39
+ self.name = name
40
+ self.version = version
41
+ self.tools = {}
42
+ self.resources = {}
43
+ self.setup_logging()
44
+
45
+ def setup_logging(self):
46
+ logging.basicConfig(
47
+ level=logging.INFO,
48
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
49
+ )
50
+ self.logger = logging.getLogger(self.name)
51
+
52
+ def tool(self, name: str, description: str = "", parameters: Dict = None):
53
+ """Decorator to register tools"""
54
+ def decorator(func):
55
+ self.tools[name] = {
56
+ "function": func,
57
+ "description": description,
58
+ "parameters": parameters or {}
59
+ }
60
+ return func
61
+ return decorator
62
+
63
+ def resource(self, uri: str, name: str = "", description: str = ""):
64
+ """Decorator to register resources"""
65
+ def decorator(func):
66
+ self.resources[uri] = {
67
+ "function": func,
68
+ "name": name,
69
+ "description": description
70
+ }
71
+ return func
72
+ return decorator
73
+
74
+ async def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
75
+ """Handle incoming MCP requests"""
76
+ try:
77
+ method = request.get("method")
78
+ params = request.get("params", {})
79
+ request_id = request.get("id", 1)
80
+
81
+ if method == "initialize":
82
+ return await self.handle_initialize(request_id, params)
83
+ elif method == "tools/list":
84
+ return await self.handle_list_tools(request_id)
85
+ elif method == "tools/call":
86
+ return await self.handle_call_tool(request_id, params)
87
+ elif method == "resources/list":
88
+ return await self.handle_list_resources(request_id)
89
+ elif method == "resources/read":
90
+ return await self.handle_read_resource(request_id, params)
91
+ else:
92
+ return {
93
+ "jsonrpc": "2.0",
94
+ "id": request_id,
95
+ "error": {
96
+ "code": -32601,
97
+ "message": f"Method not found: {method}"
98
+ }
99
+ }
100
+ except Exception as e:
101
+ self.logger.error(f"Error handling request: {e}")
102
+ return {
103
+ "jsonrpc": "2.0",
104
+ "id": request.get("id", 1),
105
+ "error": {
106
+ "code": -32603,
107
+ "message": f"Internal error: {str(e)}"
108
+ }
109
+ }
110
+
111
+ async def handle_initialize(self, request_id: int, params: Dict) -> Dict:
112
+ """Handle initialization request"""
113
+ return {
114
+ "jsonrpc": "2.0",
115
+ "id": request_id,
116
+ "result": {
117
+ "protocolVersion": "2024-11-05",
118
+ "capabilities": {
119
+ "tools": {},
120
+ "resources": {}
121
+ },
122
+ "serverInfo": {
123
+ "name": self.name,
124
+ "version": self.version
125
+ }
126
+ }
127
+ }
128
+
129
+ async def handle_list_tools(self, request_id: int) -> Dict:
130
+ """Handle tools list request"""
131
+ tools_list = []
132
+ for name, tool_info in self.tools.items():
133
+ tools_list.append({
134
+ "name": name,
135
+ "description": tool_info["description"],
136
+ "inputSchema": {
137
+ "type": "object",
138
+ "properties": tool_info["parameters"],
139
+ "required": list(tool_info["parameters"].keys()) if tool_info["parameters"] else []
140
+ }
141
+ })
142
+
143
+ return {
144
+ "jsonrpc": "2.0",
145
+ "id": request_id,
146
+ "result": {
147
+ "tools": tools_list
148
+ }
149
+ }
150
+
151
+ async def handle_call_tool(self, request_id: int, params: Dict) -> Dict:
152
+ """Handle tool call request"""
153
+ tool_name = params.get("name")
154
+ arguments = params.get("arguments", {})
155
+
156
+ if tool_name not in self.tools:
157
+ return {
158
+ "jsonrpc": "2.0",
159
+ "id": request_id,
160
+ "error": {
161
+ "code": -32602,
162
+ "message": f"Tool not found: {tool_name}"
163
+ }
164
+ }
165
+
166
+ try:
167
+ tool_func = self.tools[tool_name]["function"]
168
+ result = await tool_func(**arguments)
169
+
170
+ return {
171
+ "jsonrpc": "2.0",
172
+ "id": request_id,
173
+ "result": {
174
+ "content": [
175
+ {
176
+ "type": "text",
177
+ "text": str(result)
178
+ }
179
+ ]
180
+ }
181
+ }
182
+ except Exception as e:
183
+ return {
184
+ "jsonrpc": "2.0",
185
+ "id": request_id,
186
+ "error": {
187
+ "code": -32603,
188
+ "message": f"Tool execution error: {str(e)}"
189
+ }
190
+ }
191
+
192
+ async def handle_list_resources(self, request_id: int) -> Dict:
193
+ """Handle resources list request"""
194
+ resources_list = []
195
+ for uri, resource_info in self.resources.items():
196
+ resources_list.append({
197
+ "uri": uri,
198
+ "name": resource_info["name"],
199
+ "description": resource_info["description"],
200
+ "mimeType": "text/plain"
201
+ })
202
+
203
+ return {
204
+ "jsonrpc": "2.0",
205
+ "id": request_id,
206
+ "result": {
207
+ "resources": resources_list
208
+ }
209
+ }
210
+
211
+ async def handle_read_resource(self, request_id: int, params: Dict) -> Dict:
212
+ """Handle resource read request"""
213
+ uri = params.get("uri")
214
+
215
+ if uri not in self.resources:
216
+ return {
217
+ "jsonrpc": "2.0",
218
+ "id": request_id,
219
+ "error": {
220
+ "code": -32602,
221
+ "message": f"Resource not found: {uri}"
222
+ }
223
+ }
224
+
225
+ try:
226
+ resource_func = self.resources[uri]["function"]
227
+ content = await resource_func()
228
+
229
+ return {
230
+ "jsonrpc": "2.0",
231
+ "id": request_id,
232
+ "result": {
233
+ "contents": [
234
+ {
235
+ "uri": uri,
236
+ "mimeType": "text/plain",
237
+ "text": str(content)
238
+ }
239
+ ]
240
+ }
241
+ }
242
+ except Exception as e:
243
+ return {
244
+ "jsonrpc": "2.0",
245
+ "id": request_id,
246
+ "error": {
247
+ "code": -32603,
248
+ "message": f"Resource read error: {str(e)}"
249
+ }
250
+ }
251
+
252
+
253
+ # Create server instance
254
+ server = MCPServer("hf-mcp-server", "1.0.0")
255
+
256
+
257
+ # Sample Tools
258
+ @server.tool(
259
+ name="echo",
260
+ description="Echo back the input text",
261
+ parameters={
262
+ "text": {
263
+ "type": "string",
264
+ "description": "Text to echo back"
265
+ }
266
+ }
267
+ )
268
+ async def echo_tool(text: str) -> str:
269
+ """Simple echo tool"""
270
+ return f"Echo: {text}"
271
+
272
+
273
+ @server.tool(
274
+ name="current_time",
275
+ description="Get the current timestamp",
276
+ parameters={}
277
+ )
278
+ async def current_time_tool() -> str:
279
+ """Get current time"""
280
+ return f"Current time: {datetime.now().isoformat()}"
281
+
282
+
283
+ @server.tool(
284
+ name="calculate",
285
+ description="Perform basic mathematical calculations",
286
+ parameters={
287
+ "expression": {
288
+ "type": "string",
289
+ "description": "Mathematical expression to evaluate (e.g., '2 + 2')"
290
+ }
291
+ }
292
+ )
293
+ async def calculate_tool(expression: str) -> str:
294
+ """Simple calculator tool"""
295
+ try:
296
+ # Basic safety check - only allow certain characters
297
+ allowed_chars = set("0123456789+-*/()%. ")
298
+ if not all(c in allowed_chars for c in expression):
299
+ return "Error: Invalid characters in expression"
300
+
301
+ result = eval(expression)
302
+ return f"Result: {expression} = {result}"
303
+ except Exception as e:
304
+ return f"Error: {str(e)}"
305
+
306
+
307
+ @server.tool(
308
+ name="word_count",
309
+ description="Count words in the given text",
310
+ parameters={
311
+ "text": {
312
+ "type": "string",
313
+ "description": "Text to count words in"
314
+ }
315
+ }
316
+ )
317
+ async def word_count_tool(text: str) -> str:
318
+ """Count words in text"""
319
+ words = text.split()
320
+ chars = len(text)
321
+ chars_no_spaces = len(text.replace(" ", ""))
322
+
323
+ return f"Text analysis:\n- Words: {len(words)}\n- Characters: {chars}\n- Characters (no spaces): {chars_no_spaces}"
324
+
325
+
326
+ @server.tool(
327
+ name="reverse_text",
328
+ description="Reverse the given text",
329
+ parameters={
330
+ "text": {
331
+ "type": "string",
332
+ "description": "Text to reverse"
333
+ }
334
+ }
335
+ )
336
+ async def reverse_text_tool(text: str) -> str:
337
+ """Reverse text"""
338
+ return f"Reversed: {text[::-1]}"
339
+
340
+
341
+ # Sample Resources
342
+ @server.resource(
343
+ uri="server://info",
344
+ name="Server Information",
345
+ description="Information about this MCP server"
346
+ )
347
+ async def server_info_resource() -> str:
348
+ """Server information resource"""
349
+ return f"""
350
+ MCP Server Information:
351
+ - Name: {server.name}
352
+ - Version: {server.version}
353
+ - Tools available: {len(server.tools)}
354
+ - Resources available: {len(server.resources)}
355
+ - Started at: {datetime.now().isoformat()}
356
+ - Environment: Hugging Face Spaces
357
+ """
358
+
359
+
360
+ @server.resource(
361
+ uri="server://capabilities",
362
+ name="Server Capabilities",
363
+ description="List of available tools and their descriptions"
364
+ )
365
+ async def capabilities_resource() -> str:
366
+ """Server capabilities resource"""
367
+ capabilities = "Available Tools:\n\n"
368
+ for name, tool_info in server.tools.items():
369
+ capabilities += f"- {name}: {tool_info['description']}\n"
370
+
371
+ capabilities += "\nAvailable Resources:\n\n"
372
+ for uri, resource_info in server.resources.items():
373
+ capabilities += f"- {uri}: {resource_info['description']}\n"
374
+
375
+ return capabilities
376
+
377
+
378
+ # Main execution function for stdio mode
379
+ async def main():
380
+ """Main function for stdio-based MCP server"""
381
+ server.logger.info("Starting MCP server in stdio mode")
382
+
383
+ try:
384
+ while True:
385
+ # Read from stdin
386
+ line = await asyncio.get_event_loop().run_in_executor(
387
+ None, sys.stdin.readline
388
+ )
389
+
390
+ if not line:
391
+ break
392
+
393
+ try:
394
+ request = json.loads(line.strip())
395
+ response = await server.handle_request(request)
396
+ print(json.dumps(response))
397
+ sys.stdout.flush()
398
+ except json.JSONDecodeError:
399
+ error_response = {
400
+ "jsonrpc": "2.0",
401
+ "id": None,
402
+ "error": {
403
+ "code": -32700,
404
+ "message": "Parse error"
405
+ }
406
+ }
407
+ print(json.dumps(error_response))
408
+ sys.stdout.flush()
409
+
410
+ except KeyboardInterrupt:
411
+ server.logger.info("Server shutting down")
412
+ except Exception as e:
413
+ server.logger.error(f"Server error: {e}")
414
+
415
+
416
+ if __name__ == "__main__":
417
+ asyncio.run(main())
418
+ EOF
419
+
420
+ echo "πŸ“ Creating http_wrapper.py..."
421
+ # Create http_wrapper.py with a truncated version due to length
422
+ cat > http_wrapper.py << 'EOF'
423
+ #!/usr/bin/env python3
424
+ """
425
+ HTTP wrapper for the MCP server to run on Hugging Face Spaces
426
+ """
427
+
428
+ from fastapi import FastAPI, HTTPException, Request
429
+ from fastapi.responses import HTMLResponse
430
+ from pydantic import BaseModel
431
+ import asyncio
432
+ import json
433
+ import logging
434
+ import os
435
+ from typing import Any, Dict, Optional
436
+ from datetime import datetime
437
+ import uvicorn
438
+
439
+ # Import our MCP server
440
+ from mcp_server import server
441
+
442
+ # Setup logging
443
+ logging.basicConfig(level=logging.INFO)
444
+ logger = logging.getLogger(__name__)
445
+
446
+ # FastAPI app
447
+ app = FastAPI(
448
+ title="MCP Server HTTP Wrapper",
449
+ description="HTTP interface for Model Context Protocol server",
450
+ version="1.0.0",
451
+ docs_url="/docs",
452
+ redoc_url="/redoc"
453
+ )
454
+
455
+ # Request/Response models
456
+ class MCPRequest(BaseModel):
457
+ method: str
458
+ params: Dict[str, Any] = {}
459
+ id: Optional[int] = 1
460
+
461
+ class MCPResponse(BaseModel):
462
+ jsonrpc: str = "2.0"
463
+ id: Optional[int] = None
464
+ result: Optional[Any] = None
465
+ error: Optional[Dict[str, Any]] = None
466
+
467
+ class HealthResponse(BaseModel):
468
+ status: str
469
+ timestamp: str
470
+ server_name: str
471
+ version: str
472
+ tools_count: int
473
+ resources_count: int
474
+
475
+ # Root endpoint with basic information
476
+ @app.get("/", response_class=HTMLResponse)
477
+ async def root():
478
+ """Root endpoint with server information"""
479
+ return f"""
480
+ <!DOCTYPE html>
481
+ <html>
482
+ <head>
483
+ <title>MCP Server - Hugging Face Spaces</title>
484
+ <style>
485
+ body {{ font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }}
486
+ .container {{ max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; }}
487
+ h1 {{ color: #333; border-bottom: 2px solid #007acc; padding-bottom: 10px; }}
488
+ </style>
489
+ </head>
490
+ <body>
491
+ <div class="container">
492
+ <h1>πŸ€– MCP Server</h1>
493
+ <p>Welcome to the Model Context Protocol server!</p>
494
+ <p><strong>Tools Available:</strong> {len(server.tools)}</p>
495
+ <p><strong>Status:</strong> βœ… Running</p>
496
+ <p><a href="/docs">πŸ“š View API Documentation</a></p>
497
+ </div>
498
+ </body>
499
+ </html>
500
+ """
501
+
502
+ # Health check endpoint
503
+ @app.get("/health", response_model=HealthResponse)
504
+ async def health_check():
505
+ """Health check endpoint"""
506
+ return HealthResponse(
507
+ status="healthy",
508
+ timestamp=datetime.now().isoformat(),
509
+ server_name=server.name,
510
+ version=server.version,
511
+ tools_count=len(server.tools),
512
+ resources_count=len(server.resources)
513
+ )
514
+
515
+ # Main MCP endpoint
516
+ @app.post("/mcp", response_model=MCPResponse)
517
+ async def mcp_endpoint(request: MCPRequest):
518
+ """Main MCP endpoint for handling requests"""
519
+ try:
520
+ logger.info(f"Received MCP request: {request.method}")
521
+
522
+ # Convert Pydantic model to dict for the MCP server
523
+ mcp_request = {
524
+ "jsonrpc": "2.0",
525
+ "id": request.id,
526
+ "method": request.method,
527
+ "params": request.params
528
+ }
529
+
530
+ # Process the request through our MCP server
531
+ response = await server.handle_request(mcp_request)
532
+
533
+ # Convert response to our response model
534
+ return MCPResponse(
535
+ jsonrpc=response.get("jsonrpc", "2.0"),
536
+ id=response.get("id"),
537
+ result=response.get("result"),
538
+ error=response.get("error")
539
+ )
540
+
541
+ except Exception as e:
542
+ logger.error(f"Error processing MCP request: {e}")
543
+ return MCPResponse(
544
+ jsonrpc="2.0",
545
+ id=request.id,
546
+ error={
547
+ "code": -32603,
548
+ "message": f"Internal error: {str(e)}"
549
+ }
550
+ )
551
+
552
+ # Convenience endpoints
553
+ @app.get("/tools")
554
+ async def list_tools():
555
+ """List all available tools"""
556
+ request = MCPRequest(method="tools/list")
557
+ response = await mcp_endpoint(request)
558
+ return response.result if response.result else response.error
559
+
560
+ @app.get("/resources")
561
+ async def list_resources():
562
+ """List all available resources"""
563
+ request = MCPRequest(method="resources/list")
564
+ response = await mcp_endpoint(request)
565
+ return response.result if response.result else response.error
566
+
567
+ @app.post("/tools/{tool_name}")
568
+ async def execute_tool(tool_name: str, arguments: Dict[str, Any] = None):
569
+ """Execute a specific tool with arguments"""
570
+ if arguments is None:
571
+ arguments = {}
572
+
573
+ request = MCPRequest(
574
+ method="tools/call",
575
+ params={
576
+ "name": tool_name,
577
+ "arguments": arguments
578
+ }
579
+ )
580
+ return await mcp_endpoint(request)
581
+
582
+ # Main function
583
+ def main():
584
+ """Main function to run the HTTP server"""
585
+ port = int(os.getenv("PORT", 7860))
586
+ host = os.getenv("HOST", "0.0.0.0")
587
+
588
+ logger.info(f"Starting HTTP server on {host}:{port}")
589
+
590
+ uvicorn.run(
591
+ app,
592
+ host=host,
593
+ port=port,
594
+ log_level="info",
595
+ access_log=True
596
+ )
597
+
598
+ if __name__ == "__main__":
599
+ main()
600
+ EOF
601
+
602
+ echo "πŸ“ Creating requirements.txt..."
603
+ cat > requirements.txt << 'EOF'
604
+ fastapi>=0.104.1
605
+ uvicorn[standard]>=0.24.0
606
+ pydantic>=2.5.0
607
+ httpx>=0.25.0
608
+ requests>=2.31.0
609
+ python-json-logger>=2.0.7
610
+ python-multipart>=0.0.6
611
+ python-dotenv>=1.0.0
612
+ EOF
613
+
614
+ echo "πŸ“ Creating Dockerfile..."
615
+ cat > Dockerfile << 'EOF'
616
+ FROM python:3.11-slim
617
+
618
+ WORKDIR /app
619
+
620
+ ENV PYTHONUNBUFFERED=1
621
+ ENV PYTHONDONTWRITEBYTECODE=1
622
+ ENV PORT=7860
623
+ ENV HOST=0.0.0.0
624
+
625
+ RUN apt-get update && apt-get install -y \
626
+ build-essential \
627
+ curl \
628
+ git \
629
+ && rm -rf /var/lib/apt/lists/* \
630
+ && apt-get clean
631
+
632
+ COPY requirements.txt .
633
+ RUN pip install --no-cache-dir --upgrade pip && \
634
+ pip install --no-cache-dir -r requirements.txt
635
+
636
+ COPY . .
637
+
638
+ RUN useradd --create-home --shell /bin/bash app && \
639
+ chown -R app:app /app
640
+ USER app
641
+
642
+ EXPOSE 7860
643
+
644
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
645
+ CMD curl -f http://localhost:7860/health || exit 1
646
+
647
+ CMD ["python", "http_wrapper.py"]
648
+ EOF
649
+
650
+ echo "πŸ“ Creating .gitignore..."
651
+ cat > .gitignore << 'EOF'
652
+ __pycache__/
653
+ *.py[cod]
654
+ *$py.class
655
+ *.so
656
+ .Python
657
+ build/
658
+ develop-eggs/
659
+ dist/
660
+ downloads/
661
+ eggs/
662
+ .eggs/
663
+ lib/
664
+ lib64/
665
+ parts/
666
+ sdist/
667
+ var/
668
+ wheels/
669
+ *.egg-info/
670
+ .installed.cfg
671
+ *.egg
672
+ MANIFEST
673
+ htmlcov/
674
+ .tox/
675
+ .coverage
676
+ .pytest_cache/
677
+ *.log
678
+ .env
679
+ .venv
680
+ env/
681
+ venv/
682
+ .DS_Store
683
+ .vscode/
684
+ .idea/
685
+ Thumbs.db
686
+ *.tmp
687
+ *.swp
688
+ EOF
689
+
690
+ echo "πŸ“ Creating README.md..."
691
+ cat > README.md << 'EOF'
692
+ ---
693
+ title: HF MCP Server
694
+ emoji: πŸ€–
695
+ colorFrom: blue
696
+ colorTo: green
697
+ sdk: docker
698
+ pinned: false
699
+ license: mit
700
+ app_port: 7860
701
+ ---
702
+
703
+ # MCP Server on Hugging Face Spaces
704
+
705
+ A Model Context Protocol (MCP) server implementation running on Hugging Face Spaces with Docker.
706
+
707
+ ## πŸš€ Features
708
+
709
+ - **Multiple Tools**: Echo, calculator, word counter, text reverser, current time
710
+ - **Resources**: Server information and capabilities
711
+ - **HTTP API**: RESTful interface with automatic documentation
712
+ - **Docker Ready**: Optimized for Hugging Face Spaces deployment
713
+
714
+ ## πŸ› οΈ Available Tools
715
+
716
+ | Tool | Description | Parameters |
717
+ |------|-------------|------------|
718
+ | `echo` | Echo back input text | `text: string` |
719
+ | `current_time` | Get current timestamp | None |
720
+ | `calculate` | Basic math calculations | `expression: string` |
721
+ | `word_count` | Count words and characters | `text: string` |
722
+ | `reverse_text` | Reverse given text | `text: string` |
723
+
724
+ ## πŸ“š API Endpoints
725
+
726
+ - `GET /` - Server information page
727
+ - `GET /health` - Health check
728
+ - `POST /mcp` - Main MCP protocol endpoint
729
+ - `GET /tools` - List all available tools
730
+ - `GET /docs` - Interactive API documentation
731
+
732
+ ## πŸ”§ Usage Examples
733
+
734
+ ### Health Check
735
+ ```bash
736
+ curl https://your-space-name.hf.space/health
737
+ ```
738
+
739
+ ### List Tools
740
+ ```bash
741
+ curl -X POST "https://your-space-name.hf.space/mcp" \
742
+ -H "Content-Type: application/json" \
743
+ -d '{"method": "tools/list"}'
744
+ ```
745
+
746
+ ### Call Echo Tool
747
+ ```bash
748
+ curl -X POST "https://your-space-name.hf.space/mcp" \
749
+ -H "Content-Type: application/json" \
750
+ -d '{
751
+ "method": "tools/call",
752
+ "params": {
753
+ "name": "echo",
754
+ "arguments": {"text": "Hello World!"}
755
+ }
756
+ }'
757
+ ```
758
+
759
+ ## πŸš€ Deployment to Hugging Face Spaces
760
+
761
+ 1. Create a new Space on huggingface.co/spaces
762
+ 2. Choose "Docker" as the SDK
763
+ 3. Upload all files to your Space repository
764
+ 4. The Space will automatically build and deploy
765
+
766
+ ## πŸ“„ License
767
+
768
+ MIT License - feel free to use and modify as needed.
769
+ EOF
770
+
771
+ echo "βœ… Project setup complete!"
772
+ echo ""
773
+ echo "πŸ“‚ Files created in: $PROJECT_DIR"
774
+ echo "πŸ“‹ Next steps:"
775
+ echo " 1. cd $PROJECT_DIR"
776
+ echo " 2. Test locally: python http_wrapper.py"
777
+ echo " 3. Create Hugging Face Space and upload files"
778
+ echo " 4. Choose 'Docker' as SDK when creating the Space"
779
+ echo ""
780
+ echo "🌐 Local server will run on: http://localhost:7860"
781
+ echo "πŸ“š API docs will be at: http://localhost:7860/docs"