Home Docs Blog Demo

MCP Server API

HTTP API reference for AI agent integration.

The MCP server (ctx serve mcp) exposes both a native MCP Streamable HTTP endpoint and a REST API. MCP clients (Cursor, Claude Desktop, etc.) connect to the /mcp endpoint using the MCP JSON-RPC protocol. Custom integrations can use the REST endpoints directly. CORS is enabled by default for browser-based clients.

Starting the server

$ ctx serve mcp --config ./config/ctx.toml
Loaded 2 Lua tool(s):
  POST /tools/echo — Echoes back the input message
  POST /tools/create_jira_ticket — Create a Jira ticket enriched with related context
MCP server listening on http://127.0.0.1:7331
  MCP endpoint: http://127.0.0.1:7331/mcp

The bind address is configurable:

[server]
bind = "127.0.0.1:7331"    # Local only (default)
# bind = "0.0.0.0:7331"    # Docker / remote access

MCP Streamable HTTP endpoint

The /mcp endpoint speaks the MCP Streamable HTTP transport — JSON-RPC over HTTP with server-sent events for streaming. This is what MCP clients like Cursor and Claude Desktop connect to.

Connect from Cursor (.cursor/mcp.json):

{
  "mcpServers": {
    "context-harness": {
      "url": "http://127.0.0.1:7331/mcp"
    }
  }
}

What’s exposed via MCP:

MCP MethodDescription
tools/listAll registered tools (built-in + Lua + Rust)
tools/callExecute any tool by name
prompts/listAll registered agents as MCP prompts
prompts/getResolve an agent’s system prompt

Test with curl:

$ curl -X POST http://127.0.0.1:7331/mcp \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}'

REST endpoint reference

POST /tools/search

Full-text, semantic, or hybrid search across the knowledge base.

$ curl -s localhost:7331/tools/search \
    -H "Content-Type: application/json" \
    -d '{
      "query": "authentication",
      "mode": "hybrid",
      "limit": 5
    }' | jq .

Response:

{
  "results": [
    {
      "id": "a1b2c3d4-e5f6-...",
      "source": "git",
      "source_id": "docs/auth.md",
      "title": "Authentication Guide",
      "score": 0.94,
      "snippet": "JWT tokens are signed with RS256 and rotate every...",
      "source_url": "https://github.com/acme/platform/blob/main/docs/auth.md"
    }
  ]
}
ParameterTypeDefaultDescription
querystringrequiredSearch query text
modestring"keyword""keyword", "semantic", or "hybrid"
limitintegerfrom configMax results to return
sourcestringallFilter by source name (e.g., "git", "script:jira")

POST /tools/get

Retrieve a full document by UUID.

$ curl -s localhost:7331/tools/get \
    -H "Content-Type: application/json" \
    -d '{"id": "a1b2c3d4-e5f6-..."}' | jq .

Response:

{
  "id": "a1b2c3d4-e5f6-...",
  "source": "git",
  "source_id": "docs/auth.md",
  "source_url": "https://github.com/acme/platform/blob/main/docs/auth.md",
  "title": "Authentication Guide",
  "body": "# Authentication Guide\n\nJWT tokens are signed with...",
  "updated_at": "2024-01-15T10:30:00Z"
}
ParameterTypeDescription
idstringrequired — Document UUID from search results

GET /tools/sources

List all configured data sources and their document/chunk counts.

$ curl -s localhost:7331/tools/sources | jq .

Response:

{
  "sources": [
    {
      "source": "filesystem",
      "document_count": 45,
      "chunk_count": 213
    },
    {
      "source": "git",
      "document_count": 89,
      "chunk_count": 412
    },
    {
      "source": "script:jira",
      "document_count": 234,
      "chunk_count": 234
    }
  ]
}

GET /tools/list

Discover all registered tools (built-in, Lua, and custom Rust) with OpenAI-compatible JSON Schema. This is what AI agents use to know what tools are available:

$ curl -s localhost:7331/tools/list | jq '.tools[] | {name, description, builtin}'

