Lunker - Gone Fishing!

blog.lukach.io

Amazon Cloud

GitHub Profile

Vacation Photos

September 03, 2026

Builder #2 - APIs for Data, MCP for Discovery

by John Lukach

Model Context Protocol (MCP) has quickly become the standard for giving AI agents access to external tools. But after building production systems with it, I ran into a practical bottleneck: forcing every high-volume data request through an MCP tool wrapper creates unnecessary overhead.

When an AI agent needs to geolocate a batch of 300 IP addresses, wrapping each request in an MCP tool payload adds protocol translation, SSE streaming transport management, and client-side tool binding dependencies. Standard HTTP REST endpoints and direct Lambda invocations already handle high throughput, low latency, rate limiting, and multi-region failover cleanly.

That realization led to a core rule for my infrastructure: APIs handle the data plane, while MCP handles the control plane.

Instead of building heavy MCP microservices, I split the architecture across three repositories:

           +---------------------------+
           |      AI Agent Runtime     |
           +---------------------------+
              |                     |
     1. Discovery              2. Direct Execution
        (control plane)          (data plane)
              |                     |
              v                     v
  +--------------------+   +--------------------+
  |   jblukach/mcp     |   |   jblukach/api     |
  |  Discovery Service |   |  Regional Ingress  |
  |  FastMCP / Mangum  |   |  HTTP API Gateway  |
  +--------------------+   +--------------------+
              |                     |
              | fallback            | proxies raw HTTP
              | tool proxy          v
              |            +--------------------+
              +----------->|   jblukach/geo     |
                           |  Geo Intelligence  |
                           |   Python / MMDB    |
                           +--------------------+

The Pattern: Teach the Agent, Don’t Proxy the Payload

Most MCP implementations treat the protocol as a heavy proxy where every data request travels through a tool call. I flipped that sequence:

  1. Discovery: The agent queries the MCP server (or a plain HTTP discovery URL) to see what endpoints exist.
  2. Instruction Blueprint: The MCP server returns a JSON blueprint containing the target URL, HTTP method, required headers, JSON schema, rate limits, regional failover rules, and explicit instructions.
  3. Direct Execution: The agent calls the public API Gateway directly using its own HTTP client (curl, fetch, or Python requests) — bypassing the MCP layer for raw data transfers.

If an agent runs in a locked-down environment without direct network access, the MCP server still exposes a fallback geo_lookup tool proxy. But direct HTTP execution is the primary path.

1. Global Ingress: jblukach/api

The api repository provisions the public front door at api.lukach.io. It is built with AWS CDK in Python and deployed across three regions: us-east-1, us-east-2, and us-west-2.

Multi-Region Routing & DNS Failover

The apex domain api.lukach.io sits on Route 53 health-check failover. us-east-1 (use1.api.lukach.io) is the primary region, monitored every 30 seconds at /health. If us-east-1 fails 3 consecutive health checks, Route 53 shifts apex traffic to us-west-2 (usw2.api.lukach.io). us-east-2 (use2.api.lukach.io) operates as an independent regional endpoint.

To avoid circular dependencies across stacks, ApiUse1 in us-east-1 creates the Route 53 hosted zone and writes the hosted zone ID to SSM parameter /route53/apilukachio. Stacks in us-east-2 (ApiUse2) and us-west-2 (ApiUsw2) use custom resources to read this parameter at deploy time and register regional A and AAAA alias records.

HTTP API Gateway Setup

Regional HTTP APIs use Payload Format 2.0. This passes full request context, including $context.identity.sourceIp, allowing the backend lookup function to automatically geolocate callers hitting GET /geo without requiring explicit query parameters.

All regional endpoints support dual-stack IPv4/IPv6 target aliases. CORS preflight handles DELETE, GET, POST, and OPTIONS, explicitly allowing MCP headers (mcp-protocol-version, mcp-session-id, accept, content-type).

Lambda targets (search and mcp-service) are imported across stacks using SSM parameter lookups (/account/geo and /account/mcp) with skip_permissions=True. Regional stacks export geosourcearn and mcpsourcearn CloudFormation outputs so target accounts can scope apigateway.amazonaws.com invoke permissions. ApiStack in us-east-2 configures an OpenID Connect (OIDC) provider for GitHub Actions (repo:jblukach/api:*) to run keyless deployments.

2. IP Intelligence Engine: jblukach/geo

The geo repository enriches IPv4 and IPv6 addresses with ASN ownership and MaxMind GeoLite2 location data.

