@euqns/nudge-mcp
v1.43.0
Published
Model Context Protocol server for the Nudge board (the in-house Trello).
Downloads
5,448
Readme
Nudge MCP
A Model Context Protocol server that exposes the Nudge board (this repo's Trello-like app) to MCP clients — Claude Desktop, Claude Code, Cursor, anything that speaks MCP.
It lets an LLM list boards, create cards, assign people, move tasks between columns, drop comments, and read your notifications — using your identity, so mentions and activity logs attribute correctly.
Two ways to connect
Remote HTTP (recommended). Nudge serves the same tool surface directly from
Convex at <deployment>.convex.site/mcp, over MCP's stateless Streamable HTTP
transport (spec revision 2026-07-28). Nothing to install and nothing to keep
updated — tool changes ship with the backend deploy:
claude mcp add --transport http nudge https://<deployment>.convex.site/mcp \
--header "Authorization: Bearer nug_..."Generate the token under profile → MCP tokens in the web app; that panel prints the full command with the token already filled in. The endpoint also answers 2025-era clients on the same URL, so older MCP implementations keep working.
Local stdio (this package). Still the right choice when a client can only launch a subprocess, and it's what the local Nudge Agent companion uses. See below.
The two paths serve an identical tool surface — convex/mcpToolDefs.ts is
generated from this package by scripts/gen-convex-tooldefs.mjs, so they
cannot drift. Re-run it after changing any tool:
cd mcp && npm run build && node scripts/gen-convex-tooldefs.mjsHow it authenticates
Nudge's auth is Google-only with an @joygame.com domain restriction, and
the JWTs are signed by the Convex deployment itself — there's no key the MCP
server could re-sign with from a CLI. So the MCP uses personal access
tokens, bootstrapped over a one-shot localhost handshake:
- Run
npx -y @euqns/nudge-mcp setupin a terminal. - The CLI opens your browser to Nudge's
/mcp-setuppage (you'll sign in with Google if you aren't already). - Click Authorize. The page generates a token in your name and sends it
to the CLI over
http://127.0.0.1:<random-port>/callback. The token is never displayed, never typed, never logged. - The CLI writes
~/.nudge/credentialsat mode 0600 and prints the snippet you paste into your MCP client — that snippet contains no secrets.
Replay protection is via a fresh per-run state nonce; mismatched POSTs are
rejected with 403. The listener only binds to 127.0.0.1 and times out
after 5 minutes.
You can manage tokens (name, last-used, revoke) from profile → MCP tokens in the web app.
Nudge Agent chat
The same package can connect Nudge chat to a coding agent already installed and authenticated on the user's computer. It binds to loopback and opens Nudge with a one-time pairing secret in the URL fragment:
# Sign in to any or all supported CLIs first
codex login
claude auth login
cursor-agent login
# Run this from the repository the agent should inspect
npx -y @euqns/nudge-mcp@latest agentTo have the companion come back on its own after a reboot, install it as a login service from the same directory:
npm i -g @euqns/nudge-mcp && nudge-mcp service install agent --no-open--no-open keeps it from opening a browser tab at every login; the install
command waits for the service to come up and prints the pairing URL in the
terminal. Open it once. An installed service keeps its pairing (the loopback
port and bearer token) in ~/.nudge/pairings/<label>.json, owner-only like
everything under ~/.nudge, and reuses it on every start — rebuild restarts,
crash restarts, logout/login and reboot alike — so the browser's saved
connection keeps working: Nudge chat and the Agent View row show paired, not
reachable while the service is down and go back to connected on their own
once it is up, with no new URL and no Forget pairing. Later,
nudge-mcp service status agent prints the same URL again for pairing another
browser (only while the process is alive — a dead companion's URL is never
shown). To revoke a pairing, run nudge-mcp service restart agent
--reset-pairing: the old token is refused from then on and a new URL is
printed. A companion started by hand (nudge-mcp agent in a terminal) keeps
the old behaviour on purpose: its pairing lives in memory and a new run mints
a new URL, since a pairing that outlived the terminal would point at a port
nothing listens on. See Always-on services below for start/stop/restart/logs.
The companion checks Codex, Claude Code, and Cursor independently, starts when at
least one has a valid subscription login, and reports which CLIs are ready. One
pairing exposes every ready provider, so each chat can choose its own agent and
resume with that same agent later. --provider codex|claude|cursor remains an
optional initial preference. The companion does not accept or store provider API
keys. It keeps local filesystem access read-only and allows only the exact Nudge
origin that launched it. Authorized Nudge MCP tools can still update board data.
The Codex and Claude Code adapters supply their own Nudge MCP transport, so a
fresh computer does not need a manual mcp_servers.nudge entry in
~/.codex/config.toml. Each computer still needs the one-time
npx -y @euqns/nudge-mcp setup authorization so Nudge can identify that user.
Cursor reads MCP servers from its normal .cursor/mcp.json configuration, so
the Nudge MCP server must also be present there for board mutations.
Headless runner jobs isolate Cursor's MCP config to ~/.nudge/cursor-config/<job>
via CURSOR_CONFIG_DIR, but deliberately leave HOME/XDG_CONFIG_HOME untouched —
cursor-agent reads its login from there, not from CURSOR_CONFIG_DIR. A runner
host needs either cursor-agent login once (the usual case) or CURSOR_API_KEY
set in the runner's own environment; the CLI's own error message names both.
For Codex, the sidecar reads the installed model catalog and subscription account
metadata from the local Codex app server; it does not query provider quota or
rate-limit endpoints. Claude Code exposes its subscription model aliases and
reasoning efforts, while Cursor reads the models available to the signed-in account
from cursor-agent --list-models. Users can change models for every provider; Claude
Code and Codex also expose reasoning effort. A model or effort change starts a fresh
chat; prior chats are kept in browser-local history and resume their persisted local
agent session when reopened. The folder button browses directories on the machine
running the companion and switches new chats to the selected workspace; reopening an
old chat restores its saved workspace first. Per-turn token telemetry remains available
where the provider reports it.
When chat is opened from a board or canvas, Nudge sends the active board id, title, source, view, canvas id, and lightweight counts with each prompt. The agent therefore has the right board context without asking the user to repeat it; board contents are still read through the authorized Nudge MCP tools.
Create tools return a versioned structuredContent payload in addition to
their model-readable text result. create_board, create_card, and
create_canvas include the created entity's id, title, board id, kind, and
relative Nudge URL. Canvas node creation also points back to the selected
canvas when canvasId was explicit. Nudge Agent chat renders these
entities as deduplicated, clickable result cards; other MCP clients can consume
the same nudge.tool-result/v1 contract without parsing assistant prose.
Self-hosted runner CI/CD
Automatic npm releases
.github/workflows/publish-mcp.yml releases @euqns/nudge-mcp from main
using npm Trusted Publishing (OIDC). No npm login, OTP, or NPM_TOKEN is
needed for each release. The npm trusted publisher must specify GitHub owner
kubilayege, repository Nudge, workflow publish-mcp.yml, no environment,
and allow direct npm publish.
After a push affecting mcp/, the workflow installs locked dependencies,
tests the release tooling and MCP, and builds the package. semantic-release
then considers only commits that changed mcp/:
| Commit message | Release |
| --- | --- |
| fix(mcp): ... or perf(runners): ... | Patch |
| feat(mcp): ... | Minor |
| feat(mcp)!: ... or a BREAKING CHANGE: footer | Major |
| docs: ..., chore: ..., or no MCP changes | None |
The scope is descriptive; file changes determine whether a commit belongs to MCP. Use a Conventional Commit message for the squash-merge title as well. Release tooling changes also trigger the workflow, but do not themselves bump MCP. Any pending releasable MCP commits will still be included.
The release bumps package.json, package-lock.json, and src/version.ts
together in the CI checkout, rebuilds, publishes to npm, and creates an
mcp-vX.Y.Z Git tag and GitHub release. Version changes are not committed
back to main; tags and npm are the release history. Main's checked-in version
can therefore lag behind npm. The first run starts from the npm gitHead for
the last manual release, 1.33.0, rather than replaying the full repository
history as a new 1.0.0 release.
To preview: GitHub Actions → Publish MCP → Run workflow, select main,
and leave Preview the next release without publishing checked. Uncheck
it to release pending changes manually. A preview skips publishing and remote
tag creation; only a real publish verifies that npm accepts the configured
trusted publisher. Concurrent releases are serialized.
Release-tool tests can also run locally:
npm ci --prefix .github/release --ignore-scripts
npm test --prefix .github/releaseManually bump a development version
From either the repository root or the mcp/ directory, bump the MCP package
when a local development version needs changing:
npm run mcp:bump -- patch
# also accepts: minor, major, or an explicit version such as 0.15.0This updates mcp/package.json, both root version fields in
mcp/package-lock.json, and mcp/src/version.ts. It does not commit, tag, or
publish the package.
Runner tasks start directly in one writable implementation turn. Claude Code uses Opus/high, Codex uses GPT-6 Astra/high, and Cursor uses auto. The former planning, review, and up-front human-interference turns are not part of the task pipeline. Intake rewrites and comment questions remain separate read-only flows.
A runner works one job at a time by default. Start it with --concurrency N
(persisted by --save, capped at 8) to work up to N queued jobs at once —
each job is its own agent conversation, so queueing several cards behaves
like opening several chats. All of them share the runner's checkout: parallel
coding turns can mix uncommitted changes across tasks, so prefer tasks that
touch different areas, or run one clone per runner (--instance) instead.
One machine, one entry
The runner mints a machine id on first launch and keeps it in
~/.nudge/machine.json, independent of --save. Restarting, rebooting, or
re-running the setup command therefore re-registers the SAME machine rather
than adding another row to the board's runner list — the board just shows it
going online and offline. --instance <name> is the deliberate exception: it
registers as a second machine, for running two processes from one box.
While it runs the process heartbeats once a minute so the board can show it as
online, and reports offline on Ctrl-C. Startup probes each agent CLI (codex,
claude, cursor-agent) for version, sign-in state, and available models, and
sends the result with the registration — that is what the board's runner
settings render. Installed or signed in to an agent after startup? Press
Reload there and the running process re-probes and re-reports without a
restart.
Globally installed, supervised runners check npm's latest release on startup
and every five minutes. When a newer stable version exists, the runner stops claiming
jobs, finishes active work, updates its own npm prefix, and restarts. Failed
checks or installs are logged and retried at the next five-minute check. Set
NUDGE_RUNNER_AUTO_UPDATE=0 in the runner's environment to disable release
checks. Local checkouts and npx installs are not automatically upgraded.
Runners sharing an npm prefix serialize updates with a process lock outside
the package directory. The winner installs into a staging directory and checks
the real CLI, its imports, package version and bundled Codex executable before
activation. Waiting runners reuse that verified version. Failed installs leave
the running package intact; failed activation restores the previous build.
The previous package is retained beside the installation as
.nudge-mcp-previous until the next successful activation. Restart watchers and
supervisors wait for a complete, unlocked installation before loading it.
A running runner picks up new builds by itself. The process supervises a child
that watches its own installed code; when dist/ changes (a rebuild, or an npm
upgrade in place) it stops claiming jobs, lets in-flight jobs finish on the code
they started with, then restarts and re-registers under the same machine id.
Opt out with --no-auto-restart.
Always-on services: survive reboots, crashes and logouts
A runner or agent started from a terminal dies with that terminal, so a reboot takes the machine off the board until someone relaunches it. Install it as a per-user login service instead — the OS starts it at login and brings it back after a crash — and manage both kinds with the same verbs:
npm install -g @euqns/nudge-mcp # a stable install path; npx's cache may be pruned
cd /path/to/checkout
nudge-mcp service install runner --convex-url https://<deployment>.convex.cloud \
--token nrun_... --concurrency 8 --cwd . --save
nudge-mcp service install agent --no-open # the Nudge Agent companion, same idea
nudge-mcp service status # both services: state, version, machine, board / pairing URL
nudge-mcp service status runner --all # every installed runner instance
nudge-mcp service status agent --json # machine-readable
nudge-mcp service start runner|agent # bring an installed service up now
nudge-mcp service stop runner|agent # stop it (it still returns at next login)
nudge-mcp service restart runner|agent # e.g. after `npm i -g @euqns/nudge-mcp@latest`
nudge-mcp service logs runner|agent [-n 100] [-f]
nudge-mcp service uninstall runner|agent # stop it and remove the service for goodEvery verb takes --instance NAME for a second runner installed with
--instance. Everything after install runner is passed to the runner
unchanged; a relative --cwd is pinned to the directory you ran the command
in. Add --dry-run to print the service file without installing.
install and start wait for the process to report in and then print what
matters — the runner's registration (board, agents, checkout) or the agent's
fresh pairing URL — so you never open a log to find out whether it worked. If
it did not come up, the same command prints the exit code and the last log
lines inline, followed by the exact command to run next. status does the same
for each service:
runner com.nudge.runner
Service: launchd · running (pid 4242) · ~/Library/LaunchAgents/com.nudge.runner.plist
Process: nudge-mcp 1.24.0 · pid 4243 · started 3m ago · managed by launchd
Machine: mac-mini · id 0123abcd…
Board: Feedback Loop (org/repo) · https://<deployment>.convex.cloud
Checkout: /path/to/checkout
Agents: codex, claude · up to 8 jobs at once
Logs: nudge-mcp service logs runner (~/.nudge/logs/com.nudge.runner.log)How it works: both processes write ~/.nudge/state/<label>.json at startup
(version, pid, machine, board / listener + pairing URL). The CLI treats a
report as current only when its pid is alive and it was written after the
current boot, and install/start only accept a report written after the
command began — which is what keeps a stale pairing URL from ever being shown.
The agent service also keeps ~/.nudge/pairings/<label>.json (token + port,
mode 0600); a fresh process reads it before listening so the URL stays valid
across restarts. A pairing file that is malformed, a symlink, or readable by
anyone else is ignored and replaced by a fresh token. Service files carry only
PATH, HOME, locale and stable NUDGE_* settings — never the pairing
token, the runner-job id or the supervisor marker.
Platforms. macOS writes ~/Library/LaunchAgents/com.nudge.<kind>.plist
(mode 0600, since it can carry the token), loads it with launchctl, and logs to
~/.nudge/logs/. Linux writes ~/.config/systemd/user/nudge-<kind>.service,
enables it with systemctl --user, and logs to the journal; run
loginctl enable-linger $USER if it should start at boot before anyone logs in.
Both run the same node binary and installed copy you invoked, with your current
PATH baked in so git, gh, claude, codex, and cursor-agent resolve
exactly as they do in your shell. Once a runner is a service, the auto-restart
above still handles rebuilds; the service manager handles reboots and crashes.
Windows has no user-level equivalent here yet: every lifecycle verb prints the
Task Scheduler action to use instead, and status still reports a runner or
agent started from a terminal.
Known limitations. stop keeps the service installed, so it comes back at
the next login (use uninstall to remove it). launchd services live in the
GUI login session: they start when you log in, not at boot, and a stop from
one login session is undone by the next. The agent service keeps its pairing
across restarts and reboots (see Nudge Agent chat); only if its saved port is
taken by another process when it starts does it fall back to a free one, and
paired browsers then need the URL service status agent prints. A companion
started by hand mints a new pairing per run. A runner started with
--no-auto-restart outside a service has nothing to respawn it, so Agent View
does not offer it a remote restart.
Agent View. The web app's Agent View lists every registered machine with
health (online / running N jobs / stopped cleanly / offline with no heartbeat),
last heartbeat, nudge-mcp version, machine name and OS, board, checkout, and
how it is kept alive (managed by launchd, managed by systemd, or terminal).
Online machines get Reload (re-probe the agent CLIs) and, where the runner
reported a supervisor or a service, Restart (drain in-flight jobs, then
respawn on the new build). An offline machine cannot be reached from a browser,
so its row shows the exact local recovery command to copy — nudge-mcp service
start runner, or the relaunch-from-checkout command for a terminal runner. The
same section shows this browser's paired Nudge Agent (connected / paired but
unreachable / not paired) with its version, machine and workspace, and the
recovery command when it is down.
Verify a reboot by hand. The install-time checks are automated; the reboot itself is not. Once per machine, after installing:
nudge-mcp service status— both servicesrunning, the runner shows its board, the agent shows a pairing URL.kill -9 <runner pid>(theProcess:pid) — within ~10 sstatusshows it running again with a new pid, and the board's machine row never went offline for longer than a heartbeat.- Log out and back in (macOS) or
loginctl terminate-user $USERand log in (Linux) —statusshows both running. - Reboot — after login,
statusshows both running; the runner is online in Agent View; the Nudge Agent row and chat in the already-paired browser go back to connected by themselves, andservice status agentprints the SAME pairing URL as before (Pairing: … (persistent …)). nudge-mcp service stop runnerthenstart runner— the board row goes "Stopped cleanly" and back to "Online".
Record the date and OS version alongside the board's runner settings when you have done this; it is the one check the package cannot run for you.
During the work turn, get_card, update_card, add_comment, and subtask
completion use the job's board-scoped runner token. They are limited to the
active task and its direct subtasks, so task management does not depend on a
separate personal MCP login on the runner machine.
An @agent task created from the cloud-board composer is an intake job: it is
queued with its frozen title, instructions, assignees, labels, priority, due
date, and team before any card exists. The runner reads the linked checkout as
context, then performs one rewrite-only turn that creates exactly one card with
a polished title and structured Markdown description covering context, scope,
acceptance criteria, and verification. Intake checkout access is read-only and
its MCP surface allows only reads, create_card, and ask_human; it cannot
implement, edit, run CI, complete, move, comment, create subtasks, or request
review. Creation atomically writes the rich description, derived preview and
checklist fields, frozen metadata, and the job attachment. A cardless intake
cannot finish successfully.
Runner work turns expose two job-scoped GitHub Actions tools when the board has
a linked repository: list_ci_workflows discovers its workflows and
run_ci_workflow dispatches one with explicit workflow_dispatch inputs. The
repository is fixed to the board setting, and the tools are absent during the
read-only intake/question turns. The runner uses the machine's existing gh login; no
GitHub credential is stored in Convex.
By default, a dispatched workflow stays attached until GitHub reports its terminal result. A successful workflow that fulfills the card is treated as completed work: the agent sends the card to Review and finishes instead of asking the requester to verify the run separately.
By default a dispatch waits for completion. Queue, running, and conclusion
updates stream back to the card as a pipeline rail with the GitHub run link.
Only input names are persisted; input values remain local to the gh process.
Install and authenticate GitHub CLI on the runner machine before using it:
gh auth login -h github.comNudge MCP actions are pre-approved inside the companion because the SDK runs Codex headlessly and cannot display or answer a second approval prompt. The browser pairing and the user's revocable personal Nudge token authorize the server; Convex still applies the same board membership, role, validation, and rate-limit checks as the web app to every call.
Useful options:
nudge-mcp codex --cwd /path/to/project
nudge-mcp codex --app-url http://localhost:5173
nudge-mcp codex --port 47653 --no-openFor local development from this repository:
npm --prefix mcp run dev -- codex \
--app-url http://localhost:5173 \
--cwd ..Install
TL;DR
If you already have Node.js ≥ 18 on your machine:
npx -y @euqns/nudge-mcp setupThen paste the snippet it prints into your MCP client (see Configure). Done.
If node -v errors or prints something older than v18, follow the section for your OS below first.
Step 1 — Install Node.js
npx and npm ship with Node.js, so this is the only prerequisite. Pick one option per OS.
macOS
Option A — nvm (curl, no admin, lets you switch Node versions later):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# close and reopen your terminal (or: source ~/.zshrc)
nvm install --ltsOption B — Homebrew:
# install brew first if you don't have it
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install nodeOption C — official installer: download the .pkg from https://nodejs.org (LTS), double-click, follow the prompts.
Windows
Option A — winget (built into Windows 10/11, open PowerShell):
winget install OpenJS.NodeJS.LTSOption B — official installer: download the .msi from https://nodejs.org (LTS) and run it. Leave "Add to PATH" checked.
Option C — nvm-windows (multi-version management): grab the latest nvm-setup.exe from https://github.com/coreybutler/nvm-windows/releases, install, then:
nvm install lts
nvm use ltsAfter install, close and reopen PowerShell/CMD so
nodeandnpxare onPATH.
Linux
Option A — nvm (curl, recommended):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc # or ~/.zshrc
nvm install --ltsOption B — distro package manager (versions vary, confirm node -v ≥ 18 afterwards):
# Debian / Ubuntu
sudo apt install -y nodejs npm
# Fedora / RHEL
sudo dnf install -y nodejs
# Arch
sudo pacman -S nodejs npmIf apt gives you something older than 18, use NodeSource:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejsStep 2 — Verify
node -v # v18.x or higher
npx -v # any version numberIf either command is "not found", restart your terminal first. On Windows, sign out and back in if PATH still doesn't pick up.
Step 3 — Run setup
npx -y @euqns/nudge-mcp setupFirst run downloads the package (~5 seconds, one-time), spins up a localhost listener, and opens your browser to /mcp-setup. Sign in with Google, click Authorize, and the CLI writes ~/.nudge/credentials and prints the JSON snippet for your MCP client.
Corporate / locked-down machines: if
npxcan't write to its cache or hits a proxy, setnpm config set cache "$HOME/.npm-cache"and try again. If your network blocks the public npm registry, you'll need an internal mirror or the standalone-binary route (open an issue).
Local development of this repo
Skip the npm package and run from source:
cd mcp
npm install
npm run build
node dist/index.js setup # uses the default app URLConfigure
After setup succeeds, copy this into your MCP client. The token lives in
~/.nudge/credentials, so the client config is the same on every machine
and contains no secrets:
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"nudge": {
"command": "npx",
"args": ["-y", "@euqns/nudge-mcp"]
}
}
}Restart Claude Desktop. The Nudge tools appear under the tools menu.
Claude Code
claude mcp add nudge npx -y @euqns/nudge-mcpCursor
.cursor/mcp.json:
{
"mcpServers": {
"nudge": {
"command": "npx",
"args": ["-y", "@euqns/nudge-mcp"]
}
}
}Overrides
If you need to point at a non-production deployment (PR preview, local dev
against convex dev), setup accepts --app-url:
npx -y @euqns/nudge-mcp setup --app-url http://localhost:5173You can also bypass the credentials file entirely by setting
NUDGE_CONVEX_URL and NUDGE_TOKEN in the MCP client's env block —
useful for CI or containers.
Tools
| Tool | What it does |
|---|---|
| whoami | Identity bound to the token (id, email, displayName, team). |
| list_boards | Every board the user is a member of. |
| get_board | Board layout: lists, labels, members, your role. |
| create_board | New board (caller becomes owner, default Backlog/In progress/Review/Done columns). |
| list_lists / create_list | Read or add columns. |
| list_cards | All cards on a board (subtasks hidden by default). |
| my_cards | All cards across all boards where the caller is assigned, soonest due first. |
| get_card | One card incl. its description doc. |
| create_card | New card in a list (pass parentCardId for real, independently tracked subtasks). |
| update_card | Patch title/icon/dueDate/assigneeIds/labelIds/priority/listId/description. Description accepts Markdown and becomes native rich editor content; pass 'none' to clear dueDate/priority. |
| move_card | Drop into another list. Auto-completes if landing in a Done list. |
| set_card_completed | Flip the complete bit explicitly. |
| delete_card | Hard-delete with cascade (subtasks, comments, activity, notifications). |
| create_label | Board-scoped label. |
| list_comments / add_comment | Comment threads on a card. @username mentions notify. |
| list_members | Board members (use the returned ids for assigneeIds). |
| find_user_by_email | Resolve an email to a user id — handy before assigning. |
| my_notifications | Recent notifications (assigned / mentioned / commented / moved / updated). |
| update_runners | Update @euqns/nudge-mcp on every active self-hosted runner and restart them gracefully. plan (default) previews; apply needs confirm: true; status re-reads a run. Runners finish in-flight jobs first; the result lists per-runner success/failure and which interrupted jobs must be rerun. Locally it also updates this server's own global install. |
Boundary notes
- The server runs every call as the user the token belongs to — board access
is checked against
memberships, the same table the web app reads. There is no "elevated" mode. - Convex actions run on the same backend as the app, so MCP-driven changes appear in real time in any open browser tab.
- The token is sent as a regular argument over the wire (HTTPS to Convex). Anyone reading the JSON payload would see it, which is why we hash on the server and only ever return the plaintext at creation time.
Development
npm run dev # tsx watcher — useful with `node --inspect` on the MCP host
npm run build # tsc to dist/The server doesn't reload tools at runtime; restart the MCP client when you
change tool definitions in src/index.ts.
Local Codex runner configuration
The runner regenerates ~/.nudge/codex-home/config.toml for each job.
Put machine-owner TOML overrides in ~/.nudge/codex-overrides.toml instead;
they are appended after the generated model defaults. This can explicitly
trust a project so Codex loads its .codex/config.toml and project MCP servers:
[projects."/absolute/path/to/project"]
trust_level = "trusted"Project workspace networking can be configured with
[sandbox_workspace_write] network_access = true in that project's config.
The runner still sets the job sandbox mode and approval policy explicitly.
Overrides are read at each job startup; already-running turns retain their
existing settings. Do not repeat generated top-level model/auth keys in the
TOML overrides. Desktop MCP servers remain isolated unless explicitly enabled.
T3 Code desktop runner threads
When T3 Code is running on the runner's machine, new Codex jobs on macOS and Linux execute through T3 itself. Look for Nudge · running · Board · task … under the matching checkout's project. Open the thread to see the prompt, streaming responses, and tool activity in the conversation pane. Nudge receives the final answer and token usage, and updates the title with the job status.
Nudge resumes and steers these jobs in the same T3 conversation. Cancelling or steering a job in Nudge interrupts its T3 turn. Keep using Nudge for job questions, review, and scheduling; T3's composer also operates the underlying conversation, but does not create or reopen a Nudge job. Completed conversations remain readable in T3. Their dedicated Nudge provider is removed after completion so it disappears from both the model picker and Providers settings; resume the job through Nudge to recreate it automatically. Cleanup also removes previously disabled providers for completed jobs. Active jobs and jobs waiting for human input keep their provider.
The Codex bridge preserves the runner's Nudge MCP tools and sandbox: intake and
question turns stay read-only, ordinary tasks use workspace-write, and jobs that
explicitly need repository/network actions retain their existing access. T3 gets
a separate Codex provider instance per conversation using Nudge's isolated Codex
home. Job credentials live in an owner-only context under ~/.nudge/t3-native,
are removed when execution ends, and are never put in T3 titles or prompts.
Claude, Cursor, Windows, and conversations originally started by an external CLI continue using the terminal log view. Their previous conversation IDs are kept with the original engine. Existing log-only threads and jobs already running are not converted or backfilled.
Discovery reads ~/.t3/userdata/server-runtime.json (or the dev variant) and
only connects over loopback. On macOS it finds T3 Code or T3 Code (Nightly) in
/Applications or ~/Applications; other installs need a t3 executable on
the runner's PATH. The installed CLI issues an expiring local session and
revokes it afterward (24 hours maximum for native execution, five minutes for
status updates). T3 API credentials are held in memory. Windows gets the fallback
thread and status updates, with its log saved locally.
| Runner environment variable | Purpose |
| --- | --- |
| NUDGE_T3=0 | Disable automatic T3 threads. |
| T3CODE_HOME=/path/to/.t3 | Find a nonstandard T3 data directory. |
| NUDGE_T3_CLI=/path/to/t3 | Use a specific installed T3 CLI or executable wrapper. |
T3 must be open before the job starts. Missing T3 is silently skipped for new
jobs. A native conversation must resume in its original T3 environment; if that
environment is unavailable, the job reports an error. Once a native turn has
been submitted, the runner never falls back to a second CLI agent. Verified
against T3 Code Nightly 0.0.41-nightly.20260908.1400; native execution requires
its HTTP orchestration and provider settings RPC interfaces. Fallback JSONL
logs are owner-only under ~/.nudge/t3-logs and
retain up to 8 MiB of live output per run, followed by its final result.
They remain available after completion and can be removed when no longer needed.
Runner output files
A coding turn can optionally write the JSON manifest at the absolute path supplied
in its prompt (also NUDGE_RUNNER_ARTIFACT_MANIFEST). It is an array of
{ "path": "report.md", "contentType": "text/markdown", "name": "report.md" }.
Paths are absolute or relative to the checkout; name defaults to the basename.
Only explicitly listed files are uploaded. Engine integrations may instead return
EngineResult.artifacts with the same descriptors. No manifest means no uploads.
The runner uploads before runners:finish using
uploads:getRunnerArtifactUploadUrl, a direct signed S3 POST, then
uploads:confirmRunnerArtifactUpload. Both actions require the runner token,
runner ID and job ID; confirmation also requires the returned upload ID. The
server checks token ownership, board, active lease and job attempt, verifies the
CDN object, and persists { name, contentType, size, url, key } in job artifacts.
Card job history and job details show Output files with names, types and download
links. Text (including JSON, Markdown, CSV and HTML source), images and videos
open in an in-app preview popup. Video uses native browser controls; codec support
depends on the browser. Other types remain download-only.
Limits: 20 files per job across attempts, 10 MiB per file (empty files allowed), 255 characters for names/types, and a 64 KiB manifest. Types are opaque metadata, including custom types and parameters; bytes are never converted. Files are served as attachments. Previews fetch the bytes and use local blob URLs for media, so attachment disposition does not force a download. The CDN must allow cross-origin GET requests from the app origin, as for Docs text previews. Text previews show at most the first 256 KiB with a truncation notice; HTML is displayed as text, never executed. Download always links to the original file. URLs use the existing public CloudFront attachment model: anyone with a URL can read it.
Confirmed files survive success, failure, cancellation and retries. A failed upload fails an otherwise successful run, retaining earlier confirmed files. Unconfirmed reservations expire after 15 minutes and their S3 objects are deleted; confirmed artifacts live as long as the job output. Deploy the updated Convex backend before updating runners; existing S3/CloudFront configuration is reused.
Canvas HTML and live editor embeds
create_html_node and update_html_node accept self-contained HTML/CSS/JS
with inline assets (up to the existing ~900KB limit). They do not install
Canva or another hosted level editor. URL-only input, nested iframe/object
embeds, external resource attributes, CSS imports/URLs and common network APIs
are rejected before saving. Failed creation adds no Canvas node; failed updates
preserve the previous HTML. There is no network permission or provider override.
For example, html: "https://www.canva.com/design/ID/TOKEN/view", the tokenless
form, malformed-token URLs, an iframe wrapper, a CDN script, and a localhost
fetch all produce an unsupported-content error. Supply an offline editor with
inline scripts, styles and data instead. Ordinary outbound links can still open
in a separate tab; that is navigation, not a live Canvas embed.
Input checks provide diagnostics, not a JavaScript security proof. The actual preview uses an opaque iframe (no same-origin, forms or top-navigation grants) and an injected CSP before authored content: no network connections, external scripts/styles, nested frames, objects, base URLs or form submissions. Inline scripts/styles and data/blob images/media remain available. Existing unsupported HTML shows an explanation in Canvas. Dependencies constructed dynamically are blocked by CSP and display a warning in the preview. A returned node id confirms saved content, not successful execution of arbitrary JavaScript. Verify the preview before reporting a working editor; documentation alone is not installation.
Canva rich document embeds are a separate, provider-validated preview flow.
They canonicalize supported HTTPS Canva design links to /view?embed, preserve
share tokens, and use Canva's provider-specific sandbox. This does not authorize
network access inside Canvas HTML nodes or promise live editing. Tokenless public
links remain parseable; missing/expired share access can still be refused by
Canva. Syntax validation cannot verify a token's validity or the viewer's access.
Malformed paths, credentials, non-HTTPS URLs and unexpected ports are rejected.
