How to Develop Custom MCP Capabilities in OpenWork: 3 Integration Methods
You can develop custom MCP capabilities in OpenWork by registering a native provider in native-capabilities.ts, publishing a marketplace plugin with a plugin.json manifest, or connecting an external OAuth-protected MCP server via the registry in capability-registry.ts.
OpenWork’s Model Control Protocol (MCP) layer exposes a dynamic catalog of capabilities that agents discover through search_capabilities and invoke via execute_capability. According to the different-ai/openwork source code, the Capability Registry aggregates six source kinds—catalog, native, externalMcp, marketplace, builtinSkill, and admin—to assemble this catalog. Adding custom functionality requires extending one of these sources, most commonly the native provider list, the marketplace plugin system, or the external MCP proxy.
Understanding the OpenWork MCP Architecture
The MCP implementation in OpenWork follows a unified registry pattern. The central hub resides in ee/apps/den-api/src/mcp/capability-registry.ts, which composes multiple CapabilitySource implementations. Each source exposes a parseName function to identify capability names and an execute method to run the associated CodeMode tool.
When a client calls execute_capability, the registry iterates through sources in priority order: native capabilities, marketplace plugins, external MCP connections, and built-in skills. The first source that recognizes the capability name handles the request, executing the associated Tool.Definition.run function inside an Effect.promise or proxying to an external JSON-RPC endpoint.
Method 1: Registering a Native Capability
Native capabilities are first-party tools bundled directly with the OpenWork deployment. They offer the lowest latency and deepest system integration.
Scaffold the Provider Package
Create a new package under the packages/ directory to isolate your custom logic. The workspace configuration in pnpm-workspace.yaml automatically includes any folder under packages/*.
mkdir -p packages/custom-mcp/src
cd packages/custom-mcp
Initialize the package with a dependency on @openwork/codemode to access the Tool API.
Implement the Tool Definition
Export a provider object that implements the NativeMcpProvider shape expected by buildNativeProviderToolTree. The object must define a namespace, tool name, capability name, and a CodeMode tool definition.
// packages/custom-mcp/src/provider.ts
import { Tool } from "@openwork/codemode";
export const analyzeLogsCapability = {
namespace: "ops",
toolName: "analyzeLogs",
capabilityName: "ops:analyzeLogs",
description: "Analyze system logs for error patterns.",
readOnly: true,
authority: "den" as const,
definition: Tool.make({
description: "Scan application logs for specific error codes.",
input: {
type: "object",
properties: {
service: { type: "string", description: "Service name" },
level: { type: "string", enum: ["error", "warn"] }
},
required: ["service"]
},
run: async ({ service, level }: { service: string; level?: string }) => {
// Implementation logic here
return `Analysis complete for ${service} at level ${level || "error"}`;
},
}),
};
Create an index file to expose the capability:
// packages/custom-mcp/src/index.ts
export { analyzeLogsCapability } from "./provider";
Wire into the Capability Registry
Import your capability into the native capabilities loader. Edit ee/apps/den-api/src/mcp/native-capabilities.ts to append your export to the nativeCapabilities array.
// ee/apps/den-api/src/mcp/native-capabilities.ts
import { analyzeLogsCapability } from "custom-mcp/src/provider";
export const nativeCapabilities = [
// ...existing native capabilities
analyzeLogsCapability,
];
The NativeProvider loader automatically rebuilds the tool tree on startup. No additional registration steps are required.
Verification
Start the development server and verify the capability appears in the catalog:
pnpm dev
# From an OpenWork-connected client:
search_capabilities "analyze logs" 5
You should see output indicating the capability is available under the native source kind:
ops:analyzeLogs ops /ops/analyzeLogs (native) Analyze system logs for error patterns.
Method 2: Publishing a Marketplace Plugin
Marketplace capabilities allow you to ship plugin bundles that other organizations can install through the Den UI or CLI.
Generate the Plugin Scaffold
OpenWork provides a built-in skill to scaffold plugins. From an agent connected to your OpenWork instance, run:
create-plugin log-analyzer-plugin
This creates a folder under packages/log-analyzer-plugin containing plugin.json, entry points, and UI scaffolding.
Define the Capability Tool
Inside the plugin, define a CodeMode tool similar to the native approach, then expose it via the plugin manifest.
// packages/log-analyzer-plugin/src/tools/analyze.ts
import { Tool } from "@openwork/codemode";
export const logAnalysisTool = Tool.make({
description: "Perform deep log analysis with pattern matching.",
input: {
type: "object",
properties: {
hoursBack: { type: "number", default: 24 },
pattern: { type: "string" }
},
required: ["pattern"]
},
run: async ({ hoursBack, pattern }) => {
return `Found 42 matches for pattern ${pattern} in last ${hoursBack}h`;
},
});
Update plugin.json to register the capability:
{
"name": "log-analyzer-plugin",
"capabilities": [
{
"name": "plugin:log-analyzer-plugin:analyze",
"tool": "analyze",
"namespace": "log-analyzer"
}
],
"version": "0.1.0"
}
Register and Publish
Build and publish the plugin to the internal OpenWork registry:
cd packages/log-analyzer-plugin
pnpm build
pnpm publish
Organization admins can then install the capability via the MCP command:
codemodel add cap plugin:log-analyzer-plugin:analyze
Once installed, the capability appears under the marketplace source kind and is searchable via search_capabilities.
Method 3: Connecting an External MCP Server
For capabilities hosted outside the OpenWork monorepo—such as third-party services or legacy systems—you can register an external MCP server that communicates via JSON-RPC over HTTP.
Implement the JSON-RPC Server
Use the mock server in the repository as a template. The file scripts/mock-oauth-mcp-server.mjs demonstrates the required protocol implementation, including OAuth scope validation and request/response envelope handling.
Your external server must expose endpoints that accept JSON-RPC requests and return structured CodeMode results. Ensure the server validates bearer tokens against the OpenWork OAuth issuer.
Register the Connection
Add the connection to your organization’s MCP configuration via the Den UI or admin CLI. The payload specifies the resource URL and required scopes:
{
"connectionId": "legacy-billing-mcp",
"resource": "https://billing.internal.company.com/mcp",
"scopes": ["mcp:read", "mcp:write"]
}
Once registered, the Capability Registry routes invocations through externalMcpSource.execute, which handles token acquisition, request proxying, and error translation.
Invoke the remote capability using the standard interface:
const result = await execute_capability("legacy-billing-mcp:generateInvoice", {
body: { customerId: "C-12345", amount: 500 }
});
console.log(result.content);
Execution Flow and Key Implementation Files
Understanding the request lifecycle helps debug custom capabilities. The entry point for all MCP requests is ee/apps/den-api/src/mcp/agent.ts, which parses the JSON-RPC envelope and forwards to the registry. The core routing logic lives in ee/apps/den-api/src/mcp/capability-registry.ts, specifically in the executeCapability function.
Key files for custom development include:
ee/apps/den-api/src/mcp/capability-registry.ts– Central hub that composes sources and implementssearchCapabilityRegistryandexecuteCapabilityee/apps/den-api/src/mcp/native-capabilities.ts– Loader for native providers; edit this to wire custom first-party toolsee/apps/den-api/src/mcp/marketplace-capabilities.ts– Implementation for marketplace plugin resolutionscripts/mock-oauth-mcp-server.mjs– Reference implementation for external MCP serversee/apps/den-api/src/mcp/builtin-skills.ts– Reference for built-in skill patterns
Summary
- Native capabilities offer the tightest integration for first-party tools by exporting a provider in
packages/and wiring it intonative-capabilities.ts. - Marketplace plugins enable distribution to multiple organizations via
plugin.jsonmanifests and thecreate-pluginscaffold. - External MCP servers proxy requests to remote JSON-RPC endpoints, registered through OAuth connections in the capability registry.
- All capability types are discovered through the unified
search_capabilitiesinterface and executed viaexecute_capability, with routing handled byee/apps/den-api/src/mcp/capability-registry.ts.
Frequently Asked Questions
What is the difference between native and marketplace capabilities in OpenWork?
Native capabilities are compiled directly into the Den API binary and execute within the OpenWork process, offering lower latency and direct access to internal services. Marketplace capabilities are dynamically loaded plugins that run in isolated contexts and can be installed or uninstalled by organization admins without redeploying the core platform.
How do I test a custom capability before deploying it to production?
For native capabilities, run the development server with pnpm dev and use the search_capabilities CLI tool to verify your tool appears in the catalog. For marketplace plugins, use pnpm build to check for compilation errors, then publish to a staging registry. External MCP servers can be tested locally using the scripts/mock-oauth-mcp-server.mjs template and the dev:mock-mcp npm script.
Can I restrict access to specific capabilities by user role?
Yes. The capability registry checks authority requirements defined in the capability definition (e.g., authority: "den"). Additionally, admin-only capabilities are filtered through the adminSource in capability-registry.ts, which validates platform admin status before exposing tools. For marketplace plugins, installation permissions are controlled at the organization level via the Den UI.
What file handles the routing of MCP requests in OpenWork?
The file ee/apps/den-api/src/mcp/capability-registry.ts handles all routing logic. It aggregates multiple CapabilitySource instances—including native, marketplace, and external MCP sources—and implements the parseName logic that determines which source handles a given capability invocation. The HTTP entry point is ee/apps/den-api/src/mcp/agent.ts, which delegates to the registry after parsing the JSON-RPC envelope.
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 →