Skip to main content
Glama
0xJem

omnifocus-mcp-bridge

by 0xJem

omnifocus-mcp-bridge

Authenticated Streamable HTTP bridge for omnifocus-mcp-enhanced.

Use this when an MCP client needs remote access to OmniFocus on your Mac. The upstream OmniFocus MCP server is a local stdio process; this repo wraps it in a small authenticated Streamable HTTP server.

What it does:

  • installs the published omnifocus-mcp-enhanced package as a pinned dependency

  • launches that package locally as a child stdio MCP process

  • exposes MCP over HTTP at /mcp

  • requires Bearer auth on every request

  • defaults to read-only tool exposure

  • optionally publishes the bridge through Tailscale Serve at /omnifocus-mcp

What it does not do:

  • it does not import upstream server internals

  • it does not require the upstream repo to become a monorepo

  • it does not make OmniFocus itself remote; OmniFocus stays on the Mac

Requirements

  • macOS with OmniFocus installed and automation access allowed

  • Node.js 22+

  • pnpm 11+

  • Tailscale, only if using pnpm start:tailscale

Related MCP server: Levitate

Quick Start

pnpm install
pnpm token:generate
pnpm start

Default local endpoint:

http://127.0.0.1:3050/mcp

Clients must send:

Authorization: Bearer <contents of .secrets/omnifocus-mcp-token>

Smoke test:

TOKEN="$(cat .secrets/omnifocus-mcp-token)"

curl -i http://127.0.0.1:3050/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  --data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

pnpm token:generate writes .secrets/omnifocus-mcp-token with private permissions and does not print the token. Rotate it with:

pnpm token:generate -- --force

Security

  • Bearer auth is required on every request.

  • The bridge refuses to start without OMNIFOCUS_MCP_TOKEN, OMNIFOCUS_MCP_TOKEN_FILE, or .secrets/omnifocus-mcp-token.

  • Token files must be regular files and must not be group/world readable.

  • The default bind host is 127.0.0.1.

  • Read-only mode is enabled by default. Set OMNIFOCUS_MCP_READ_ONLY=false only when remote mutation is intended.

  • The upstream OmniFocus server stays local to the Mac and is launched over stdio; no upstream internals are imported.

Configuration

.env is optional. If present, it is loaded before process environment values, so shell, launchd, or service-manager env vars override .env.

Variable

Default

Description

OMNIFOCUS_MCP_TOKEN_FILE

.secrets/omnifocus-mcp-token when present

Private file containing the bearer token.

OMNIFOCUS_MCP_TOKEN

none

Direct bearer token override. Avoid inline shell usage because it can leak through history.

OMNIFOCUS_MCP_ENV_FILE

.env when present

Optional dotenv file path. If explicitly set, the file must exist.

OMNIFOCUS_MCP_HOST

127.0.0.1

HTTP bind host. Keep this as 127.0.0.1 for Tailscale Serve mode.

OMNIFOCUS_MCP_PORT

3050

HTTP bind port.

OMNIFOCUS_MCP_READ_ONLY

true

Set to false to expose mutating upstream tools.

OMNIFOCUS_MCP_VERBOSE

false

Set to true for redacted request logs.

OMNIFOCUS_MCP_UPSTREAM_COMMAND

Node executable

Optional override for the stdio upstream command.

OMNIFOCUS_MCP_UPSTREAM_ARGS

resolved dependency bin path

Optional override args. Supports JSON arrays or shell-like quoted strings.

To expose plain HTTP on a trusted LAN, set OMNIFOCUS_MCP_HOST=0.0.0.0. This is not HTTPS; prefer Tailscale Serve for remote access.

Tailscale Serve

For tailnet HTTPS:

pnpm start:tailscale

This starts the bridge on 127.0.0.1:${OMNIFOCUS_MCP_PORT:-3050} and registers a persistent background Tailscale Serve route. With the default port, the Serve command is:

tailscale serve --bg --set-path /omnifocus-mcp http://127.0.0.1:3050/mcp

