Repository navigation
feat: add MCP connections and browser auth, skill and plugin setup - #37
Conversation
Add SDK transports, trusted configuration imports, exact schema validation, bounded discovery and result views, and CLI lifecycle integration. Preserve the fixed native tool prefix and object arguments, with task literal retrieval and explicit tool-name priority. Validate with 862 tests, real Motif-3 tasks against Filesystem, Memory, Playwright and OpenAI Docs, negative-result checks, PTY cancellation, and Node 20.3 distributable smoke tests. Record comparison outcomes and known model failures in docs.
Ship five built-in workflow plugins and six skills. Show available MCP presets beside registered servers and support reviewed setup in the running session. Delegate GitHub MCP authentication to the saved GitHub CLI account with persistent local consent, human-only browser login, and Motif-only logout. Update product documentation and cover catalog, lifecycle, authorization and resume behavior.
"/debug it doesn't start" failed with "skill arguments contain an unterminated quote" and the task never ran, although the debug skill uses no positional placeholders. Every invocation tokenized its input first. Tokenize only when the body uses positional or named placeholders, and treat an apostrophe between letters as prose rather than a quote. A genuinely unterminated quote in positional input is still rejected.
A server that answered tools/call with a JSON-RPC error was reported to the model as "execution unknown · connection_error", the healthy connection was marked failed, and the identical call stayed blocked for the session. The server's message was lost, so the model could not correct its request. Record our own tools/call ids at the send boundary and observe JSON-RPC error replies before the SDK handles them. Such a reply proves the server received and answered the call: return it as a completed isError result with the bounded, control-stripped message, and leave the connection ready. Local timeouts, cancellation and result validation after a reply stay unknown and are still never replayed automatically.
A dispatched call that was cancelled or timed out blocked every identical call for the rest of the session. The message said to "reconcile the outcome manually", but nothing could clear the entry short of restarting, and read-only tools such as browser_snapshot were blocked the same way. Read-only tools are no longer recorded, so they can be called again. In an interactive session the person is asked before an identical write is repeated; approval runs it once and a completed repeat clears the entry. One-shot runs still refuse it, and the refusal still happens before any connection or credential work. The message and the troubleshooting row now describe what actually happens.
Each chat task appended the complete MCP runtime context (servers, control schemas and selected tool cards) after the task, and history kept every copy. Seven tasks against two small servers carried seven copies: 28 KB of a 49 KB request, before any real catalog. History still keeps each task's context, so the cached prefix never changes. When servers, controls and browser guidance match the latest complete context already in the conversation, the next task sends a one-line update plus only the schemas selected for that task. A restart that drops history (compaction or a channel switch) re-appends the complete context through the new contextOnRestart option.
Every interactive session listed all six bundled workflow skills to the model, although five of them work through Context7, Playwright or GitHub MCP servers that most sessions never enable. With the always-present mcp tool and its guidance, the default prefix grew from 7.5 KB to 11 KB. A bundle that declares mcpPresets now joins the model's skill index only while one of those servers is enabled. The registry reads the live MCP configuration, so a preset enabled through /mcp adds its skills from the next task. Slash commands still run them, the mcp tool and its guidance stay so a person can ask for and add servers mid-session, and corpus-spec emits the no-MCP prompt. The default prefix is back to 10 KB (under 3k tokens); the README figures now say so.
…gress Every chat task prepares the MCP catalog first. A server that timed out or exited during startup was restarted by every task and every tool lookup, so each task waited its whole startupTimeoutMs again (10 s by default, 60 s for the npx presets) with nothing on screen but "esc to interrupt". After a startup failure, automatic use waits 30 s, doubling to 5 minutes, and a tool call on that server fails fast with server_unavailable and a hint. A person's /mcp connect or reconnect tries immediately, a completed connection clears the backoff, and a cancellation does not count. The chat shows "Connecting to MCP…" while a server that is not ready is starting.
After choosing Sign in, the chat showed only "esc to interrupt": nothing said a browser had opened or that Motif was waiting. Where no browser could be launched (SSH, headless Linux), `motif mcp login` failed at once with "MCP authorization failed. Check the provider configuration", and the URL was never shown, so sign-in could not be completed at all. The chat's login panel now shows the authorization URL, and an interactive terminal prints it too. When the URL has been shown, a failed browser launch no longer aborts the login; otherwise the error is browser_open_failed with a remedy. `--no-browser` prints the URL without launching anything. The URL stays out of the transcript, and a command run without a terminal, as the agent's shell runs it, never prints it.
"Add the GitHub MCP link <url> to the README", its Korean equivalent and "Add <url> to the list of MCP examples in docs/mcp.md" attached the mcp-setup procedure, and "Add a test for the plugin loader using <url> as fixture" attached plugin-setup. In a repository about MCP, such as this one, ordinary edits then carried instructions to register servers. A request whose destination is a README, CHANGELOG, docs/ path or markdown file, or that writes a test or fixture, is no longer read as a setup request. Generic words such as "documentation" or "in this project" are left alone so real installation requests still route.
Every edit, including a preset installed from /mcp, re-serialized the whole file from parsed servers: object-form "servers" became an array, parse-time defaults such as protocol: "legacy" and ids were written into every entry, and a relative cwd was rewritten as an absolute path, which breaks a project configuration checked into a repository. Edits now keep the document's servers shape and version, copy entries that did not change verbatim, and apply only the fields that actually changed. New entries are written as given (keyed by id in object form).
Argument validation matched $schema literally, so a tool declaring "http://json-schema.org/draft-07/schema" (no "#"), draft-06, draft-04 or "https://json-schema.org/draft/2020-12/schema#" could never be called ("unsupported_schema"). The https draft-07 spelling was accepted by name but Ajv then failed to find its meta-schema, with the same result. Recognize the http/https and "#" variants of drafts 04, 06, 07, 2019-09 and 2020-12, and compile with the id Ajv registered for each meta-schema. Drafts 04 and 06 validate under draft-07 rules, which extend them; a draft-04 form draft-07 cannot express, and unknown dialects, still fail closed.
Every MCP form field was typed as bullets, even an enum choice ("Choices:
a, b") or a count, so a person could not see what they were about to send.
Secret-looking fields are already declined before the form opens.
Choices, numbers and arrays of choices are now visible while typed.
Free-form text stays hidden by default, since a server may ask for private
details, and none of it reaches the transcript.
A tool marked anthropic/requiresUserInteraction showed two identical "Call server/method?" prompts in a row in ask mode: the ordinary tool prompt, then the server's required per-call confirmation, whose second option meant the same as the first. When a person approves the call in the ordinary prompt, that answer now serves as the per-call confirmation for that same call and is discarded when it ends. Auto mode and "don't ask again" still show the server's required prompt once. Root and subagent executors share one invoker.
The /mcp filesystem setup showed the path exactly as typed ("Read/write
root: ../fsroot"), although read/write access is granted to whatever that
resolves to, symlinks included. "~/project" was not expanded and failed only
after the review.
Expand ~, resolve the path against the project and follow symlinks before
the review, show that canonical directory, and save the same path. A path
that is not an existing directory is refused before anything is reviewed
or written.
The /mcp panels wrapped text one character at a time, splitting words
("Anonymous access h / as service limits") and command flags ("motif mcp
install gmail --" / "token-env GOOGLE_ACCESS_TOKEN"), so the setup hints a
person should copy were broken across rows.
Add wrapWords, which breaks at spaces, keeps a line's indentation on its
continuation rows and hard-wraps only a word wider than the row, such as a
long URL. The manager, setup prompts and login panel use it; transcript
wrapping is unchanged.
Pressing d (disconnect) or logout on a built-in preset listed as "available" in /mcp answered "Unknown MCP server", though the preset was on screen. Say that it is not set up yet and how to set it up.
Every command, including `motif --help` and `motif sessions`, evaluated the bundled MCP SDK and compiled four Ajv validators before doing anything: startup rose from about 34 ms to 70-100 ms. Move the SDK-free primitives (errors, deadline, operation budget and the auth/elicitation types) into operation.ts. The manager imports the connection class when it opens the first connection, OAuth helpers load on first login or refresh, and the host control validators compile on first use. esbuild keeps the single-file bundle and initializes these modules lazily; `motif --help` now takes about 58 ms. Most of the remaining gap is parsing the larger bundle, which only splitting it into several files would avoid.
Seventeen imports and one dynamic import in the CLI reached into ../../mcp/src/*.js directly, bypassing the workspace alias every other package uses (tsconfig paths, vitest and the esbuild bundle). The package entry already exports each of these names, and after the previous commit importing it no longer loads the MCP SDK at startup.
The mcp-setup, skill-setup and plugin-setup instructions and descriptions were written in Korean, unlike the rest of the repository. Translate them without changing any step, command or policy. The quoted Korean words that map a request to user scope stay as examples. The character-based token estimate grows with English, so mcp-setup and skill-setup now declare a 1500-token budget; they are still attached only on demand.
…ential Free-form form answers were always typed as bullets, so a person could not check a name, description or message before sending it. Show them, and keep masking fields whose name, title or description names a credential: a token, OTP, PIN, passcode, passphrase, credential, or an API, access, private, SSH or signing key. Forms whose field names ask for a password, secret, access token or API key are still declined before they open, and answers still never reach the transcript.
The new SIGINT, SIGTERM and SIGHUP handlers end an interrupted one-shot run with 130, 143 or 129 and restore the terminal, but the aborted loop then wrote a root scope_end, and resume refused the session as already ended. Before the handlers the process simply died and the journal stayed resumable, so Ctrl-C and a dropped SSH session, the usual reasons to resume, stopped working. Once a signal arrives, stop writing root checkpoints and skip the root scope_end, leaving the journal where the old process exit left it. A run stopped while the model was answering resumes from its last checkpoint; a run stopped while a command ran keeps that command's in-flight checkpoint, so resume still refuses to guess its outcome.
A one-shot run recorded without MCP servers has eight tools. Adding any server afterwards made resume pick nine and refuse the session because the tool schemas changed; a preset server that gates a bundled workflow skill also changed the skill index inside the system prompt. When resuming, keep the recorded tool set. A run without the mcp tool now indexes no MCP-gated skills and gets no MCP runtime context, which also keeps the model from reading about a tool it cannot call. New runs choose their tools as before.
…ntial gh auth token ran with only the base environment allowlist, which also removed the variables gh uses to find its login: GH_CONFIG_DIR, XDG_CONFIG_HOME and, on Windows, APPDATA and USERPROFILE, plus the session bus a Linux credential store needs. A person with a custom gh config directory, a Windows profile or a Linux keyring was told to run gh auth login even though gh was logged in, and logging in again could not help. Pass those location variables through. Device login through gh auth login uses the same environment, so the login it writes is the one later reads find. Token overrides (GH_TOKEN, GITHUB_TOKEN and the enterprise tokens) and GH_HOST are still removed. Motif reads only MOTIF_* keys from a project .env and never exports them, so these variables come from the person's own environment.
The READMEs covered MCP and skills only briefly, and the Korean one had fallen behind: it left out the GitHub preset and the workflow bundles and still said every registration needs a relaunch. Give both READMEs matching MCP servers and Skills sections. They list the eight built-in presets with their transport and requirements, and every way to add a server: /mcp, a preset from the shell, a stdio, HTTP or SSE registration, a Codex or Claude import, or a request to mcp-setup. They explain browser OAuth and GitHub CLI sign-in, Claude Code and Codex skill compatibility, and installing skills by request, from links, repositories and folders, from Claude Code or Codex, from marketplaces and from plugin packages, or writing one. The layouts now name packages/mcp and ~/.motif/auth, and the command tables list /plugin-setup and /mcp login.
|
Reviewed the integrated 0.4.0 candidate at aa62c21, including the settings UI already merged in #36. Type checking, build, tool-schema lint and all 1,636 tests in 94 files pass. The exact head also passed all five CI jobs: Node 20, Node 22, Linux ARM64, Python and goldens: https://github.com/TaewoooPark/Motifcode/actions/runs/36240506216 The built CLI was exercised in a visible xterm.js 6 terminal backed by a real PTY, with an isolated home and local model/MCP fixtures: all four dashboards, the MCP catalog, 100x32 and 60x24 resizing, a delayed write approval arriving while Config was open, denial restoring the dashboard, masked login/cancellation, an approved MCP echo call, Korean/emoji draft editing, and clean The integration commit resolves the shared dashboard/MCP panel API and preserves approval/secret precedence. It also forwards |
Motifcode can connect external MCP tools, install compatible Claude Code and Codex skills and plugins, and set up services from the terminal UI. This preserves Motif-3's canonical tool order while adding bounded discovery, reviewed installation, browser authorization and persistent GitHub access.
Related to #23. The remaining MCP roadmap stays open.
Behavior
MCP
mcptool, so the model's tool list does not change. Arguments are checked against each server's original JSON Schema (draft-04 through 2020-12), results are bounded and retrievable in portions, and cancellation and child cleanup are handled./mcpbrowses them offline, shows each one's prerequisites and registers, enables and connects it in the same session;motif mcp presetsandmotif mcp installdo the same from the shell. Filesystem requires an explicit directory, reviewed as its canonical path.motif mcp add), Codex/Claude configuration import (a preview that writes disabled entries and never copies inline secrets) and explicit trust hashes for project files. Edits keep the configuration's authored shape.--no-browsercovers SSH and headless machines. Legacy form and URL elicitation stays with the person; free-text answers are visible while typed unless the field looks like a credential.ghowns the token, so later processes sign in without another login. Motif passesghthe variables that locate its login (config directory, Windows profile, Linux keyring bus) and ignores token overrides andGH_HOST.Skills and plugins
${CLAUDE_SKILL_DIR}and${CLAUDE_PLUGIN_ROOT}, invocation policies (disable-model-invocation,user-invocable, Codexallow_implicit_invocation) and supporting resources read only inside the loaded skill.motif skills add|import|marketplace|installed|update|removeinstalls from links, repositories, folders, Claude Code, Codex and.claude-pluginor.agentsmarketplaces into immutable managed snapshots, with receipts, namespaces and user or project scope.motif plugins add|inspect|connectreviews a package's MCP servers and connects them after approval. Hooks, agents and commands written for other hosts stay inactive.mcp-setup,skill-setupandplugin-setupskills handle clear Korean and English setup requests. Requests to edit documentation or write tests no longer pull them in.Sessions
Validation
--no-browser./mcpbrowsing and preset setup from 120×34 down to 40×12, a symlinked Filesystem root reviewed as its canonical path, a server-gated tool asking once, the unknown-outcome repeat prompt (declining sent nothing), a form with visible answers and a masked token field, connection progress and backoff with a slow server, and OAuth completed from the URL shown after the browser failed to launch.bashis refused; a session resumes after an MCP server is added; a 0.3.4 session is refused.Boundaries
~/.motif/auth; the GitHub provider keeps only local delegation consent there. Claude and Codex credentials are not transferred, and a local auth-status record is not proof of current remote access./mcpapplies at once. Enabling a preset that has a workflow bundle adds its skill to the system prompt, so the next request misses the prompt cache once.skilltool gained anargumentsfield. Startup is about 24 ms slower and the single-file bundle is 2.5 MB; splitting it is left for a separate change.