Response:

{
  "tools": [
    {
      "name": "search",
      "description": "Search indexed documents by keyword, semantic, or hybrid query",
      "builtin": true,
      "parameters": {
        "type": "object",
        "properties": {
          "query": { "type": "string", "description": "Search query" },
          "mode": { "type": "string", "enum": ["keyword", "semantic", "hybrid"] },
          "limit": { "type": "integer" },
          "source": { "type": "string" }
        },
        "required": ["query"]
      }
    },
    {
      "name": "get",
      "description": "Get full document content by ID",
      "builtin": true,
      "parameters": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Document UUID" }
        },
        "required": ["id"]
      }
    },
    {
      "name": "sources",
      "description": "List all configured data sources",
      "builtin": true,
      "parameters": { "type": "object", "properties": {} }
    },
    {
      "name": "create_jira_ticket",
      "description": "Create a Jira ticket enriched with related context",
      "builtin": false,
      "parameters": {
        "type": "object",
        "properties": {
          "title": { "type": "string", "description": "Ticket title" },
          "priority": { "type": "string", "enum": ["Low", "Medium", "High", "Critical"] }
        },
        "required": ["title"]
      }
    }
  ]
}

POST /tools/{name}

Call any registered tool by name. Works for both built-in and Lua-defined tools.

# Call a built-in tool
$ curl -s -X POST localhost:7331/tools/search \
    -H "Content-Type: application/json" \
    -d '{"query": "error handling"}'

# Call a Lua tool
$ curl -s -X POST localhost:7331/tools/create_jira_ticket \
    -H "Content-Type: application/json" \
    -d '{"title": "Fix auth bug", "priority": "High"}'

Response:

{"result": {"key": "ENG-1234", "url": "https://acme.atlassian.net/browse/ENG-1234", "related_docs": 3}}
StatusMeaning
200Success — {"result": {...}}
400Parameter validation failed
404Unknown tool name
408Lua script timed out
500Script execution error

GET /agents/list

Discover all registered agents with their metadata, tool lists, and argument schemas:

$ curl -s localhost:7331/agents/list | jq '.agents[] | {name, description, tools, source}'

Response:

{
  "agents": [
    {
      "name": "code-reviewer",
      "description": "Reviews code changes against project conventions",
      "tools": ["search", "get"],
      "source": "toml",
      "arguments": []
    },
    {
      "name": "incident-responder",
      "description": "Helps triage production incidents with relevant runbooks",
      "tools": ["search", "get", "create_jira_ticket"],
      "source": "lua",
      "arguments": [
        { "name": "service", "description": "The service experiencing the incident", "required": false },
        { "name": "severity", "description": "Incident severity (P1, P2, P3)", "required": false }
      ]
    }
  ]
}

POST /agents/{name}/prompt

Resolve an agent’s system prompt. For Lua agents, this executes the script’s agent.resolve() function with access to the context bridge.

$ curl -s localhost:7331/agents/incident-responder/prompt \
    -H "Content-Type: application/json" \
    -d '{"service": "payments-api", "severity": "P1"}' | jq .

Response:

{
  "system": "You are an incident responder for the payments-api service (P1 severity)...",
  "tools": ["search", "get", "create_jira_ticket"],
  "messages": [
    {
      "role": "assistant",
      "content": "I'm ready to help with the P1 payments-api incident..."
    }
  ]
}
ParameterTypeDescription
(body)objectAgent-specific arguments as key-value pairs
StatusMeaning
200Success
404Agent not found
500Lua resolve() failed
408Lua resolve() timed out

GET /health

Health check endpoint. Returns 200 OK with {"status": "ok"}.

$ curl -s localhost:7331/health
{"status":"ok"}

Connecting to AI agents

All MCP clients connect to http://127.0.0.1:7331/mcp (the Streamable HTTP endpoint). The REST endpoints above are available for custom integrations that don’t speak MCP.

See the Agent Integration guide for step-by-step setup with: