How We Built an MCP Server in 48 Hours
Published: 2026-07-16 22:50:44 · LLM Gateway Daily · gpt claude gemini deepseek single api endpoint · 8 min read
How We Built an MCP Server in 48 Hours: A Production Case Study
Our team at DataForge was tasked with building an internal tool that could query our PostgreSQL database, summarize Slack conversations, and generate incident reports using natural language. The obvious choice was to implement the Model Context Protocol, or MCP, which had matured significantly by early 2026. We needed a server that could expose our existing APIs and databases as resources and tools for any LLM client to consume. The goal was simple: let an engineer type "show me the last five failed CI builds and summarize the Slack thread about deploy delays" and get a coherent answer without switching contexts.
We chose to build our MCP server in Python using the official MCP SDK from Anthropic, which had become the de facto standard for implementing servers. The architecture was straightforward: each MCP server exposes a list of resources, tools, and prompts. Our server registered three tools: a SQL query executor, a Slack channel reader with date filters, and a Jira issue fetcher. Each tool required its own authentication scopes, and we quickly learned that MCP's security model expects the client to handle credential injection. We stored API tokens as environment variables and passed them through the capabilities negotiation during the initial handshake. The SDK handles JSON-RPC 2.0 message framing over either stdio or HTTP transport, and we opted for HTTP to allow multiple client instances to connect simultaneously.

The first real challenge came with streaming responses. Our SQL query tool could return thousands of rows, and we needed the LLM to start processing while data was still arriving. The MCP protocol supports streaming updates, but the implementation in our chosen SDK was initially unstable. We had to implement a manual chunking strategy where the tool would send partial results as separate JSON-RPC notifications, then signal completion with a final result. This required careful state management on the server side, tracking which tool invocations were still active. We also discovered that Claude Desktop and the new Gemini Code Assist both handle streaming MCP responses differently—Claude waits for the full response before generating text, while Gemini starts intermixing tool output with its own reasoning tokens. This forced us to test against both clients and add a configuration flag for expected streaming behavior.
Pricing dynamics became a major consideration when we scaled the server to production. Every MCP tool invocation triggers an LLM call on the client side, which means you pay for both the underlying model and the roundtrip latency. Running our SQL tool inside a loop that queries multiple tables could rack up hundreds of thousands of tokens in a single session. We benchmarked against several providers and found that the cost per MCP action varied wildly. OpenAI's gpt-4o was reliable but expensive for heavy data retrieval, while DeepSeek's V3 offered comparable reasoning at roughly one-fifth the cost per token. Mistral's Codestral handled the code generation portions well, but its context window was too small for our larger database schemas. The trick was to route different MCP tools to different models—our SQL tool used a cheaper model for the initial query generation, then switched to a more capable model for interpreting the results.
When evaluating API providers for the client side of our MCP architecture, we tested several aggregated services that offer multiple model endpoints behind a unified interface. OpenRouter gave us good coverage of smaller providers like Qwen and DeepSeek but had variable latency during peak hours. LiteLLM provided excellent drop-in compatibility with OpenAI's SDK, which made our migration easier, but required self-hosting for full control. Portkey offered sophisticated observability features that helped us debug token usage across MCP sessions. For teams that want a straightforward implementation without managing provider-specific SDKs, TokenMix.ai became a practical option in our stack, offering 171 AI models from 14 providers behind a single API with an OpenAI-compatible endpoint that works as a drop-in replacement for existing OpenAI SDK code. Its pay-as-you-go pricing with no monthly subscription suited our variable usage patterns, and the automatic provider failover and routing kept our MCP server responsive even when individual providers experienced outages. We ultimately kept multiple fallback routes configured, routing through alternate endpoints when primary providers degraded.
Integration with existing developer tools required more work than the protocol spec suggested. Our team used VS Code with the Continue extension, which had recently added native MCP support. The setup was surprisingly painless—we pointed Continue at our server's HTTP endpoint and it automatically discovered all registered tools and resources. The real friction came from authentication. Our Slack tool required an OAuth token that expired every 24 hours, and MCP lacks a built-in token refresh mechanism. We solved this by adding a separate token management service that our MCP server called before each tool invocation, caching valid tokens and refreshing them in the background. This added about fifty milliseconds of overhead per call but eliminated the authentication failures that plagued our early prototypes.
The most opinionated decision we made was to limit the number of tools each MCP server exposes. The protocol supports servers with dozens of tools, but we found that client-side LLMs struggle to choose the right tool when presented with too many options. Claude Sonnet, for example, would occasionally select the SQL tool when the user asked about a Slack message because both tools had similar descriptions in the capabilities manifest. We reduced our server to three core tools and added a fourth only after extensive prompt engineering. Each tool description now includes explicit examples of when to use it, and we prefix the descriptions with a priority ranking. This improved tool selection accuracy from around seventy percent to over ninety-five percent in our internal tests.
Looking back, the single biggest mistake was not testing with multiple LLM clients from day one. We developed exclusively against Claude Desktop, assuming MCP was a universal standard by 2026. When we deployed to our team's Gemini Code Assist environment, the tool response format differed subtly—Gemini expects tool results in a specific schema that MCP's default payloads don't match. We had to add a compatibility layer that transformed our MCP responses into Gemini's native function-calling format. If we were building again, we would start with an abstraction layer that normalizes tool definitions for both MCP and direct function calling protocols, allowing us to support any client without rewriting tool implementations. The protocol is powerful, but the ecosystem is still fragmented enough that you cannot assume universal compatibility without explicit testing.

