# How to Develop Custom MCP Capabilities in OpenWork: 3 Integration Methods

> Learn to develop custom MCP capabilities in OpenWork using three integration methods: native providers, marketplace plugins, or external OAuth servers. Enhance your OpenWork functionality today.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-21

---

**You can develop custom MCP capabilities in OpenWork by registering a native provider in [`native-capabilities.ts`](https://github.com/different-ai/openwork/blob/main/native-capabilities.ts), publishing a marketplace plugin with a [`plugin.json`](https://github.com/different-ai/openwork/blob/main/plugin.json) manifest, or connecting an external OAuth-protected MCP server via the registry in [`capability-registry.ts`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/pnpm-workspace.yaml) automatically includes any folder under `packages/*`.

```bash
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.

```typescript
// 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:

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/mcp/native-capabilities.ts) to append your export to the `nativeCapabilities` array.

```typescript
// 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:

```bash
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`](https://github.com/different-ai/openwork/blob/main/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.

```typescript
// 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`](https://github.com/different-ai/openwork/blob/main/plugin.json) to register the capability:

```json
{
  "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:

```bash
cd packages/log-analyzer-plugin
pnpm build
pnpm publish

```

Organization admins can then install the capability via the MCP command:

```bash
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:

```json
{
  "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:

```typescript
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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/mcp/capability-registry.ts)** – Central hub that composes sources and implements `searchCapabilityRegistry` and `executeCapability`
- **[`ee/apps/den-api/src/mcp/native-capabilities.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/mcp/native-capabilities.ts)** – Loader for native providers; edit this to wire custom first-party tools
- **[`ee/apps/den-api/src/mcp/marketplace-capabilities.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/mcp/marketplace-capabilities.ts)** – Implementation for marketplace plugin resolution
- **`scripts/mock-oauth-mcp-server.mjs`** – Reference implementation for external MCP servers
- **[`ee/apps/den-api/src/mcp/builtin-skills.ts`](https://github.com/different-ai/openwork/blob/main/ee/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 into [`native-capabilities.ts`](https://github.com/different-ai/openwork/blob/main/native-capabilities.ts).
- **Marketplace plugins** enable distribution to multiple organizations via [`plugin.json`](https://github.com/different-ai/openwork/blob/main/plugin.json) manifests and the `create-plugin` scaffold.
- **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_capabilities` interface and executed via `execute_capability`, with routing handled by [`ee/apps/den-api/src/mcp/capability-registry.ts`](https://github.com/different-ai/openwork/blob/main/ee/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/mcp/agent.ts), which delegates to the registry after parsing the JSON-RPC envelope.