Plugin Architecture
Monday Morning uses a plugin system to extend the MCP server with new tools, settings, and UI contributions. Plugins are loaded at server startup and their tools become available to any AI agent connected to the MCP server.
How Plugins Work
Section titled “How Plugins Work”The plugin system has three layers:
- Plugin Registry — Discovers plugin directories, validates exports, and calls
register(). - Plugin Interface — The
MondayMorningPlugincontract that every plugin implements. - MCP Server — Merges plugin tools into the server’s tool list so agents can call them.
Plugin Locations
Section titled “Plugin Locations”There are two kinds of plugins:
| Type | Location | Loaded By |
|---|---|---|
| Bundled | Ships with the app in mcp-servers/monday-morning/plugins/ | Always loaded |
| Community | Installed to ~/.monday-morning/plugins/{id}/ | Loaded if present |
Bundled plugins are compiled alongside the MCP server. Community plugins are standalone pre-compiled JavaScript packages installed from a zip or dropped into the plugins folder.
Both types implement the same MondayMorningPlugin interface and go through the same loading pipeline. Bundled plugins take precedence on ID conflicts.
The MondayMorningPlugin Interface
Section titled “The MondayMorningPlugin Interface”Every plugin must default-export an object implementing MondayMorningPlugin:
interface MondayMorningPlugin { /** Unique plugin identifier, e.g., "github" */ id: string; /** Human-readable name, e.g., "GitHub Integration" */ name: string; /** Brief description */ description: string; /** Semver version string */ version: string; /** Classification: "integration" | "migration" | "export" */ category: PluginCategory; /** Tier for future entitlement gating (defaults to "free") */ tier?: PluginTier; /** MCP tools this plugin provides */ tools: PluginToolDefinition[]; /** Called during registration to initialize the plugin */ register(context: PluginContext): Promise<void>; /** Declarative settings schema for auto-rendered settings UI */ settings?: PluginSettingsSchema; /** UI category for desktop sidebar grouping */ uiCategory?: PluginUICategory; /** Declarative UI contributions (slots, sidebar items, widgets, actions) */ ui?: PluginUIRegistration; /** Called when the plugin is enabled by the user */ onEnable?(): Promise<void>; /** Called when the plugin is disabled by the user */ onDisable?(): Promise<void>; /** Called when the user switches to a different project */ onProjectSwitch?(projectPath: string): Promise<void>;}Required Fields
Section titled “Required Fields”| Field | Type | Purpose |
|---|---|---|
id | string | Unique identifier used internally |
name | string | Display name in the desktop app |
description | string | Shown in plugin listings |
version | string | Semver version for the plugin |
category | PluginCategory | One of "integration", "migration", "export" |
tools | PluginToolDefinition[] | Array of MCP tools the plugin provides |
register | function | Async init function called at load time |
Optional Fields
Section titled “Optional Fields”| Field | Type | Purpose |
|---|---|---|
tier | "free" | "pro" | Future entitlement gating (default: "free") |
settings | PluginSettingsSchema | Declarative settings for auto-rendered UI |
uiCategory | PluginUICategory | Sidebar grouping in desktop app |
ui | PluginUIRegistration | Slots, sidebar items, widgets, actions |
onEnable | function | Called when user enables the plugin |
onDisable | function | Called when user disables the plugin |
onProjectSwitch | function | Called when user switches projects |
Plugin Lifecycle
Section titled “Plugin Lifecycle”1. Discovery
Section titled “1. Discovery”On server startup, the PluginRegistry scans two directories for subdirectories containing an index.ts or index.js file:
- Bundled plugins — compiled output in the MCP server’s
plugins/directory - Community plugins —
~/.monday-morning/plugins/for user-installed plugins
const registry = new PluginRegistry(pluginsDir);await registry.discoverAndLoad();Bundled plugins are discovered first. Community plugins are discovered second — if a community plugin has the same id as a bundled plugin, it is skipped.
2. Validation
Section titled “2. Validation”After importing, the registry validates the default export:
idmust be a non-empty stringnamemust be a non-empty stringdescription,versionmust be stringscategorymust be"integration","migration", or"export"toolsmust be an arrayregistermust be a function- No duplicate
idvalues across plugins
If validation fails, the plugin is skipped with a warning.
3. Entitlement Check
Section titled “3. Entitlement Check”Tier gating is live. Before registering a plugin, the registry resolves whether the session is entitled to its declared tier:
"free"(or absent) tier plugins always pass."pro"plugins pass when the resolved license tier isproorteam, or — for a free personal tier — when the user is on a team (the team-member overlay, resolved over the network and persisted in a durable snapshot so the catalog stays stable across sessions).- If tier resolution fails with an error, gating fails open with a warning — a corrupted license file should not silently shrink the toolset.
An unentitled pro plugin is locked, not dropped: it is discovered and listed in the manifest (so the desktop app can show it with an upgrade prompt), but register() is never called and its tools never enter the MCP tool surface. If entitlement is gained mid-session (e.g. the user joins a team), locked plugins are promoted live and the server emits notifications/tools/list_changed. Revocation works the other way at invocation time: each plugin tool call re-checks entitlement (from a short TTL cache) and returns an upgrade message instead of executing when the session is no longer entitled — tools are not yanked out of the listing mid-session.
4. Registration
Section titled “4. Registration”The registry calls register() with a PluginContext:
interface PluginContext { /** Absolute path to the project root */ projectPath: string; /** Path utility functions for resolving .mm/ directory structure */ paths: typeof paths; /** Logger for plugin output */ logger: PluginLogger;}The logger writes to stderr (appropriate for MCP servers). Use it instead of console.log:
register: async (context) => { context.logger.info("My plugin registered");}5. Tool Merging
Section titled “5. Tool Merging”After all plugins load, the server calls registry.getTools() to collect every PluginToolDefinition and merges them into the MCP server’s tool list. Agents see plugin tools alongside built-in tools with no distinction.
6. Global Manifest
Section titled “6. Global Manifest”After loading, the registry writes a manifest.json to ~/.monday-morning/. The desktop app reads this manifest to populate the Plugins settings section without needing a running MCP server. Each entry carries a locked flag — true for a discovered-but-unentitled pro plugin, which the app lists with an upgrade prompt:
{ "version": 2, "generatedAt": "2026-03-31T10:00:00.000Z", "plugins": [ { "id": "github", "name": "GitHub Integration", "description": "Sync issues and link pull requests", "version": "1.0.0", "category": "integration", "tier": "free", "tools": [ { "name": "mm_sync_github", "description": "..." }, { "name": "mm_link_github_pr", "description": "..." } ], "settings": { "credentials": ["..."] }, "uiCategory": "development", "ui": { "icon": "...", "slots": ["..."] }, "locked": false } ]}7. Hot Reload
Section titled “7. Hot Reload”Community plugins can be reloaded without restarting the server. The mm_reload_plugins tool (opt-in workflow group, always dispatchable by name) rescans ~/.monday-morning/plugins/, loads new plugins, unloads plugins whose directories were removed, and rewrites the global manifest. Bundled plugins are never touched by a reload.
Plugin Categories
Section titled “Plugin Categories”Classification Categories
Section titled “Classification Categories”| Category | Purpose |
|---|---|
integration | Connect external services (GitHub, Slack, etc.) |
migration | Import data from other tools (Obsidian, Jira) |
export | Export Monday Morning data to other formats |
UI Categories
Section titled “UI Categories”UI categories determine how plugins are grouped in the desktop app’s plugin listings:
| UI Category | Examples |
|---|---|
communications | Slack, email integrations |
development | GitHub, GitLab |
project-management | Jira, Trello, Linear |
financial | Harvest, invoicing |
data-storage | Vector stores, local files |
export-reporting | PDF export, dashboards |
Plugin Tiers
Section titled “Plugin Tiers”| Tier | Behavior |
|---|---|
free | Always loads and registers |
pro | Requires a pro/team license or team membership. Otherwise the plugin is listed as locked: visible in the desktop app with an upgrade prompt, but never registered and its tools absent from the MCP tool surface. If tier resolution errors, gating fails open. |
Entitlement is also re-checked live on every plugin tool call, so a mid-session revocation returns an upgrade message rather than executing the tool. See Entitlement Check above for the full mechanics.
Next Steps
Section titled “Next Steps”- Creating a Plugin — Build a plugin from scratch with a step-by-step tutorial.
- Installing & Sharing Plugins — Package, install, and share a plugin.