# How to Extend the Functionality of OpenWork: A Complete Plugin Development Guide

> Extend OpenWork functionality with custom plugins and React UI components. Learn to add new MCP actions without core modifications. A complete guide for developers.

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

---

**You can extend OpenWork by creating Markdown-based plugins, adding custom UI components in React, or implementing new MCP actions—all without modifying the core Electron application.**

OpenWork is a modular AI workspace platform built on a plugin-first architecture. According to the `different-ai/openwork` source code, every capability—from simple skills to complex enterprise integrations—flows through the same Model-Control-Plane (MCP) protocol. This design lets you add new functionality by dropping a Markdown file into the `plugins/` folder or extending the typed TypeScript APIs in `packages/types/src/`.

## Understanding OpenWork's Extension Architecture

Before writing code, you need to understand how the five layers communicate. Each layer is isolated and typed, so changes in one rarely break others.

| Layer | Purpose | Key Source Files |
|------|---------|------------------|
| **Desktop front-end** (Electron + React) | Renders UI, talks to Den via CDP | [`packages/ui/src/react/platform-detect.ts`](https://github.com/different-ai/openwork/blob/main/packages/ui/src/react/platform-detect.ts), [`packages/ui/src/common/paper.ts`](https://github.com/different-ai/openwork/blob/main/packages/ui/src/common/paper.ts) |
| **Typed API contracts** | Defines data shapes between layers | [`packages/types/src/den/mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts), [`packages/types/src/openwork-context.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/openwork-context.ts) |
| **MCP client** | Thin library for agents to talk to OpenWork | [`packages/enterprise-mcp-client/src/enterprise-mcp-client.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-client/src/enterprise-mcp-client.ts) |
| **MCP mock server** | CI/E2E tests and protocol reference | [`packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts) |
| **Skill/Plugin definition** | Markdown files describing capabilities | [`packages/enterprise-mcp-client/src/tool-catalog.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-client/src/tool-catalog.ts) |

The flow works like this: an AI agent calls the MCP client, which forwards to the **Den backend** (Node/Express). The Den parses plugin definitions using [`tool-catalog.ts`](https://github.com/different-ai/openwork/blob/main/tool-catalog.ts) and returns capabilities. The desktop UI optionally renders components that call the same MCP endpoints.

## Method 1: Create a Plugin Using Markdown (No Code Required)

The fastest way to extend OpenWork functionality is adding a **skill definition**—a Markdown file with YAML front-matter. The [`tool-catalog.ts`](https://github.com/different-ai/openwork/blob/main/tool-catalog.ts) scanner discovers these automatically.

### Step 1: Write the Plugin File

Create a file in the `plugins/` folder (or any watched directory):

```markdown
---
name: My Cool Skill
description: Returns a random joke from an external API.
skill: my_cool_joke
---

{{#fetch "https://official-joke-api.appspot.com/jokes/random"}}
{{json .setup}} – {{json .punchline}}
{{/fetch}}

```

The `skill` key in front-matter registers `my_cool_joke` as an exposed tool. The template body uses OpenWork's built-in `fetch` helper to call external APIs.

### Step 2: Start the Development Server

Run the watcher to rebuild the MCP catalog automatically:

```bash
pnpm dev

# or for isolated worktrees

pnpm dev:worktree

```

The `enterprise-mcp-client` rescans `plugins/` on every change. No restart required.

### Step 3: Test with an AI Agent

From any MCP-enabled agent (Claude Code, Cursor, OpenCode):

```text
search_capabilities

```

The response includes `my_cool_joke`. Execute it:

```text
execute_capability my_cool_joke

```

The agent receives the rendered template output.

## Method 2: Add Custom UI Components in React

To surface your plugin in the desktop app, extend the React layer in `packages/ui/src/react/`.

### Step 1: Create the Component

```tsx
// packages/ui/src/react/my-cool-joke.tsx
import { useQuery } from '@tanstack/react-query';
import { executeCapability } from '@/connect-link';

export const MyCoolJoke = () => {
  const { data, error, isLoading } = useQuery('joke', () =>
    executeCapability('my_cool_joke')
  );

  if (isLoading) return <p>Loading…</p>;
  if (error) return <p>Failed: {(error as Error).message}</p>;

  return <blockquote className="p-4 bg-gray-100">{data}</blockquote>;
};

```

The `executeCapability` wrapper is typed against [`packages/types/src/den/mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts), ensuring compile-time safety for payload shapes.

### Step 2: Export the Component

Update the barrel file:

```typescript
// packages/ui/src/react/index.ts
export * from './my-cool-joke';

```

### Step 3: Platform-Conditional Rendering (Optional)

Use [`platform-detect.ts`](https://github.com/different-ai/openwork/blob/main/platform-detect.ts) to enable features only on specific operating systems:

```typescript
import { isMacOS, isWindows } from './platform-detect';

const showJokeWidget = isMacOS(); // or your own logic

```

## Method 3: Implement Custom MCP Actions

For deeper integrations, extend the protocol itself by adding new action types.

### Define the Action Contract

Add your action to the typed schema:

```typescript
// packages/types/src/den/mcp-connection-action.ts
export type MCPConnectionAction =
  | { type: 'install'; mcpId: string }
  | { type: 'grant'; mcpId: string; scope: string }
  | { type: 'archive'; mcpId: string }
  | { type: 'my_custom_action'; payload: MyCustomPayload }; // your extension

```

### Implement the Handler

Reference [`mcp-handler.ts`](https://github.com/different-ai/openwork/blob/main/mcp-handler.ts) for the routing pattern:

```typescript
// packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts (reference)
async function handleAction(action: MCPConnectionAction): Promise<Result> {
  switch (action.type) {
    case 'my_custom_action':
      return await handleCustomAction(action.payload);
    // ...
  }
}

```

The mock server serves as both **test infrastructure** and **reference implementation** for protocol extensions.

## Method 4: Expose OAuth and Credentials

Plugins often need authenticated API access. Study [`oauth-provider.ts`](https://github.com/different-ai/openwork/blob/main/oauth-provider.ts) for the credential flow:

```typescript
// packages/enterprise-mcp-client/src/oauth-provider.ts
export class OAuthProvider {
  async getCredentials(skillName: string): Promise<OAuthToken> {
    // Retrieves stored tokens from the Den backend
  }
}

```

Your plugin's Markdown front-matter can declare required OAuth scopes, and the MCP client handles the consent flow before executing the skill.

## Configuration Reference

All MCP endpoints and plugin sources are declared in [`opencode.json`](https://github.com/different-ai/openwork/blob/main/opencode.json):

```json
{
  "mcp": {
    "openwork": {
      "command": "node",
      "args": ["./packages/enterprise-mcp-client/dist/index.js"],
      "env": {
        "OPENWORK_API_KEY": "${OPENWORK_API_KEY}"
      }
    }
  }
}

```

Agents read this file to discover available capabilities. The `mcp` section points to your local or remote Den instance.

## Key Extension Points in the Source Code

| File | Purpose | When to Modify |
|------|---------|--------------|
| [`packages/enterprise-mcp-client/src/enterprise-mcp-client.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-client/src/enterprise-mcp-client.ts) | Core MCP client library | Adding protocol features |
| [`packages/enterprise-mcp-client/src/tool-catalog.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-client/src/tool-catalog.ts) | Plugin discovery | Changing how skills are found |
| [`packages/types/src/den/mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts) | Action type definitions | New MCP operations |
| [`packages/types/src/openwork-context.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/openwork-context.ts) | Global plugin context | Exposing new runtime data |
| [`packages/ui/src/react/platform-detect.ts`](https://github.com/different-ai/openwork/blob/main/packages/ui/src/react/platform-detect.ts) | OS feature flags | UI per-platform behavior |
| [`packages/ui/src/react/index.ts`](https://github.com/different-ai/openwork/blob/main/packages/ui/src/react/index.ts) | Component exports | New UI components |
| [`opencode.json`](https://github.com/different-ai/openwork/blob/main/opencode.json) | MCP configuration | Adding/removing endpoints |

## Summary

- **Markdown plugins** (`plugins/*.md`) add capabilities instantly—no build step, no code changes.
- **React components** in `packages/ui/src/react/` surface plugins in the desktop UI with full TypeScript safety.
- **MCP actions** extend the protocol by adding types to [`mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/mcp-connection-action.ts) and handlers following [`mcp-handler.ts`](https://github.com/different-ai/openwork/blob/main/mcp-handler.ts) patterns.
- **OAuth flows** reuse [`oauth-provider.ts`](https://github.com/different-ai/openwork/blob/main/oauth-provider.ts) for secure credential management.
- Every layer respects the contracts in `packages/types/src/`, preventing breaking changes across the stack.

## Frequently Asked Questions

### Can I extend OpenWork without writing TypeScript?

Yes. Create a Markdown file in `plugins/` with YAML front-matter declaring `name`, `description`, and `skill`. The [`tool-catalog.ts`](https://github.com/different-ai/openwork/blob/main/tool-catalog.ts) scanner picks it up automatically. Template helpers like `{{#fetch}}` handle HTTP calls without custom code.

### How do I test my plugin before deploying?

Run `pnpm dev` to start the file watcher, then use any MCP-enabled agent (Claude Code, Cursor, etc.) with `search_capabilities` and `execute_capability` commands. For automated testing, reference the mock server in [`packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts`](https://github.com/different-ai/openwork/blob/main/packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts).

### What limits exist on plugin functionality?

Plugins can call external APIs via template helpers, access OAuth credentials through [`oauth-provider.ts`](https://github.com/different-ai/openwork/blob/main/oauth-provider.ts), and receive context from [`openwork-context.ts`](https://github.com/different-ai/openwork/blob/main/openwork-context.ts). They cannot directly modify the Electron main process or Den backend without contributing to those packages—this is intentional for security isolation.

### Where should I place custom UI components?

Create files in `packages/ui/src/react/` and re-export them from [`packages/ui/src/react/index.ts`](https://github.com/different-ai/openwork/blob/main/packages/ui/src/react/index.ts). Components use [`platform-detect.ts`](https://github.com/different-ai/openwork/blob/main/platform-detect.ts) to conditionally render based on the operating system, ensuring consistent behavior across Windows, macOS, and Linux.