MCP CLI API Technical Documentation
April 8, 2025 · View on GitHub
This document provides technical details about the implementation of the MCP CLI API server (server.py). It covers the architecture, design patterns, and internal workings of the API module.
Architecture Overview
The MCP CLI API server is built using Flask, a lightweight WSGI web application framework. The server exposes RESTful endpoints that map to core MCP CLI functionality, allowing clients to interact with MCP servers via HTTP requests.
Key Components
- Flask Application: The main web application that handles HTTP requests
- CORS Support: Cross-Origin Resource Sharing enabled for frontend integration
- Async Handling: Custom integration of asyncio with Flask for async operations
- JSON API: Consistent JSON-based request and response formats
- Error Handling: Standardized error responses with appropriate HTTP status codes
Module Structure
The server.py module is organized as follows:
server.py
├── Imports and setup
├── Helper functions (run_async)
├── Status endpoint
├── Servers endpoints
│ ├── GET /api/servers
│ ├── GET /api/servers/<name>
│ ├── POST /api/servers
│ ├── PUT /api/servers/<name>
│ └── DELETE /api/servers/<name>
├── Query endpoint
│ └── POST /api/query
├── Tools endpoint
│ └── GET /api/servers/<name>/tools
├── Configuration endpoints
│ ├── POST /api/config/export
│ └── POST /api/config/import
├── Command-line argument parsing
└── Main entry point
Core Functionality
Asynchronous Operations
The MCP CLI core functions are asynchronous, but Flask routes are synchronous. To bridge this gap, the API server uses a helper function run_async that creates a new event loop for each async operation:
def run_async(coroutine):
"""Run an async function in a Flask route."""
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
return loop.run_until_complete(coroutine)
finally:
loop.close()
This function is used whenever an endpoint needs to call an async function from the MCP CLI core, such as running queries or listing tools.
Request/Response Handling
All endpoints follow a consistent pattern:
- Validate the request (body parameters, URL parameters, etc.)
- Call the appropriate core function
- Return a JSON response with appropriate status codes
Success responses include a status field with value "success" and optionally a message field with a human-readable message. Error responses include an error field with a description of the error.
Tool Parsing
The /api/servers/<name>/tools endpoint includes special logic for parsing the text output from MCP CLI's list_tools function into a structured JSON format. This involves:
- Parsing each tool's name, description, and parameters
- Handling potential JSON parsing errors for parameters
- Constructing a clean JSON response with properly formatted tool definitions
# Parse the text result into a structured format
tools = []
current_tool = None
for line in result.split('\n'):
if line.startswith('• '):
if current_tool:
tools.append(current_tool)
current_tool = {
'name': line[2:].strip(),
'description': '',
'parameters': {}
}
elif current_tool and line.strip().startswith('Description:'):
current_tool['description'] = line.replace('Description:', '').strip()
elif current_tool and line.strip().startswith('Parameters:'):
# Try to parse parameters as JSON
try:
params_text = line.replace('Parameters:', '').strip()
# Complex JSON parsing logic...
current_tool['parameters'] = json.loads(params_text)
except json.JSONDecodeError:
current_tool['parameters'] = {"error": "Failed to parse parameters"}
Error Handling Strategy
The API server implements a consistent error handling strategy:
- Validation Errors: Return 400 Bad Request with a descriptive error message
- Resource Not Found: Return 404 Not Found with information about available resources
- Server Errors: Return 500 Internal Server Error with a description of what went wrong
All errors are caught and handled to prevent the server from crashing and to provide helpful information to clients.
Configuration Management
The API server interacts with the MCP CLI configuration file using the core functions load_config, save_config, export_config, and import_config. These functions handle:
- Reading the configuration from disk
- Validating changes to the configuration
- Writing updated configuration back to disk
- Exporting/importing configuration to/from files
Command Line Interface
The server includes a command-line interface with the following options:
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description="MCP CLI API Server")
parser.add_argument("--host", default="0.0.0.0", help="Host to run the server on")
parser.add_argument("--port", type=int, default=5000, help="Port to run the server on")
parser.add_argument("--debug", action="store_true", help="Run in debug mode")
return parser.parse_args()
These options allow users to customize the host, port, and debug settings when starting the server.
Security Considerations
The current implementation does not include authentication or authorization mechanisms. When deploying in a production environment, consider:
- Adding authentication (e.g., API keys, OAuth)
- Implementing TLS/SSL
- Setting up proper CORS restrictions
- Adding rate limiting to prevent abuse
Logging
The API server uses Python's standard logging module for logging information and errors:
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
Logs include timestamps, module names, log levels, and messages, which can be useful for debugging and monitoring.
Extending the API
To add a new endpoint to the API:
- Define a new Flask route with the appropriate HTTP method
- Implement request validation
- Call the necessary MCP CLI core function(s)
- Return a JSON response following the established patterns
Example:
@app.route('/api/new_endpoint', methods=['POST'])
def new_endpoint():
"""Description of the new endpoint."""
data = request.json
if not data:
return jsonify({'error': 'No data provided'}), 400
# Validate parameters
if 'required_param' not in data:
return jsonify({'error': 'required_param is required'}), 400
try:
# Call core function
result = some_core_function(data['required_param'])
# Return success response
return jsonify({
'status': 'success',
'result': result
})
except Exception as e:
return jsonify({'error': str(e)}), 500
Performance Considerations
- Each asynchronous operation creates a new event loop, which has some overhead
- For high-traffic deployments, consider using an ASGI server like Uvicorn with an async framework like FastAPI
- The current implementation does not include caching, which could improve performance for frequently accessed data
Future Improvements
Potential improvements to consider:
- Authentication: Add API key or OAuth-based authentication
- Pagination: Implement pagination for endpoints that return lists
- Websockets: Add websocket support for streaming query results
- Metrics: Add endpoint for monitoring and metrics
- Swagger/OpenAPI: Generate OpenAPI documentation
- Async Framework: Consider migrating to an async-native framework like FastAPI
- Environment Variables: Support configuration via environment variables
- Dockerization: Provide a Dockerfile for containerized deployment
Troubleshooting Common Issues
Connection Refused
If clients cannot connect to the API server, check:
- The server is running
- The host and port are correctly configured
- Firewall settings allow connections to the specified port
Timeout Errors
If requests time out, consider:
- Increasing the timeout for long-running operations
- Implementing background tasks for expensive operations
- Checking if MCP servers are responding in a timely manner
JSON Parsing Errors
If clients report JSON parsing errors:
- Check that all responses are valid JSON
- Ensure that tools with complex parameters are properly parsed
- Validate that error responses maintain the expected format