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

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, packages/ui/src/common/paper.ts
Typed API contracts Defines data shapes between layers packages/types/src/den/mcp-connection-action.ts, 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
MCP mock server CI/E2E tests and protocol reference 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

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 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 scanner discovers these automatically.

Step 1: Write the Plugin File

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

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

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):

search_capabilities

The response includes my_cool_joke. Execute it:

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

// 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, ensuring compile-time safety for payload shapes.

Step 2: Export the Component

Update the barrel file:

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

Step 3: Platform-Conditional Rendering (Optional)

Use platform-detect.ts to enable features only on specific operating systems:

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:

// 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 for the routing pattern:

// 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 for the credential flow:

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

{
  "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 Core MCP client library Adding protocol features
packages/enterprise-mcp-client/src/tool-catalog.ts Plugin discovery Changing how skills are found
packages/types/src/den/mcp-connection-action.ts Action type definitions New MCP operations
packages/types/src/openwork-context.ts Global plugin context Exposing new runtime data
packages/ui/src/react/platform-detect.ts OS feature flags UI per-platform behavior
packages/ui/src/react/index.ts Component exports New UI components
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 and handlers following mcp-handler.ts patterns.
  • OAuth flows reuse 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 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.

What limits exist on plugin functionality?

Plugins can call external APIs via template helpers, access OAuth credentials through oauth-provider.ts, and receive context from 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. Components use platform-detect.ts to conditionally render based on the operating system, ensuring consistent behavior across Windows, macOS, and Linux.

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 →