Skip to content

feat: add MCP connections and browser auth, skill and plugin setup - #37

Merged
TaewoooPark merged 44 commits into
mainfrom
mcp-adapter
Sep 26, 2026
Merged

TaewoooPark merged 44 commits into
mainfrom
mcp-adapter

Conversation

@TaewoooPark

@TaewoooPark TaewoooPark commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

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

  • Connect stdio, Streamable HTTP and legacy SSE servers behind the single mcp tool, 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.
  • Eight built-in presets: Context7, GitHub, Playwright, Filesystem, Hugging Face, OpenAI Docs, Tauri and Gmail. /mcp browses them offline, shows each one's prerequisites and registers, enables and connects it in the same session; motif mcp presets and motif mcp install do the same from the shell. Filesystem requires an explicit directory, reviewed as its canonical path.
  • CLI registration (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.
  • Standard MCP OAuth: protected-resource and authorization-server discovery, PKCE, dynamic or pre-registered clients, a loopback callback, refresh and logout. The login panel and interactive terminals show the authorization URL, and --no-browser covers 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.
  • GitHub delegates to the account saved by GitHub CLI. Motif keeps a private consent marker while gh owns the token, so later processes sign in without another login. Motif passes gh the variables that locate its login (config directory, Windows profile, Linux keyring bus) and ignores token overrides and GH_HOST.
  • Remote calls stay honest. A server's JSON-RPC rejection reaches the model as a completed error. A dispatched write with an unknown outcome is never replayed automatically: in a chat Motif asks the person first, and read-only tools stay callable. A server that fails to start backs off from 30 seconds up to 5 minutes, with visible connection progress, and an explicit connect retries at once. Unchanged MCP runtime context is not repeated every task.

Skills and plugins

  • Claude Code and Codex skills load with their YAML frontmatter, arguments, ${CLAUDE_SKILL_DIR} and ${CLAUDE_PLUGIN_ROOT}, invocation policies (disable-model-invocation, user-invocable, Codex allow_implicit_invocation) and supporting resources read only inside the loaded skill.
  • motif skills add|import|marketplace|installed|update|remove installs from links, repositories, folders, Claude Code, Codex and .claude-plugin or .agents marketplaces into immutable managed snapshots, with receipts, namespaces and user or project scope.
  • motif plugins add|inspect|connect reviews a package's MCP servers and connects them after approval. Hooks, agents and commands written for other hosts stay inactive.
  • The built-in mcp-setup, skill-setup and plugin-setup skills handle clear Korean and English setup requests. Requests to edit documentation or write tests no longer pull them in.
  • Five workflow plugins add six skills: library documentation, browser testing, GitHub workflow, frontend quality, React composition and MCP building. A bundle tied to an MCP preset joins the model's skill index only while that server is enabled; its slash command works either way.

Sessions

  • One-shot runs stopped by Ctrl-C, SIGTERM or SIGHUP exit with 130, 143 or 129, restore the terminal and stay resumable. A run stopped in the middle of a command still refuses to resume.
  • A resume selects the recorded tool set and validates prompt/tool compatibility, including when MCP servers were added after recording. An incompatible prompt or tool schema is refused.
  • The MCP SDK loads with the first connection instead of at startup.

Validation

  • Local: typecheck, build and tool-schema lint pass, and 94 files / 1,636 tests pass on the integrated 0.4.0 candidate. Integration with feat(cli): add settings and usage panels with compact tool output #36 preserves dashboard, MCP, approval and masked-input panels; regression coverage exercises their transitions. GitHub device login also honors --no-browser.
  • CI on the final head: TypeScript on Node 20 and 22, linux-arm64, Python and goldens.
  • An actual PTY shown in xterm.js 6.0.0, running the built CLI with a temporary home and local model, MCP and OAuth fixtures: /mcp browsing 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.
  • Built-CLI resume checks: Ctrl-C, SIGTERM and SIGHUP resume; Ctrl-C during bash is refused; a session resumes after an MCP server is added; a 0.3.4 session is refused.
  • Earlier live checks, before the review fixes: real GitHub MCP discovered 45 tools and read PR metadata in two processes without another login, and Motif logout blocked access while GitHub CLI stayed signed in. Motif-3 used GitHub MCP and the bundled workflows. Context7, Playwright, Filesystem, OpenAI Docs, public Hugging Face and installed-package connections were exercised, and the packed CLI with its 17 bundled asset files was installed locally.

Boundaries

  • Provider registration, account permissions and branch protections still apply. Standard OAuth credentials use private files under ~/.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.
  • Gmail remains a conditional preview that needs separate token setup, and Tauri needs its application bridge. Figma provider registration returned 403 during a live attempt and is not claimed. Modern multi-round input, MCP Apps and other non-tool capabilities remain outside this implementation.
  • External configuration edits and newly installed skills need a session restart; preset setup in /mcp applies 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.
  • For the release notes: sessions interrupted on 0.3.4 cannot be resumed after upgrading, because the skill tool gained an arguments field. Startup is about 24 ms slower and the single-file bundle is 2.5 MB; splitting it is left for a separate change.
  • Natural-language setup remains model-dependent. Requires Node 20.3+. The integrated candidate sets the CLI version to 0.4.0. Publication is a separate tag-triggered release after merge and CI verification.

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.
@TaewoooPark TaewoooPark added enhancement New feature or request area:mcp Model Context Protocol integration area:skills Skill discovery, installation, compatibility, and execution labels Sep 25, 2026
@TaewoooPark TaewoooPark changed the title feat: add MCP client adapter, connection controls, and setup skill feat: add MCP connections and presets, skill compatibility and marketplace imports Sep 25, 2026
@TaewoooPark TaewoooPark changed the title feat: add MCP connections and presets, skill compatibility and marketplace imports feat: add MCP connections and browser auth, skill and plugin setup Sep 26, 2026
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.
@TaewoooPark

Copy link
Copy Markdown
Owner Author

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 /quit exit. No new blocking regression was found.

The integration commit resolves the shared dashboard/MCP panel API and preserves approval/secret precedence. It also forwards --no-browser through GitHub device login and adds regression coverage. Squash merging this maintainer-authored ecosystem integration for 0.4.0. The remaining acceptance criteria in #22/#23 and existing permission/cancellation issues remain open; publication follows the verified main CI and version tag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:mcp Model Context Protocol integration area:skills Skill discovery, installation, compatibility, and execution enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant