How OpenWork's Tool Catalog and Capability Inventory System Works: MCP Discovery and Validation

OpenWork separates MCP tool discovery from remote skill inventory, enforcing strict pagination limits, schema validation, and byte-size constraints to ensure LLM-safe catalogs.

The different-ai/openwork repository implements a dual-layer discovery architecture that distinguishes between local MCP tool catalogs and remote capability inventories. This design ensures AI agents only receive validated, size-constrained tool definitions while maintaining a separate registry of cloud-hosted skills. Understanding how the tool catalog and capability inventory system works is essential for developers building secure, scalable agentic workflows.

Enterprise MCP Tool Catalog Architecture

The Enterprise MCP Tool Catalog handles discovery and validation of local MCP-exposed tools. Implemented in packages/enterprise-mcp-client/src/tool-catalog.ts, the collectEnterpriseMcpTools function orchestrates pagination, validation, and strict resource limits to prevent malformed tools from reaching the LLM context.

Pagination and Hard Collection Limits

The catalog enforces aggressive pagination boundaries to prevent resource exhaustion. Page limit (ENTERPRISE_MCP_TOOL_PAGE_LIMIT) caps discovery at 20 pages, while the item limit (ENTERPRISE_MCP_TOOL_ITEM_LIMIT) stops collection at 2,000 tools. Exceeding either threshold triggers specific error codes: MCP_CATALOG_PAGE_LIMIT or MCP_CATALOG_ITEM_LIMIT.

// Conceptual flow from tool-catalog.ts
for (let page = 0; page < ENTERPRISE_MCP_TOOL_PAGE_LIMIT; page++) {
  const result = await listPage(cursor, opts);
  // Collection logic with limits...
  if (tools.length >= ENTERPRISE_MCP_TOOL_ITEM_LIMIT) {
    throw new EnterpriseMcpCatalogError('MCP_CATALOG_ITEM_LIMIT');
  }
}

Schema Validation and Safety Constraints

Every tool undergoes rigorous validation via the assertTool function. Name uniqueness is enforced through a Set tracking tool names; duplicates raise MCP_CATALOG_DUPLICATE_TOOL. Schema validation uses measureSchema to inspect inputSchema and outputSchema, rejecting cyclic references, depth exceeding 64 levels, or schemas larger than 512 KB.

Byte budgeting accumulates catalogBytes against ENTERPRISE_MCP_CATALOG_LIMIT_BYTES (8 MiB). Exceeding this total catalog size aborts collection with MCP_CATALOG_BYTE_LIMIT. Cursor safety validates nextCursor size, detects pagination loops, and enforces ENTERPRISE_MCP_CURSOR_LIMIT_BYTES.

Typed Error Handling

All validation failures surface as EnterpriseMcpCatalogError instances with specific error codes. This typing allows calling code to translate technical failures into user-friendly messages or fallback behaviors without parsing raw error strings.

OpenWork Connect Capability Inventory

The Capability Inventory manages discovery of remote agent skills hosted on OpenWork Cloud. Located in apps/server/src/connect-skill-catalog.ts, this system retrieves skill metadata from MCP endpoints, validates payloads, and renders instructions for LLM system prompts.

Remote Skill Discovery and Validation

The readMcpSkillIndex function contacts OpenWork-cloud MCP endpoints by executing an initialize RPC followed by reading the skill://index.json resource. Raw JSON responses are parsed and validated against skillIndexSchema using Zod, enforcing name patterns, length limits, and required fields like capability strings.

// From connect-skill-catalog.ts
const text = await readResource('skill://index.json');
const parsed = skillIndexSchema.parse(JSON.parse(text));

Multi-Source Resolution and Caching

readOpenWorkConnectSkillCatalog implements a cascading resolution strategy. It first checks server-level MCP configuration, then falls back to workspace runtime configs. Successfully resolved workspace configs are promoted to server scope for future reads, optimizing subsequent calls.

Results are cached per-configuration hash for CATALOG_CACHE_TTL_MS (30 seconds) to minimize network round-trips. The cache stores expiration timestamps and validated values, ensuring stale entries are refreshed automatically.

Graceful Degradation

If any discovery step fails—whether from invalid URLs, disabled configurations, transport errors, or malformed RPC responses—the function returns an empty array rather than throwing. This safe fallback allows agent operations to continue even when remote skills are temporarily unavailable.

Prompt Integration for LLM Consumption

The renderOpenWorkConnectSkillInstruction function converts validated OpenWorkConnectSkill objects into XML-like instruction blocks injected into system prompts. The generated <available_skills> block explicitly marks these capabilities as remote, requiring invocation via openwork-cloud_execute_capability with exact capability strings.

When OPENWORK_DEV_MODE=1 is set, the renderer logs injected marketplace skills for debugging purposes.

How Tool Catalog and Capability Inventory Work Together

Tool Catalog operates at the MCP transport layer, providing validated tool definitions (names, schemas, descriptions) for local execution. Capability Inventory operates at the OpenWork Connect layer, aggregating metadata about remote skills hosted in the cloud.

In practice, when a user requests tool execution, the server first uses collectEnterpriseMcpTools to verify the tool exists and conforms to size limits. For remote operations, the server calls readOpenWorkConnectSkillCatalog to embed skill metadata in the prompt via renderOpenWorkConnectSkillInstruction, allowing the LLM to select and invoke remote capabilities through the MCP transport.

Practical Implementation Examples

Fetching the Enterprise MCP Tool Catalog

import { collectEnterpriseMcpTools } from "@/tool-catalog";
import { mcpClient } from "@modelcontextprotocol/sdk/client";

async function getTools() {
  const tools = await collectEnterpriseMcpTools({
    listPage: (cursor, opts) => mcpClient.listTools({ cursor }, opts),
    requestOptions: { timeoutMs: 5000 },
  });
  console.log(`Found ${tools.length} validated tools`);
  return tools;
}

This implementation handles pagination automatically, enforces all 2,000-item and 8-MiB limits, and throws typed EnterpriseMcpCatalogError exceptions for validation failures.

Reading the Remote Skill Catalog

import { readOpenWorkConnectSkillCatalog } from "@/connect-skill-catalog";
import type { ServerConfig } from "@/types";

async function listSkills(config: ServerConfig) {
  const skills = await readOpenWorkConnectSkillCatalog(config);
  skills.forEach((s) => {
    console.log(`${s.title ?? s.name}: ${s.description}`);
    console.log(`Capability: ${s.capability}`);
  });
}

This function automatically resolves configuration across server and workspace scopes, leverages the 30-second cache, and always returns an array—empty if discovery fails.

Rendering Skill Instructions for LLM Prompts

import { renderOpenWorkConnectSkillInstruction } from "@/connect-skill-catalog";

function buildPrompt(skills) {
  const instruction = renderOpenWorkConnectSkillInstruction(skills);
  return `
You are an AI assistant. ${instruction}
Your task: …
`;
}

The rendered <available_skills> block provides deterministic capability inventory for model reasoning while enforcing the requirement to use openwork-cloud_execute_capability for remote invocations.

Summary

  • Dual-layer architecture: OpenWork separates local MCP tool catalogs (tool-catalog.ts) from remote capability inventories (connect-skill-catalog.ts) to enforce different validation rules for each.
  • Strict limits: The tool catalog enforces hard caps of 20 pages, 2,000 tools, 8 MiB total size, and 64-level schema depth to protect LLM context windows.
  • Zod validation: Remote skills are validated against skillIndexSchema with 30-second caching to balance freshness and performance.
  • Graceful degradation: The capability inventory returns empty arrays on failure, ensuring agent continuity during network outages or configuration errors.
  • Typed errors: EnterpriseMcpCatalogError provides specific codes (MCP_CATALOG_ITEM_LIMIT, MCP_CATALOG_DUPLICATE_TOOL) for precise error handling.

Frequently Asked Questions

What is the maximum number of tools OpenWork can catalog?

The Enterprise MCP Tool Catalog limits collection to 2,000 tools (ENTERPRISE_MCP_TOOL_ITEM_LIMIT) across a maximum of 20 pages (ENTERPRISE_MCP_TOOL_PAGE_LIMIT). Exceeding either limit throws an EnterpriseMcpCatalogError with codes MCP_CATALOG_ITEM_LIMIT or MCP_CATALOG_PAGE_LIMIT respectively.

How does OpenWork handle invalid or oversized tool schemas?

Each tool schema is validated using measureSchema in packages/enterprise-mcp-client/src/tool-catalog.ts. Schemas exceeding 512 KB, containing cyclic references, or nesting deeper than 64 levels are rejected. The entire catalog is also capped at 8 MiB (ENTERPRISE_MCP_CATALOG_LIMIT_BYTES) to prevent context window overflow.

What happens when the remote skill catalog is unreachable?

The readOpenWorkConnectSkillCatalog function in apps/server/src/connect-skill-catalog.ts implements graceful degradation. If the MCP endpoint returns errors, the URL is invalid, or the configuration is disabled, the function catches exceptions and returns an empty array, allowing the agent to continue operating with local tools only.

How do tool catalogs differ from capability inventories in OpenWork?

Tool catalogs contain local MCP tool definitions (names, input/output schemas) validated for immediate execution via collectEnterpriseMcpTools. Capability inventories contain remote skill metadata fetched from OpenWork Cloud endpoints, cached for 30 seconds, and rendered into prompts via renderOpenWorkConnectSkillInstruction to inform the LLM about available remote actions without exposing implementation details.

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 →