Back to Feed
article 3m read

Tool endpoint checked the protocol version and never asked who was calling

A checkpoint added an MCP endpoint to my observability tool that validates the request envelope, then runs any registered tool with whatever repository ID the caller typed. Canned data is the only reason nothing real has leaked.

Article DepthPractice

Flow

The order I want my handler to check things

  1. 1Who is calling

    Verify the login before the method is even looked up.

  2. 2May they use this method

    Listing tools and running a tool need different grants.

  3. 3Are the arguments valid

    Check them against the schema the tool already advertises.

  4. 4Whose data is it

    The tenant comes from the login, never from the arguments.

  5. 5What goes back

    A plain denial; the error details stay in my logs.

The new tool endpoint runs any tool for anyone who can reach it. The only thing it checks is that the request names the right protocol version.

It came in with a big checkpoint on my observability tool for coding agents. The web app got a route that speaks MCP, the protocol AI clients use to find tools and call them. The handler reads the request, checks the version, and picks a branch by method name. It looks neat. But no step asks who is calling, and no middleware sits in front of it. I can't tell from the code whether anything upstream would block a stranger.

What a stranger needs to call a tool

Just a tool name and some arguments. The tool then runs with whatever was sent. Most of the data tools say they need a repository ID, but nothing checks the arguments, and nothing checks whether the caller may see that repository. The ID you type picks the data and is also the only permission check. The version check felt like a safety check. It only checks the format.

Smaller problems around it

Error messages go straight back to the caller. A plain GET on the same address lists every tool name. Nothing limits run time or size. And the list of tools is written twice, in two places that don't match.

Fake data, for now

Every tool returns canned sample data right now, and the times are built from the current clock, so a sample scan always looks an hour old. That's the only reason nothing real has leaked. Once real data is wired in, any guessed repository ID will return it, and the tools meant to call paid services could spend my money. The test script in the same commit calls tools with no credentials and counts that as passing.

The admin app got this right

In the same commit, my admin app got a helper that works out what you may see from your verified login. It limits wide views to certain roles and refuses to query when it can't work out a scope. The tool endpoint skips all of that.

The order I want the handler to follow

  • Check who is calling before looking up the method.
  • Check that this caller has the grant for this method.
  • Validate the arguments against the tool's own schema.
  • Take the tenant from the login, never from the arguments.

The change is small.

In effect it's one refusal: if there's no verified caller, or they lack the grant for that tool, deny.

I'd also build the tool list once from one place, make the sample services refuse to run in a real deployment, and keep error details in my logs. A gateway in front would stop strangers, but not one customer asking for another's data.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward