Skip AI Frameworks - Use the API and MCP Servers Directly

M
Matthew Diakonov

Skip AI Frameworks - Use the API and MCP Servers Directly

Every few months a new list of AI frameworks appears. LangChain, LlamaIndex, CrewAI, AutoGen - each promising to simplify building AI applications. And every few months, developers who adopted the previous framework are ripping it out and rewriting.

This cycle is predictable because the frameworks are solving the wrong problem.

What Frameworks Actually Cost You

The Claude API is genuinely simple. A request has a model, a list of messages, and optionally a list of tools. The response has content and possibly tool calls. That is it. Frameworks add abstraction layers on top of this simplicity - chains, agents, memory stores, callback handlers, vector retrievers - that feel productive early and become obstacles later.

The real cost shows up when you need to do something the framework did not anticipate. A LangChain chain that was easy to set up becomes hard to modify when you need to change the tool call format, add streaming to a specific step, or debug why the model is making unexpected decisions. You end up reading framework source code and working around opinionated abstractions instead of building your product.

Real overhead from framework adoption:

  • Learning the framework's API on top of the model's API
  • Debugging through multiple abstraction layers
  • Upgrading when the framework breaks on new model versions
  • Fighting type systems designed around yesterday's model capabilities

The Raw API Approach

Here is a minimal agent loop in plain Python using the Anthropic API directly:

import anthropic
import json

client = anthropic.Anthropic()

def run_agent(task: str, tools: list) -> str:
    messages = [{"role": "user", "content": task}]

    while True:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=tools,
            messages=messages
        )

        if response.stop_reason == "end_turn":
            return response.content[0].text

        if response.stop_reason == "tool_use":
            messages.append({"role": "assistant", "content": response.content})

            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })

            messages.append({"role": "user", "content": tool_results})

This is the entire agent loop. No framework needed. You can read it, debug it, modify it. When something breaks, you know exactly where to look.

Adding MCP for Tool Management

Where MCP becomes valuable is tool organization and reuse. Instead of defining tools inline in every script, you write an MCP server once and connect any client to it.

A minimal MCP server in Python:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-tools")

@mcp.tool()
def read_file(path: str) -> str:
    """Read a file and return its contents."""
    with open(path) as f:
        return f.read()

@mcp.tool()
def write_file(path: str, content: str) -> str:
    """Write content to a file."""
    with open(path, "w") as f:
        f.write(content)
    return f"Written {len(content)} bytes to {path}"

@mcp.tool()
def list_directory(path: str) -> list[str]:
    """List files in a directory."""
    import os
    return os.listdir(path)

if __name__ == "__main__":
    mcp.run()

That is a functional MCP server with three tools. Add it to your ~/.claude/claude_desktop_config.json and it is available in Claude Desktop, in your scripts, or in any MCP-compatible client:

{
  "mcpServers": {
    "my-tools": {
      "command": "python",
      "args": ["/path/to/server.py"]
    }
  }
}

The MCP protocol is JSON-RPC. Every request and response is a plain JSON object. When something goes wrong, you can read the wire format and understand exactly what happened.

The Comparison Is Not Even Close

Consider building a file management agent that can read, write, search, and organize files. With LangChain:

  1. Install langchain, langchain-anthropic, and several dependencies
  2. Learn the Chain, Agent, and Tool abstractions
  3. Figure out which agent type (ReAct, OpenAI Functions, etc.) fits your use case
  4. Debug callback handlers when something silently fails
  5. Update all the above when LangChain releases a breaking change

With the direct approach:

  1. Install anthropic (one package)
  2. Write a 30-line agent loop
  3. Write tools as plain Python functions
  4. Done

The LangChain path is not slower because it produces better code. It is slower because you are learning someone else's mental model on top of the actual problem.

When Frameworks Make Sense

There are two legitimate use cases for frameworks:

Rapid prototyping with a short shelf life. If you are building a demo in an afternoon and will throw it away, framework abstractions let you move faster initially. Just do not let it become production code.

Specific pre-built integrations. If a framework has a well-maintained integration with a service you need (vector databases, specific APIs), using that integration is faster than writing your own. Extract the integration code if possible, rather than taking the whole framework.

For anything you plan to maintain and build on, the direct approach - Anthropic API plus custom MCP servers - produces code you can actually own.

Fazm is an open source macOS AI agent. Open source on GitHub.

More on This Topic

Related Posts