MCPSeptember 20, 2026

A Worked MCP Example: Turning a Website Into an Agent Tool

Most MCP explainers stop at the protocol. This one shows an actual session: three JSON-RPC calls against a Tixl hub, with the real responses. You'll see what an AI agent gets, and just as important, what it never sees.

New to MCP? Start with the overview. This post assumes you know that an agent calls tools/list to discover tools and tools/call to run one.

The setup

We use Hacker News search as the example because anyone can reproduce it: no login, public data. One honest caveat: HN happens to have a public search API behind it. For a legacy portal with no API, the only thing that changes is the capture step, where someone records the workflow once. Nothing the agent sees is different.

The transcript below was captured on September 20, 2026 from a local Tixl hub running the same code as the hosted one. The tool, hn-search, is a single-step integration. Real integrations chain several requests; the agent-facing contract looks the same.

Step 1: the handshake

Every MCP client starts with initialize. Clients send it to POST /mcp/messages; the SSE endpoint tells them where to post.

// request
{"jsonrpc":"2.0","id":1,"method":"initialize",
 "params":{"protocolVersion":"2024-11-05","capabilities":{},
           "clientInfo":{"name":"demo","version":"0"}}}

// response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": { "tools": { "listChanged": true } },
    "serverInfo": { "name": "tixl", "version": "0.1.0" }
  }
}

The server names itself and advertises one capability: tools. That's all the agent needs to know before it asks what's available.

Step 2: discovery

Next the client calls tools/list. This is the whole interface the model gets:

{
  "name": "hn-search",
  "description": "Search Hacker News stories, Ask HN, and Show HN posts by keyword",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Search query (e.g. 'AI agents', 'FastAPI', 'YC W26')"
      },
      "type": {
        "type": "string",
        "description": "Post type filter: story, ask_hn, show_hn, job (default: story)",
        "enum": ["story", "ask_hn", "show_hn", "job"]
      },
      "limit": {
        "type": "integer",
        "description": "Number of results to return (max 20, default 10)"
      }
    },
    "required": ["query"]
  }
}

Nothing else is exposed. The model decides whether to call the tool, and how, from this schema alone. That makes the description and the constraints do real work: the enum stops the model from inventing a post type, and required tells it which argument it can't skip.

Step 3: the call

Now the agent runs the tool with business parameters, and only those:

// request
{"jsonrpc":"2.0","id":3,"method":"tools/call",
 "params":{"name":"hn-search",
           "arguments":{"query":"MCP server","type":"show_hn","limit":3}}}

The response wraps the result as MCP text content. Here is what result.content[0].text held (two of the three results shown):

{
  "stories": [
    {
      "title": "Show HN: Ghidra MCP Server – 110 tools for AI-assisted reverse engineering",
      "url": "https://github.com/bethington/ghidra-mcp",
      "points": 298,
      "author": "xerzes",
      "comments": 68,
      "posted_at": 1770187911,
      "story_id": "46882389",
      "hn_link": "https://news.ycombinator.com/item?id=46882389"
    },
    {
      "title": "Show HN: MCP server for searching and downloading documents from Anna's Archive",
      "url": "https://github.com/iosifache/annas-mcp",
      "points": 256,
      "author": "iosifache",
      "comments": 79,
      "posted_at": 1752095198,
      "story_id": "44514753",
      "hn_link": "https://news.ycombinator.com/item?id=44514753"
    }
  ]
}

The whole round trip took about 0.4 seconds in our run, including the upstream request. That's one run on a laptop, not a benchmark. The point is the shape: structured fields the model can reason over, not a screenshot to interpret and not HTML to parse. Note that posted_at is a raw Unix timestamp; shaping output like that is part of building an integration.

What the agent never sees

Compare what crossed the wire with what didn't. The agent saw a name, a description, a schema and a result. It never saw the URLs, headers, extraction rules or the order of requests behind the tool. Those live on the hub, and the agent that executes a step receives one plain HTTP request at a time.

Logins are the other half. When a capture records a session, cookies and CSRF tokens show up as inputs of the request. Those must never become parameters a model fills in. The hub strips them from the schema before the agent sees it. Here is an invented example run through the real cleaning code, a policy lookup where the recording captured a session:

// as recorded                          // as the agent sees it
"properties": {                          "properties": {
  "policy_number": {...},                  "policy_number": {...},
  "include_history": {...},                "include_history": {...}
  "cookies": {...},                      },
  "csrf_token": {...}                    "required": ["policy_number"]
},
"required": ["policy_number",
             "cookies", "csrf_token"]

At run time the user's own saved session is injected on their machine, at the moment the request is sent. The model never handles it, and the hub doesn't store it. This is also why a tool like this can reach a system that only accepts connections from inside your network: the request is sent from inside it.

Where the tool comes from

Someone records the workflow once, with the Tixl Capture app or the command line recorder. We generate the integration from the recording, test it against the real system, and publish it as a tool. You don't write the definition and you don't maintain it.

Maintenance matters more than the first build. When a step starts failing, the hub tries to repair the definition and keeps the fix only if the repaired step works. A failed login is different: expired credentials can't be fixed by rewriting a request, so those surface as a re-authentication, not a silent patch.

What to take from this

  • An MCP tool is a contract: a name, a description, a schema, and a result. Everything else is implementation.
  • The contract for a website with no API looks exactly like the contract for one that has an API.
  • Credentials and integration logic stay out of the model's view by design, not by prompt.

The connection details, including the hosted endpoint and the local option for session-based tools, are in the docs.

Have a system with no API?

Tell us the workflow. We'll capture it, build the tool, and have it available to your agents.

Book a scoping call