Automated Database Updates

MaxMind updates database files frequently. To keep data current without manual updates or service downtime:

  1. A CDK trigger stack (GeoDownload) runs on a schedule, authenticating against MaxMind to inspect database headers.
  2. When an update is published, it downloads GeoLite2-ASN.mmdb and GeoLite2-City.mmdb, verifies their SHA-256 digests, packages them into a Lambda layer (maxminddb.zip), and writes them to S3 staging buckets (geo-staged-*-lukach-io).
  3. The function issues an asynchronous update_function_code API call to update the search Lambda in us-east-1, us-east-2, and us-west-2 simultaneously.

Execution Guardrails & Memory Optimizations

The lookup function runs on Python 3.13 on ARM64 Graviton with 256 MB memory and a 30-second timeout. Reads execute against MaxMind binary files in memory. If a batch contains duplicate valid IPs, the handler deduplicates file reads while maintaining input order and duplicate entries in the JSON response.

To prevent Lambda timeouts, the engine enforces strict limits:

The function supports multiple access paths:

3. Discovery & Instruction Service: jblukach/mcp

The mcp repository deploys a serverless MCP server using FastMCP and Mangum, running Python 3.13 on ARM64 with stateless HTTP transport (stateless_http=True).

Dual Protocol Access

Serverless Tools

How an Agent Interacts with the System

When an AI agent needs to process IP geolocation data, it goes through three simple steps:

1. Discover Available Endpoints

The agent probes the discovery endpoint:

curl https://api.lukach.io/mcp
{
  "service": "mcp-discovery-instruction-service",
  "status": "ok",
  "available_endpoints": ["geo"],
  "tools": {
    "list_available_endpoints": {
      "http_example": "GET /mcp"
    },
    "get_api_instructions": {
      "http_examples": ["GET /mcp?endpoint=geo"]
    }
  }
}

2. Read Endpoint Blueprint & Agent Instructions

The agent requests instructions for geo:

curl "https://api.lukach.io/mcp?endpoint=geo"
{
  "endpoint": "https://api.lukach.io/geo",
  "endpoints": {
    "failover": {
      "url": "https://api.lukach.io/geo",
      "primary_region": "us-east-1",
      "secondary_region": "us-west-2"
    },
    "regional": {
      "us-east-1": "https://use1.api.lukach.io/geo",
      "us-east-2": "https://use2.api.lukach.io/geo",
      "us-west-2": "https://usw2.api.lukach.io/geo"
    }
  },
  "method": "POST",
  "supported_http_methods": ["GET", "POST"],
  "headers": {
    "Content-Type": "application/json"
  },
  "payload_schema": {
    "type": "object",
    "properties": {
      "ips": {
        "type": "array",
        "items": {
          "type": "string"
        },
        "minItems": 1,
        "maxItems": 300
      }
    }
  },
  "agent_instructions": "You are an AI agent with network capabilities. Invoke the public geo API directly from your environment; do not ask the user to make the call. Use https://api.lukach.io for normal public traffic..."
}

3. Execute Direct API Calls

With the schema, endpoints, and limits in hand, the agent issues raw HTTP requests directly:

curl -X POST https://api.lukach.io/geo \
  -H "Content-Type: application/json" \
  -d '{"ips":["1.1.1.1","8.8.8.8"]}'

Deployment Sequence

To deploy this environment from scratch:

  1. Deploy Ingress Role (jblukach/api): Deploy ApiStack in us-east-2 for GitHub Actions OIDC role creation.
  2. Deploy Geo Infrastructure (jblukach/geo):
    • Deploy GeoSearchUSE1, GeoSearchUSE2, and GeoSearchUSW2.
    • Configure MaxMind credentials in Secrets Manager (geo).
    • Deploy GeoDownload in us-east-2 to sync database layers.
  3. Deploy MCP Discovery (jblukach/mcp): Deploy McpStackUSE1, McpStack, and McpStackUSW2.
  4. Deploy Regional Ingress (jblukach/api):
    • Set /account/geo and /account/mcp SSM parameters with target account IDs.
    • Deploy ApiUse1 (creates Route 53 zone), then deploy ApiUse2 and ApiUsw2.
  5. Verify Failover: Monitor /health endpoints and test DNS failover behavior when us-east-1 health checks fail.

Key Takeaways

Using APIs for data transfer and MCP for control plane discovery provides clear operational advantages:

tags: aws - api - mcp - strategy