NiWaRe commited on
Commit
04c12c4
·
1 Parent(s): a643202

fixed mcp routing issue

Browse files
Files changed (3) hide show
  1. HUGGINGFACE_DEPLOYMENT.md +45 -5
  2. app.py +79 -338
  3. uv.lock +5 -7
HUGGINGFACE_DEPLOYMENT.md CHANGED
@@ -8,15 +8,16 @@ The application runs as a FastAPI server on port 7860 (HF Spaces default) with:
8
  - **Main landing page**: `/` - Serves the index.html with setup instructions
9
  - **Health check**: `/health` - Returns server status and W&B configuration
10
  - **MCP endpoint**: `/mcp` - Streamable HTTP transport endpoint for MCP
11
- - POST `/mcp` - Handles JSON-RPC requests (initialize, tools/list, tools/call)
12
- - GET `/mcp` - SSE endpoint for server-initiated messages and long-lived connections
 
13
 
14
  ## Key Changes for HF Spaces
15
 
16
  ### 1. app.py
17
  - Creates a FastAPI application that serves the landing page
18
- - Implements MCP streamable HTTP protocol directly (no FastMCP mounting issues)
19
- - Handles both POST requests (JSON-RPC) and GET requests (SSE) at `/mcp`
20
  - Configured to run on `0.0.0.0:7860` (HF Spaces requirement)
21
  - Sets W&B cache directories to `/tmp` to avoid permission issues
22
 
@@ -29,6 +30,11 @@ The application runs as a FastAPI server on port 7860 (HF Spaces default) with:
29
  - FastAPI and uvicorn moved to main dependencies (not optional)
30
  - All dependencies listed in requirements.txt for HF Spaces
31
 
 
 
 
 
 
32
  ## Environment Variables
33
 
34
  Required in HF Spaces settings:
@@ -98,7 +104,41 @@ The server will start on http://localhost:7860
98
 
99
  ## MCP Client Configuration
100
 
101
- Example configuration for Claude Desktop or other MCP clients:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  ```json
104
  {
 
8
  - **Main landing page**: `/` - Serves the index.html with setup instructions
9
  - **Health check**: `/health` - Returns server status and W&B configuration
10
  - **MCP endpoint**: `/mcp` - Streamable HTTP transport endpoint for MCP
11
+ - Uses Server-Sent Events (SSE) for responses
12
+ - Requires `Accept: application/json, text/event-stream` header
13
+ - Supports initialize, tools/list, tools/call methods
14
 
15
  ## Key Changes for HF Spaces
16
 
17
  ### 1. app.py
18
  - Creates a FastAPI application that serves the landing page
19
+ - Mounts FastMCP server using `mcp.streamable_http_app()` pattern (following HuggingFace example)
20
+ - Uses lifespan context manager for session management
21
  - Configured to run on `0.0.0.0:7860` (HF Spaces requirement)
22
  - Sets W&B cache directories to `/tmp` to avoid permission issues
23
 
 
30
  - FastAPI and uvicorn moved to main dependencies (not optional)
31
  - All dependencies listed in requirements.txt for HF Spaces
32
 
33
+ ### 4. Lazy Loading Fix
34
+ - Fixed `TraceService` initialization in `query_weave.py` to use lazy loading
35
+ - This allows the server to start even without a W&B API key
36
+ - The service is only initialized when first needed
37
+
38
  ## Environment Variables
39
 
40
  Required in HF Spaces settings:
 
104
 
105
  ## MCP Client Configuration
106
 
107
+ ### Important Notes
108
+
109
+ The MCP server uses the Streamable HTTP transport which:
110
+ - Returns responses in Server-Sent Events (SSE) format
111
+ - Requires the client to send `Accept: application/json, text/event-stream` header
112
+ - Uses session management for stateful operations
113
+
114
+ ### Testing with curl
115
+
116
+ ```bash
117
+ # Initialize the server
118
+ curl -X POST https://[your-username]-[space-name].hf.space/mcp \
119
+ -H "Content-Type: application/json" \
120
+ -H "Accept: application/json, text/event-stream" \
121
+ -d '{
122
+ "jsonrpc": "2.0",
123
+ "method": "initialize",
124
+ "params": {
125
+ "protocolVersion": "0.1.0",
126
+ "capabilities": {},
127
+ "clientInfo": {"name": "test-client", "version": "1.0"}
128
+ },
129
+ "id": 1
130
+ }'
131
+
132
+ # List available tools
133
+ curl -X POST https://[your-username]-[space-name].hf.space/mcp \
134
+ -H "Content-Type: application/json" \
135
+ -H "Accept: application/json, text/event-stream" \
136
+ -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}'
137
+ ```
138
+
139
+ ### MCP Client Configuration
140
+
141
+ For MCP clients that support streamable HTTP:
142
 
143
  ```json
144
  {
app.py CHANGED
@@ -2,17 +2,14 @@
2
  """
3
  HuggingFace Spaces entry point for the Weights & Biases MCP Server.
4
 
5
- This implements MCP streamable HTTP transport directly in FastAPI.
6
  """
7
 
8
  import os
9
  import sys
10
  import logging
11
- import json
12
- import asyncio
13
  from pathlib import Path
14
- from typing import Dict, Any, Optional, List
15
- from datetime import datetime
16
 
17
  # Add the src directory to Python path
18
  sys.path.insert(0, str(Path(__file__).parent / "src"))
@@ -25,10 +22,20 @@ os.environ["HOME"] = "/tmp"
25
  os.environ["WANDB_SILENT"] = "True"
26
  os.environ["WEAVE_SILENT"] = "True"
27
 
28
- from fastapi import FastAPI, Request, Response, HTTPException
29
- from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
30
  from fastapi.middleware.cors import CORSMiddleware
31
- import uvicorn
 
 
 
 
 
 
 
 
 
 
32
 
33
  # Configure logging
34
  logging.basicConfig(
@@ -42,11 +49,49 @@ INDEX_HTML_PATH = Path(__file__).parent / "index.html"
42
  with open(INDEX_HTML_PATH, "r") as f:
43
  INDEX_HTML_CONTENT = f.read()
44
 
45
- # Create FastAPI app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  app = FastAPI(
47
  title="Weights & Biases MCP Server",
48
- description="Model Context Protocol server for querying W&B data",
49
- version="0.1.0"
50
  )
51
 
52
  # Add CORS middleware
@@ -58,115 +103,7 @@ app.add_middleware(
58
  allow_headers=["*"],
59
  )
60
 
61
- # MCP server state
62
- mcp_initialized = False
63
- mcp_tools = {}
64
- wandb_configured = False
65
-
66
- @app.on_event("startup")
67
- async def startup_event():
68
- """Initialize W&B and MCP tools on startup."""
69
- global mcp_initialized, mcp_tools, wandb_configured
70
-
71
- logger.info("Starting Weights & Biases MCP Server on HuggingFace Spaces")
72
-
73
- try:
74
- # Import W&B components
75
- from wandb_mcp_server.server import (
76
- validate_and_get_api_key,
77
- setup_wandb_login,
78
- configure_wandb_logging,
79
- initialize_weave_tracing,
80
- ServerMCPArgs
81
- )
82
-
83
- # Configure W&B
84
- configure_wandb_logging()
85
-
86
- args = ServerMCPArgs(
87
- transport="http",
88
- host="0.0.0.0",
89
- port=7860,
90
- wandb_api_key=os.environ.get("WANDB_API_KEY")
91
- )
92
-
93
- try:
94
- api_key = validate_and_get_api_key(args)
95
- setup_wandb_login(api_key)
96
- initialize_weave_tracing()
97
- wandb_configured = True
98
- logger.info("W&B API configured successfully")
99
- except ValueError as e:
100
- logger.warning(f"W&B API key not configured: {e}")
101
- logger.warning("Server will start but W&B operations will fail")
102
-
103
- # Import MCP tools
104
- from wandb_mcp_server.mcp_tools.query_weave import (
105
- QUERY_WEAVE_TRACES_TOOL_DESCRIPTION,
106
- query_paginated_weave_traces
107
- )
108
- from wandb_mcp_server.mcp_tools.count_traces import (
109
- COUNT_WEAVE_TRACES_TOOL_DESCRIPTION,
110
- count_traces
111
- )
112
- from wandb_mcp_server.mcp_tools.query_wandb_gql import (
113
- QUERY_WANDB_GQL_TOOL_DESCRIPTION,
114
- query_paginated_wandb_gql
115
- )
116
- from wandb_mcp_server.mcp_tools.create_report import (
117
- CREATE_WANDB_REPORT_TOOL_DESCRIPTION,
118
- create_report
119
- )
120
- from wandb_mcp_server.mcp_tools.list_wandb_entities_projects import (
121
- LIST_ENTITY_PROJECTS_TOOL_DESCRIPTION,
122
- list_entity_projects
123
- )
124
- from wandb_mcp_server.mcp_tools.query_wandbot import (
125
- WANDBOT_TOOL_DESCRIPTION,
126
- query_wandbot_api
127
- )
128
-
129
- # Register tools with their descriptions
130
- mcp_tools = {
131
- "query_weave_traces_tool": {
132
- "function": query_paginated_weave_traces,
133
- "description": QUERY_WEAVE_TRACES_TOOL_DESCRIPTION,
134
- "async": True
135
- },
136
- "count_weave_traces_tool": {
137
- "function": count_traces,
138
- "description": COUNT_WEAVE_TRACES_TOOL_DESCRIPTION,
139
- "async": False
140
- },
141
- "query_wandb_tool": {
142
- "function": query_paginated_wandb_gql,
143
- "description": QUERY_WANDB_GQL_TOOL_DESCRIPTION,
144
- "async": False
145
- },
146
- "create_wandb_report_tool": {
147
- "function": create_report,
148
- "description": CREATE_WANDB_REPORT_TOOL_DESCRIPTION,
149
- "async": False
150
- },
151
- "query_wandb_entity_projects": {
152
- "function": list_entity_projects,
153
- "description": LIST_ENTITY_PROJECTS_TOOL_DESCRIPTION,
154
- "async": False
155
- },
156
- "query_wandb_support_bot": {
157
- "function": query_wandbot_api,
158
- "description": WANDBOT_TOOL_DESCRIPTION,
159
- "async": False
160
- }
161
- }
162
-
163
- mcp_initialized = True
164
- logger.info(f"MCP server initialized with {len(mcp_tools)} tools")
165
-
166
- except Exception as e:
167
- logger.error(f"Error initializing MCP server: {e}")
168
- mcp_initialized = False
169
-
170
  @app.get("/", response_class=HTMLResponse)
171
  async def index():
172
  """Serve the landing page."""
@@ -175,231 +112,35 @@ async def index():
175
  @app.get("/health")
176
  async def health():
177
  """Health check endpoint."""
 
 
 
 
 
 
 
178
  return {
179
  "status": "healthy",
180
  "service": "wandb-mcp-server",
181
  "wandb_configured": wandb_configured,
182
- "mcp_initialized": mcp_initialized,
183
- "tools_count": len(mcp_tools)
184
  }
185
 
186
- # MCP Streamable HTTP Implementation
187
- @app.post("/mcp")
188
- async def handle_mcp_post(request: Request):
189
- """
190
- Handle MCP POST requests following the streamable HTTP transport protocol.
191
- """
192
- if not mcp_initialized:
193
- return JSONResponse(
194
- status_code=503,
195
- content={
196
- "jsonrpc": "2.0",
197
- "error": {
198
- "code": -32603,
199
- "message": "MCP server not initialized"
200
- }
201
- }
202
- )
203
-
204
- try:
205
- body = await request.json()
206
- method = body.get("method", "")
207
- params = body.get("params", {})
208
- request_id = body.get("id")
209
-
210
- # Handle different MCP methods
211
- if method == "initialize":
212
- return {
213
- "jsonrpc": "2.0",
214
- "id": request_id,
215
- "result": {
216
- "protocolVersion": "0.1.0",
217
- "capabilities": {
218
- "tools": {"listChanged": True},
219
- "prompts": {"listChanged": False},
220
- "resources": {"listChanged": False}
221
- },
222
- "serverInfo": {
223
- "name": "wandb-mcp-server",
224
- "version": "0.1.0"
225
- }
226
- }
227
- }
228
-
229
- elif method == "tools/list":
230
- tools_list = []
231
- for tool_name, tool_info in mcp_tools.items():
232
- # Extract a shorter description (first line)
233
- desc_lines = tool_info["description"].split('\n')
234
- short_desc = desc_lines[0] if desc_lines else f"W&B tool: {tool_name}"
235
-
236
- tools_list.append({
237
- "name": tool_name,
238
- "description": short_desc,
239
- "inputSchema": {
240
- "type": "object",
241
- "properties": {},
242
- "required": []
243
- }
244
- })
245
-
246
- return {
247
- "jsonrpc": "2.0",
248
- "id": request_id,
249
- "result": {"tools": tools_list}
250
- }
251
-
252
- elif method == "tools/call":
253
- tool_name = params.get("name")
254
- tool_args = params.get("arguments", {})
255
-
256
- if tool_name not in mcp_tools:
257
- return {
258
- "jsonrpc": "2.0",
259
- "id": request_id,
260
- "error": {
261
- "code": -32601,
262
- "message": f"Tool not found: {tool_name}"
263
- }
264
- }
265
-
266
- try:
267
- tool_info = mcp_tools[tool_name]
268
- tool_function = tool_info["function"]
269
-
270
- # Execute the tool
271
- if tool_info["async"]:
272
- result = await tool_function(**tool_args)
273
- else:
274
- result = tool_function(**tool_args)
275
-
276
- # Format the result
277
- if isinstance(result, str):
278
- content_text = result
279
- elif isinstance(result, dict):
280
- content_text = json.dumps(result, indent=2)
281
- else:
282
- content_text = str(result)
283
-
284
- return {
285
- "jsonrpc": "2.0",
286
- "id": request_id,
287
- "result": {
288
- "content": [
289
- {
290
- "type": "text",
291
- "text": content_text
292
- }
293
- ]
294
- }
295
- }
296
-
297
- except Exception as e:
298
- logger.error(f"Error executing tool {tool_name}: {e}")
299
- return {
300
- "jsonrpc": "2.0",
301
- "id": request_id,
302
- "error": {
303
- "code": -32603,
304
- "message": f"Tool execution error: {str(e)}"
305
- }
306
- }
307
-
308
- else:
309
- return {
310
- "jsonrpc": "2.0",
311
- "id": request_id,
312
- "error": {
313
- "code": -32601,
314
- "message": f"Method not found: {method}"
315
- }
316
- }
317
-
318
- except Exception as e:
319
- logger.error(f"Error handling MCP request: {e}")
320
- return JSONResponse(
321
- status_code=500,
322
- content={
323
- "jsonrpc": "2.0",
324
- "error": {
325
- "code": -32603,
326
- "message": f"Internal error: {str(e)}"
327
- }
328
- }
329
- )
330
-
331
- @app.get("/mcp")
332
- async def handle_mcp_sse(request: Request):
333
- """
334
- Handle MCP GET requests for SSE (Server-Sent Events) streaming.
335
- This enables server-initiated messages and long-lived connections.
336
- """
337
- if not mcp_initialized:
338
- return JSONResponse(
339
- status_code=503,
340
- content={"error": "MCP server not initialized"}
341
- )
342
-
343
- async def event_stream():
344
- """Generate server-sent events for MCP."""
345
- try:
346
- # Send initial connection confirmation
347
- yield f"data: {json.dumps({'jsonrpc': '2.0', 'method': 'connection/ready', 'params': {'status': 'connected', 'timestamp': datetime.utcnow().isoformat()}})}\n\n"
348
-
349
- # Keep connection alive with periodic heartbeats
350
- while True:
351
- await asyncio.sleep(30)
352
- yield f"data: {json.dumps({'jsonrpc': '2.0', 'method': 'ping', 'params': {'timestamp': datetime.utcnow().isoformat()}})}\n\n"
353
-
354
- except asyncio.CancelledError:
355
- logger.info("SSE connection closed")
356
- raise
357
- except Exception as e:
358
- logger.error(f"Error in SSE stream: {e}")
359
- yield f"data: {json.dumps({'jsonrpc': '2.0', 'method': 'error', 'params': {'message': str(e)}})}\n\n"
360
-
361
- return StreamingResponse(
362
- event_stream(),
363
- media_type="text/event-stream",
364
- headers={
365
- "Cache-Control": "no-cache",
366
- "Connection": "keep-alive",
367
- "X-Accel-Buffering": "no",
368
- "Access-Control-Allow-Origin": "*"
369
- }
370
- )
371
 
372
- # Additional MCP endpoints for better compatibility
373
- @app.options("/mcp")
374
- async def handle_mcp_options():
375
- """Handle OPTIONS requests for CORS preflight."""
376
- return Response(
377
- status_code=200,
378
- headers={
379
- "Access-Control-Allow-Origin": "*",
380
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
381
- "Access-Control-Allow-Headers": "Content-Type, Accept",
382
- "Access-Control-Max-Age": "3600"
383
- }
384
- )
385
 
386
- def main():
387
- """Main entry point for HuggingFace Spaces."""
388
- port = 7860
389
- host = "0.0.0.0"
390
-
391
- logger.info(f"Starting server on {host}:{port}")
392
  logger.info("Landing page: /")
393
  logger.info("Health check: /health")
394
- logger.info("MCP endpoint (Streamable HTTP): /mcp")
395
-
396
- uvicorn.run(
397
- app,
398
- host=host,
399
- port=port,
400
- log_level="info",
401
- reload=False
402
- )
403
-
404
- if __name__ == "__main__":
405
- main()
 
2
  """
3
  HuggingFace Spaces entry point for the Weights & Biases MCP Server.
4
 
5
+ Using the correct FastMCP mounting pattern with streamable_http_app().
6
  """
7
 
8
  import os
9
  import sys
10
  import logging
11
+ import contextlib
 
12
  from pathlib import Path
 
 
13
 
14
  # Add the src directory to Python path
15
  sys.path.insert(0, str(Path(__file__).parent / "src"))
 
22
  os.environ["WANDB_SILENT"] = "True"
23
  os.environ["WEAVE_SILENT"] = "True"
24
 
25
+ from fastapi import FastAPI
26
+ from fastapi.responses import HTMLResponse
27
  from fastapi.middleware.cors import CORSMiddleware
28
+ from mcp.server.fastmcp import FastMCP
29
+
30
+ # Import W&B setup functions
31
+ from wandb_mcp_server.server import (
32
+ validate_and_get_api_key,
33
+ setup_wandb_login,
34
+ configure_wandb_logging,
35
+ initialize_weave_tracing,
36
+ register_tools,
37
+ ServerMCPArgs
38
+ )
39
 
40
  # Configure logging
41
  logging.basicConfig(
 
49
  with open(INDEX_HTML_PATH, "r") as f:
50
  INDEX_HTML_CONTENT = f.read()
51
 
52
+ # Initialize W&B
53
+ logger.info("Initializing W&B configuration...")
54
+ configure_wandb_logging()
55
+
56
+ args = ServerMCPArgs(
57
+ transport="http",
58
+ host="0.0.0.0",
59
+ port=7860,
60
+ wandb_api_key=os.environ.get("WANDB_API_KEY")
61
+ )
62
+
63
+ wandb_configured = False
64
+ try:
65
+ api_key = validate_and_get_api_key(args)
66
+ setup_wandb_login(api_key)
67
+ initialize_weave_tracing()
68
+ wandb_configured = True
69
+ logger.info("W&B API configured successfully")
70
+ except ValueError as e:
71
+ logger.warning(f"W&B API key not configured: {e}")
72
+ logger.warning("Server will start but W&B operations will fail")
73
+
74
+ # Create the MCP server
75
+ logger.info("Creating W&B MCP server...")
76
+ mcp = FastMCP("wandb-mcp-server")
77
+
78
+ # Register all W&B tools
79
+ register_tools(mcp)
80
+
81
+ # Create lifespan context manager for session management
82
+ @contextlib.asynccontextmanager
83
+ async def lifespan(app: FastAPI):
84
+ """Manage MCP session lifecycle."""
85
+ async with mcp.session_manager.run():
86
+ logger.info("MCP session manager started")
87
+ yield
88
+ logger.info("MCP session manager stopped")
89
+
90
+ # Create the main FastAPI app with lifespan
91
  app = FastAPI(
92
  title="Weights & Biases MCP Server",
93
+ description="Model Context Protocol server for W&B",
94
+ lifespan=lifespan
95
  )
96
 
97
  # Add CORS middleware
 
103
  allow_headers=["*"],
104
  )
105
 
106
+ # Add custom routes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  @app.get("/", response_class=HTMLResponse)
108
  async def index():
109
  """Serve the landing page."""
 
112
  @app.get("/health")
113
  async def health():
114
  """Health check endpoint."""
115
+ # list_tools is async, so we need to handle it properly
116
+ try:
117
+ tools = await mcp.list_tools()
118
+ tool_count = len(tools)
119
+ except:
120
+ tool_count = 0
121
+
122
  return {
123
  "status": "healthy",
124
  "service": "wandb-mcp-server",
125
  "wandb_configured": wandb_configured,
126
+ "tools_registered": tool_count
 
127
  }
128
 
129
+ # Mount the MCP streamable HTTP app
130
+ # Note: streamable_http_app() creates internal routes at /mcp
131
+ # So we mount at root to avoid /mcp/mcp double path
132
+ mcp_app = mcp.streamable_http_app()
133
+ logger.info("Mounting MCP streamable HTTP app")
134
+ # Mount at root, so MCP endpoint will be at /mcp (not /mcp/mcp)
135
+ app.mount("/", mcp_app)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ # Port for HF Spaces
138
+ PORT = int(os.environ.get("PORT", "7860"))
 
 
 
 
 
 
 
 
 
 
 
139
 
140
+ if __name__ == "__main__":
141
+ import uvicorn
142
+ logger.info(f"Starting server on 0.0.0.0:{PORT}")
 
 
 
143
  logger.info("Landing page: /")
144
  logger.info("Health check: /health")
145
+ logger.info("MCP endpoint: /mcp")
146
+ uvicorn.run(app, host="0.0.0.0", port=PORT)
 
 
 
 
 
 
 
 
 
 
uv.lock CHANGED
@@ -2092,6 +2092,7 @@ name = "wandb-mcp-server"
2092
  version = "0.1.0"
2093
  source = { editable = "." }
2094
  dependencies = [
 
2095
  { name = "httpx" },
2096
  { name = "mcp", extra = ["cli"] },
2097
  { name = "networkx" },
@@ -2100,16 +2101,13 @@ dependencies = [
2100
  { name = "requests" },
2101
  { name = "simple-parsing" },
2102
  { name = "tiktoken" },
 
2103
  { name = "wandb" },
2104
  { name = "wandb-workspaces" },
2105
  { name = "weave" },
2106
  ]
2107
 
2108
  [package.optional-dependencies]
2109
- http = [
2110
- { name = "fastapi" },
2111
- { name = "uvicorn" },
2112
- ]
2113
  test = [
2114
  { name = "anthropic" },
2115
  { name = "litellm" },
@@ -2125,7 +2123,7 @@ dev = [
2125
  [package.metadata]
2126
  requires-dist = [
2127
  { name = "anthropic", marker = "extra == 'test'", specifier = ">=0.50.0" },
2128
- { name = "fastapi", marker = "extra == 'http'", specifier = ">=0.104.0" },
2129
  { name = "httpx", specifier = ">=0.28.1" },
2130
  { name = "litellm", marker = "extra == 'test'", specifier = ">=1.67.2" },
2131
  { name = "mcp", extras = ["cli"], specifier = ">=1.0.0" },
@@ -2137,12 +2135,12 @@ requires-dist = [
2137
  { name = "requests", specifier = ">=2.31.0" },
2138
  { name = "simple-parsing", specifier = ">=0.1.7" },
2139
  { name = "tiktoken", specifier = ">=0.9.0" },
2140
- { name = "uvicorn", marker = "extra == 'http'", specifier = ">=0.24.0" },
2141
  { name = "wandb", specifier = ">=0.19.8" },
2142
  { name = "wandb-workspaces", specifier = ">=0.1.12" },
2143
  { name = "weave", specifier = ">=0.51.56" },
2144
  ]
2145
- provides-extras = ["test", "http"]
2146
 
2147
  [package.metadata.requires-dev]
2148
  dev = [{ name = "ruff", specifier = ">=0.11.12" }]
 
2092
  version = "0.1.0"
2093
  source = { editable = "." }
2094
  dependencies = [
2095
+ { name = "fastapi" },
2096
  { name = "httpx" },
2097
  { name = "mcp", extra = ["cli"] },
2098
  { name = "networkx" },
 
2101
  { name = "requests" },
2102
  { name = "simple-parsing" },
2103
  { name = "tiktoken" },
2104
+ { name = "uvicorn" },
2105
  { name = "wandb" },
2106
  { name = "wandb-workspaces" },
2107
  { name = "weave" },
2108
  ]
2109
 
2110
  [package.optional-dependencies]
 
 
 
 
2111
  test = [
2112
  { name = "anthropic" },
2113
  { name = "litellm" },
 
2123
  [package.metadata]
2124
  requires-dist = [
2125
  { name = "anthropic", marker = "extra == 'test'", specifier = ">=0.50.0" },
2126
+ { name = "fastapi", specifier = ">=0.104.0" },
2127
  { name = "httpx", specifier = ">=0.28.1" },
2128
  { name = "litellm", marker = "extra == 'test'", specifier = ">=1.67.2" },
2129
  { name = "mcp", extras = ["cli"], specifier = ">=1.0.0" },
 
2135
  { name = "requests", specifier = ">=2.31.0" },
2136
  { name = "simple-parsing", specifier = ">=0.1.7" },
2137
  { name = "tiktoken", specifier = ">=0.9.0" },
2138
+ { name = "uvicorn", specifier = ">=0.24.0" },
2139
  { name = "wandb", specifier = ">=0.19.8" },
2140
  { name = "wandb-workspaces", specifier = ">=0.1.12" },
2141
  { name = "weave", specifier = ">=0.51.56" },
2142
  ]
2143
+ provides-extras = ["test"]
2144
 
2145
  [package.metadata.requires-dev]
2146
  dev = [{ name = "ruff", specifier = ">=0.11.12" }]