Skip to content
K

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.

The plugin system has three layers:

  1. Plugin Registry — Discovers plugin directories, validates exports, and calls register().
  2. Plugin Interface — The MondayMorningPlugin contract that every plugin implements.
  3. MCP Server — Merges plugin tools into the server’s tool list so agents can call them.

There are two kinds of plugins:

TypeLocationLoaded By
BundledShips with the app in mcp-servers/monday-morning/plugins/Always loaded
CommunityInstalled 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 the Plugin Marketplace or manually.

Both types implement the same MondayMorningPlugin interface and go through the same loading pipeline. Bundled plugins take precedence on ID conflicts.

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>;
}
FieldTypePurpose
idstringUnique identifier used internally
namestringDisplay name in the desktop app
descriptionstringShown in plugin listings
versionstringSemver version for the plugin
categoryPluginCategoryOne of "integration", "migration", "export"
toolsPluginToolDefinition[]Array of MCP tools the plugin provides
registerfunctionAsync init function called at load time
FieldTypePurpose
tier"free" | "pro"Future entitlement gating (default: "free")
settingsPluginSettingsSchemaDeclarative settings for auto-rendered UI
uiCategoryPluginUICategorySidebar grouping in desktop app
uiPluginUIRegistrationSlots, sidebar items, widgets, actions
onEnablefunctionCalled when user enables the plugin
onDisablefunctionCalled when user disables the plugin
onProjectSwitchfunctionCalled when user switches projects

On server startup, the PluginRegistry scans two directories for subdirectories containing an index.ts or index.js file:

  1. Bundled plugins — compiled output in the MCP server’s plugins/ directory
  2. 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.

After importing, the registry validates the default export:

  • id must be a non-empty string
  • name must be a non-empty string
  • description, version must be strings
  • category must be "integration", "migration", or "export"
  • tools must be an array
  • register must be a function
  • No duplicate id values across plugins

If validation fails, the plugin is skipped with a warning.

The registry calls checkEntitlement(plugin) before loading. Currently all plugins pass (both "free" and "pro" tiers). When a licensing backend is added, "pro" plugins will require a valid entitlement.

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");
}

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.

After loading, the registry writes a manifest.json to ~/.monday-morning/. The desktop app reads this manifest to populate the Plugins settings tab without needing a running MCP server:

{
"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": ["..."] }
}
]
}
CategoryPurpose
integrationConnect external services (GitHub, Slack, etc.)
migrationImport data from other tools (Obsidian, Jira)
exportExport Monday Morning data to other formats

UI categories determine how plugins are grouped in the desktop sidebar:

UI CategoryExamples
communicationsSlack, email integrations
developmentGitHub, GitLab
project-managementJira, Trello, Linear
financialHarvest, invoicing
data-storageObsidian, Notion
export-reportingPDF export, dashboards
TierBehavior
freeAlways loads, no restrictions
proReserved for future entitlement gating

All plugins currently load regardless of tier. When a licensing system is added, pro plugins will require a valid subscription.