← Blog
April 28, 2026 · 12 min

Build an autonomous research agent with MCP in 50 lines

An agent that takes an open-ended question, decides which tools it needs (Google search, scraping, image generation), executes them via Model Context Protocol and returns a structured report — all in under 50 lines of TypeScript, with no servers of your own.

Why MCP changes the rules for agents

Until recently, giving an AI agent "tools" meant writing three layers: the JSON Schema contract for each function, the code that executes it, and the loop that feeds results back to the model. When you wanted to add web scraping you had to orchestrate Puppeteer; for Google Search, integrate with Serper; for images, manage your own Flux or DALL·E API. Each new capability was a new service to maintain.

Anthropic's Model Context Protocol solves this at the protocol level: an MCP endpoint exposes a dynamic catalog of tools that any compatible client can consume. The important part for agent builders: tools are advertised with their inputSchema — the agent can learn at runtime what tools exist and how to use them.

LLM4Agents combines two things into a single API key:

  1. An OpenAI-compatible proxy to 345+ models (Claude, GPT, Gemini, Llama, etc.)
  2. An MCP Streamable HTTP endpoint with headless scraper, Google Search, image generation and more

This means we can build a real agent — one that decides, executes, and reasons about results — without standing up our own infrastructure.

The goal

We'll build an agent you can ask things like:

"Research the latest Claude Sonnet model release, look for community opinions, and give me an executive summary with an illustrative chart."

The agent should:

Minimal setup

One dependency, one API key, no servers of your own:

npm install @llmforagents/sdk
export LLM4AGENTS_API_KEY="sk-proxy-..."

If you don't have an API key yet, register one with a public POST (no form, no KYC):

curl -X POST https://api.llm4agents.com/api/v1/agents/register \
  -H "Content-Type: application/json" \
  -d '{"name":"research-agent"}'

Then generate a deposit wallet and send 5 USDT to that address — the balance is credited automatically once the on-chain transaction confirms.

The complete agent

Here's the full code. We'll break it down after:

// research-agent.ts
import { LLM4AgentsClient } from '@llmforagents/sdk';
import { writeFileSync } from 'node:fs';

const client = new LLM4AgentsClient({
  apiKey: process.env.LLM4AGENTS_API_KEY!,
});

const agent = client.chat.conversation({
  model: 'anthropic/claude-sonnet-4',
  system: `You are a technical research analyst.
You use the available MCP tools to research and deliver
executive reports with verified data. Always generate an
illustrative image of the topic at the end of the report.`,
  tools: client.tools,
  maxToolRounds: 8,
  onToolCall: (name, args) => {
    console.log(`→ ${name}(${JSON.stringify(args).slice(0, 80)})`);
    return true;
  },
  onToolResult: (name, _result, durationMs) => {
    console.log(`✓ ${name} · ${durationMs}ms`);
  },
});

const question = process.argv.slice(2).join(' ') ||
  'Research the latest Claude Sonnet release and summarize community opinions.';

const answer = await agent.say(question);

writeFileSync('report.md', answer.content);
console.log(`\n📄 Report saved · ${answer.toolCalls.length} tool calls`);

That's it. 43 lines exactly, a single file, no infrastructure of your own. Run it with:

npx tsx research-agent.ts "Compare LangGraph vs CrewAI for research workflows"

What's happening under the hood

The key line is tools: client.tools. The SDK auto-introspects the MCP endpoint, fetches the full list of available tools (scraper, search, images…) and translates them into the function-calling format any OpenAI-compatible model understands. When the model emits tool_calls, the SDK executes them against the MCP endpoint using the same API key — and feeds the results back to the model in the next turn of the loop.

The maxToolRounds: 8 parameter is the safety rail: if the agent enters an infinite loop (hallucinates the need to keep searching forever), the SDK aborts after 8 rounds. For complex investigations, 5-10 rounds is typical.

Tip: the model picks when and in what order to use tools. For a question about current news, it'll start with google_news; if you ask about a specific company, it'll go straight to fetch_html on its website. That autonomy is exactly what separates an agent from a script.

Real output from a run

An example with "Compare LangGraph vs CrewAI for research workflows":

→ google_search({"q":"LangGraph vs CrewAI comparison 2026"})
✓ google_search · 412ms
→ google_search({"q":"CrewAI features research workflows"})
✓ google_search · 389ms
→ markdown({"url":"https://www.langchain.com/langgraph"})
✓ markdown · 1240ms
→ markdown({"url":"https://docs.crewai.com/concepts/agents"})
✓ markdown · 1189ms
→ google_news({"q":"CrewAI release","tbs":"qdr:m"})
✓ google_news · 401ms
→ generate_image({"prompt":"comparison diagram of LangGraph and CrewAI..."})
✓ generate_image · 4521ms

📄 Report saved · 6 tool calls

The agent decided on its own to do two Google searches to cover both products, scrape their respective docs, run a news search for freshness, and close with an AI-generated diagram. None of those steps were programmed explicitly.

Real costs

Each chat completion call returns an X-Cost-Usd-Cents header with the exact cost. For this typical run:

An agent with 5 USDT can run ~100 reports like this. No card, no subscription, debiting from the same balance.

Without our SDK: the equivalent pattern with the OpenAI SDK

If your stack is tied to the official OpenAI SDK, the pattern is slightly more verbose but just as direct:

from openai import OpenAI
import requests, json

API_KEY = "sk-proxy-..."
HDRS    = {"Authorization": f"Bearer {API_KEY}"}

# Auto-fetch ALL MCP tools
mcp_tools = requests.post("https://mcp.llm4agents.com/mcp", headers=HDRS,
    json={"jsonrpc":"2.0","id":1,"method":"tools/list"}).json()["result"]["tools"]

tools = [{"type":"function","function":{
    "name":t["name"],"description":t["description"],
    "parameters":t["inputSchema"]}} for t in mcp_tools]

client = OpenAI(api_key=API_KEY, base_url="https://api.llm4agents.com/v1")
# The model picks; you handle the loop manually over tool_calls

You write the tool→model loop yourself, but the tool list is dynamic — add new tools to the MCP endpoint and your agent discovers them with no code changes.

Ideas to extend

The important point

Real agent code shouldn't be 500 lines of orchestration. If your model has access to the right tools and a clear prompt, the agent decides. Your job is to provide the catalog of capabilities and let the model choose.

That's exactly what the tools: client.tools pattern does: in one line, you expose the entire MCP tool universe and let any of the 345+ available models orchestrate them based on the prompt.

Build yours

One API key, a USDT balance, all the MCP tools. No card, no KYC.

Register my agent