Skip to content

feat: make an MCP server the agent interface, serving its own skill - #23

Open
oveddan wants to merge 1 commit into
mainfrom
claude/issue-12-mcp-server-8a0a64
Open

oveddan wants to merge 1 commit into
mainfrom
claude/issue-12-mcp-server-8a0a64

Conversation

@oveddan

@oveddan oveddan commented Sep 22, 2026

Copy link
Copy Markdown
Owner

Closes #12.

Decision

  • No standalone binary. Node 20+ is an acceptable requirement, so the native-addon bundling and macOS signing/notarization work isn't justified. Reasoning recorded in docs/agent-skill.md.
  • An MCP server is the primary agent interface. mft-config mcp runs a stdio server; the CLI stays for people running one-off commands.

Install is now one line per host:

claude mcp add -s user mft-config -- npx -y mft-config mcp
codex mcp add mft-config -- npx -y mft-config mcp

What the server does (src/mcp.ts)

Skill served over MCP (src/skills.ts)

  • Implements the MCP Skills Extension (SEP-2640, io.modelcontextprotocol/skills): each skill file is a skill://mft-configurator/… resource, and skills/list / skills/get return frontmatter plus per-file SHA-256 digests and sizes computed from the served bytes.
  • The skill moves from .claude/skills/ to skills/ and ships in the npm package as the single copy, rewritten around the tools.
  • Hosts without the extension still reach it: the server instructions point at skill://mft-configurator/SKILL.md.
  • The extension's own implementation list does not name Claude Code or Codex as hosts yet, so for those agents the instructions pointer is the path that works today.

SDK: v2, both protocol eras

The extension is specified against MCP revision 2026-07-28 (declared via server/discover), which SDK v1 cannot speak. The server uses the v2 TypeScript SDK (@modelcontextprotocol/server), whose serveStdio handles each connection on either the 2025 initialize handshake or the 2026-07-28 one, from one server factory. The device lock and snapshot store are shared across the instances that factory builds.

No SDK implements the Skills Extension yet (the TypeScript SDK's is an open PR), so skills/list / skills/get are custom methods on both.

This stays plain async TypeScript on purpose; adopting Effect across the codebase is tracked separately in #24.

Core code unchanged

No hardware was available to test against, so this PR leaves every existing hardware path as it is on main:

  • midi.ts, protocol.ts, exporter.ts, planner.ts, applier.ts, journal.ts, model.ts, snapshot.ts, usb.ts and the rest of src/ are untouched.
  • cli.ts is purely additive: the mcp command is parsed and dispatched before any existing command's code runs. The only non-added line widens the command type union to include "mcp".
  • The server calls the existing discover / connect / exportConfiguration / createPatchPlan exactly as the CLI does; its device-list and device-selection logic is a copy in mcp.ts, not a refactor of the CLI's.
  • Test-only change: the applier tests' simulated Twister moved to test/fake-twister.ts so the MCP tests can share it; the applier assertions themselves are unchanged and pass.

Code review fixes

From a /code-review pass on this PR:

  • The skills extension was only declared on the 2025 handshake → fixed by the v2 SDK migration above.
  • The CLI's multi-device error had lost its --device hint → resolved by reverting the CLI refactor that caused it (see "Core code unchanged").
  • README's Develop step failed for anyone who had already added the server → it now removes the registered server first.

Removed

  • .claude-plugin/ (the plugin only wrapped the server once the skill moved) and the Codex curl | tar install. Existing plugin users are covered by the README's "Upgrading from the plugin" section — still worth a release note.

README

Setup now leads with adding the MCP server: -s user scope so it works in every project, a /mcp check and a first prompt, an "Upgrading from the plugin" section for existing plugin users, and the CLI moved under "Without an agent". "What this tool does" lists the server's tools first, the CLI-recipe sections note how agents do the same through tools, and Develop shows how to point an agent at a local checkout's build.

Testing

  • npm run check: 42 tests pass, including new test/mcp.test.ts (tool surface, export → plan, snapshot provenance, multi-device refusal, serialization, skill digests vs. served bytes, skills/get errors).
  • The real mft-config mcp entry point is exercised over stdio on both protocol eras (2025 and 2026-07-28), each asserting the extension is declared and skills/list answers.
  • A wire-level test reads the raw stdio stream to check resultType: "complete", ttlMs and cacheScope on skills/list / skills/get. The v2 client consumes resultType before returning a result, so SDK-level assertions cannot see it.
  • The test runner is now TypeScript (test/run-tests.ts, run with tsx); the repo has no plain-JS sources left.
  • Not tested: real hardware (no Twister attached; export ran against the simulated device) and a live Claude Code/Codex session consuming skills/list. npx -y mft-config mcp works only after the next npm release.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 22, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-23T00:15:58.832663Z 4e5871c New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@oveddan
oveddan force-pushed the claude/issue-12-mcp-server-8a0a64 branch from fbfb4c8 to 2d5484a Compare September 22, 2026 23:54

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fbfb4c8917

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/skills.ts
Comment on lines +65 to +68
const field = /^([A-Za-z][\w-]*):\s+(.+)$/.exec(line);
if (!field) throw new Error(`Unsupported SKILL.md frontmatter line: ${line}`);
const value = field[2]!.trim();
if (/^["'[{|>&*!]/.test(value)) throw new Error(`Unsupported SKILL.md frontmatter value for ${field[1]}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject invalid plain-scalar YAML values

Reject YAML syntax that this flat parser cannot preserve. For example, description: b: c is currently accepted as the string b: c, although standard YAML rejects the unquoted : sequence; similarly, description: b # c is returned with the comment text while a YAML consumer reads only b. When such frontmatter is added, the manifest disagrees with—or cannot be parsed from—the served SKILL.md, causing conforming hosts and the ordinary-resource fallback to reject the skill.

Useful? React with 👍 / 👎.

Comment thread src/skills.ts
Comment on lines +98 to +100
text: bytes.toString("utf8"),
digest: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
size: bytes.length,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve binary skill resources when serving them

Serve non-text skill files as MCP blob content rather than decoding every file as UTF-8. If a skill contains a binary resource such as a PNG, toString("utf8") replaces invalid byte sequences, while digest and size still describe the original bytes; clients that verify the bytes returned by resources/read against the manifest will therefore reject the entire skill.

Useful? React with 👍 / 👎.

@oveddan
oveddan force-pushed the claude/issue-12-mcp-server-8a0a64 branch from 2d5484a to f9ecf5c Compare September 23, 2026 00:07
Decides #12: no standalone binary (Node 20 is an acceptable requirement), and
agents reach the Twister through `mft-config mcp`, a stdio MCP server.

- Tools: list_devices, export_configuration, plan_changes. No write tool while
  the #14 defects are open. Plans are built only from snapshots the server
  exported itself, and device operations are serialized.
- No MIDI port is held between calls, so the server contends with a DAW or the
  vendor utility no more than one CLI invocation does.
- Serves the mft-configurator skill over the MCP Skills Extension (SEP-2640):
  skill:// resources plus skills/list and skills/get with per-file digests.
  The skill moves to skills/ in the npm package as its single copy, rewritten
  around the tools.
- Built on the v2 TypeScript SDK, which serves both the 2025 initialize
  handshake and the 2026-07-28 server/discover handshake the extension is
  specified against.
- Removes the Claude Code plugin and the Codex curl install; both hosts now
  install with one `mcp add` line. The README leads with that setup.
- The test runner is now TypeScript, run through tsx.

Closes #12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@oveddan
oveddan force-pushed the claude/issue-12-mcp-server-8a0a64 branch from f9ecf5c to 4e5871c Compare September 23, 2026 00:10

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e5871c50b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread test/mcp.test.ts
child.on("error", reject);
child.stdout.on("data", (chunk: Buffer) => {
buffer += chunk.toString("utf8");
for (let newline = buffer.indexOf("\n"); newline >= 0; newline = buffer.indexOf("\n")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject when the wire-test server exits

Handle the child's exit/close event (or add a timeout) by rejecting done. If the MCP process starts successfully but then exits before returning both skill responses—for example because startup or protocol negotiation regresses—error is never emitted, done remains pending, and the test suite hangs until the external CI timeout instead of reporting the failure.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate: distribution shapes for easier integration (standalone binary and/or MCP server)

1 participant