📖 Lecture — From API Calls to Agent-Native CLIs and MCP Tools

Last week you learned how MCP hosts and clients negotiate a connection and discover a server's capabilities. This week we zoom out one layer. Before you expose a capability to an agent through MCP, you should ask a more basic engineering question: what is the cleanest programmable surface for this API?

For many integrations, the answer is not "hand the model the API docs" and hope for the best. A raw REST API is optimized for application developers, not for an LLM reading a small tool description during a task. APIs often require multiple calls, pagination, auth headers, rate-limit handling, field selection, and awkward joins before they produce the answer a user actually wants.

A well-designed CLI can compress that mess into one reliable command.

Why CLIs matter for agents

The Printing Press project is a useful reference pattern here. It frames the CLI as an agent-native integration surface: a token-efficient command-line tool for an API, app, or site, often paired with an agent skill and an MCP server. The important design lesson is not that every class project needs to use that generator. The lesson is that a good CLI does more than wrap endpoints one-for-one.

An agent-native CLI should:

This is why "agent-native" is a different design bar from "human can type it." A human may tolerate paging through docs, copy-pasting IDs, and manually joining results. An agent benefits from commands with crisp names, stable schemas, and enough built-in workflow knowledge to complete a task in one or two calls.

The integration pipeline

The clean pattern for this course is:

  1. API client layer — a small Python module that handles HTTP requests, auth/config, retries/timeouts, and typed result shaping.
  2. CLI layer — commands that call the client layer and present human- and agent-friendly output.
  3. MCP layer — tools/resources/prompts that call the same client layer, so the protocol surface and the CLI surface do not drift apart.

This separation matters. If your MCP tool and your CLI both contain their own copy of the API logic, one will eventually handle errors differently, format fields differently, or use stale authentication behavior. Put the API logic in one place, then wrap it twice.

CLI command design

A useful CLI command has a narrow contract:

For agent use, structured output is not optional. A model can read a table, but a stable JSON shape lets downstream scripts, tests, and MCP tools reuse the same command safely. A command like:

weather-cli alerts CA --json --select event,severity,area,headline

is much easier for an agent to reason over than a raw API response with hundreds of fields.

How this connects to MCP tools, resources, and prompts

MCP still uses the same three server primitives you studied:

Primitive Who controls it Good fit this week
Tool Model-controlled executable action Call the same API operation your CLI command uses, such as get_alerts(state)
Resource Host/application-driven context Expose stable reference data, cached data, schemas, or command documentation
Prompt User-invoked template Create a reusable briefing request that instructs the model which tool to call and how to summarize results

The difference is that this week you are not building the MCP server in isolation. You are building a toolchain: API client, CLI, and MCP server around one shared capability.

Common mistakes

The first mistake is wrapping every API endpoint as its own command without asking what job the user or agent is trying to do. That creates breadth without usefulness. A better CLI may expose fewer commands but make each command answer a real operational question.

The second mistake is making the CLI and MCP server separate implementations. Keep one API client layer and two wrappers.

The third mistake is forgetting observability and setup checks. A doctor command is not polish. It is infrastructure. When an agent gets a 401 or an empty response, doctor --json should quickly reveal whether the token is missing, the base URL is wrong, or the API is reachable but returning no data.

Finally, keep the stdio rule from last week: if your MCP server uses stdio transport, do not write debugging output to stdout. Use logging to stderr or a file. Your CLI can print normal command output; your MCP stdio server cannot.