Generated by Answer me with HTML 0.5.5 · 2026-10-17 12:34 · ★ Star on GitHub
MCP vs Calling APIs Directly
Explained with the GitHub example
Lead: Both ways end in the same GitHub API call. The difference is who writes and owns the GitHub code. With direct calls, your app owns it. With MCP, an MCP server owns it, and your app talks to that server in a standard way.
Answer
MCP wraps API calls; it does not replace them
- Calling the API directly: your app holds GitHub-specific code: endpoints, auth, JSON formats, and a tool schema for each action.
- MCP: the GitHub MCP server holds that code. Your app discovers its tools with
tools/listand runs them withtools/call. - The same MCP client code works for GitHub, Slack, a database, or any other MCP server.
What changes in the architecture
Use the Before / After switch to compare the two set-ups.
Side by side
| Calling the API directly | MCP | |
|---|---|---|
| Interface | GitHub's own REST / GraphQL | Standard JSON-RPC methods |
| Who writes the GitHub code | You | The MCP server author |
| Tool schemas for Claude | You write each one | tools/list returns them |
| Tool descriptions | You write them for the model | Written for models by the server author |
| Adding Slack next | Write it all again | Connect one more server |
| Reuse in Claude Desktop or Claude Code | Locked inside your app | Any MCP host can use the server |
Calling directly: your server holds GitHub code
tools = [{ "name": "list_open_prs", "description": "List open pull requests for a user", "input_schema": {"type": "object", "properties": {"owner": {"type": "string"}}},}]def list_open_prs(owner): return requests.get( "https://api.github.com/search/issues", params={"q": f"is:pr is:open user:{owner}"}, headers={"Authorization": f"Bearer {TOKEN}"}, ).json()You repeat this for every action: repos, issues, projects, and every other area you need.
With MCP: your server holds no GitHub code
async with ClientSession(read, write) as session: await session.initialize() tools = (await session.list_tools()).tools # schemas from the server # ... pass tools to Claude; Claude picks one ... result = await session.call_tool( "search_pull_requests", {"query": "is:open author:me"})The same few lines work for any MCP server. The tool name here is illustrative.
One request, two layers
When to choose which
| Situation | Better choice |
|---|---|
| One or two simple endpoints | Direct API calls |
| You need full control of requests, retries or caching | Direct API calls |
| No MCP server exists for the service | Direct API calls, or write an MCP server |
| Many actions across a large service like GitHub | MCP |
| Several services in one app | MCP |
| The same tools in your app and in Claude Desktop or Claude Code | MCP |
Analogy
Device plugs and a standard port
Each API is a device with its own plug. MCP is a standard port, and an MCP server is the adapter from one plug to that port. With direct calls, your app carries every plug itself.