Title: Lecture 10 — From API Calls to Agent-Native CLIs and MCP Tools
Course: AIINFRA 300: Agentic AI & the Model Context Protocol (MCP) > Section: Week 10 — Building Agent-Native CLI Tools and MCP Servers

📖 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:

- Expose commands that match real tasks, not only raw endpoint names.
- Return predictable, structured output such as --json, --csv, or selected fields.
- Include a doctor command that checks auth, config, API reachability, and common setup mistakes.
- Keep secrets in environment variables or config files, never in source code.
- Make safe defaults obvious: read-only commands first, limits on expensive calls, and clear dry-run behavior for mutations.
- Add compound commands when a single user task requires several API calls.
- Optionally sync or cache useful API data locally, often in SQLite, so search and cross-resource analysis do not burn a fresh API call every time.

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:

- Name: a verb-noun shape such as alerts active, forecast point, or packages search.
- Inputs: flags with documented formats, constraints, and defaults.
- Output: a deterministic table for humans and structured output for agents.
- Exit behavior: nonzero exit codes for failures, clear stderr messages, and no silent partial success.
- Safety: --limit, --dry-run, and read-only defaults when touching real systems.

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.