使用说明
work-diary-mcp
A Model Context Protocol (MCP) server for managing a weekly work diary. Built with FastMCP in Python.
Interact with your diary conversationally via the Claude CLI or Zed. The server writes Markdown files to disk that can be copied directly into Microsoft Loop (or any Markdown-compatible tool).
For a running summary of changes, see CHANGELOG.md.
Index
- Quick Start
- Copilot CLI Skill (when the MCP server can't be used directly)
- Configuration
- Tools
- Usage
- Features
- Supported Status Values
- Supported Role Values
- Jira Auto-Linking
- Data Format
- Data Location
- Carry-Forward Behaviour
- Project Structure
- Testing
Quick Start
Prerequisites
- Python 3.11+
uv— install it using the instructions for your platform in the official docs: https://docs.astral.sh/uv/getting-started/installation/
This project is intended to work on both macOS and Windows. The examples below use Unix-style paths where needed; on Windows, use the equivalent local paths and uv executable location for your environment.
Install dependencies
cd ~/work-diary-mcp/python
uv sync
Register with Zed
Add to your Zed settings.json (cmd+,). This example shows a macOS-style path for uv; on Windows, use the path to uv.exe or another command that resolves to uv in your environment:
{
"context_servers": {
"work-diary": {
"command": "/path/to/uv",
"args": [
"--directory",
"/Users/yourname/work-diary-mcp/python",
"run",
"work-diary-mcp"
],
"env": {}
}
}
}
Register with Claude CLI
Use whatever uv executable path is correct for your platform:
claude mcp add work-diary /path/to/uv \
--directory $HOME/work-diary-mcp/python run work-diary-mcp
Copilot CLI Skill (when the MCP server can't be used directly)
Some environments and policies don't allow connecting to this repo's MCP server directly (e.g. GitHub Copilot CLI sessions that restrict arbitrary MCP servers). For those cases, this repo also ships an Agent Skill at skill/ that gives an agent the same functionality via a plain CLI script instead of the MCP protocol.
The skill's cli.py imports the work_diary_mcp library directly (config.py, diary.py, jira.py, markdown.py, roles.py, statuses.py — everything except server.py, which only exists to speak MCP). It reads and writes the exact same diary data files and respects the exact same configuration (env vars and/or the settings file), so it's fully interchangeable with the MCP tools — nothing about your diary data or settings needs to change to use one vs. the other. No third-party dependencies are required (fastmcp/mcp are only needed by server.py); plain Python 3.11+ is enough.
Install:
mkdir -p ~/.agents/skills/work-diary
cp skill/SKILL.md skill/cli.py ~/.agents/skills/work-diary/
chmod +x ~/.agents/skills/work-diary/cli.py
The skill expects this repository to be checked out at ~/work-diary-mcp (it adds ~/work-diary-mcp/python to sys.path to import the library). If you cloned it elsewhere, edit the _LIB_DIR path near the top of cli.py accordingly.
Once installed, an agent that supports Copilot CLI-style skills (per its SKILL.md frontmatter description) will invoke it automatically for requests like "update Project Phoenix to at risk" or "show me my work diary" — you don't need to run the CLI yourself. See skill/SKILL.md for the full command reference, or run:
python3 ~/.agents/skills/work-diary/cli.py --help
python3 ~/.agents/skills/work-diary/cli.py <command> --help
Configuration
By default, diary files are written to data/ inside the repository. You can point the server at any directory and configure Jira auto-linking using either environment variables or the settings file.
Environment variables
Data directory
Set WORK_DIARY_DATA_DIR to an absolute (or ~-prefixed) path:
export WORK_DIARY_DATA_DIR=~/Documents/work-diary
To make it permanent, add the export to your shell profile and pass it through to the server in your MCP client config.
Zed — add an env entry:
{
"context_servers": {
"work-diary": {
"command": "/path/to/uv",
"args": ["--directory", "/Users/yourname/work-diary-mcp/python", "run", "work-diary-mcp"],
"env": {
"WORK_DIARY_DATA_DIR": "/Users/yourname/Documents/work-diary"
}
}
}
}
Claude CLI — add the env var when registering:
claude mcp add work-diary /path/to/uv \
--directory $HOME/work-diary-mcp/python run work-diary-mcp \
--env WORK_DIARY_DATA_DIR=$HOME/Documents/work-diary
On Windows, use Windows-style paths for both the uv executable and WORK_DIARY_DATA_DIR.
Jira auto-linking
You can also configure Jira linkification via environment variables:
WORK_DIARY_JIRA_BASE_URLWORK_DIARY_JIRA_PREFIXES
Example:
export WORK_DIARY_JIRA_BASE_URL=https://jira.example.com/browse
export WORK_DIARY_JIRA_PREFIXES=PROJ,INFRA,ENG
WORK_DIARY_JIRA_BASE_URL must be a non-empty URL and must include a scheme such as https://.
Multi-process safety
By default, work-diary-mcp uses in-process threading.Locks to coordinate writes. This is safe for the common case where a single MCP server process is the only writer to the data directory, and avoids per-call flock / msvcrt.locking overhead.
If you run multiple processes that write to the same data directory, set WORK_DIARY_FILE_LOCKS=1 to additionally acquire filesystem locks for week and reminder writes:
export WORK_DIARY_FILE_LOCKS=1
Cloud sync
Set WORK_DIARY_SYNC_PATH to the path of a cloud-synced folder (Box Drive, OneDrive, iCloud Drive, Dropbox, etc.):
export WORK_DIARY_SYNC_PATH="~/Box/Work Diary"
When configured, the push_diary_to_sync_folder tool copies the current (or any specified) week's Markdown diary file into this folder. The folder is created automatically if it does not already exist. The sync client then handles the upload in the background.
If WORK_DIARY_SYNC_PATH is not set, the tool returns a helpful error message rather than failing silently.
To sync automatically after every diary write without calling the tool explicitly, also set:
export WORK_DIARY_AUTO_SYNC=1
When WORK_DIARY_AUTO_SYNC is enabled, every write operation (project updates, notes, reminders, etc.) silently copies the affected week's diary to the sync folder. Failures during auto-sync are suppressed so they never interfere with the primary write.
Settings file
Create a settings file with any combination of the following keys:
- macOS / Linux:
~/.config/work-diary/settings.toml - Windows:
%APPDATA%\work-diary\settings.toml
data_dir = "~/Documents/work-diary"
jira_base_url = "https://jira.example.com/browse"
jira_prefixes = ["PROJ", "INFRA", "ENG", "OPS", "SEC", "DATA"]
sync_path = "~/Box/Work Diary"
auto_sync = true
Settings file keys:
data_dir— where diary files and reminder storage should livejira_base_url— the base browse URL for your Jira instancejira_prefixes— the list of Jira project key prefixes that should be linkifiedsync_path— path to a cloud-synced folder forpush_diary_to_sync_folderauto_sync— boolean (true/false); whentrue, every diary write automatically copies the affected week to the sync folder
jira_base_url must be a non-empty URL and must include a scheme such as https://.
The configured path is expanded automatically and the directory is created on first use.
Resolution order
Data directory
WORK_DIARY_DATA_DIRenvironment variabledata_dirin the platform-native settings file- Built-in default:
<repo root>/data
Jira auto-linking
WORK_DIARY_JIRA_BASE_URL/WORK_DIARY_JIRA_PREFIXESenvironment variablesjira_base_url/jira_prefixesin the platform-native settings file- Built-in defaults:
https://jira.example.com/browse["PROJ", "INFRA", "ENG", "OPS", "SEC", "DATA"]
Cloud sync
WORK_DIARY_SYNC_PATHenvironment variablesync_pathin the platform-native settings file- Not set —
push_diary_to_sync_folderreturns an error when unconfigured
Auto-sync on save
WORK_DIARY_AUTO_SYNCenvironment variable (truthy:1,true,yes,on)auto_syncin the platform-native settings file (boolean)- Not set — defaults to
false(opt-in)
Tools
| Tool | Description |
|---|---|
update_project_status |
Update or add a project's status, with an optional inline note and an optional role. Pass append_note: true to append to an existing note instead of replacing it. For role, pass null (or omit it) to leave any existing role unchanged, an empty string to clear it, or any accepted role value to set it. Supports an optional date to target a specific week. Existing projects can also be referenced by row number, for example project 2. Ambiguous row references raise an error, project 0 is always invalid, and out-of-range positive references are treated as literal project names rather than raising. |
bulk_update_projects |
Update multiple projects in a single operation — more efficient than calling update_project_status repeatedly. Each entry may optionally include a role, with the same null/""/value semantics as update_project_status. Supports an optional date to target a specific week. Existing projects can also be referenced by row number, for example project 2. Ambiguous row references raise an error, project 0 is always invalid, and out-of-range positive references are treated as literal project names rather than raising. |
set_project_role |
Set or clear the role for an existing project. Accepts canonical role names (Sponsor, Guide, Catcher, Advisor, Catalyst, Participant), emoji shortcodes (:rocket:, :world_map:, :fire_extinguisher:, :compass:, :test_tube:, :raising_hand:), bare emoji, or already-formatted display values. Pass an empty string to clear the role. Supports an optional date to target a specific week. |
rename_project |
Rename a project, preserving its status and note. Supports an optional date to target a specific week. Existing projects can also be referenced by row number, for example project 2. Ambiguous row references raise an error, project 0 is always invalid, and row references must be in range for rename operations. |
add_note |
Append a note to the general notes section. Supports an optional date to target a specific week. |
edit_note |
Replace the content of an existing note by its index number. Supports an optional date to target a specific week. |
delete_note |
Delete a note by its index number. Supports an optional date to target a specific week. |
add_reminder |
Add a reminder for the current or a future week without creating a future diary page. Supports an optional due_date and date. |
list_reminders |
List reminders for the target week, including checkbox state and any due date. Supports an optional date to target a specific week. |
complete_reminder |
Mark a reminder as completed for the target week. Supports an optional date. |
reopen_reminder |
Mark a completed reminder as incomplete for the target week. Supports an optional date. |
get_diary |
Retrieve the full Markdown diary for the current or any past week. |
list_projects |
List all projects and their statuses for the current or any past week. |
list_weeks |
List all weeks that have diary entries, sorted oldest to newest. |
remove_project |
Remove a project and its note from the target week. Supports an optional date to target a specific week. Existing projects can also be referenced by row number, for example project 2. Ambiguous row references raise an error, project 0 is always invalid, and row references must be in range for removal. |
clear_project_note |
Clear the inline note for a project, leaving its status intact. Supports an optional date to target a specific week. Existing projects can also be referenced by row number, for example project 2. Ambiguous row references raise an error, project 0 is always invalid, and row references must be in range for note clearing. |
push_diary_to_sync_folder |
Copy the current (or any specified) week's Markdown diary file to the configured cloud-sync folder (Box Drive, OneDrive, iCloud Drive, Dropbox, etc.). The destination folder is created automatically if it does not exist; repeated calls overwrite the previously copied file. Requires WORK_DIARY_SYNC_PATH or sync_path to be configured. Supports an optional date to target a specific week. |
Usage
Just talk naturally — your MCP client will call the right tool automatically:
Update Project Phoenix to On Track
Platform Infra is now blocked — waiting on the infra team
Add a note: had a productive all-hands today, big Q3 roadmap updates
Show me this week's diary
What projects am I tracking this week?
Show me my diary from last week
Show me my diary from 2 weeks ago
What weeks do I have diary entries for?
Remove Project Phoenix from this week
Clear the note on Platform Infra
Update [Project Phoenix](https://jira.example.com/PROJ-123) to At Risk with a note: blocked on dependency
Rename Project Phoenix to Phoenix Rewrite
Update all my projects: Phoenix Rewrite is On Track, Platform Infra is Blocked, Auth Service is Done
Append a note to Platform Infra: dependency resolved, unblocked as of Friday
Edit note 2: corrected — the all-hands covered Q3 and Q4 roadmap
Delete note 3
PROJ-1234 is now On Track
Add a note: opened PROJ-1234 and INFRA-5678 to track the rollout
Add a note to last week's diary: wrapped up the migration checklist
Edit note 2 in last week's diary: corrected the rollout status
Delete note 1 from 2 weeks ago
Update Stacks on TFE to Blocked in last week's diary with a note: waiting on dependency
Update project 2 to Done
Clear the note on project 3
Rename project 1 to Phoenix Rewrite
Add a reminder for next week: follow up with the perf team
Add a reminder for next week with due date Friday: confirm rollout checklist
Add a reminder in 4 weeks: prepare rollout notes
List reminders for next week
Complete reminder 1 for next week
Reopen reminder 1 for next week
Features
- Project status table — track projects with a status, an optional inline note, and an optional engagement role per project
- Engagement roles — tag each project with a Principal Engineer–style engagement role (Sponsor, Guide, Catcher, Advisor, Catalyst, Participant) rendered with emoji in a dedicated
Rolecolumn. Roles are carried forward week-over-week alongside the project itself. - General notes — append notes throughout the week
- Carry-forward — on the first interaction of each new week, non-completed projects and uncompleted reminders are automatically carried forward from the prior week, while project notes reset for the new week
- Jira auto-linking — bare Jira ticket references (for supported prefixes such as
PROJ-1234orINFRA-5678) are automatically converted to Markdown links - Markdown links — use standard Markdown link syntax anywhere:
[text](url) - Relative date support — target weeks with ISO dates and natural language such as
"last week","next week","2 weeks ago","2 weeks from now", or"in 4 weeks" - Previous-week write support — add notes and update projects in a past week by specifying a date such as
"last week"or"2026-03-02" - Project row references — refer to existing projects by table row using phrases like
"project 2"when updating, bulk updating, renaming, removing, or clearing project notes. If a reference like"project 2"could also mean a literal project namedProject 2, the server raises an ambiguity error instead of guessing.project 0is always invalid, even if a literal project with that name exists. Out-of-range positive references are treated as literal project names. - Reminders — store reminders for the current or future weeks without creating future diary pages, render them in a dedicated section, and mark them complete with checkboxes
- Configurable data directory — store diary files in the repo default location or point the server at a custom directory
Supported Status Values
Well-known statuses are automatically formatted with emoji. Any other string is stored as-is.
| Input | Rendered |
|---|---|
| On Track | 🟢 On Track |
| At Risk | 🟡 At Risk |
| Blocked | 🔴 Blocked |
| Done | ✅ Done |
| Complete | ✅ Complete |
| Completed | ✅ Completed |
| In Progress | 🔵 In Progress |
| Not Started | ⚪ Not Started |
| Cancelled | ⛔ Cancelled |
| Canceled | ⛔ Canceled |
| Paused | ⏸️ Paused |
| Shipped | 🚀 Shipped |
| GA | 🚀 GA |
| In Planning | 💡 In Planning |
Terminal statuses are used to decide what should not be carried forward into a new week.
Supported Role Values
Project roles are inspired by the Principal Engineer role framework. Each project may optionally have a role assigned alongside its status; the role is rendered in a dedicated Role column in the diary's project table.
| Input | Rendered |
|---|---|
Sponsor / :rocket: / 🚀 |
🚀 Sponsor |
Guide / :world_map: / 🗺️ |
🗺️ Guide |
Catcher / :fire_extinguisher: / 🧯 |
🧯 Catcher |
Advisor / :compass: / 🧭 |
🧭 Advisor |
Catalyst / :test_tube: / 🧪 |
🧪 Catalyst |
Participant / :raising_hand: / 🙋 |
🙋 Participant |
Roles are case-insensitive on input and accept canonical names, emoji shortcodes, bare emoji, or already-formatted display values. Pass an empty string to clear a previously-set role. Unknown values are stored as-is so callers can use arbitrary role labels if they want to.
Jira Auto-Linking
Bare Jira ticket references are automatically converted to Markdown links whenever text is saved to the diary — in project names, inline notes, and general notes. Already-linked references are never double-linked.
The default Jira configuration is:
- Base URL:
https://jira.example.com/browse - Prefixes:
PROJ,INFRA,ENG,OPS,SEC,DATA
You can override these through either:
- environment variables:
WORK_DIARY_JIRA_BASE_URLWORK_DIARY_JIRA_PREFIXES(comma-separated, for examplePROJ,INFRA,ENG)
- the settings file:
jira_base_urljira_prefixes
Examples:
| You type | Stored as |
|---|---|
PROJ-1234 |
[PROJ-1234](https://jira.example.com/browse/PROJ-1234) |
blocked by INFRA-5678 |
blocked by [INFRA-5678](https://jira.example.com/browse/INFRA-5678) |
[PROJ-1234](https://jira.example.com/browse/PROJ-1234) |
unchanged |
Ticket keys are uppercased in generated links. References that do not match a supported prefix are left as-is.
WORK_DIARY_JIRA_BASE_URL / jira_base_url must be a non-empty URL and must include a scheme such as https://.
Data Format
Each week's diary is stored as two files in the configured data directory, keyed by the Monday of that week.
YYYY-MM-DD.json — source of truth:
{
"weekKey": "2026-03-02",
"projects": {
"[Project Phoenix](https://jira.example.com/browse/PROJ-123)": "On Track"
},
"projectNotes": {
"[Project Phoenix](https://jira.example.com/browse/PROJ-123)": "A few bugs found; [PROJ-124](https://jira.example.com/browse/PROJ-124) opened to track."
},
"projectRoles": {
"[Project Phoenix](https://jira.example.com/browse/PROJ-123)": "🚀 Sponsor"
},
"notes": [
{
"content": "Kickoff meeting with TPM for Platform Infra."
}
]
}
YYYY-MM-DD.md — rendered output:
# Work Diary — Week of Mar 2, 2026
## Reminders for this week
- [ ] Due Date: 2026-03-06 Follow up with the perf team
- [x] Confirm rollout checklist
## Project Status
| Project | Role | Status | Notes |
|---------|------|--------|-------|
| [Project Phoenix](https://jira.example.com/browse/PROJ-123) | 🚀 Sponsor | 🟢 On Track | A few bugs found; [PROJ-124](https://jira.example.com/browse/PROJ-124) opened to track. |
## Notes
- **[1]** Kickoff meeting with TPM for Platform Infra.
Data Location
Diary files are stored in the configured data directory described in Configuration.
Each week is represented by two files keyed on the Monday of that week:
YYYY-MM-DD.json— source of truth (raw state)YYYY-MM-DD.md— rendered Markdown, ready to copy into Microsoft Loop
Reminders are stored separately in:
reminders.json— source of truth for reminders across current and future weeks
Write operations default to the current week, but can also target a specific week using a relative date or ISO date, including "last week", "next week", "N weeks ago", "N weeks from now", "in N weeks", or values such as "2026-03-02". If a past week does not yet exist, the server creates an empty diary page for that week instead of carrying forward state. Future reminders do not create future diary pages.
Carry-Forward Behaviour
On the first interaction of each new week, a fresh diary page is created automatically. Non-terminal projects are copied forward from the most recent prior week so you never start from a blank slate.
Carry-forward behavior is currently:
- Project statuses are carried forward
- Project roles are carried forward
- Project inline notes are not carried forward
- General notes are not carried forward
- Completed or cancelled projects are not carried forward
- Uncompleted reminders are carried forward from the prior week (due dates are dropped since they are week-specific)
- Reminders for that week are rendered in the new diary page without creating future diary pages ahead of time
- When reminders change for an existing week, that week's persisted Markdown is regenerated
Projects with a terminal status such as Done, Complete, Completed, Cancelled, Canceled, Shipped, or GA stay in the week they were finished and will not clutter the new week's diary.
Project Structure
work-diary-mcp/
├── .gitignore
├── README.md
├── CHANGELOG.md
├── data/ # Diary files (gitignored) — default location
│ ├── YYYY-MM-DD.json # Raw state for each week
│ └── YYYY-MM-DD.md # Rendered Markdown, ready to copy into Loop
├── skill/ # Agent Skill: same functionality without an MCP client
│ ├── SKILL.md # Skill description and command reference
│ └── cli.py # CLI wrapper around work_diary_mcp (no MCP protocol)
└── python/
├── README.md # Python-specific setup details
├── pyproject.toml
├── uv.lock
├── tests/
│ ├── __init__.py
│ └── test_diary.py
└── work_diary_mcp/
├── __init__.py
├── config.py # Data directory resolution and Jira configuration
├── diary.py # State management, reminders, week helpers, persistence
├── jira.py # Jira ticket auto-linking
├── markdown.py # Markdown renderer
├── roles.py # Engagement role definitions and normalization
├── server.py # FastMCP server and tool definitions
└── statuses.py # Status definitions (emoji map, completion set)
Testing
From python/:
uv run --group dev pytest -v
