DEV Community

QuietDesk Studio
QuietDesk Studio

Posted on

MCP vs. simple scripts: when to actually use MCP in production

Every week someone posts a new "MCP server" that's really just a single API call wrapped in boilerplate. Meanwhile, teams that actually need MCP — multiple agents, multiple tools, shared auth, dynamic discovery — sometimes skip it and end up hand-rolling a worse version of it inside their agent loop.

This post is a decision framework, not a hype piece. You'll see the actual code for both approaches, a checklist for picking the right one, and where the tradeoffs bite in production.

What MCP actually buys you

The Model Context Protocol standardizes how an AI agent (the "host") discovers and calls tools exposed by a separate process (the "server"), instead of every agent framework inventing its own tool-calling format. An MCP host is typically an AI agent that interacts with an LLM and requires services from one or more MCP servers, and for each of these MCP servers, the MCP host will create a dedicated MCP client that communicates with that server, with client and host typically running on the same machine while the MCP servers may be local or remote.

That indirection is the whole value proposition. It's not about "AI-ifying" your code — it's about making a tool callable by any MCP-compatible agent, with a standard discovery format, standard auth story, and standard transport.

The Model Context Protocol (MCP) is an open standard that gives AI models a universal way to connect to external tools, data sources, and services, and it has since become the de facto protocol for connecting AI to the real world, adopted by OpenAI, Google DeepMind, Microsoft, and thousands of development teams. That's real adoption, not just Anthropic marketing — but adoption at the ecosystem level doesn't mean every internal tool call needs to go through it.

The simple script case

If your "agent" is really: fetch some data, format it, hand it to one model, done — you don't need MCP. A plain Python function the LLM calls directly (via your framework's native tool-calling, or even just a manual function-call loop) is faster to write, faster to debug, and has zero extra moving parts.

# simple_script.py — no MCP, just a function the agent calls directly
import requests

def get_weather(city: str) -> dict:
    resp = requests.get(
        "https://api.example.com/weather",
        params={"city": city},
        timeout=5,
    )
    resp.raise_for_status()
    return resp.json()

# Wired into your agent framework's tool list directly:
tools = [get_weather]
Enter fullscreen mode Exit fullscreen mode

No server process, no transport layer, no session handling, no auth server. If this tool is only ever called by one agent, in one codebase, by one team — this is correct. Shipping an MCP server here adds a deployable, a port, a token flow, and a discovery endpoint for zero functional gain.

The MCP case

MCP starts paying for itself once any of these become true:

  • More than one agent or host needs the same tool. A support bot and an internal ops agent both need "look up order status" — you don't want that logic duplicated and drifting in two codebases.
  • The tool needs to run somewhere else. Remote execution, a different security boundary, a different team owning the code.
  • You need per-caller auth, not just an API key baked into your script. MCP's authorization model is built for this: MCP servers act as OAuth 2.1 resource servers only, validating tokens issued by an external, dedicated authorization server, which aligns with enterprise architectures where security is centralized — the MCP server's job is to validate tokens and enforce RBAC/permissions internally, but not to manage user logins or token issuance.
  • You want tool discovery instead of hardcoded tool lists, so new capabilities show up to agents without redeploying the agent itself.
  • Multiple LLM providers or agent frameworks need to share the tool without each one needing custom glue code.

Here's the same weather lookup as a minimal MCP server using the Python SDK's high-level API:

# mcp_server.py — same functionality, exposed as an MCP tool
from mcp.server.fastmcp import FastMCP
import requests

mcp = FastMCP("weather-server")

@mcp.tool()
def get_weather(city: str) -> dict:
    """Get current weather for a city."""
    resp = requests.get(
        "https://api.example.com/weather",
        params={"city": city},
        timeout=5,
    )
    resp.raise_for_status()
    return resp.json()

if __name__ == "__main__":
    mcp.run(transport="streamable-http")
Enter fullscreen mode Exit fullscreen mode

