diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..09d628b --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,29 @@ +name: Deploy MCP Server API + +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v4 + + - name: Install AWS Copilot CLI + run: | + curl -Lo copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-linux \ + && chmod +x copilot \ + && sudo mv copilot /usr/local/bin/copilot + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Deploy service + run: copilot svc deploy --name mcp-server-api diff --git a/src/server.py b/src/server.py index 871fc08..5c9521d 100644 --- a/src/server.py +++ b/src/server.py @@ -1,4 +1,5 @@ import uvicorn +import time from starlette.middleware import Middleware from starlette.middleware.cors import CORSMiddleware from starlette.middleware.base import BaseHTTPMiddleware @@ -18,6 +19,29 @@ mcp = FastMCP("SerpApi MCP Server") logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +def emit_metric(namespace: str, metrics: dict, dimensions: dict = {}): + emf_event = { + "_aws": { + "Timestamp": int(time.time() * 1000), + "CloudWatchMetrics": [ + { + "Namespace": namespace, + "Dimensions": [list(dimensions.keys())] if dimensions else [], + "Metrics": [ + {"Name": name, "Unit": unit} + for name, (_, unit) in metrics.items() + ], + } + ], + }, + **dimensions, + **{name: value for name, (value, _) in metrics.items()}, + } + + logger.info(json.dumps(emf_event)) def extract_error_response(exception) -> str: @@ -94,6 +118,28 @@ async def dispatch(self, request: Request, call_next): return await call_next(request) +class RequestMetricsMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + start = time.time() + response = await call_next(request) + duration = time.time() - start + + emit_metric( + namespace="mcp", + metrics={ + "RequestCount": (1, "Count"), + "ResponseTime": (duration * 1000, "Milliseconds"), + }, + dimensions={ + "Service": "mcp-server-api", + "Method": request.method, + "StatusCode": str(response.status_code), + }, + ) + + return response + + @mcp.tool() async def search(params: dict[str, Any] = {}, mode: str = "complete") -> str: """Universal search tool supporting all SerpApi engines and result types. @@ -201,6 +247,7 @@ async def healthcheck_handler(request): def main(): middleware = [ + Middleware(RequestMetricsMiddleware), Middleware(ApiKeyMiddleware), Middleware( CORSMiddleware,