How OpenWork MCP Exposes `search_capabilities` and `execute_capability` for Agent Integration

OpenWork MCP exposes exactly two tools—search_capabilities and execute_capability—through the /mcp/agent endpoint, giving AI agents a searchable, type-safe way to discover and invoke API capabilities without hardcoding endpoint details.

OpenWork's Model Context Protocol (MCP) implementation provides a minimal, deterministic surface for agent integration. Rather than exposing raw API endpoints, the [ee/apps/den-api/src/mcp/agent.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/agent.ts) module advertises only two tools that agents use to find and execute capabilities. This article breaks down how these tools work, complete with source code paths and runnable examples.


The Two-Tool Surface

OpenWork MCP deliberately limits agent exposure to two operations:

Tool Defined In Purpose
search_capabilities [search.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/search.ts) Ranked, semantic search across the capability catalog
execute_capability [agent.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/agent.ts) Validated execution of a discovered capability

This design follows a search-first workflow: agents must discover capabilities before invoking them, making integrations resilient to API changes.


How search_capabilities Discovers Capabilities

The search_capabilities tool implements semantic search over OpenWork's internal OpenAPI catalog. Located in [ee/apps/den-api/src/mcp/search.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/search.ts), the searchCapabilities function scores operations against free-text queries.

The Search Algorithm

  1. Tokenization — The query string is split into searchable tokens
  2. Scoring — Each McpToolOperation receives a relevance score based on:
    • Name matches (highest weight)
    • Summary/description tokens
    • Path segment matches
  3. Ranking and trimming — Results sort by score and return a configurable limit (default: 5)

SearchResponse Shape

Each CapabilityMatch in the response includes everything an agent needs to construct a valid call:

{
  "name": "listWorkers",
  "method": "GET",
  "path": "/api/v1/workers",
  "pathParams": [],
  "queryParams": ["limit", "offset"],
  "hasBody": false,
  "bodySchema": null,
  "score": 0.94
}

Example: Calling search_capabilities

// Agent discovers capabilities matching "list workers"
const searchResult = await callTool(
  "https://api.openworklabs.com/mcp/agent",
  mcpToken,
  "search_capabilities",
  { 
    query: "list workers", 
    limit: 5 
  }
);

// Response contains ranked matches with full invocation metadata
const bestMatch = searchResult[0];  // Highest scoring capability
const capabilityName = bestMatch.name;  // "listWorkers"

The agent uses name for the subsequent execute_capability call and pathParams/queryParams/hasBody to construct valid arguments.


How execute_capability Executes Discovered Tools

The execute_capability tool in [ee/apps/den-api/src/mcp/agent.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/agent.ts) validates and forwards agent requests to the underlying OpenAPI implementation.

Validation Steps

  1. Name verification — Confirms the supplied name exists in the current catalog (prevents stale capability calls)
  2. Argument validation — Checks that provided pathParams, queryParams, and body match the operation's schema
  3. Schema drift detection — Returns unknown_capability or invalid_capability_arguments if the capability changed since discovery

Execution Flow

After validation, [agent.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/agent.ts) forwards to the generic OpenAPI invoker ([invoke.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/invoke.ts)), which:

  • Substitutes path parameters into the URL template
  • Appends query parameters
  • Serializes and sends the request body (if hasBody)
  • Returns the raw HTTP response to the agent

Example: Executing a Discovered Capability

// Execute using the name and arguments from search_capabilities
const result = await callTool(
  "https://api.openworklabs.com/mcp/agent",
  mcpToken,
  "execute_capability",
  {
    name: "listWorkers",           // Must match search result exactly
    queryParams: { limit: 20 },    // Matches bestMatch.queryParams
    pathParams: {},                // Empty for this capability
    // body omitted: hasBody === false
  }
);

console.log(result);  // Raw API response: { workers: [...], total: 47 }

Building the Capability Catalog

Both tools rely on the in-process catalog built at server startup. The [ee/apps/den-api/src/mcp/catalog.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/catalog.ts) module:

  • Scrapes the OpenAPI specification for all registered operations
  • Wraps each as a McpToolOperation with invocation metadata
  • Makes the catalog available to searchCapabilities and execution validation

This centralized catalog ensures that search_capabilities and execute_capability always reference the same, up-to-date API surface.


Security and Policy Enforcement

All MCP calls pass through OpenWork's policy layer before reaching business logic:

The minimal two-tool surface reduces attack exposure by preventing agents from directly constructing arbitrary HTTP requests.


Complete Agent Integration Flow

A production agent implementation combines both tools in a resilient discovery-execution loop:

async function invokeOpenWorkCapability(
  query: string,
  args: Record<string, unknown>
) {
  // 1️⃣ DISCOVER: Find capabilities matching natural language query
  const matches = await callTool(
    "https://api.openworklabs.com/mcp/agent",
    mcpToken,
    "search_capabilities",
    { query, limit: 3 }
  );

  if (matches.length === 0) {
    throw new Error(`No capabilities found for: ${query}`);
  }

  const capability = matches[0];
  console.log(`Selected: ${capability.name} (score: ${capability.score})`);

  // 2️⃣ VALIDATE: Check argument compatibility
  const providedArgs = Object.keys(args);
  const requiredArgs = [
    ...capability.pathParams,
    ...(capability.queryParams || [])
  ];
  
  const missing = requiredArgs.filter(p => !(p in providedArgs));
  if (missing.length > 0) {
    throw new Error(`Missing required arguments: ${missing.join(', ')}`);
  }

  // 3️⃣ EXECUTE: Call the validated capability
  const result = await callTool(
    "https://api.openworklabs.com/mcp/agent",
    mcpToken,
    "execute_capability",
    {
      name: capability.name,
      pathParams: args.pathParams || {},
      queryParams: args.queryParams || {},
      body: capability.hasBody ? args.body : undefined
    }
  );

  return result;
}

// Usage
const workers = await invokeOpenWorkCapability(
  "list workers", 
  { queryParams: { limit: 50 } }
);

MCP Endpoint Discovery

Agents register OpenWork MCP at the standardized endpoint:


GET https://api.openworklabs.com/mcp/agent

The tool list response confirms both capabilities are available:

{
  "tools": [
    { "name": "search_capabilities" },
    { "name": "execute_capability" }
  ]
}

This format is validated by the test suite in [connect-debug-proxy.test.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/connect-debug-proxy.test.ts), ensuring consistent agent onboarding.


Summary


Frequently Asked Questions

How does an agent know which arguments to pass to execute_capability?

The search_capabilities response includes pathParams, queryParams, and hasBody for every match. Agents inspect these fields from the selected CapabilityMatch to construct valid arguments. If the capability requires a JSON body, bodySchema contains the validation schema.

What happens if a capability changes after an agent discovers it?

[agent.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/agent.ts) validates the capability name against the current catalog before execution. If the name no longer exists or the arguments don't match the updated schema, the server returns unknown_capability or invalid_capability_arguments. Agents should trap these errors and call search_capabilities again to refresh their capability cache.

Can agents call execute_capability without calling search_capabilities first?

Technically yes, but discouraged. The agent must know the exact capability name and argument structure. Since names and schemas evolve, direct execution risks unknown_capability errors. The [AGENTS.md](https://github.com/different-ai/openwork/blob/dev/AGENTS.md) documentation explicitly recommends the search-then-execute pattern for forward compatibility.

Where is the capability catalog sourced from?

[catalog.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/catalog.ts) builds the catalog from OpenWork's internal OpenAPI specification at server startup. The catalog includes all McpToolOperation instances with their HTTP method, path, parameters, and request body schemas, making it the single source of truth for both search and execution.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →