Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dc821cd07 | |||
| b3918c3ee6 | |||
| 7d80e9f586 |
@@ -620,14 +620,23 @@ function startRedirectServer(expectedState, timeoutMs, listenPort = 0) {
|
|||||||
|
|
||||||
/** Best-effort open of a URL in the system browser (macOS / Windows / Linux). */
|
/** Best-effort open of a URL in the system browser (macOS / Windows / Linux). */
|
||||||
function openBrowser(url) {
|
function openBrowser(url) {
|
||||||
const cmd =
|
|
||||||
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
||||||
try {
|
try {
|
||||||
const child = spawn(cmd, process.platform === "win32" ? ["", url] : [url], {
|
let child;
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
// Never route the URL through cmd.exe (`start`): cmd splits unquoted
|
||||||
|
// commands at "&", which truncates the authorize URL after the first
|
||||||
|
// query parameter (the user then sees AADSTS900144: missing 'scope').
|
||||||
|
// rundll32 opens the default browser with the URL as a plain argument.
|
||||||
|
child = spawn("rundll32", ["url.dll,FileProtocolHandler", url], {
|
||||||
stdio: "ignore",
|
stdio: "ignore",
|
||||||
detached: true,
|
detached: true,
|
||||||
shell: process.platform === "win32",
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
child = spawn(process.platform === "darwin" ? "open" : "xdg-open", [url], {
|
||||||
|
stdio: "ignore",
|
||||||
|
detached: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
child.on("error", () => {}); // ignore; the CLI also prints the URL as a fallback
|
child.on("error", () => {}); // ignore; the CLI also prints the URL as a fallback
|
||||||
child.unref();
|
child.unref();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@
|
|||||||
|
|
||||||
# Node
|
# Node
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
# Built plugin bundle (source lives in plugin/; artifact goes on releases)
|
||||||
|
*.plugin
|
||||||
|
|||||||
18
README.md
18
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/` |
|
| **Skill** | Claude Code on your own machine | `.claude/skills/ms-todo/` |
|
||||||
| **Desktop extension (`.mcpb`)** | Claude Desktop, distributed org-wide | `mcp-extension/` |
|
| **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
|
## Repo layout
|
||||||
|
|
||||||
@@ -38,16 +39,23 @@ Two ways to use it, sharing one core Graph client (`graph.mjs`):
|
|||||||
│ └── references/
|
│ └── references/
|
||||||
│ ├── setup.md # Entra app registration walkthrough (public client + loopback redirect)
|
│ ├── setup.md # Entra app registration walkthrough (public client + loopback redirect)
|
||||||
│ └── graph-api.md # todoTask field formats + OData query reference
|
│ └── graph-api.md # todoTask field formats + OData query reference
|
||||||
└── mcp-extension/ # Claude Desktop extension (imports the same graph.mjs)
|
├── mcp-extension/ # Claude Desktop extension (imports the same graph.mjs)
|
||||||
├── manifest.json # .mcpb manifest (baked client/tenant id, user_config)
|
│ ├── manifest.json # .mcpb manifest (baked client/tenant id, user_config)
|
||||||
├── server/index.mjs # MCP server (stdio) exposing todo_* tools
|
│ ├── server/index.mjs # MCP server (stdio) exposing todo_* tools
|
||||||
├── scripts/sync-core.mjs # copies the canonical graph.mjs into the bundle
|
│ ├── scripts/sync-core.mjs # copies the canonical graph.mjs into the bundle
|
||||||
└── README.md # build + install + admin setup
|
│ └── 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
|
For Claude Desktop, build and distribute the extension — see
|
||||||
[`mcp-extension/README.md`](mcp-extension/README.md). (Claude Desktop can't run
|
[`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.)
|
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)
|
## Prerequisites (one-time)
|
||||||
|
|
||||||
|
|||||||
@@ -84,11 +84,19 @@ npx -y @anthropic-ai/mcpb validate manifest.json
|
|||||||
npx -y @anthropic-ai/mcpb info ms-todo.mcpb
|
npx -y @anthropic-ai/mcpb info ms-todo.mcpb
|
||||||
```
|
```
|
||||||
|
|
||||||
### Optional: sign the extension
|
### Signing — currently disabled
|
||||||
|
|
||||||
Unsigned extensions install with a "not verified" warning. You can self-sign so
|
Unsigned extensions install with a "not verified" warning, which is fine for
|
||||||
the warning shows your identity (full removal of the warning needs a CA-issued
|
internal distribution (just click through it).
|
||||||
cert your org trusts):
|
|
||||||
|
> ⚠️ **Do not sign with `mcpb sign` right now.** As of `@anthropic-ai/mcpb` 2.1.2
|
||||||
|
> (and 2.0.x/2.1.x), `sign` corrupts the bundle: it writes an invalid ZIP EOCD
|
||||||
|
> comment length, so Claude Desktop fails to open it ("Invalid comment length …
|
||||||
|
> extra bytes at the end of the file") and `mcpb verify` reports it as unsigned.
|
||||||
|
> Ship the **unsigned** bundle until this is fixed upstream.
|
||||||
|
|
||||||
|
When signing works again (or with a CA-trusted cert deployed to your fleet), the
|
||||||
|
command is:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx -y @anthropic-ai/mcpb sign ms-todo.mcpb --cert cert.pem --key key.pem
|
npx -y @anthropic-ai/mcpb sign ms-todo.mcpb --cert cert.pem --key key.pem
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": "0.3",
|
"manifest_version": "0.3",
|
||||||
"name": "ms-todo",
|
"name": "ms-todo",
|
||||||
"display_name": "Microsoft To Do",
|
"display_name": "Microsoft To Do",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "Read and write your Microsoft To Do tasks, lists, and subtasks from Claude.",
|
"description": "Read and write your Microsoft To Do tasks, lists, and subtasks from Claude.",
|
||||||
"long_description": "Connects Claude to Microsoft To Do via the Microsoft Graph API. You sign in once as yourself (a browser window opens); the connection then reads and writes your task lists, tasks, and checklist items. Each user's sign-in is private to their own machine.",
|
"long_description": "Connects Claude to Microsoft To Do via the Microsoft Graph API. You sign in once as yourself (a browser window opens); the connection then reads and writes your task lists, tasks, and checklist items. Each user's sign-in is private to their own machine.",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
4
mcp-extension/package-lock.json
generated
4
mcp-extension/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ms-todo-mcp-extension",
|
"name": "ms-todo-mcp-extension",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ms-todo-mcp-extension",
|
"name": "ms-todo-mcp-extension",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ms-todo-mcp-extension",
|
"name": "ms-todo-mcp-extension",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Microsoft To Do MCP server, packaged as a Claude Desktop .mcpb extension",
|
"description": "Microsoft To Do MCP server, packaged as a Claude Desktop .mcpb extension",
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ const TASK_WRITE_FIELDS = {
|
|||||||
|
|
||||||
// ---- server ---------------------------------------------------------------
|
// ---- server ---------------------------------------------------------------
|
||||||
|
|
||||||
const server = new McpServer({ name: "ms-todo", version: "1.0.0" });
|
const server = new McpServer({ name: "ms-todo", version: "1.0.1" });
|
||||||
|
|
||||||
// --- auth ---
|
// --- auth ---
|
||||||
server.registerTool(
|
server.registerTool(
|
||||||
|
|||||||
10
plugin/.claude-plugin/plugin.json
Normal file
10
plugin/.claude-plugin/plugin.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
12
plugin/.mcp.json
Normal file
12
plugin/.mcp.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
plugin/README.md
Normal file
36
plugin/README.md
Normal file
@@ -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.
|
||||||
29
scripts/build-plugin.sh
Executable file
29
scripts/build-plugin.sh
Executable file
@@ -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 ' '))"
|
||||||
Reference in New Issue
Block a user