Used for feedback · quests · score card
Connect the AI agent and MCP server with Docker Compose so they can discover and call each other over the internal network.
Docker Compose connecting agent + MCP server
Services communicate via Docker DNS
Verify the full pipeline with curl
The compose file defines both services on a shared network. The agent references the MCP server
by its Docker DNS name mcp-server.
Each language uses the same compose structure but points to different build contexts.
services: mcp-server: build: ./services/mcp-server ports: - "8000:8000" environment: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 10s retries: 3 agent: build: ./services/agent ports: - "8001:8001" environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_MODEL=${OPENAI_MODEL:-gemini-3.5-flash-lite} - MCP_SERVER_URL=http://mcp-server:8000 depends_on: mcp-server: condition: service_healthy
services: mcp-server: build: ./services/mcp-server ports: - "8000:8000" environment: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 10s retries: 3 agent: build: ./services/agent ports: - "8001:8001" environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_MODEL=${OPENAI_MODEL:-gemini-3.5-flash-lite} - MCP_SERVER_URL=http://mcp-server:8000 depends_on: mcp-server: condition: service_healthy
services: mcp-server: build: ./services/mcp-server ports: - "8000:8000" environment: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 10s retries: 3 agent: build: ./services/agent ports: - "8001:8001" environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_MODEL=${OPENAI_MODEL:-gemini-3.5-flash-lite} - MCP_SERVER_URL=http://mcp-server:8000 depends_on: mcp-server: condition: service_healthy
services: mcp-server: build: ./services/mcp-server ports: - "8000:8000" environment: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 10s retries: 3 agent: build: ./services/agent ports: - "8001:8001" environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_MODEL=${OPENAI_MODEL:-gemini-3.5-flash-lite} - MCP_SERVER_URL=http://mcp-server:8000 depends_on: mcp-server: condition: service_healthy
services: mcp-server: build: ./services/mcp-server ports: - "8000:8000" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 10s retries: 3 agent: build: ./services/agent ports: - "8001:8001" environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_MODEL=${OPENAI_MODEL:-gemini-3.5-flash-lite} - MCP_SERVER_URL=http://mcp-server:8000 depends_on: mcp-server: condition: service_healthy
services: mcp-server: build: ./services/mcp-server ports: - "8000:8000" environment: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 10s retries: 3 agent: build: ./services/agent ports: - "8001:8001" environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_BASE_URL=${OPENAI_BASE_URL:-} - OPENAI_MODEL=${OPENAI_MODEL:-gemini-3.5-flash-lite} - MCP_SERVER_URL=http://mcp-server:8000 depends_on: mcp-server: condition: service_healthy
Two differences from the plain Python compose:
(1) OPENAI_BASE_URL is forwarded so LiteLlm can reach Gemini, Ollama or any OpenAI-compatible endpoint.
(2) If your MCP server uses FastMCP, make sure it starts with transport="sse" —
ADK's MCPToolset connects to /sse, not /message.
Key insight: The agent uses MCP_SERVER_URL=http://mcp-server:8000.
Docker Compose creates a shared network where services can reach each other by name.
The depends_on with service_healthy
ensures the MCP server is ready before the agent starts.
Store your API keys in a .env file.
Docker Compose reads this file automatically and injects the variables into your containers.
# Required: your Gemini API key OPENAI_API_KEY=your-gemini-api-key-here OPENAI_MODEL=gemini-3.5-flash-lite # The weather tool needs no key: yr.no and Nominatim are free. # Optional: another OpenAI-compatible provider (OpenAI, Ollama, ...) # OPENAI_BASE_URL=http://ollama:11434/v1
Important: Never commit your .env file to git.
Add it to .gitignore to keep your secrets safe.
Build and start both services. Docker Compose will create the network, build the images, and start the containers in the correct order.
# Build and start all services docker compose up --build -d # Watch the logs to verify startup docker compose logs -f # Check service health docker compose ps
You should see both services running and healthy:
NAME STATUS PORTS mcp-server Up 10 seconds (healthy) 0.0.0.0:8000->8000/tcp agent Up 5 seconds 0.0.0.0:8001->8001/tcp
Before testing the full pipeline, verify the MCP server is responding correctly on its own.
# Health check curl http://localhost:8000/health # List available tools curl -X POST http://localhost:8000/message \ -H "Content-Type: application/json" \ -H "MCP-Protocol-Version: 2026-07-28" \ -H "Mcp-Method: tools/list" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}' # Call the weather tool directly curl -X POST http://localhost:8000/message \ -H "Content-Type: application/json" \ -H "MCP-Protocol-Version: 2026-07-28" \ -H "Mcp-Method: tools/call" \ -H "Mcp-Name: get_weather_forecast" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather_forecast","arguments":{"location":"Oslo"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
Now test the full pipeline: your query goes to the agent, which calls the LLM, which decides to use a tool, the agent calls the MCP server, and returns the final answer.
# Query the agent (full pipeline: Agent → LLM → MCP → LLM → Response) curl -X POST http://localhost:8001/query \ -H "Content-Type: application/json" \ -d '{"query": "What is the weather in Oslo?"}'
You should get a natural-language response about the weather in Oslo, something like:
{
"response": "The current weather in Oslo is 8°C with partly cloudy skies..."
}
Congratulations! If you see a weather response, the full pipeline is working: Agent discovered tools from MCP, sent your query to the LLM, the LLM called the weather tool, and the agent returned the result. The MCP protocol is doing its job.
Common issues and how to fix them:
# Agent can't reach MCP server? Check the network docker compose exec agent curl http://mcp-server:8000/health # See detailed logs for a specific service docker compose logs agent --tail=50 docker compose logs mcp-server --tail=50 # Rebuild everything from scratch docker compose down docker compose up --build -d # Check environment variables are injected docker compose exec agent env | grep OPENAI
Common pitfall: If the agent starts before the MCP server is healthy, tool discovery
will fail. The depends_on with service_healthy
condition prevents this, but make sure your MCP server has a working health check endpoint.
Your feedback helps us improve the workshop.
Your handle is sent with this feedback; leave it blank in the header and the submission stays anonymous. Please keep personal data out of the comment too — no name, e-mail address or employer, yours or anyone else's.