Notice what changed: nothing about the actual logic. What you gained is a standard transport, a tools/list discovery endpoint, and a hook point for adding OAuth without touching the function body. What you also took on: a process to deploy, monitor, and version.

The decision framework

Question Script MCP
Only one agent/app calls this tool? ✅ Script
Multiple agents or teams need the same tool? ✅ MCP
Tool runs in-process, same trust boundary? ✅ Script
Tool needs to run remotely or cross a security boundary? ✅ MCP
Auth is just "our one API key"? ✅ Script
You need per-user/per-caller permissions? ✅ MCP
You're prototyping or it's a weekend project? ✅ Script
You're standing up a capability other teams will build on? ✅ MCP

If you scored mostly "Script," stop reading MCP tutorials and ship the function. If you scored mostly "MCP," keep going — but know what you're signing up for operationally.

What "production MCP" actually requires in 2026

The spec has moved fast, and a lot of blog posts are already stale. As of the mid-2026 revision, the practical requirements look like this:

Stateless-first transport. The days of pinning clients to the server instance that issued their session are ending. The biggest architecture mistake in production MCP deployments isn't picking the wrong transport or the wrong database — it's designing for session affinity that the protocol no longer requires, since as of the July 28, 2026 specification, MCP is stateless-first, and most of the sticky-session infrastructure teams built over the last two years is now unnecessary weight. Concretely: the default stack is Kubernetes-hosted Streamable HTTP servers, one per capability, behind a round-robin load balancer routing on Mcp-Method, with an external OAuth 2.1 authorization server issuing audience-scoped tokens.

OAuth 2.1, not a shared API key. Every MCP server that touches real data needs authentication, and the spec mandates OAuth 2.1 as the standard, which means every team deploying MCP servers to production will eventually need to understand how the OAuth flow works in this context. Your server should validate tokens, not issue them — MCP servers MUST implement OAuth 2.0 Protected Resource Metadata (RFC9728), and MCP clients MUST use OAuth 2.0 Protected Resource Metadata for authorization server discovery.

Not every server needs auth. If your tool is read-only and public — docs lookup, public data — you can skip the whole flow: you can have a remote MCP server that requires no authentication or authorization, an example of this is the context7 MCP servers, which are remote but because they surface documentation and are read-only, don't require any verification of the MCP client. Don't build an auth server you don't need.

One deployment doesn't fit all capabilities. Skip a heavier multi-deployment design if you're serving a handful of tools to one internal team over stdio — the operational overhead isn't worth it yet. Start with stdio transport for internal tools and only move to remote Streamable HTTP when a second consumer actually shows up.

A middle path: start as a script, graduate to MCP

You don't have to pick once and commit forever. A pragmatic path:

  1. Ship the plain function/tool inside your agent framework.
  2. When a second consumer needs it, extract the function body unchanged into an MCP server (as shown above — the logic doesn't change, only the wrapper).
  3. Add OAuth only when you have a real second caller with different trust than the first — don't pre-build an authorization server for a hypothetical.
  4. Move off stdio to remote Streamable HTTP only when something outside your own machine needs to call it.

This keeps you from either extreme: shipping unauthenticated internal scripts as "production MCP servers," or wrapping every function call in a server process nobody else will ever use.

Where teams get stuck

In practice, the friction isn't the decision above — it's everything that comes after you decide MCP is right: which OAuth flow variant to implement, how to structure token validation so it doesn't leak across tool boundaries, how to write tests that actually catch a broken tool schema before an agent does, and how to avoid shipping a server that technically works but fails the first time a real agent hammers it with concurrent calls.

That's exactly the gap the MCP Production Checklist on Gumroad is built to close: a step-by-step production readiness checklist, a minimal working MCP server template (the pattern above, extended with proper OAuth resource-server validation), and a set of agent-eval test templates so you can verify your server behaves correctly before an agent finds the edge case for you. If you're past the "does MCP make sense for us" question and into "let's not get this wrong," it's worth a look.


Written with AI assistance and reviewed for accuracy.

Top comments (0)