adetoye@systems ~/speaking % cat gophers-nigeria-2026.md

Building Production-Ready MCP Servers for AI Applications and Agents

How Go and Google ADK reshape agentic system design.

My first talk — given to the Nigerian Gophers Meetup on 22 August 2026, over Meet, to a room of experienced software engineers. The demo was a Go MCP server for running a meetup, built for the meetup it was being presented at.

Nigerian Gophers Meetup 22 Aug 2026 Virtual Go · MCP · ADK
A desk lit purple: a monitor showing the meeting with someone else's slide up, and a laptop with my title slide — Building Production-Ready MCP Servers for AI Applications and Agents.
Waiting to go on. My title slide is up on the laptop; someone else still has the room.

The argument

I believe plenty of people in that room had already shipped something agentic. Some had done it in n8n — a webhook, an LLM node, an HTTP call, a Slack message — and it worked, and it delivered real value. Put that beside a Go service: a handler, an agent loop, an http.Get, a store. Same picture.

So the claim wasn't that anyone needed a new skill set. It was that the skill set is already there and hasn't been pointed at this yet. Structs. A map behind a mutex. An HTTP client that handles its own failure. Service boundaries. Least privilege. That is most of what a production-grade MCP server is made of — what changes is who calls it, and how much you can trust the caller.

The demo was built against the event itself: Meetup Ops MCP, an MCP server for running a meetup. Speakers, events and RSVPs in Go maps behind a sync.RWMutex — guarding them because an agent may call several tools at once — plus one real external call, to GitHub's public API, enriching a speaker from their handle. Unauthenticated, 60 requests an hour, no key to configure. Which mattered: it had to run live.

Tools, resources, prompts

Most MCP examples show you tools and stop there. The protocol gives you three primitives, and they are not three names for the same idea — they are three different cost and trust boundaries.

Tools

verb get_speaker(handle)

Actions the agent decides to invoke. The model reasons about whether to call one, then calls it.

Side effects, plus latency and tokens on every call — and the set you expose is the set that client is allowed to do.

Ordering the dish.

Resources

noun meetup://events/next

Context read by URI, with no “should I call this?” reasoning step in front of it.

Cheap, cacheable context — a URI is a cache key, an action is not.

The menu.

Prompts

template draft_rsvp_reminder

Parameterised prompts the server ships for its own domain, invoked explicitly rather than triggered.

Domain phrasing owned by the server that owns the domain, instead of duplicated across every client.

The waiter's recommended pairing.

The restaurant is how you remember it. The reason to keep them straight is operational: collapse all three into tools and every piece of context costs a reasoning step, every client reinvents the same phrasing slightly differently, and the permission surface becomes simply all of it — because nobody ever drew the line where the boundaries already were.

I wrote that argument up on its own, with the code, in Tools, Resources, Prompts.

stdio or streamableHTTP

stdio runs the server as a subprocess over stdin and stdout: zero network config, one machine, one process tree. Good for local development, CLI tools, single-binary deployments. streamableHTTP makes it a real network service — two processes with two lifecycles, reachable from anywhere, with server-sent events for streaming back.

Will my agent and my tool server ever need to scale, deploy, or fail independently?

Yes — streamableHTTP from day one. No, one local tool for one user — stdio is fine. And the opinion I'd defend in the Q&A: test locally on the transport you intend to run in production. Developing against stdio and switching at deploy time is where the subtle bugs hide, because buffering behaviour, connection lifecycle and error propagation all differ between the two.

The payoff for getting it right is that the agent and the MCP server become two ordinary services — different repos, different scaling policies, different teams — and the contract between them is still just MCP.

One server, many agents

Same server, two agents, two grants — and the second one is built by filtering the first, not by writing a second server.

organizer_agent
full toolset
  • get_speaker
  • list_events
  • rsvp
  • create_event
  • enrich_speaker_from_github
attendee_agent
read-only subset
  • get_speaker
  • list_events
  • rsvp
  • create_event
  • enrich_speaker_from_github

One MCP server, two blast radii. Nothing about the server changed — the attendee agent simply never sees the tools that write.

Least privilege applies to agents exactly as it applies to service accounts and API keys. The toolset a client sees is the permission surface; filtering it is how you grant.

The demo

Meetup Ops MCP is the whole talk in a repo — the store, the one GitHub call, the MCP layer of tools, resources and prompts, and two Google ADK agents built on top of it, one scoped down.

Dependencies are injected at the command binaries and nothing below reaches for a global, which is what lets the tests drive the real protocol against a fake GitHub with no network at all. There is also a probe binary — a raw MCP client that walks every tool, resource and prompt with no model in the loop. That is how you find out whether the server is broken or the agent is just being creative.

# the whole thing, from nothing
git clone https://github.com/TheGh0xt/meetup-ops-mcp

# full suite, race detector on — no network, no API key
make test

# streamable HTTP on :8080 — the production shape
make run-http

First time out

My first talk, and the first time presenting to a technical audience. What I would keep from it: building the demo against the event itself, so every abstraction had something concrete underneath it, and running it live rather than playing a recording of it.

Looking for the next one.