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
- Tokenization — The query string is split into searchable tokens
- Scoring — Each
McpToolOperationreceives a relevance score based on:- Name matches (highest weight)
- Summary/description tokens
- Path segment matches
- 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
- Name verification — Confirms the supplied
nameexists in the current catalog (prevents stale capability calls) - Argument validation — Checks that provided
pathParams,queryParams, andbodymatch the operation's schema - Schema drift detection — Returns
unknown_capabilityorinvalid_capability_argumentsif 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
McpToolOperationwith invocation metadata - Makes the catalog available to
searchCapabilitiesand 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:
- Permission checks verify the caller's access rights to each capability
- Rate limiting applies per-tool and per-tenant quotas
- Credential isolation ensures agents never receive raw API tokens—authentication happens server-side in [
agent.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/agent.ts)
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
- Two-tool design:
search_capabilitiesandexecute_capabilityform the complete OpenWork MCP surface, defined in [search.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/search.ts) and [agent.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/agent.ts) - Search-first workflow: Agents discover capabilities by natural language query before execution, preventing hardcoded endpoint dependencies
- Rich metadata: Search results include
name,method,path, parameter requirements, andbodySchemafor self-contained invocation - Server-side validation: [
agent.ts](https://github.com/different-ai/openwork/blob/dev/ee/apps/den-api/src/mcp/agent.ts) validates capability names and arguments against the live catalog, detecting schema drift - Secure by default: Policy enforcement, rate limiting, and credential isolation happen transparently in the MCP layer
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →