The Missing Level in REST: API Discoverability from Academic Ideal to AI Necessity

Most REST APIs today are incomplete. We build APIs that expose resources (Level 1) and use HTTP verbs correctly (Level 2), but we stop there. We skip Level 3 - the one that tells clients what they can actually do next.

This exploration examines two technologies that tackle API discoverability: HATEOAS (Hypermedia as the Engine of Application State) and MCP (Model Context Protocol). While HATEOAS struggled to find adoption among human developers, the rise of AI agents has created the perfect use case for its concepts. I'll walk through why this shift matters and share a practical implementation that combines both approaches to create truly dynamic, AI-friendly APIs.

Dynamic API discovery compass - representing APIs that guide AI agents contextually

When Roy Fielding introduced REST in his dissertation, he outlined three maturity levels:

For decades, Level 3 remained an academic curiosity. Human developers didn't need it - they could parse extensive documentation and maintain context across complex workflows. But AI agents thrive on targeted, structured input delivered precisely when needed. They excel when APIs can provide exactly the right information at exactly the right moment.

What is HATEOAS?

Hypermedia as the Engine of Application State is the principle that APIs should tell clients what actions are available through embedded links. Instead of clients needing to know all possible endpoints upfront, the API response includes navigation instructions - like a GPS for your API interactions.

// HATEOAS response example - positive balance
{
  "accountId": "acc-123",
  "accountHolder": "John Doe",
  "balance": 1250.75,
  "currency": "USD",
  "_links": {
    "self": {
      "href": "/accounts/acc-123"
    },
    "deposit": {
      "href": "/accounts/acc-123/deposit",
      "method": "POST"
    },
    "withdraw": {
      "href": "/accounts/acc-123/withdraw",
      "method": "POST"
    }
  }
}

// HATEOAS response example - overdraft
{
  "accountId": "acc-456",
  "accountHolder": "Jane Smith",
  "balance": -150.25,
  "currency": "USD",
  "_links": {
    "self": {
      "href": "/accounts/acc-456"
    },
    "deposit": {
      "href": "/accounts/acc-456/deposit",
      "method": "POST"
    }
  }
}

This demonstrates HATEOAS in action: when the account has a positive balance, both deposit and withdraw operations are available. But when the account is in overdraft, only the deposit link appears - the server dynamically controls what actions are possible based on the current state.

Why didn't we adopt HATEOAS?

HATEOAS was challenging to implement because those theoretical advantages weren't meaningful in practice. Human developers prefer clear API documentation over parsing hypermedia, and despite the promise of decoupling, backend and frontend lifecycles tend to remain tightly coupled in real-world development.

What is MCP?

The Model Context Protocol (MCP) is a new approach to API discoverability designed specifically for machine clients, particularly AI agents. Unlike traditional APIs that require upfront knowledge of available endpoints, MCP allows agents to ask "what can I do?" and receive structured, actionable responses at runtime.

// MCP list tools request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

// MCP server response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "read_file",
        "description": "Read contents of a file",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": {
              "type": "string",
              "description": "File path to read"
            }
          },
          "required": ["path"]
        }
      },
      {
        "name": "list_directory",
        "description": "List contents of a directory",
        "inputSchema": {
          "type": "object",
          "properties": {
            "path": {
              "type": "string",
              "description": "Directory path to list"
            }
          },
          "required": ["path"]
        }
      }
    ]
  }
}

This demonstrates MCP's dynamic tool discovery - the AI agent queries available tools at runtime

AI-First API Design

Both technologies tackle discoverability, but they solve different pieces of the puzzle. MCP provides the protocol for AI agents to discover available tools, while HATEOAS provides the state-aware logic for when those tools should be available. What happens when we combine them?

The result is something neither could achieve alone: APIs that don't just tell AI agents what's possible, but dynamically adjust those possibilities based on real-time state. Instead of static tool lists, the available actions change as the underlying state changes.

This led me to build a system where an MCP server learns its capabilities directly from a HATEOAS API. The agent's available tools automatically reflect the current state without needing to understand the business logic behind those constraints.

A Practical Example

To test this concept, I created a banking API that uses HATEOAS for state management and an MCP server that discovers its capabilities from that API.

// MCP server discovers available actions from HATEOAS API
async discover_links() {
    const response = await this.client.get('/account');
    const data = response.json();
    
    // Update available tools based on current state
    this.cached_links = data._links;
    this.cached_tools = this._generate_tools_from_links();
    
    // Notify AI agent that tools have changed
    if (tools_changed) {
        await this._send_tool_refresh_notification();
    }
}

// Generate MCP tools from HATEOAS links
_generate_tools_from_links() {
    const tools = [];
    for (const [linkName, linkData] of Object.entries(this.cached_links)) {
        tools.push({
            name: linkName,
            description: `Execute ${linkData.rel} (${linkData.method})`,
            inputSchema: linkData.method === 'POST' ? {
                type: 'object',
                properties: { amount: { type: 'number' } },
                required: ['amount']
            } : {}
        });
    }
    return tools;
}

This creates a feedback loop where:

AI Banking Agent

Balance: $850.00
Tools available: check balance, deposit, withdraw
Available Actions:

This architecture enables AI agents to understand not just what actions exist, but when they're contextually appropriate. The agent doesn't need to understand business rules - it simply adapts to what the API makes available.

The implementation in mcp-server.py shows how this works in practice: the MCP server polls the HATEOAS API, regenerates its tool list based on available links, and notifies connected AI agents when capabilities change.

You can find the complete implementation and examples at https://github.com/JonWoodlief/MCP-for-tier3-REST.

Looking Forward

The rise of AI agents is forcing us to reconsider foundational assumptions about API design. What seemed like academic over-engineering twenty years ago—APIs that tell clients what they can do—turns out to be exactly what machine clients need to operate effectively.

This isn't just about HATEOAS or MCP. It's about designing APIs for a world where the primary consumers aren't human developers reading documentation, but autonomous agents that need to discover and adapt to capabilities in real-time. The missing Level 3 of REST may finally have found its moment.