@torsday/omnifocus-mcp 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +185 -5
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@ All notable changes to `@torsday/omnifocus-mcp` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). See [ADR-0011](./docs/adr/0011-versioning-and-stability.md) for the explicit definition of breaking vs additive changes in this project.
|
|
6
6
|
|
|
7
|
+
## [1.0.2](https://github.com/torsday/omnifocus-mcp/compare/v1.0.1...v1.0.2) (2026-04-26)
|
|
8
|
+
|
|
9
|
+
**Summary** — One contributor-facing fix and one architectural-decision spike note; otherwise an internal-infrastructure release validating the post-v1.0.1 release flow under the new release-please + OIDC + PAT identity. **Bytes on the wire are identical to v1.0.1**: the published bundle, tool surface, tool descriptions, and runtime behaviour are unchanged. Consumers running `npx -y @torsday/omnifocus-mcp` see no difference. Internal-only commits (CI hygiene, release-please workflow tuning, comment-block cleanups) are intentionally hidden from this CHANGELOG by `release-please-config.json` and are not enumerated below.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`scripts/seed-integration-db.js` runs under ESM** — `package.json` declares `"type": "module"`, so every `.js` file is interpreted as ESM by Node. The seed script was the lone holdout still using CommonJS `require()` and threw `ReferenceError: require is not defined in ES module scope` at the first line of execution. One-line conversion: `const { spawnSync } = require("node:child_process")` → `import { spawnSync } from "node:child_process"`. Affects only contributors running `pnpm test:integration` locally or the `integration.yml` workflow on `mac-local`; the published package's runtime is unchanged. ([#448](https://github.com/torsday/omnifocus-mcp/issues/448))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- **Tier-3 CHANGELOG-action spike resolved — don't automate** — `docs/spikes/2026-04-tier-3-changelog-action.md` records the decision: stick with the existing manual `/release-notes` polish flow rather than building a GitHub Action that auto-polishes the Release PR via the Anthropic API. Three structural reasons in the note: ~10 min/release × ~6 releases/year is roughly 1 hour/year of polish work — automation that adds operational surface (new repo secret, rotation burden, API outage as a release-block failure mode) to save 1 hour/year is the wrong trade; the skip-the-polish decision (chore-only releases need no polish) requires maintainer judgment that an action can't make; and with a Closed contributing stance there are no future maintainers to onboard. Empirical anchor: v1.0.1's polish — the first release under release-please — showed substantial quality delta from auto-draft to polished prose at acceptable manual cost. ([#432](https://github.com/torsday/omnifocus-mcp/issues/432))
|
|
19
|
+
|
|
20
|
+
## [1.0.1](https://github.com/torsday/omnifocus-mcp/compare/v1.0.0...v1.0.1) (2026-04-26)
|
|
21
|
+
|
|
22
|
+
**Summary** — Documentation polish pass. The README, SPEC, DESIGN, and `docs/project-views.md` are now aligned with the post-v1.0.0 reality of the project (public, shipped, npm-published) rather than the pre-1.0 framing they carried at v1.0.0's tag. Quick Start is restructured so every supported MCP client — not only Claude Desktop — gets equal-footing setup instructions; a new agent-readable Security & trust section surfaces the existing threat-model guarantees with file-level enforcement references. No behavioural or API changes: bytes on the wire, tool surface, and tool descriptions are identical to v1.0.0.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **PR-title lint allows periods inside the subject** — the bash port of [`amannn/action-semantic-pull-request`](https://github.com/amannn/action-semantic-pull-request)'s `subjectPattern` in `pr-title.yml` was stricter than the original. `[^.]*` in the middle banned every period, so release-please's auto-PRs with version numbers in the title (e.g. `chore(main): release 1.0.1`) failed lint. The middle is now `.*`; only the trailing character is constrained to non-period and non-space. Behaviour matches the original action. ([6170320](https://github.com/torsday/omnifocus-mcp/commit/6170320ea72bfb0e71444d1685e29a02d47eec3d))
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
- **README — agent-agnostic Quick Start with per-client matrix** — Quick Start step 2 leads with the universal `command + args + env` shape, then surfaces a per-client matrix as expandable `<details>` blocks (alphabetical: Claude Code, Claude Desktop, Cline, Codex, Cursor, Windsurf, Generic). Each block names the file path and serialization (JSON vs TOML). Previous flow led with Claude Desktop and treated other clients as a "any MCP client uses the same shape" footnote — the new flow makes finding your client a one-line scan of the `<summary>` headers. New `docs/clients/codex.md` mirrors the structure of the existing `claude-code.md` / `claude-desktop.md` / `generic-stdio.md` guides (prerequisites, install, config snippet, verification, Automation permission, env vars, troubleshooting). The setup-guide table at the bottom of the README now includes Codex and is alphabetised. ([#433](https://github.com/torsday/omnifocus-mcp/issues/433))
|
|
31
|
+
|
|
32
|
+
- **README — Security & trust section** — new top-level section between Quick Start and Example interactions (~70 lines, well inside the 60–100-line target). Every guarantee links to enforcement code or file evidence: the no-network-import lint rule (`src/linting/customRules.ts` Rule 4), the `installStdoutGuard()` contract test (`src/server/stdoutGuard.test.ts`), the prod dependency list (`package.json` — six packages, no analytics SDK, no `postinstall`/`preinstall` lifecycle scripts), `redactConfig` (`src/config/env.ts`), and `assertAttachmentPath` (`src/attachment/assertAttachmentPath.ts`). Three "verify it yourself" recipes — source audit, Sigstore attestation via `npm view dist.attestations`, and tarball file-count via `npm view dist.tarball | tar -tzvf -`; the recipe's expected file count was verified against the actually-published v1.0.0 artifact (5 files: `LICENSE`, `dist/index.js`, `package.json`, `CHANGELOG.md`, `README.md`). The opt-in `OMNIFOCUS_ALLOW_RAW_SCRIPT=1` escape hatch is enumerated by name with audit-log behaviour and ADR-0004 cited. ([#422](https://github.com/torsday/omnifocus-mcp/issues/422))
|
|
33
|
+
|
|
34
|
+
- **README + SPEC + DESIGN — refreshed post-1.0 framing** — README "Status and roadmap" lead replaced "v1.0.0 is in preparation for npm release" with shipped-state framing that links the [npm package](https://www.npmjs.com/package/@torsday/omnifocus-mcp), the [Project board](https://github.com/users/torsday/projects/4), and the `[Unreleased]` CHANGELOG section. SPEC.md front-matter changed from `Status: Draft — assumptions flagged for review` to `Status: v1.0 — locked` with the explicit note that future scope changes route through ADRs, not edits to that file. DESIGN.md front-matter from `Status: v1 Draft — design-complete, implementation-ready` to `Status: v1.0 — implemented and shipped 2026-04-25`. `docs/project-views.md` status enum updated from the old six-state list (`Ready · Todo · In Progress · In Review · Blocked · Done`) to the canonical current six (`Backlog · Up Next · In Progress · In Review · On Hold · Done`); the frozen pre-1.0 issue count was removed. ([#425](https://github.com/torsday/omnifocus-mcp/issues/425))
|
|
35
|
+
|
|
36
|
+
- **README — CI status badge** — adds a status badge for the `main` branch's CI workflow at the top of the README. A glance at the repo's GitHub page now shows whether `main`'s last run is green without clicking through to the Actions tab. ([#438](https://github.com/torsday/omnifocus-mcp/pull/438))
|
|
37
|
+
|
|
38
|
+
- **`docs/runner-setup-macos-omnifocus.md` — new self-hosted runner setup guide** — step-by-step setup for the `macos-omnifocus` runner that hosts the integration test workflow, including how to launch OmniFocus on user-session start (LaunchAgent) so tag-pushed integration runs don't fail with the "OmniFocus is not running" preflight error. Pairs with the integration-fixture seed step landed in #426. ([#443](https://github.com/torsday/omnifocus-mcp/pull/443))
|
|
39
|
+
|
|
7
40
|
## [Unreleased]
|
|
8
41
|
|
|
9
42
|
_Nothing yet — see [GitHub Issues](https://github.com/torsday/omnifocus-mcp/issues) and [Project #4](https://github.com/users/torsday/projects/4) for the live backlog and status._
|
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# omnifocus-mcp
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@torsday/omnifocus-mcp)
|
|
4
|
+
[](https://github.com/torsday/omnifocus-mcp/actions/workflows/ci.yml?query=branch%3Amain)
|
|
4
5
|
[](./LICENSE)
|
|
5
6
|
[](./package.json)
|
|
6
7
|
[](https://www.apple.com/macos/)
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
|
|
14
15
|
- [Why this exists](#why-this-exists)
|
|
15
16
|
- [Quick start](#quick-start)
|
|
17
|
+
- [Security & trust](#security--trust)
|
|
16
18
|
- [Example interactions](#example-interactions)
|
|
17
19
|
- [Prompts](#prompts)
|
|
18
20
|
- [If you are an AI agent](#if-you-are-an-ai-agent)
|
|
@@ -53,7 +55,29 @@ The server is built to a single-user local-first standard: no network surface, n
|
|
|
53
55
|
npm install -g @torsday/omnifocus-mcp
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
2. **
|
|
58
|
+
2. **Configure your MCP client.** Every client uses the same `command` + `args` + `env` shape — only the file path and serialization (JSON vs TOML) differ. The universal shape:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
command: omnifocus-mcp
|
|
62
|
+
args: (none)
|
|
63
|
+
env: OMNIFOCUS_LOG_LEVEL=info # optional; "debug" is verbose
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Find your client below. Order is alphabetical; no client is recommended over another.
|
|
67
|
+
|
|
68
|
+
<details>
|
|
69
|
+
<summary><strong>Claude Code</strong> (CLI; no file edit)</summary>
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
claude mcp add omnifocus omnifocus-mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Detailed guide: [`docs/clients/claude-code.md`](./docs/clients/claude-code.md)
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
<details>
|
|
79
|
+
<summary><strong>Claude Desktop</strong> — <code>~/Library/Application Support/Claude/claude_desktop_config.json</code> (JSON)</summary>
|
|
80
|
+
|
|
57
81
|
```json
|
|
58
82
|
{
|
|
59
83
|
"mcpServers": {
|
|
@@ -65,7 +89,92 @@ The server is built to a single-user local-first standard: no network surface, n
|
|
|
65
89
|
}
|
|
66
90
|
}
|
|
67
91
|
```
|
|
68
|
-
|
|
92
|
+
|
|
93
|
+
Detailed guide: [`docs/clients/claude-desktop.md`](./docs/clients/claude-desktop.md)
|
|
94
|
+
</details>
|
|
95
|
+
|
|
96
|
+
<details>
|
|
97
|
+
<summary><strong>Cline</strong> (VS Code extension) — extension settings (JSON)</summary>
|
|
98
|
+
|
|
99
|
+
In VS Code, open the Cline extension's MCP settings panel and add:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"omnifocus": {
|
|
105
|
+
"command": "omnifocus-mcp",
|
|
106
|
+
"args": [],
|
|
107
|
+
"env": { "OMNIFOCUS_LOG_LEVEL": "info" }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Cline's settings UI may surface the same fields as labelled inputs rather than raw JSON; the values are identical. Verify the panel location against the current Cline docs.
|
|
114
|
+
</details>
|
|
115
|
+
|
|
116
|
+
<details>
|
|
117
|
+
<summary><strong>OpenAI Codex CLI</strong> — <code>~/.codex/config.toml</code> (TOML)</summary>
|
|
118
|
+
|
|
119
|
+
```toml
|
|
120
|
+
[mcp_servers.omnifocus]
|
|
121
|
+
command = "omnifocus-mcp"
|
|
122
|
+
args = []
|
|
123
|
+
env = { OMNIFOCUS_LOG_LEVEL = "info" }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Detailed guide: [`docs/clients/codex.md`](./docs/clients/codex.md)
|
|
127
|
+
</details>
|
|
128
|
+
|
|
129
|
+
<details>
|
|
130
|
+
<summary><strong>Cursor</strong> — <code>~/.cursor/mcp.json</code> (JSON)</summary>
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"mcpServers": {
|
|
135
|
+
"omnifocus": {
|
|
136
|
+
"command": "omnifocus-mcp",
|
|
137
|
+
"args": [],
|
|
138
|
+
"env": { "OMNIFOCUS_LOG_LEVEL": "info" }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Verify the path against the current Cursor docs — recent versions also accept project-scoped `.cursor/mcp.json` at the repo root.
|
|
145
|
+
</details>
|
|
146
|
+
|
|
147
|
+
<details>
|
|
148
|
+
<summary><strong>Windsurf</strong> — <code>~/.codeium/windsurf/mcp_config.json</code> (JSON)</summary>
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"mcpServers": {
|
|
153
|
+
"omnifocus": {
|
|
154
|
+
"command": "omnifocus-mcp",
|
|
155
|
+
"args": [],
|
|
156
|
+
"env": { "OMNIFOCUS_LOG_LEVEL": "info" }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Verify the path against the current Windsurf docs — Codeium occasionally relocates config under `~/.codeium/`.
|
|
163
|
+
</details>
|
|
164
|
+
|
|
165
|
+
<details>
|
|
166
|
+
<summary><strong>Generic stdio client</strong> (anything else that speaks MCP/stdio)</summary>
|
|
167
|
+
|
|
168
|
+
Use your client's MCP config form. The shape is:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
command: "omnifocus-mcp"
|
|
172
|
+
args: []
|
|
173
|
+
env: { "OMNIFOCUS_LOG_LEVEL": "info" }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Detailed guide: [`docs/clients/generic-stdio.md`](./docs/clients/generic-stdio.md)
|
|
177
|
+
</details>
|
|
69
178
|
|
|
70
179
|
3. **Grant macOS Automation permission** on first use — the app running the MCP server will prompt to control OmniFocus; click **OK**. If denied by mistake: **System Settings → Privacy & Security → Automation → [app] → OmniFocus** ✓
|
|
71
180
|
|
|
@@ -75,6 +184,76 @@ Detailed per-client guides: [`docs/clients/`](./docs/clients/)
|
|
|
75
184
|
|
|
76
185
|
---
|
|
77
186
|
|
|
187
|
+
## Security & trust
|
|
188
|
+
|
|
189
|
+
`omnifocus-mcp` is a **local-only** Node.js process that drives a **local** OmniFocus app via Apple's `osascript` runtime. Installing this package does not introduce cloud connectivity, telemetry, or network egress that wasn't already on your machine.
|
|
190
|
+
|
|
191
|
+
### Data flow
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
OmniFocus DB (local) ─→ JXA / OmniJS via osascript (local) ─→ MCP server (local stdio)
|
|
195
|
+
│
|
|
196
|
+
↓
|
|
197
|
+
MCP client (local)
|
|
198
|
+
│
|
|
199
|
+
↓
|
|
200
|
+
LLM provider (only if your client uses one)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The LLM hop at the bottom is **your client's** choice, not this package's. If you run a local-only client (or a client configured to use a local model), nothing in this stack reaches the network.
|
|
204
|
+
|
|
205
|
+
### Hard guarantees
|
|
206
|
+
|
|
207
|
+
Each guarantee is enforced by code, not by promise. Click through to verify.
|
|
208
|
+
|
|
209
|
+
- **No network I/O at the source level** — a custom lint rule (`no-network-import`) bans `import` of `node:http`, `node:https`, `node-fetch`, `axios`, `undici`, and `cross-fetch`. CI fails on any new import that would enable network calls. See [`src/linting/customRules.ts` Rule 4](./src/linting/customRules.ts).
|
|
210
|
+
- **No stdout writes outside the MCP framing path** — `installStdoutGuard()` proxies `process.stdout.write` at server boot and rejects any write that wouldn't corrupt MCP's JSON-RPC stream. The contract is pinned by [`src/server/stdoutGuard.test.ts`](./src/server/stdoutGuard.test.ts).
|
|
211
|
+
- **No telemetry / analytics** — production [`dependencies` in `package.json`](./package.json) are six packages: `@modelcontextprotocol/sdk`, `lru-cache`, `pino`, `ulid`, `zod`, `zod-to-json-schema`. No analytics SDK; nothing phones home.
|
|
212
|
+
- **No `postinstall` / `preinstall` scripts** — `package.json` ships with one lifecycle script (`prepublishOnly`) and one dev hook (`prepare` for git hooks). Neither runs when a downstream consumer installs the package.
|
|
213
|
+
- **Config secrets redacted from logs** — the boot-time `server.started` event runs config through [`redactConfig`](./src/config/env.ts) before logging; path-shaped values are sha256-hashed (12-char prefix) so even local stderr doesn't leak attachment-path layout.
|
|
214
|
+
- **Attachment paths are allowlist-bounded** — every attachment operation passes through [`assertAttachmentPath`](./src/attachment/assertAttachmentPath.ts), which resolves symlinks *before* checking against `OMNIFOCUS_ATTACHMENT_PATHS` (default: `$HOME`) to defeat symlink-escape, and hard-blocks `/System`, `/Library`, and their `/private/*` mirrors regardless of the allowlist.
|
|
215
|
+
|
|
216
|
+
### Opt-in escape hatch
|
|
217
|
+
|
|
218
|
+
There is exactly one feature that's gated behind an environment variable because enabling it broadens the threat surface:
|
|
219
|
+
|
|
220
|
+
- **`OMNIFOCUS_ALLOW_RAW_SCRIPT=1`** — exposes `run_jxa_script` and `run_omnijs_script`, which run arbitrary JXA / OmniJS supplied by the agent. Off by default. When enabled, every invocation emits a `raw_script.invoked` audit event at `info` level (regardless of `OMNIFOCUS_LOG_LEVEL`) including the full script body and tool name. See [ADR-0004](./docs/adr/0004-raw-script-escape-hatch.md) for the rationale.
|
|
221
|
+
|
|
222
|
+
### Verify it yourself
|
|
223
|
+
|
|
224
|
+
Three recipes that take seconds; you don't have to take this README's word for any of the above.
|
|
225
|
+
|
|
226
|
+
1. **Audit the source.** The repo at [github.com/torsday/omnifocus-mcp](https://github.com/torsday/omnifocus-mcp) is the canonical source. The published artifact is built from a tagged commit (`v1.0.0`); compare `dist/index.js` against the build output of that tag.
|
|
227
|
+
2. **Verify the published artifact's provenance.** npm publishes attestations via [Sigstore](https://www.sigstore.dev/):
|
|
228
|
+
```bash
|
|
229
|
+
npm view @torsday/omnifocus-mcp dist.attestations
|
|
230
|
+
```
|
|
231
|
+
The `provenance` URL points to the GitHub Actions run that built the artifact, signed with the workflow's OIDC identity.
|
|
232
|
+
3. **Inspect what's actually in the tarball.** It should be five files — no more, no less, and no install scripts:
|
|
233
|
+
```bash
|
|
234
|
+
curl -sL "$(npm view @torsday/omnifocus-mcp dist.tarball)" | tar -tzvf -
|
|
235
|
+
```
|
|
236
|
+
Expected output (file count = 5):
|
|
237
|
+
```
|
|
238
|
+
package/LICENSE
|
|
239
|
+
package/dist/index.js
|
|
240
|
+
package/package.json
|
|
241
|
+
package/CHANGELOG.md
|
|
242
|
+
package/README.md
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Out of scope
|
|
246
|
+
|
|
247
|
+
The threat model deliberately excludes anything outside this codebase: vulnerabilities in OmniFocus itself, Apple's JXA / OmniJS / `osascript` runtimes, transitive npm-dependency CVEs (track and patch via `npm audit` / Dependabot, but not part of this project's guarantees), and any attacker with root-equivalent local access (who could replace `osascript`, the MCP server binary, or your shell). See [SECURITY.md § Scope](./SECURITY.md#scope).
|
|
248
|
+
|
|
249
|
+
### Reference docs
|
|
250
|
+
|
|
251
|
+
- [`SECURITY.md`](./SECURITY.md) — vulnerability reporting, scope
|
|
252
|
+
- [`DESIGN.md` § 18 Security posture](./DESIGN.md#18-security-posture) — full threat model
|
|
253
|
+
- [ADR-0004](./docs/adr/0004-raw-script-escape-hatch.md) — raw-script gating decision
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
78
257
|
## Example interactions
|
|
79
258
|
|
|
80
259
|
**"What's in my inbox right now?"**
|
|
@@ -117,7 +296,7 @@ The assistant calls `task_list` with `{ "available": true, "limit": 20 }` and re
|
|
|
117
296
|
|
|
118
297
|
## Prompts
|
|
119
298
|
|
|
120
|
-
`omnifocus-mcp` ships four **MCP prompt templates** — structured workflows you can invoke by name from any MCP client that
|
|
299
|
+
`omnifocus-mcp` ships four **MCP prompt templates** — structured workflows you can invoke by name from any MCP client that surfaces `prompts/list` (most clients with a prompt picker UI).
|
|
121
300
|
|
|
122
301
|
### `daily-review` — triage your day
|
|
123
302
|
|
|
@@ -519,7 +698,7 @@ The full layered diagram with queues, circuit breakers, and the test adapter liv
|
|
|
519
698
|
|
|
520
699
|
## Status and roadmap
|
|
521
700
|
|
|
522
|
-
|
|
701
|
+
v1.0.0 is [published on npm](https://www.npmjs.com/package/@torsday/omnifocus-mcp). The phase table below records the milestone work that landed; the live backlog and future enhancements track on the [Project board](https://github.com/users/torsday/projects/4), and the [unreleased section of the CHANGELOG](./CHANGELOG.md#unreleased) lists what's already merged toward the next release.
|
|
523
702
|
|
|
524
703
|
| Phase | Milestone | Status |
|
|
525
704
|
|---|---|---|
|
|
@@ -681,8 +860,9 @@ Writes are saved locally and show up immediately in subsequent tool calls. Chang
|
|
|
681
860
|
|
|
682
861
|
| Client | Guide |
|
|
683
862
|
|---|---|
|
|
684
|
-
| Claude Desktop | [`docs/clients/claude-desktop.md`](./docs/clients/claude-desktop.md) |
|
|
685
863
|
| Claude Code (CLI) | [`docs/clients/claude-code.md`](./docs/clients/claude-code.md) |
|
|
864
|
+
| Claude Desktop | [`docs/clients/claude-desktop.md`](./docs/clients/claude-desktop.md) |
|
|
865
|
+
| OpenAI Codex CLI | [`docs/clients/codex.md`](./docs/clients/codex.md) |
|
|
686
866
|
| Generic stdio client | [`docs/clients/generic-stdio.md`](./docs/clients/generic-stdio.md) |
|
|
687
867
|
|
|
688
868
|
---
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {McpServer,ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {createHash}from'crypto';import fu,{homedir}from'os';import {z}from'zod';import go from'pino';import {spawn,execFile}from'child_process';import rt from'fs';import to,{join,sep}from'path';import {createInterface}from'readline';import {fileURLToPath}from'url';import {AsyncLocalStorage}from'async_hooks';import {ulid}from'ulid';import {EventEmitter}from'events';import {LRUCache}from'lru-cache';import {realpath,stat}from'fs/promises';var re={name:"@torsday/omnifocus-mcp",version:"1.0.0",description:"MCP server exposing the full OmniFocus surface to LLM agents \u2014 80 typed tools spanning tasks, projects, tags, folders, perspectives, forecast, review, notes, attachments, and sync, with a strict typed-error taxonomy and per-tool circuit breakers, rate limiter, and idempotency-key support. macOS-only; talks to OmniFocus 4 via JXA + OmniJS.",homepage:"https://github.com/torsday/omnifocus-mcp#readme"};var Ue=Object.freeze({listTasks:"jxa",getTask:"jxa",getTasksMany:"jxa",createTask:"jxa",updateTask:"jxa",completeTask:"jxa",uncompleteTask:"jxa",dropTask:"jxa",undropTask:"jxa",deleteTask:"jxa",moveTask:"omnijs",batchMoveTasks:"omnijs",reorderTask:"omnijs",duplicateTask:"jxa",batchCreateTasks:"jxa",batchUpdateTasks:"jxa",batchCompleteTasks:"jxa",batchUncompleteTasks:"jxa",batchDeleteTasks:"jxa",batchDropTasks:"jxa",batchUndropTasks:"jxa",listProjects:"jxa",getProject:"jxa",getProjectsMany:"jxa",createProject:"jxa",updateProject:"jxa",completeProject:"jxa",batchCompleteProjects:"jxa",dropProject:"jxa",batchDropProjects:"jxa",moveProject:"jxa",deleteProject:"jxa",markProjectReviewed:"jxa",listProjectsDueForReview:"jxa",setProjectReviewInterval:"jxa",listTags:"jxa",getTag:"jxa",getTagsMany:"jxa",createTag:"jxa",updateTag:"jxa",deleteTag:"jxa",listFolders:"jxa",getFolder:"jxa",createFolder:"jxa",updateFolder:"jxa",deleteFolder:"jxa",searchTasks:"jxa",getForecast:"jxa",listPerspectives:"jxa",evaluatePerspective:"jxa",evaluateCustomPerspective:"omnijs",syncTrigger:"jxa",getLastSync:"jxa",listAttachments:"jxa",addAttachment:"jxa",removeAttachment:"jxa",saveAttachmentToPath:"jxa",appLaunch:"jxa",pluginInvoke:"omnijs",getChangesSince:"jxa",runJxaScript:"jxa",runOmniJsScript:"omnijs"});var Le=class n{jxa;omnijs;constructor(e){this.jxa=e.jxa,this.omnijs=e.omnijs;}get routingTable(){return Ue}static fromTransports(e,t){return new n({jxa:e,omnijs:t})}pick(e){return Ue[e]==="jxa"?this.jxa:this.omnijs}listTasks(e){return this.pick("listTasks").listTasks(e)}getTask(e){return this.pick("getTask").getTask(e)}getTasksMany(e){return this.pick("getTasksMany").getTasksMany(e)}createTask(e){return this.pick("createTask").createTask(e)}updateTask(e,t){return this.pick("updateTask").updateTask(e,t)}completeTask(e,t){return this.pick("completeTask").completeTask(e,t)}uncompleteTask(e){return this.pick("uncompleteTask").uncompleteTask(e)}dropTask(e,t){return this.pick("dropTask").dropTask(e,t)}undropTask(e){return this.pick("undropTask").undropTask(e)}deleteTask(e){return this.pick("deleteTask").deleteTask(e)}moveTask(e,t){return this.pick("moveTask").moveTask(e,t)}batchMoveTasks(e){return this.pick("batchMoveTasks").batchMoveTasks(e)}reorderTask(e,t){return this.pick("reorderTask").reorderTask(e,t)}duplicateTask(e,t){return this.pick("duplicateTask").duplicateTask(e,t)}batchCreateTasks(e){return this.pick("batchCreateTasks").batchCreateTasks(e)}batchUpdateTasks(e){return this.pick("batchUpdateTasks").batchUpdateTasks(e)}batchCompleteTasks(e){return this.pick("batchCompleteTasks").batchCompleteTasks(e)}batchUncompleteTasks(e){return this.pick("batchUncompleteTasks").batchUncompleteTasks(e)}batchDeleteTasks(e){return this.pick("batchDeleteTasks").batchDeleteTasks(e)}batchDropTasks(e){return this.pick("batchDropTasks").batchDropTasks(e)}batchUndropTasks(e){return this.pick("batchUndropTasks").batchUndropTasks(e)}listProjects(e){return this.pick("listProjects").listProjects(e)}getProject(e){return this.pick("getProject").getProject(e)}getProjectsMany(e){return this.pick("getProjectsMany").getProjectsMany(e)}createProject(e){return this.pick("createProject").createProject(e)}updateProject(e,t){return this.pick("updateProject").updateProject(e,t)}completeProject(e,t){return this.pick("completeProject").completeProject(e,t)}dropProject(e,t){return this.pick("dropProject").dropProject(e,t)}batchCompleteProjects(e){return this.pick("batchCompleteProjects").batchCompleteProjects(e)}batchDropProjects(e){return this.pick("batchDropProjects").batchDropProjects(e)}moveProject(e,t){return this.pick("moveProject").moveProject(e,t)}deleteProject(e){return this.pick("deleteProject").deleteProject(e)}markProjectReviewed(e){return this.pick("markProjectReviewed").markProjectReviewed(e)}listProjectsDueForReview(){return this.pick("listProjectsDueForReview").listProjectsDueForReview()}setProjectReviewInterval(e,t){return this.pick("setProjectReviewInterval").setProjectReviewInterval(e,t)}listTags(e){return this.pick("listTags").listTags(e)}getTag(e){return this.pick("getTag").getTag(e)}getTagsMany(e){return this.pick("getTagsMany").getTagsMany(e)}createTag(e){return this.pick("createTag").createTag(e)}updateTag(e,t){return this.pick("updateTag").updateTag(e,t)}deleteTag(e){return this.pick("deleteTag").deleteTag(e)}listFolders(e){return this.pick("listFolders").listFolders(e)}getFolder(e){return this.pick("getFolder").getFolder(e)}createFolder(e){return this.pick("createFolder").createFolder(e)}updateFolder(e,t){return this.pick("updateFolder").updateFolder(e,t)}deleteFolder(e){return this.pick("deleteFolder").deleteFolder(e)}searchTasks(e){return this.pick("searchTasks").searchTasks(e)}getForecast(e){return this.pick("getForecast").getForecast(e)}listPerspectives(){return this.pick("listPerspectives").listPerspectives()}evaluatePerspective(e){return this.pick("evaluatePerspective").evaluatePerspective(e)}evaluateCustomPerspective(e){return this.pick("evaluateCustomPerspective").evaluateCustomPerspective(e)}syncTrigger(){return this.pick("syncTrigger").syncTrigger()}getLastSync(){return this.pick("getLastSync").getLastSync()}listAttachments(e){return this.pick("listAttachments").listAttachments(e)}addAttachment(e){return this.pick("addAttachment").addAttachment(e)}removeAttachment(e){return this.pick("removeAttachment").removeAttachment(e)}saveAttachmentToPath(e){return this.pick("saveAttachmentToPath").saveAttachmentToPath(e)}appLaunch(){return this.pick("appLaunch").appLaunch()}pluginInvoke(e){return this.pick("pluginInvoke").pluginInvoke(e)}runJxaScript(e,t){let r=this.pick("runJxaScript");return typeof r.runJxaScript!="function"?Promise.reject(new TypeError("Router dispatched runJxaScript to a transport that does not implement it")):r.runJxaScript(e,t)}runOmniJsScript(e,t){let r=this.pick("runOmniJsScript");return typeof r.runOmniJsScript!="function"?Promise.reject(new TypeError("Router dispatched runOmniJsScript to a transport that does not implement it")):r.runOmniJsScript(e,t)}getChangesSince(e){return this.pick("getChangesSince").getChangesSince(e)}};var ac=new Set(["createTask","updateTask","completeTask","uncompleteTask","dropTask","undropTask","deleteTask","moveTask","reorderTask","duplicateTask","batchCreateTasks","batchUpdateTasks","batchCompleteTasks","createProject","updateProject","completeProject","dropProject","moveProject","deleteProject","markProjectReviewed","setProjectReviewInterval","createTag","updateTag","deleteTag","createFolder","updateFolder","deleteFolder","syncTrigger","addAttachment","removeAttachment","appLaunch","pluginInvoke","runJxaScript","runOmniJsScript"]);function sc(n,e){return Ue[n]==="omnijs"?e.omniJsQueue:ac.has(n)?e.jxaWriteQueue:e.readPool}function po(n,e){return new Proxy(n,{get(t,r,o){if(typeof r!="string"||!(r in Ue))return Reflect.get(t,r,o);let a=r,s=sc(a,e),l=Reflect.get(t,a,o).bind(t);return (...u)=>s.run(()=>l(...u))}})}var st=class{name;size;inFlight=0;waiters=[];constructor(e){if(!Number.isInteger(e.size)||e.size<1)throw new RangeError(`ReadPool.size must be a positive integer (got ${String(e.size)})`);this.size=e.size,this.name=e.name??"read-pool";}async run(e){await this.acquire();try{return await e()}finally{this.release();}}inFlightCount(){return this.inFlight}waitingCount(){return this.waiters.length}pendingCount(){return this.inFlight+this.waiters.length}acquire(){return this.inFlight<this.size?(this.inFlight++,Promise.resolve()):new Promise(e=>{this.waiters.push(()=>{this.inFlight++,e();});})}release(){this.inFlight--;let e=this.waiters.shift();e!==void 0&&e();}};var E=class extends Error{code;remediationClass;suggestion;details;constructor(e,t,r={}){super(t,r.cause!==void 0?{cause:r.cause}:void 0),this.name=new.target.name,this.code=e,r.remediationClass!==void 0&&(this.remediationClass=r.remediationClass),r.suggestion!==void 0&&(this.suggestion=r.suggestion),r.details!==void 0&&(this.details=r.details);}toJSON(){let e={name:this.name,code:this.code,message:this.message};return this.remediationClass!==void 0&&(e.remediationClass=this.remediationClass),this.suggestion!==void 0&&(e.suggestion=this.suggestion),this.details!==void 0&&(e.details=this.details),e}};function uo(n){return n instanceof E}var we=class extends E{constructor(e={}){super("OF_NOT_RUNNING","OmniFocus is not running.",{remediationClass:"environment",suggestion:"Launch OmniFocus and retry.",...e});}},je=class extends E{constructor(e={}){super("OF_PERMISSION_DENIED","Automation permission for OmniFocus is denied.",{remediationClass:"environment",suggestion:"Open System Settings \u2192 Privacy & Security \u2192 Automation; grant this terminal or client access to OmniFocus. See docs/troubleshooting.md for step-by-step recovery.",...e});}},it=class extends E{constructor(e,t={}){super("OF_FEATURE_REQUIRES_PRO",e,{remediationClass:"environment",suggestion:"This feature requires OmniFocus Pro. Upgrade or use a different tool.",...t});}};var I=class extends E{constructor(e,t={}){super("OF_VALIDATION",e,{remediationClass:"input",suggestion:"Fix the input and retry. See `details` for field-level reasons.",...t});}},P=class extends E{constructor(e,t={}){super("OF_NOT_FOUND",e,{remediationClass:"input",suggestion:"Confirm the ID with the corresponding `*_list` tool. Use OmniFocus persistent IDs, not names.",...t});}},be=class extends E{constructor(e,t={}){super("OF_CONFLICT",e,{remediationClass:"input",suggestion:"The resource was modified since you read it. Re-read with the corresponding `*_get` tool, merge your changes, and retry with the fresh `modifiedAt` value.",...t});}},Pe=class extends E{constructor(e,t={}){super("OF_TIMEOUT",e,{remediationClass:"transient",suggestion:"Retry once. If repeated, OmniFocus may be wedged \u2014 relaunch it.",...t});}},ct=class extends E{constructor(e,t={}){super("OF_RATE_LIMITED",e,{remediationClass:"transient",suggestion:"Wait details.retryAfterMs milliseconds then retry.",...t,details:{retryAfterMs:6e4,...t.details}});}},dt=class extends E{constructor(e,t={}){super("OF_QUEUE_FULL",e,{remediationClass:"transient",suggestion:"The write queue is saturated. Wait for in-flight writes to drain, then retry.",...t});}},Je=class extends E{constructor(e,t={}){super("OF_CIRCUIT_OPEN",e,{remediationClass:"transient",suggestion:"This tool failed repeatedly and is rejecting calls fast. Wait details.retryAfterMs milliseconds for the circuit to half-open.",...t,details:{retryAfterMs:6e4,...t.details}});}},Oe=class extends E{constructor(e,t={}){super("OF_TRANSPORT_UNAVAILABLE",e,{remediationClass:"infrastructure",suggestion:"The required transport is unreachable. Verify OmniFocus is running and responsive.",...t});}},L=class extends E{constructor(e,t={}){super("OF_SCRIPT_ERROR",e,{remediationClass:"infrastructure",suggestion:"The OmniFocus script failed. Inspect `details.transport` and `details.reason` for context.",...t});}},lt=class extends E{constructor(e={}){super("OF_SHUTTING_DOWN","Server is shutting down; not accepting new requests.",{remediationClass:"lifecycle",suggestion:"Reconnect to a fresh server instance.",...e});}},pt=class extends E{constructor(e,t,r,o={}){super("OF_LOOP_DETECTED",`Tool "${e}" has been called ${t} time(s) with identical arguments within ${r}s. The agent appears to be stuck.`,{remediationClass:"input",suggestion:"Act on the result of the previous call before repeating this tool. If you need the same data again, verify the previous response was consumed.",details:{tool:e,count:t,windowSeconds:r},...o});}};var Be=class{name;cap;inFlight=0;queue=[];constructor(e){if(!Number.isInteger(e.cap)||e.cap<1)throw new RangeError(`WriteQueue.cap must be a positive integer (got ${String(e.cap)})`);this.cap=e.cap,this.name=e.name??"write-queue";}run(e){if(this.pendingCount()>=this.cap)throw new dt(`${this.name} is full (cap ${this.cap}); ${this.pendingCount()} pending`,{details:{queue:this.name,cap:this.cap,pending:this.pendingCount()}});let{promise:t,resolve:r,reject:o}=Promise.withResolvers();return this.queue.push({fn:e,resolve:r,reject:o}),this.pump(),t}inFlightCount(){return this.inFlight}waitingCount(){return this.queue.length}pendingCount(){return this.inFlight+this.queue.length}pump(){if(this.inFlight>0)return;let e=this.queue.shift();e!==void 0&&(this.inFlight++,(async()=>{try{let t=await e.fn();e.resolve(t);}catch(t){e.reject(t);}finally{this.inFlight--,this.pump();}})());}};var dc=z.string().regex(/^\d+\/\d+$/,'must be "N/SECONDS" format, e.g. "120/60"').transform(n=>{let[e,t]=n.split("/").map(Number);return {limit:e,windowSeconds:t}}),lc=z.object({OMNIFOCUS_LOG_LEVEL:z.enum(["trace","debug","info","warn","error"]).default("info"),OMNIFOCUS_INTEGRATION:z.string().prefault("").transform(n=>n==="1"),OMNIFOCUS_E2E:z.string().prefault("").transform(n=>n==="1"),OMNIFOCUS_E2E_USE_MEMORY:z.string().prefault("").transform(n=>n==="1"),OMNIFOCUS_ALLOW_RAW_SCRIPT:z.string().prefault("").transform(n=>n==="1"),OMNIFOCUS_CACHE_TTL_MS:z.coerce.number().int().positive().default(3e4),OMNIFOCUS_CACHE_CAPACITY:z.coerce.number().int().positive().default(256),OMNIFOCUS_READ_POOL_SIZE:z.coerce.number().int().min(1).max(8).default(2),OMNIFOCUS_WRITE_QUEUE_CAP:z.coerce.number().int().positive().default(50),OMNIFOCUS_JXA_TIMEOUT_MS:z.coerce.number().int().positive().default(3e4),OMNIFOCUS_OMNIJS_TIMEOUT_MS:z.coerce.number().int().positive().default(45e3),OMNIFOCUS_ATTACHMENT_PATHS:z.string().prefault(homedir()).transform(n=>n.split(":").filter(Boolean)),OMNIFOCUS_MAX_ATTACHMENT_MB:z.coerce.number().int().positive().default(100),OMNIFOCUS_TOOL_RATE_LIMIT:dc.prefault("120/60")});function mo(n=process.env,e=t=>{process.stderr.write(`[omnifocus-mcp] Config error: ${t}
|
|
2
|
+
import {McpServer,ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import {createHash}from'crypto';import fu,{homedir}from'os';import {z}from'zod';import go from'pino';import {spawn,execFile}from'child_process';import rt from'fs';import to,{join,sep}from'path';import {createInterface}from'readline';import {fileURLToPath}from'url';import {AsyncLocalStorage}from'async_hooks';import {ulid}from'ulid';import {EventEmitter}from'events';import {LRUCache}from'lru-cache';import {realpath,stat}from'fs/promises';var re={name:"@torsday/omnifocus-mcp",version:"1.0.2",description:"MCP server exposing the full OmniFocus surface to LLM agents \u2014 80 typed tools spanning tasks, projects, tags, folders, perspectives, forecast, review, notes, attachments, and sync, with a strict typed-error taxonomy and per-tool circuit breakers, rate limiter, and idempotency-key support. macOS-only; talks to OmniFocus 4 via JXA + OmniJS.",homepage:"https://github.com/torsday/omnifocus-mcp#readme"};var Ue=Object.freeze({listTasks:"jxa",getTask:"jxa",getTasksMany:"jxa",createTask:"jxa",updateTask:"jxa",completeTask:"jxa",uncompleteTask:"jxa",dropTask:"jxa",undropTask:"jxa",deleteTask:"jxa",moveTask:"omnijs",batchMoveTasks:"omnijs",reorderTask:"omnijs",duplicateTask:"jxa",batchCreateTasks:"jxa",batchUpdateTasks:"jxa",batchCompleteTasks:"jxa",batchUncompleteTasks:"jxa",batchDeleteTasks:"jxa",batchDropTasks:"jxa",batchUndropTasks:"jxa",listProjects:"jxa",getProject:"jxa",getProjectsMany:"jxa",createProject:"jxa",updateProject:"jxa",completeProject:"jxa",batchCompleteProjects:"jxa",dropProject:"jxa",batchDropProjects:"jxa",moveProject:"jxa",deleteProject:"jxa",markProjectReviewed:"jxa",listProjectsDueForReview:"jxa",setProjectReviewInterval:"jxa",listTags:"jxa",getTag:"jxa",getTagsMany:"jxa",createTag:"jxa",updateTag:"jxa",deleteTag:"jxa",listFolders:"jxa",getFolder:"jxa",createFolder:"jxa",updateFolder:"jxa",deleteFolder:"jxa",searchTasks:"jxa",getForecast:"jxa",listPerspectives:"jxa",evaluatePerspective:"jxa",evaluateCustomPerspective:"omnijs",syncTrigger:"jxa",getLastSync:"jxa",listAttachments:"jxa",addAttachment:"jxa",removeAttachment:"jxa",saveAttachmentToPath:"jxa",appLaunch:"jxa",pluginInvoke:"omnijs",getChangesSince:"jxa",runJxaScript:"jxa",runOmniJsScript:"omnijs"});var Le=class n{jxa;omnijs;constructor(e){this.jxa=e.jxa,this.omnijs=e.omnijs;}get routingTable(){return Ue}static fromTransports(e,t){return new n({jxa:e,omnijs:t})}pick(e){return Ue[e]==="jxa"?this.jxa:this.omnijs}listTasks(e){return this.pick("listTasks").listTasks(e)}getTask(e){return this.pick("getTask").getTask(e)}getTasksMany(e){return this.pick("getTasksMany").getTasksMany(e)}createTask(e){return this.pick("createTask").createTask(e)}updateTask(e,t){return this.pick("updateTask").updateTask(e,t)}completeTask(e,t){return this.pick("completeTask").completeTask(e,t)}uncompleteTask(e){return this.pick("uncompleteTask").uncompleteTask(e)}dropTask(e,t){return this.pick("dropTask").dropTask(e,t)}undropTask(e){return this.pick("undropTask").undropTask(e)}deleteTask(e){return this.pick("deleteTask").deleteTask(e)}moveTask(e,t){return this.pick("moveTask").moveTask(e,t)}batchMoveTasks(e){return this.pick("batchMoveTasks").batchMoveTasks(e)}reorderTask(e,t){return this.pick("reorderTask").reorderTask(e,t)}duplicateTask(e,t){return this.pick("duplicateTask").duplicateTask(e,t)}batchCreateTasks(e){return this.pick("batchCreateTasks").batchCreateTasks(e)}batchUpdateTasks(e){return this.pick("batchUpdateTasks").batchUpdateTasks(e)}batchCompleteTasks(e){return this.pick("batchCompleteTasks").batchCompleteTasks(e)}batchUncompleteTasks(e){return this.pick("batchUncompleteTasks").batchUncompleteTasks(e)}batchDeleteTasks(e){return this.pick("batchDeleteTasks").batchDeleteTasks(e)}batchDropTasks(e){return this.pick("batchDropTasks").batchDropTasks(e)}batchUndropTasks(e){return this.pick("batchUndropTasks").batchUndropTasks(e)}listProjects(e){return this.pick("listProjects").listProjects(e)}getProject(e){return this.pick("getProject").getProject(e)}getProjectsMany(e){return this.pick("getProjectsMany").getProjectsMany(e)}createProject(e){return this.pick("createProject").createProject(e)}updateProject(e,t){return this.pick("updateProject").updateProject(e,t)}completeProject(e,t){return this.pick("completeProject").completeProject(e,t)}dropProject(e,t){return this.pick("dropProject").dropProject(e,t)}batchCompleteProjects(e){return this.pick("batchCompleteProjects").batchCompleteProjects(e)}batchDropProjects(e){return this.pick("batchDropProjects").batchDropProjects(e)}moveProject(e,t){return this.pick("moveProject").moveProject(e,t)}deleteProject(e){return this.pick("deleteProject").deleteProject(e)}markProjectReviewed(e){return this.pick("markProjectReviewed").markProjectReviewed(e)}listProjectsDueForReview(){return this.pick("listProjectsDueForReview").listProjectsDueForReview()}setProjectReviewInterval(e,t){return this.pick("setProjectReviewInterval").setProjectReviewInterval(e,t)}listTags(e){return this.pick("listTags").listTags(e)}getTag(e){return this.pick("getTag").getTag(e)}getTagsMany(e){return this.pick("getTagsMany").getTagsMany(e)}createTag(e){return this.pick("createTag").createTag(e)}updateTag(e,t){return this.pick("updateTag").updateTag(e,t)}deleteTag(e){return this.pick("deleteTag").deleteTag(e)}listFolders(e){return this.pick("listFolders").listFolders(e)}getFolder(e){return this.pick("getFolder").getFolder(e)}createFolder(e){return this.pick("createFolder").createFolder(e)}updateFolder(e,t){return this.pick("updateFolder").updateFolder(e,t)}deleteFolder(e){return this.pick("deleteFolder").deleteFolder(e)}searchTasks(e){return this.pick("searchTasks").searchTasks(e)}getForecast(e){return this.pick("getForecast").getForecast(e)}listPerspectives(){return this.pick("listPerspectives").listPerspectives()}evaluatePerspective(e){return this.pick("evaluatePerspective").evaluatePerspective(e)}evaluateCustomPerspective(e){return this.pick("evaluateCustomPerspective").evaluateCustomPerspective(e)}syncTrigger(){return this.pick("syncTrigger").syncTrigger()}getLastSync(){return this.pick("getLastSync").getLastSync()}listAttachments(e){return this.pick("listAttachments").listAttachments(e)}addAttachment(e){return this.pick("addAttachment").addAttachment(e)}removeAttachment(e){return this.pick("removeAttachment").removeAttachment(e)}saveAttachmentToPath(e){return this.pick("saveAttachmentToPath").saveAttachmentToPath(e)}appLaunch(){return this.pick("appLaunch").appLaunch()}pluginInvoke(e){return this.pick("pluginInvoke").pluginInvoke(e)}runJxaScript(e,t){let r=this.pick("runJxaScript");return typeof r.runJxaScript!="function"?Promise.reject(new TypeError("Router dispatched runJxaScript to a transport that does not implement it")):r.runJxaScript(e,t)}runOmniJsScript(e,t){let r=this.pick("runOmniJsScript");return typeof r.runOmniJsScript!="function"?Promise.reject(new TypeError("Router dispatched runOmniJsScript to a transport that does not implement it")):r.runOmniJsScript(e,t)}getChangesSince(e){return this.pick("getChangesSince").getChangesSince(e)}};var ac=new Set(["createTask","updateTask","completeTask","uncompleteTask","dropTask","undropTask","deleteTask","moveTask","reorderTask","duplicateTask","batchCreateTasks","batchUpdateTasks","batchCompleteTasks","createProject","updateProject","completeProject","dropProject","moveProject","deleteProject","markProjectReviewed","setProjectReviewInterval","createTag","updateTag","deleteTag","createFolder","updateFolder","deleteFolder","syncTrigger","addAttachment","removeAttachment","appLaunch","pluginInvoke","runJxaScript","runOmniJsScript"]);function sc(n,e){return Ue[n]==="omnijs"?e.omniJsQueue:ac.has(n)?e.jxaWriteQueue:e.readPool}function po(n,e){return new Proxy(n,{get(t,r,o){if(typeof r!="string"||!(r in Ue))return Reflect.get(t,r,o);let a=r,s=sc(a,e),l=Reflect.get(t,a,o).bind(t);return (...u)=>s.run(()=>l(...u))}})}var st=class{name;size;inFlight=0;waiters=[];constructor(e){if(!Number.isInteger(e.size)||e.size<1)throw new RangeError(`ReadPool.size must be a positive integer (got ${String(e.size)})`);this.size=e.size,this.name=e.name??"read-pool";}async run(e){await this.acquire();try{return await e()}finally{this.release();}}inFlightCount(){return this.inFlight}waitingCount(){return this.waiters.length}pendingCount(){return this.inFlight+this.waiters.length}acquire(){return this.inFlight<this.size?(this.inFlight++,Promise.resolve()):new Promise(e=>{this.waiters.push(()=>{this.inFlight++,e();});})}release(){this.inFlight--;let e=this.waiters.shift();e!==void 0&&e();}};var E=class extends Error{code;remediationClass;suggestion;details;constructor(e,t,r={}){super(t,r.cause!==void 0?{cause:r.cause}:void 0),this.name=new.target.name,this.code=e,r.remediationClass!==void 0&&(this.remediationClass=r.remediationClass),r.suggestion!==void 0&&(this.suggestion=r.suggestion),r.details!==void 0&&(this.details=r.details);}toJSON(){let e={name:this.name,code:this.code,message:this.message};return this.remediationClass!==void 0&&(e.remediationClass=this.remediationClass),this.suggestion!==void 0&&(e.suggestion=this.suggestion),this.details!==void 0&&(e.details=this.details),e}};function uo(n){return n instanceof E}var we=class extends E{constructor(e={}){super("OF_NOT_RUNNING","OmniFocus is not running.",{remediationClass:"environment",suggestion:"Launch OmniFocus and retry.",...e});}},je=class extends E{constructor(e={}){super("OF_PERMISSION_DENIED","Automation permission for OmniFocus is denied.",{remediationClass:"environment",suggestion:"Open System Settings \u2192 Privacy & Security \u2192 Automation; grant this terminal or client access to OmniFocus. See docs/troubleshooting.md for step-by-step recovery.",...e});}},it=class extends E{constructor(e,t={}){super("OF_FEATURE_REQUIRES_PRO",e,{remediationClass:"environment",suggestion:"This feature requires OmniFocus Pro. Upgrade or use a different tool.",...t});}};var I=class extends E{constructor(e,t={}){super("OF_VALIDATION",e,{remediationClass:"input",suggestion:"Fix the input and retry. See `details` for field-level reasons.",...t});}},P=class extends E{constructor(e,t={}){super("OF_NOT_FOUND",e,{remediationClass:"input",suggestion:"Confirm the ID with the corresponding `*_list` tool. Use OmniFocus persistent IDs, not names.",...t});}},be=class extends E{constructor(e,t={}){super("OF_CONFLICT",e,{remediationClass:"input",suggestion:"The resource was modified since you read it. Re-read with the corresponding `*_get` tool, merge your changes, and retry with the fresh `modifiedAt` value.",...t});}},Pe=class extends E{constructor(e,t={}){super("OF_TIMEOUT",e,{remediationClass:"transient",suggestion:"Retry once. If repeated, OmniFocus may be wedged \u2014 relaunch it.",...t});}},ct=class extends E{constructor(e,t={}){super("OF_RATE_LIMITED",e,{remediationClass:"transient",suggestion:"Wait details.retryAfterMs milliseconds then retry.",...t,details:{retryAfterMs:6e4,...t.details}});}},dt=class extends E{constructor(e,t={}){super("OF_QUEUE_FULL",e,{remediationClass:"transient",suggestion:"The write queue is saturated. Wait for in-flight writes to drain, then retry.",...t});}},Je=class extends E{constructor(e,t={}){super("OF_CIRCUIT_OPEN",e,{remediationClass:"transient",suggestion:"This tool failed repeatedly and is rejecting calls fast. Wait details.retryAfterMs milliseconds for the circuit to half-open.",...t,details:{retryAfterMs:6e4,...t.details}});}},Oe=class extends E{constructor(e,t={}){super("OF_TRANSPORT_UNAVAILABLE",e,{remediationClass:"infrastructure",suggestion:"The required transport is unreachable. Verify OmniFocus is running and responsive.",...t});}},L=class extends E{constructor(e,t={}){super("OF_SCRIPT_ERROR",e,{remediationClass:"infrastructure",suggestion:"The OmniFocus script failed. Inspect `details.transport` and `details.reason` for context.",...t});}},lt=class extends E{constructor(e={}){super("OF_SHUTTING_DOWN","Server is shutting down; not accepting new requests.",{remediationClass:"lifecycle",suggestion:"Reconnect to a fresh server instance.",...e});}},pt=class extends E{constructor(e,t,r,o={}){super("OF_LOOP_DETECTED",`Tool "${e}" has been called ${t} time(s) with identical arguments within ${r}s. The agent appears to be stuck.`,{remediationClass:"input",suggestion:"Act on the result of the previous call before repeating this tool. If you need the same data again, verify the previous response was consumed.",details:{tool:e,count:t,windowSeconds:r},...o});}};var Be=class{name;cap;inFlight=0;queue=[];constructor(e){if(!Number.isInteger(e.cap)||e.cap<1)throw new RangeError(`WriteQueue.cap must be a positive integer (got ${String(e.cap)})`);this.cap=e.cap,this.name=e.name??"write-queue";}run(e){if(this.pendingCount()>=this.cap)throw new dt(`${this.name} is full (cap ${this.cap}); ${this.pendingCount()} pending`,{details:{queue:this.name,cap:this.cap,pending:this.pendingCount()}});let{promise:t,resolve:r,reject:o}=Promise.withResolvers();return this.queue.push({fn:e,resolve:r,reject:o}),this.pump(),t}inFlightCount(){return this.inFlight}waitingCount(){return this.queue.length}pendingCount(){return this.inFlight+this.queue.length}pump(){if(this.inFlight>0)return;let e=this.queue.shift();e!==void 0&&(this.inFlight++,(async()=>{try{let t=await e.fn();e.resolve(t);}catch(t){e.reject(t);}finally{this.inFlight--,this.pump();}})());}};var dc=z.string().regex(/^\d+\/\d+$/,'must be "N/SECONDS" format, e.g. "120/60"').transform(n=>{let[e,t]=n.split("/").map(Number);return {limit:e,windowSeconds:t}}),lc=z.object({OMNIFOCUS_LOG_LEVEL:z.enum(["trace","debug","info","warn","error"]).default("info"),OMNIFOCUS_INTEGRATION:z.string().prefault("").transform(n=>n==="1"),OMNIFOCUS_E2E:z.string().prefault("").transform(n=>n==="1"),OMNIFOCUS_E2E_USE_MEMORY:z.string().prefault("").transform(n=>n==="1"),OMNIFOCUS_ALLOW_RAW_SCRIPT:z.string().prefault("").transform(n=>n==="1"),OMNIFOCUS_CACHE_TTL_MS:z.coerce.number().int().positive().default(3e4),OMNIFOCUS_CACHE_CAPACITY:z.coerce.number().int().positive().default(256),OMNIFOCUS_READ_POOL_SIZE:z.coerce.number().int().min(1).max(8).default(2),OMNIFOCUS_WRITE_QUEUE_CAP:z.coerce.number().int().positive().default(50),OMNIFOCUS_JXA_TIMEOUT_MS:z.coerce.number().int().positive().default(3e4),OMNIFOCUS_OMNIJS_TIMEOUT_MS:z.coerce.number().int().positive().default(45e3),OMNIFOCUS_ATTACHMENT_PATHS:z.string().prefault(homedir()).transform(n=>n.split(":").filter(Boolean)),OMNIFOCUS_MAX_ATTACHMENT_MB:z.coerce.number().int().positive().default(100),OMNIFOCUS_TOOL_RATE_LIMIT:dc.prefault("120/60")});function mo(n=process.env,e=t=>{process.stderr.write(`[omnifocus-mcp] Config error: ${t}
|
|
3
3
|
`),process.exit(1);}){let t=lc.safeParse({OMNIFOCUS_LOG_LEVEL:n.OMNIFOCUS_LOG_LEVEL,OMNIFOCUS_INTEGRATION:n.OMNIFOCUS_INTEGRATION,OMNIFOCUS_E2E:n.OMNIFOCUS_E2E,OMNIFOCUS_E2E_USE_MEMORY:n.OMNIFOCUS_E2E_USE_MEMORY,OMNIFOCUS_ALLOW_RAW_SCRIPT:n.OMNIFOCUS_ALLOW_RAW_SCRIPT,OMNIFOCUS_CACHE_TTL_MS:n.OMNIFOCUS_CACHE_TTL_MS,OMNIFOCUS_CACHE_CAPACITY:n.OMNIFOCUS_CACHE_CAPACITY,OMNIFOCUS_READ_POOL_SIZE:n.OMNIFOCUS_READ_POOL_SIZE,OMNIFOCUS_WRITE_QUEUE_CAP:n.OMNIFOCUS_WRITE_QUEUE_CAP,OMNIFOCUS_JXA_TIMEOUT_MS:n.OMNIFOCUS_JXA_TIMEOUT_MS,OMNIFOCUS_OMNIJS_TIMEOUT_MS:n.OMNIFOCUS_OMNIJS_TIMEOUT_MS,OMNIFOCUS_ATTACHMENT_PATHS:n.OMNIFOCUS_ATTACHMENT_PATHS,OMNIFOCUS_MAX_ATTACHMENT_MB:n.OMNIFOCUS_MAX_ATTACHMENT_MB,OMNIFOCUS_TOOL_RATE_LIMIT:n.OMNIFOCUS_TOOL_RATE_LIMIT});if(!t.success){let r=t.error.issues.map(o=>` ${o.path.join(".")}: ${o.message}`);return e(`Invalid environment configuration:
|
|
4
4
|
${r.join(`
|
|
5
5
|
`)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@torsday/omnifocus-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "MCP server exposing the full OmniFocus surface to LLM agents — 80 typed tools spanning tasks, projects, tags, folders, perspectives, forecast, review, notes, attachments, and sync, with a strict typed-error taxonomy and per-tool circuit breakers, rate limiter, and idempotency-key support. macOS-only; talks to OmniFocus 4 via JXA + OmniJS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|