Remote endpoint:

https://<mac-name>.<tailnet>.ts.net/omnifocus-mcp

The wrapper accepts an existing /omnifocus-mcp route when it already points to the expected local bridge. It refuses to overwrite a route pointing elsewhere, leaves unrelated Serve routes alone, and does not run tailscale serve reset. Tailscale Serve mode requires a fixed port; OMNIFOCUS_MCP_PORT=0 is rejected because a persistent route must keep the same local target across restarts.

Run in the Background

Use a macOS LaunchAgent, not a LaunchDaemon, so OmniFocus automation runs in the logged-in user's GUI session.

pnpm launchd:install

This renders launchd/com.0xjem.omnifocus-mcp-bridge.plist.template to:

~/Library/LaunchAgents/com.0xjem.omnifocus-mcp-bridge.plist

The service runs an installed copy of scripts/omnifocus-mcp-bridge.sh, so macOS Login Items show a named bridge entry instead of pnpm. The installed launcher lives outside ~/Documents to avoid macOS background-item privacy restrictions, keeps the bridge alive, and writes logs to:

~/Library/Logs/omnifocus-mcp-bridge/

Check status:

launchctl print "gui/$(id -u)/com.0xjem.omnifocus-mcp-bridge"
pnpm launchd:logs

Uninstall:

pnpm launchd:uninstall

Upstream Launch

The upstream package is pinned in package.json. At runtime, the bridge:

  1. resolves omnifocus-mcp-enhanced/package.json

  2. reads the package bin entry

  3. starts node <resolved-bin-path> as a child stdio MCP process

Override launch only when testing a different stdio server:

OMNIFOCUS_MCP_UPSTREAM_COMMAND=node \
OMNIFOCUS_MCP_UPSTREAM_ARGS='["/absolute/path/to/custom/server.js"]' \
pnpm start

Read-Only Mode

When OMNIFOCUS_MCP_READ_ONLY is unset or true, only these tools are exposed:

  • dump_database

  • get_task_by_id

  • read_task_attachment

  • get_tasks

  • filter_tasks

  • get_projects

  • manage_perspectives (list and get only)

  • count_tasks

In omnifocus-mcp-enhanced 2.2, custom-perspective task reads use get_tasks with source: "custom"; task IDs are returned in structured output.

Known and unknown mutating tools are blocked in read-only mode.

Diagnostics

Enable redacted request logs:

pnpm start:tailscale -- --verbose

or:

OMNIFOCUS_MCP_VERBOSE=true pnpm start:tailscale

Verbose logs include method, path, status, duration, remote address, forwarded-for, user agent, content type, accept header, whether an Authorization header was present, and whether bearer auth passed. They do not include bearer tokens or request bodies.

If a client gets 502 Bad Gateway and no bridge request log appears, the request did not reach the bridge. Check:

tailscale serve status --json
lsof -nP -iTCP:3050 -sTCP:LISTEN

If the bridge logs statusCode:401, the request reached the bridge but the token was missing or invalid.

Development

pnpm install
pnpm run format:check
pnpm run lint
pnpm run typecheck
pnpm test
pnpm run build

Tests use a fake stdio MCP child process. They do not launch OmniFocus or call the real upstream package.

pnpm start, pnpm start:tailscale, and pnpm token:generate run pnpm run build before executing compiled output.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    HTTP bridge that exposes MCP tools via POST endpoint, enabling remote LLMs to access local functionality with bearer authentication.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Lifts local stdio MCP servers into remote Streamable HTTP endpoints for cloud-hosted AI clients, with bearer-token auth and tool policy filtering.
    4 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables remote MCP clients to securely access a trusted macOS machine's local file system and command execution tools (mcp-combiner) via OAuth-authenticated HTTPS through Cloudflare Tunnel, without opening router ports.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Exposes the official Anytype MCP server (which only speaks stdio) as a remote Streamable HTTP endpoint, enabling Claude mobile/web to connect securely via bearer-token auth and Tailscale Funnel.
    3
    -