Creating a Plugin
This tutorial walks through building a community plugin as a standalone project. By the end, you will have a working plugin that can be installed into any Monday Morning instance and discovered by AI agents.
Prerequisites
Section titled “Prerequisites”- Node.js 18+
- Basic TypeScript knowledge
- A working Monday Morning installation (to test with)
How Community Plugins Work
Section titled “How Community Plugins Work”Community plugins are standalone JavaScript packages that implement the MondayMorningPlugin interface. They are:
- Developed independently — in their own repo, not inside the Monday Morning monorepo
- Pre-compiled to JavaScript — shipped as
.jsfiles, not TypeScript - Installed globally — to
~/.monday-morning/plugins/{your-plugin-id}/ - Loaded at MCP server startup — alongside bundled plugins, with the same capabilities
Project Setup
Section titled “Project Setup”-
Create your plugin project
Terminal window mkdir mm-plugin-hello-worldcd mm-plugin-hello-worldnpm init -y -
Install dependencies
Your plugin needs
zodfor input schema validation (the MCP server provides it at runtime, but you need it for development):Terminal window npm install zodnpm install -D typescript @types/node -
Configure TypeScript
Create
tsconfig.json:{"compilerOptions": {"target": "ES2022","module": "ESNext","moduleResolution": "bundler","outDir": "dist","rootDir": "src","declaration": true,"strict": true,"esModuleInterop": true,"skipLibCheck": true},"include": ["src/**/*"]} -
Create the plugin types
Create
src/plugin-types.tswith the interfaces your plugin needs. You can copy these from the Plugin Architecture page, or use this minimal version:src/plugin-types.ts import type { ZodType } from "zod";export type PluginCategory = "integration" | "migration" | "export";export type PluginTier = "free" | "pro";export type PluginUICategory =| "communications"| "development"| "project-management"| "financial"| "data-storage"| "export-reporting";export interface PluginToolDefinition {name: string;description: string;inputSchema: ZodType;handler: (args: unknown) => Promise<unknown>;}export interface PluginLogger {info(message: string, ...args: unknown[]): void;warn(message: string, ...args: unknown[]): void;error(message: string, ...args: unknown[]): void;}export interface PluginContext {projectPath: string;paths: Record<string, (...args: string[]) => string>;logger: PluginLogger;}export interface CredentialField {key: string;label: string;type: "text" | "password";required: boolean;helpUrl?: string;helpText?: string;scopes?: string[];}export interface ConfigField {key: string;label: string;type: "text" | "number" | "boolean" | "select" | "channel" | "repo-picker" | "board-picker";default?: string | number | boolean;options?: { label: string; value: string }[];description?: string;dependsOn?: string;placeholder?: string;}export interface PluginSettingsSchema {credentials?: CredentialField[];config?: ConfigField[];projectConfig?: ConfigField[];}export interface PluginAction {id: string;label: string;context: "command-palette" | "entity-context-menu";command: string;}export interface PluginUIRegistration {icon?: string;logo?: string;tagline?: string;actions?: PluginAction[];}export interface MondayMorningPlugin {id: string;name: string;description: string;version: string;category: PluginCategory;tier?: PluginTier;tools: PluginToolDefinition[];register(context: PluginContext): Promise<void>;settings?: PluginSettingsSchema;uiCategory?: PluginUICategory;ui?: PluginUIRegistration;onEnable?(): Promise<void>;onDisable?(): Promise<void>;onProjectSwitch?(projectPath: string): Promise<void>;} -
Write your plugin
Create
src/index.ts:src/index.ts import { z } from "zod";import type { MondayMorningPlugin, PluginContext } from "./plugin-types.js";let pluginContext: PluginContext | undefined;const GreetInputSchema = z.object({project_path: z.string().describe("Absolute path to the project root"),name: z.string().optional().describe("Name to greet (default: World)"),});const helloWorldPlugin: MondayMorningPlugin = {id: "hello-world",name: "Hello World",description: "A minimal example plugin that greets the user",version: "1.0.0",category: "integration",tier: "free",uiCategory: "development",ui: {icon: "👋",tagline: "A friendly greeting plugin",actions: [{id: "hello-greet",label: "Say Hello",context: "command-palette",command: "mm_hello_world",},],},tools: [{name: "mm_hello_world",description: "Say hello — a minimal example tool",inputSchema: GreetInputSchema,handler: async (args: unknown) => {const input = GreetInputSchema.parse(args);const name = input.name ?? "World";pluginContext?.logger.info(`Greeting ${name}`);return {success: true,message: `Hello, ${name}! Project: ${input.project_path}`,};},},],register: async (context: PluginContext): Promise<void> => {pluginContext = context;context.logger.info("Hello World plugin registered");},};export default helloWorldPlugin;Key points:
- The default export must be a
MondayMorningPluginobject. - The
idmust be unique across all plugins. - Always include
project_pathin your tool input schemas. - Use
pluginContext.loggerinstead ofconsole.log.
- The default export must be a
-
Add a manifest.json
Create
manifest.jsonin the project root. This is required for the marketplace and the plugin loader:{"id": "hello-world","name": "Hello World","version": "1.0.0","description": "A minimal example plugin that greets the user","author": {"name": "Your Name","github": "your-github-username"},"category": "integration","uiCategory": "development","icon": "👋","keywords": ["hello", "example", "starter"],"minAppVersion": "1.0.0","license": "MIT"} -
Build the plugin
Add a build script to
package.json:{"type": "module","scripts": {"build": "tsc"}}Then compile:
Terminal window npm run buildThis produces
dist/index.js— the file the MCP server will load. -
Install locally for testing
Copy the compiled output, manifest,
package.json, andnode_modulesto the global plugins directory:Terminal window mkdir -p ~/.monday-morning/plugins/hello-worldcp dist/index.js ~/.monday-morning/plugins/hello-world/cp manifest.json ~/.monday-morning/plugins/hello-world/cp package.json ~/.monday-morning/plugins/hello-world/cp -r node_modules ~/.monday-morning/plugins/hello-world/Terminal window mkdir "$env:USERPROFILE\.monday-morning\plugins\hello-world" -Forcecp dist\index.js "$env:USERPROFILE\.monday-morning\plugins\hello-world\"cp manifest.json "$env:USERPROFILE\.monday-morning\plugins\hello-world\"cp package.json "$env:USERPROFILE\.monday-morning\plugins\hello-world\"cp -r node_modules "$env:USERPROFILE\.monday-morning\plugins\hello-world\" -
Verify the plugin loads
Restart the MCP server (or restart the Monday Morning desktop app). Check the logs for:
[plugin-registry] INFO: Loaded plugin "Hello World" (hello-world) v1.0.0The tool
mm_hello_worldis now available to any connected AI agent.
Testing Your Plugin
Section titled “Testing Your Plugin”You can test the tool by asking an AI agent to call it:
“Call the mm_hello_world tool with name ‘Developer’”
The agent will receive:
{ "success": true, "message": "Hello, Developer! Project: /Users/you/your-project"}Plugin Directory Structure
Section titled “Plugin Directory Structure”A complete community plugin looks like this when installed:
~/.monday-morning/plugins/hello-world/├── index.js # Compiled plugin entry point (required)├── manifest.json # Plugin metadata (required)├── package.json # Must include "type": "module" (required)├── node_modules/ # Dependencies like zod (required)│ └── zod/├── plugin-types.js # Compiled type definitions└── lib/ # Optional: additional modules └── helpers.jsThe minimum required files are:
index.js— default export implementingMondayMorningPluginmanifest.json— plugin metadatapackage.json— with"type": "module"node_modules/zod— the Zod validation library
Adding Settings
Section titled “Adding Settings”To give your plugin a settings UI in the desktop app, add a settings property:
const myPlugin: MondayMorningPlugin = { // ... existing fields ...
settings: { credentials: [ { key: "api_token", label: "API Token", type: "password", required: true, helpUrl: "https://example.com/tokens", helpText: "Generate a token from your account settings", }, ], config: [ { key: "default_name", label: "Default Name", type: "text", default: "World", description: "The default name to use when greeting", }, ], },};See Settings & Credentials for the full settings schema reference.
Adding UI Contributions
Section titled “Adding UI Contributions”Plugins can contribute to the desktop app UI declaratively:
const myPlugin: MondayMorningPlugin = { // ... existing fields ...
ui: { icon: "👋", tagline: "A friendly greeting plugin", actions: [ { id: "hello-greet", label: "Say Hello", context: "command-palette", command: "mm_hello_world", }, ], },};This registers a “Say Hello” action in the command palette. See Plugin Architecture for all UI contribution types.
Real-World Example: GitHub Plugin
Section titled “Real-World Example: GitHub Plugin”The built-in GitHub plugin is a good reference for a production plugin. It demonstrates:
- Two tools:
mm_sync_githubandmm_link_github_pr - Credential settings: A personal access token with help URL and scope requirements
- UI contributions: Card badges, command palette actions, and context menu items
- Separate handler files: Logic split into
lib/sync.tsandlib/link-pr.ts
You can browse the source at WeekendDevsOrg/monday-morning/mcp-servers/monday-morning/plugins/github.
Next Steps
Section titled “Next Steps”- Tool Registration — Deep dive into
PluginToolDefinition, Zod schemas, and handler patterns. - Settings & Credentials — Add credential fields and configuration options.
- Publishing & Marketplace — Submit your plugin to the marketplace for others to install.