From 8dc821cd076aee86084904d1198c39ce6d36a9b0 Mon Sep 17 00:00:00 2001 From: Spencer McGuire Date: Fri, 10 Jul 2026 08:09:38 -0600 Subject: [PATCH] Add .plugin packaging (Cowork/Claude Code plugin variant) plugin/ holds the plugin manifest, .mcp.json (CLAUDE_PLUGIN_ROOT paths), and README; scripts/build-plugin.sh syncs the core client, esbuild-bundles the server into a single ESM file (the plugin uploader rejects zip entries containing "@", so no node_modules), and zips it as ms-todo.plugin. Same server, two install formats (.mcpb + .plugin). Mirrors claude-msplanner. Verified: the bundled server registers all 17 tools over stdio as ms-todo v1.0.1. Co-Authored-By: Claude Fable 5 --- .gitignore | 3 +++ README.md | 18 +++++++++++----- plugin/.claude-plugin/plugin.json | 10 +++++++++ plugin/.mcp.json | 12 +++++++++++ plugin/README.md | 36 +++++++++++++++++++++++++++++++ scripts/build-plugin.sh | 29 +++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 plugin/.claude-plugin/plugin.json create mode 100644 plugin/.mcp.json create mode 100644 plugin/README.md create mode 100755 scripts/build-plugin.sh diff --git a/.gitignore b/.gitignore index a4033e5..b99582c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ # Node node_modules/ + +# Built plugin bundle (source lives in plugin/; artifact goes on releases) +*.plugin diff --git a/README.md b/README.md index 106f0a6..b667c1a 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Two ways to use it, sharing one core Graph client (`graph.mjs`): |---|---|---| | **Skill** | Claude Code on your own machine | `.claude/skills/ms-todo/` | | **Desktop extension (`.mcpb`)** | Claude Desktop, distributed org-wide | `mcp-extension/` | +| **Plugin (`.plugin`)** | Claude Cowork / Claude Code plugin picker | `plugin/` (same server, bundled by `scripts/build-plugin.sh`) | ## Repo layout @@ -38,16 +39,23 @@ Two ways to use it, sharing one core Graph client (`graph.mjs`): │ └── references/ │ ├── setup.md # Entra app registration walkthrough (public client + loopback redirect) │ └── graph-api.md # todoTask field formats + OData query reference -└── mcp-extension/ # Claude Desktop extension (imports the same graph.mjs) - ├── manifest.json # .mcpb manifest (baked client/tenant id, user_config) - ├── server/index.mjs # MCP server (stdio) exposing todo_* tools - ├── scripts/sync-core.mjs # copies the canonical graph.mjs into the bundle - └── README.md # build + install + admin setup +├── mcp-extension/ # Claude Desktop extension (imports the same graph.mjs) +│ ├── manifest.json # .mcpb manifest (baked client/tenant id, user_config) +│ ├── server/index.mjs # MCP server (stdio) exposing todo_* tools +│ ├── scripts/sync-core.mjs # copies the canonical graph.mjs into the bundle +│ └── README.md # build + install + admin setup +├── plugin/ # .plugin (Cowork/Claude Code) packaging metadata +│ ├── .claude-plugin/plugin.json # plugin manifest +│ └── .mcp.json # server launch config (CLAUDE_PLUGIN_ROOT paths) +└── scripts/build-plugin.sh # stages plugin/ + esbuild-bundled server -> ms-todo.plugin ``` For Claude Desktop, build and distribute the extension — see [`mcp-extension/README.md`](mcp-extension/README.md). (Claude Desktop can't run Claude Code skills; it loads MCP servers, so the `.mcpb` is the right vehicle.) +For Claude Cowork / Claude Code, run `./scripts/build-plugin.sh` and upload the +resulting `ms-todo.plugin` in the plugin picker (that dialog only accepts +`.zip`/`.plugin` — a `.mcpb` upload is rejected). ## Prerequisites (one-time) diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json new file mode 100644 index 0000000..8a57d20 --- /dev/null +++ b/plugin/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "ms-todo", + "version": "1.0.1", + "description": "Microsoft To Do connector — read and write your task lists, tasks, and checklist items via Microsoft Graph. Sign in once with todo_login.", + "author": { + "name": "PESCO Inc." + }, + "license": "MIT", + "keywords": ["microsoft", "to do", "todo", "tasks", "graph", "productivity"] +} diff --git a/plugin/.mcp.json b/plugin/.mcp.json new file mode 100644 index 0000000..34fe4b4 --- /dev/null +++ b/plugin/.mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "ms-todo": { + "command": "node", + "args": ["${CLAUDE_PLUGIN_ROOT}/server/index.mjs"], + "env": { + "TODO_CLIENT_ID": "9af4a8a3-5290-4089-9058-a29dcce63c4f", + "TODO_TENANT_ID": "94c6c62d-8fe7-416f-8fef-7d8620b95819" + } + } + } +} diff --git a/plugin/README.md b/plugin/README.md new file mode 100644 index 0000000..fbb586a --- /dev/null +++ b/plugin/README.md @@ -0,0 +1,36 @@ +# Microsoft To Do plugin + +Connects Claude to Microsoft To Do via the Microsoft Graph API. This is the +plugin (`.plugin`) packaging of the same MCP stdio server shipped as the +`ms-todo.mcpb` Claude Desktop extension — one codebase, two install formats. + +## Install + +Upload `ms-todo.plugin` in the plugin picker. Then ask Claude to run +`todo_login` once — a browser window opens for Microsoft sign-in. The refresh +token is cached at `~/.ms-todo/token-cache.json` (shared with the +Desktop-extension install, so you only ever sign in once per machine). + +## Requirements + +- Node.js >= 18 on PATH (the server is a Node stdio process). +- A Microsoft work account in the PESCO tenant. Auth is delegated + (auth-code + PKCE); scopes: Tasks.ReadWrite, User.Read. + +## Configuration + +Environment is baked into `.mcp.json` (client + tenant id). Optional overrides: + +| Variable | Purpose | +| ------------------ | -------------------------------------------------------- | +| `TODO_TOKEN_CACHE` | Alternate token cache path | +| `TODO_TIMEZONE` | Zone for due/start/reminder dates (default UTC) | +| `TODO_READONLY` | "true" registers only the viewing tools | + +## Tools + +17 tools: `todo_login/logout/test`, list CRUD (`todo_list_lists/create_list/ +update_list/delete_list`), task CRUD (`todo_list_tasks/get_task/create_task/ +update_task/complete_task/delete_task`), checklist items (`todo_list_checklist/ +add_checklist_item/check_checklist_item/delete_checklist_item`). +See the repository README for the full list. diff --git a/scripts/build-plugin.sh b/scripts/build-plugin.sh new file mode 100755 index 0000000..ac77882 --- /dev/null +++ b/scripts/build-plugin.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Build ms-todo.plugin (the Cowork/Claude Code plugin bundle). +# +# The plugin uploader rejects zip entries containing "@" (npm scope directories +# like node_modules/@modelcontextprotocol), so we don't ship node_modules at +# all: esbuild bundles the server and its dependencies into a single ESM file. +# Output: ./ms-todo.plugin with contents at the archive root, as the plugin +# format requires. +set -euo pipefail +cd "$(dirname "$0")/.." + +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT + +cp -R plugin/.claude-plugin "$STAGE/.claude-plugin" +cp plugin/.mcp.json plugin/README.md "$STAGE/" +mkdir -p "$STAGE/server" + +# Sync the canonical core client into the extension, then install prod deps. +(cd mcp-extension && npm run sync --silent && npm ci --omit=dev --silent) +# The require() shim is for CJS dependencies inside the ESM bundle. +npx -y esbuild mcp-extension/server/index.mjs \ + --bundle --platform=node --format=esm --target=node18 \ + --banner:js="import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);" \ + --outfile="$STAGE/server/index.mjs" --log-level=warning + +rm -f ms-todo.plugin +(cd "$STAGE" && zip -qr - . -x "*.DS_Store") > ms-todo.plugin +echo "Built $(pwd)/ms-todo.plugin ($(du -h ms-todo.plugin | cut -f1 | tr -d ' '))"