Nicolás Duque

Designing an MCP server an LLM can use safely

Aug 19, 2026 · 2 min read

I trade short-dated options, and I built my own analytics platform for it: a FastAPI backend, a React dashboard with real-time updates over WebSockets, and infrastructure on AWS with Terraform. The piece I use most every day is an MCP server that lets Claude read that data and help me think through a session.

It exposes 22 tools. Here's what I learned designing them.

Group tools by what they're allowed to do

The tools fall into four groups, and the groups matter more than the individual tools:

  1. Market data: quotes, futures, options positioning, key levels, the economic calendar.
  2. Account monitoring, read-only: positions, balances, day P&L, recent fills.
  3. Order staging: prepares an order ticket and never sends it.
  4. Trade journal: read and write structured reflections on each trade and each session.

Only the journal writes anything, and it only writes to my own journal.

Keep the human on the irreversible step

The order tool is called stage_option_order, and it does exactly that. It returns a ticket: the option symbol, the order payload, and a preview with notional, maximum risk and breakeven. I review it and place the order myself in the broker's app.

This rule is written in three places: the tool's name, its description, and the server-level instructions the model sees when it connects. Models read tool descriptions carefully; saying "this NEVER submits" in plain words is part of the safety design, not documentation.

Give the model snapshots, and fail per field

Early on, answering "how does the market look?" took the model five or six tool calls. Now there are snapshot tools that gather several sources in parallel and return one object.

The important detail is how they fail. Each source is fetched independently, and if one fails, its slot contains an error message while the rest of the snapshot still comes back:

def _safe(v):
    return {"error": str(v)} if isinstance(v, Exception) else v

A model handles "futures quote unavailable, here's everything else" much better than a single opaque exception. Explicit, specific errors are the best prompt you can give.

Authentication is not optional

The server speaks MCP over streamable HTTP and every request needs a token scoped to MCP, minted by the main app for an authenticated user, with an OAuth flow using PKCE for clients that need it. Every tool resolves the current user from that token. Personal financial data behind an MCP server deserves the same care as behind any other API.

Descriptions are the interface

Each tool has a docstring with what it returns, every argument, the accepted values and sensible defaults. That text is what the model uses to decide which tool to call and how. When a tool is misused, the first fix is usually the description, not the code.

What I'd tell someone building their first MCP server

  • Group tools by permission, and make irreversible actions impossible, not just discouraged.
  • Prefer a few well-shaped aggregate tools over many tiny ones.
  • Return partial results with explicit errors.
  • Write descriptions for the model, in plain language, and treat them as part of the product.