diff --git a/app/_config/releases/ai-gateway/v1.yml b/app/_config/releases/ai-gateway/v1.yml index 4ecc0e8dd1..959284b2e7 100644 --- a/app/_config/releases/ai-gateway/v1.yml +++ b/app/_config/releases/ai-gateway/v1.yml @@ -57,8 +57,7 @@ app/_how-tos/ai-gateway/v1/mcp/map-API-to-mcp-tools.md: app/_how-tos/ai-gateway/v1/mcp/map-weather-api-to-mcp-tools.md: canonical_url: /ai-gateway/get-started-with-mcp-server/ app/_how-tos/ai-gateway/v1/mcp/observe-autogenerated-mcp-tools-for-weather-api.md: - status: pending - canonical_url: /ai-gateway/mcp/ + canonical_url: /ai-gateway/monitor-mcp-traffic-with-http-log/ app/_how-tos/ai-gateway/v1/mcp/observe-mcp-traffic-with-acls.md: status: pending canonical_url: /ai-gateway/mcp/ diff --git a/app/_how-tos/ai-gateway/monitor-mcp-traffic-with-http-log.md b/app/_how-tos/ai-gateway/monitor-mcp-traffic-with-http-log.md new file mode 100644 index 0000000000..ffe2e28d57 --- /dev/null +++ b/app/_how-tos/ai-gateway/monitor-mcp-traffic-with-http-log.md @@ -0,0 +1,373 @@ +--- +title: Monitor MCP traffic with the HTTP Log plugin +content_type: how_to +related_resources: + - text: "{{site.ai_gateway}}" + url: /ai-gateway/ + - text: AI MCP Server entity + url: /ai-gateway/entities/ai-mcp-server/ + - text: HTTP Log + url: /ai-gateway/policies/http-log/ + +description: | + Create an AI MCP from a mock API using `conversion-listener` mode, then use the HTTP Log Policy to record and inspect the payloads and responses from the example MCP. + +products: + - ai-gateway + +permalink: /ai-gateway/monitor-mcp-traffic-with-http-log/ + +series: + id: mcp-conversion-2-0 + position: 2 + +works_on: + - konnect + +min_version: + ai-gateway: '2.0' + +tags: + - ai + - mcp + +tldr: + q: How do I observe and validate MCP traffic from autogenerated tools? + a: | + Use an AI MCP entity to expose a mock API as an MCP tool, then use the HTTP Log Policy to capture tool calls and validate their payloads and responses. + +tools: + - kongctl + +cleanup: + inline: + - title: Destroy the {{site.base_gateway}} container + include_content: cleanup/products/gateway + icon_url: /assets/icons/gateway.svg + - title: Clean up {{site.konnect_short_name}} environment + include_content: cleanup/platform/konnect + icon_url: /assets/icons/gateway.svg + +automated_tests: false + +--- + +## Reconfigure the AI MCP Proxy plugin + +To observe traffic for MCP tools, you first must **enable logging and statistics** on the AI MCP. Apply the below configuration to reconfigure the AI MCP to log full payloads: + +```sh +kongctl apply -f - --auto-approve --pat "$KONNECT_TOKEN" < log_server.py +from http.server import BaseHTTPRequestHandler, HTTPServer +import datetime + +LOG_FILE = "kong_logs.txt" + +class LogHandler(BaseHTTPRequestHandler): + def do_POST(self): + timestamp = datetime.datetime.now().isoformat() + + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length).decode('utf-8') + + log_entry = f"{timestamp} - {post_data}\n" + with open(LOG_FILE, "a") as f: + f.write(log_entry) + + print("="*60) + print(f"Received POST request at {timestamp}") + print(f"Path: {self.path}") + print("Headers:") + for header, value in self.headers.items(): + print(f" {header}: {value}") + print("Body:") + print(post_data) + print("="*60) + + # Send OK response + self.send_response(200) + self.end_headers() + self.wfile.write(b"OK") + +if __name__ == '__main__': + server_address = ('', 9999) + httpd = HTTPServer(server_address, LogHandler) + print("Starting log server on http://0.0.0.0:9999") + httpd.serve_forever() +EOF +``` + +Now, run this script with Python and UV: + +```sh +uv run log_server.py +``` + +If the script is successful, you'll receive the following prompt in your terminal: + +```sh +Starting log server on http://0.0.0.0:9999 +``` + +## Validate MCP tools configuration + +You can validate that the HTTP Logs plugin is collecting metrics by generating MCP traffic to the mock API server. Enter the following question in Chatwise: + +```text +what has bob ordered ? +``` + +Once Chatwise has finished reasoning, you will see MCP audit log entries logged by the HTTP plugin in your terminal similar to the following: + + +```sh +Received POST request at 2026-07-31T15:56:28.232507 +Path: / +Headers: + Content-Length: 5145 + Authorization: Bearer some-token + Content-Type: application/json + Host: host.docker.internal:9999 + User-Agent: lua-resty-http/0.17.2 (Lua) ngx_lua/10028 +Body: +{ + "upstream_uri": "/", + "latencies": { + "redis": 0, + "socket": 0, + "request": 15, + "kong": 14, + "proxy": -1, + "third_party": 5.936128, + "client": 0.7616, + "receive": 0, + "http_client": 5.936128, + "kong_internal": 8.50176, + "dns": 0 + }, + "request": { + "headers": { + "mcp-protocol-version": "2025-11-25", + "content-length": "126", + "accept": "application/json, text/event-stream", + "connection": "keep-alive", + "accept-language": "en-GB", + "mcp-session-id": "8b2d1534-3ef9-4708-9baf-5cf36b4f5b1f", + "content-type": "application/json", + "host": "localhost:8000", + "user-agent": "ChatWise/26.7.4" + }, + "size": 483, + "method": "POST", + "url": "http://localhost:8000/marketplace", + "id": "cc054f73a6b04f3573cdf02883d92f53", + "uri": "/marketplace", + "querystring": {} + }, + "route": { + "service": { + "id": "c16f08e6-4cfe-5c39-8627-f744f091e592" + }, + "regex_priority": 0, + "preserve_host": false, + "protocols": [ + "http", + "https" + ], + "path_handling": "v0", + "name": "marketplace-mcp-route", + "id": "a878697a-8bbc-591b-8640-e1bd5aba261c", + "ws_id": "17dadd2f-94c5-4ba4-86bb-8d2d4e6e22cd", + "paths": [ + "/marketplace" + ], + "updated_at": 1785509686, + "strip_path": true, + "https_redirect_status_code": 426, + "response_buffering": true, + "request_buffering": true, + "created_at": 1785509686 + }, + "started_at": 1785509788214, + "client_ip": "192.168.165.1", + "workspace": "17dadd2f-94c5-4ba4-86bb-8d2d4e6e22cd", + "workspace_name": "default", + "ai": { + "mcp": { + "mcp_session_id": "8b2d1534-3ef9-4708-9baf-5cf36b4f5b1f", + "rpc": [ + { + "id": "2", + "method": "tools/call", + "response_body_size": 2175, + "tool_name": "get-orders-for-user", + "latency": 15, + "payload": { + "request": "{\"id\":2,\"params\":{\"name\":\"get-orders-for-user\",\"arguments\":{\"query_userid\":\"e5f6g7h8\"}},\"jsonrpc\":\"2.0\",\"method\":\"tools/call\"}", + "response": "{\"id\":2,\"jsonrpc\":\"2.0\",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"[{\\\"id\\\":\\\"ord001\\\",\\\"name\\\":\\\"Sugar (50kg)\\\",\\\"userId\\\":\\\"a1b2c3d4\\\"},{\\\"id\\\":\\\"ord002\\\",\\\"name\\\":\\\"Cleaning Supplies Pack\\\",\\\"userId\\\":\\\"a1b2c3d4\\\"},{\\\"id\\\":\\\"ord003\\\",\\\"name\\\":\\\"Canned Tomatoes (100 cans)\\\",\\\"userId\\\":\\\"a1b2c3d4\\\"},{\\\"id\\\":\\\"ord004\\\",\\\"name\\\":\\\"Flour (100kg)\\\",\\\"userId\\\":\\\"e5f6g7h8\\\"},{\\\"id\\\":\\\"ord005\\\",\\\"name\\\":\\\"Dish Soap (10 bottles)\\\",\\\"userId\\\":\\\"e5f6g7h8\\\"},{\\\"id\\\":\\\"ord006\\\",\\\"name\\\":\\\"Salt (25kg)\\\",\\\"userId\\\":\\\"e5f6g7h8\\\"},{\\\"id\\\":\\\"ord007\\\",\\\"name\\\":\\\"Olive Oil (20L)\\\",\\\"userId\\\":\\\"i9j0k1l2\\\"},{\\\"id\\\":\\\"ord008\\\",\\\"name\\\":\\\"Baking Powder (10kg)\\\",\\\"userId\\\":\\\"i9j0k1l2\\\"},{\\\"id\\\":\\\"ord009\\\",\\\"name\\\":\\\"Rice (200kg)\\\",\\\"userId\\\":\\\"m3n4o5p6\\\"},{\\\"id\\\":\\\"ord010\\\",\\\"name\\\":\\\"Vegetable Oil (15L)\\\",\\\"userId\\\":\\\"m3n4o5p6\\\"},{\\\"id\\\":\\\"ord011\\\",\\\"name\\\":\\\"Pasta (80kg)\\\",\\\"userId\\\":\\\"m3n4o5p6\\\"},{\\\"id\\\":\\\"ord012\\\",\\\"name\\\":\\\"Canned Beans (50 cans)\\\",\\\"userId\\\":\\\"m3n4o5p6\\\"},{\\\"id\\\":\\\"ord013\\\",\\\"name\\\":\\\"Toilet Paper (Case of 48)\\\",\\\"userId\\\":\\\"q7r8s9t0\\\"},{\\\"id\\\":\\\"ord014\\\",\\\"name\\\":\\\"Hand Sanitizer (20 bottles)\\\",\\\"userId\\\":\\\"q7r8s9t0\\\"},{\\\"id\\\":\\\"ord015\\\",\\\"name\\\":\\\"Laundry Detergent (10L)\\\",\\\"userId\\\":\\\"u1v2w3x4\\\"},{\\\"id\\\":\\\"ord016\\\",\\\"name\\\":\\\"Trash Bags (100 ct)\\\",\\\"userId\\\":\\\"u1v2w3x4\\\"},{\\\"id\\\":\\\"ord017\\\",\\\"name\\\":\\\"Disinfectant Spray (5 bottles)\\\",\\\"userId\\\":\\\"u1v2w3x4\\\"},{\\\"id\\\":\\\"ord018\\\",\\\"name\\\":\\\"Coffee Beans (30kg)\\\",\\\"userId\\\":\\\"k7l8m9n0\\\"},{\\\"id\\\":\\\"ord019\\\",\\\"name\\\":\\\"Tea Bags (500ct)\\\",\\\"userId\\\":\\\"k7l8m9n0\\\"},{\\\"id\\\":\\\"ord020\\\",\\\"name\\\":\\\"Condensed Milk (40 cans)\\\",\\\"userId\\\":\\\"k7l8m9n0\\\"},{\\\"id\\\":\\\"ord021\\\",\\\"name\\\":\\\"Paper Towels (24 rolls)\\\",\\\"userId\\\":\\\"g3h4i5j6\\\"},{\\\"id\\\":\\\"ord022\\\",\\\"name\\\":\\\"Broom & Mop Set\\\",\\\"userId\\\":\\\"g3h4i5j6\\\"},{\\\"id\\\":\\\"ord023\\\",\\\"name\\\":\\\"Cereal (20 boxes)\\\",\\\"userId\\\":\\\"c9d0e1f2\\\"},{\\\"id\\\":\\\"ord024\\\",\\\"name\\\":\\\"Powdered Milk (10kg)\\\",\\\"userId\\\":\\\"c9d0e1f2\\\"},{\\\"id\\\":\\\"ord025\\\",\\\"name\\\":\\\"Snacks Variety Pack\\\",\\\"userId\\\":\\\"c9d0e1f2\\\"},{\\\"id\\\":\\\"ord026\\\",\\\"name\\\":\\\"Cooking Gas Cylinder\\\",\\\"userId\\\":\\\"y5z6a7b8\\\"},{\\\"id\\\":\\\"ord027\\\",\\\"name\\\":\\\"Napkins (1000ct)\\\",\\\"userId\\\":\\\"y5z6a7b8\\\"}]\"}],\"isError\":false}}" + } + } + ] + } + }, + "tries": [], + "response": { + "status": 200, + "headers": { + "connection": "close", + "cache-control": "no-store, no-transform", + "transfer-encoding": "chunked", + "x-kong-request-id": "cc054f73a6b04f3573cdf02883d92f53", + "server": "kong/2.0.1-ai-gateway", + "content-type": "text/event-stream", + "x-kong-response-latency": "14" + }, + "size": 2503 + }, + "service": { + "name": "marketplace-mcp", + "ws_id": "17dadd2f-94c5-4ba4-86bb-8d2d4e6e22cd", + "host": "host.docker.internal", + "write_timeout": 60000, + "connect_timeout": 60000, + "updated_at": 1785509686, + "read_timeout": 60000, + "retries": 5, + "id": "c16f08e6-4cfe-5c39-8627-f744f091e592", + "created_at": 1785509686, + "port": 3000, + "enabled": true, + "protocol": "http" + }, + "source": "kong", + "upstream_status": "" +} + +```