Maple MCP

Speeds frontend reviews by placing comments directly on a deployed preview environment, which are then resolved by agents. A CI check holds the merge until all comments are resolved and maple's visual lint gates pass.

Documentation

Maple: visual review comments on deployed previews, written for people and read by agents. Read the intro post.

maple-kit.org · Intro post · Pick a setup · Docs

CI @maple-kit/core on npm Apache-2.0

A reviewer points at something on a preview deployment and says what is wrong. Maple captures where they pointed, what they were looking at and who they are, hands it to a coding agent in a form it can act on, and holds the merge until every comment is resolved.

Status: 0.x. All eight packages are published, with provenance, through a trusted publisher. 0.x makes no compatibility promise: a public interface is broken when breaking it is the right shape, and the changeset says what broke.

How it works

Four steps in a loop: mount Maple in a preview, a reviewer comments on the page, an agent fixes it over MCP, and a CI check holds the merge until every comment is resolved.

  1. Mount: One route in your application, and one script in the preview build.
  2. Comment: A reviewer points to an issue on the app. Maple records all the context needed for the agent to pick it up.
  3. Fix: Your agent monitors new comments via the MCP, implements a fix and marks it as resolved.
  4. Gate: A CI check holds the merge until all comments are resolved, and all visual gates pass.

Code got fast. Planning, definitions of done and edge cases did not, so they get skipped and surface in testing. Maple moves that review to the preview, where the comment can still be acted on.

What it does

Three picks in a row: a metric card, a box dragged over part of a chart, and a sentence selected under it.
An element, an area, or a passage. Pick a component, drag a box, or select the words that are wrong. The anchor finds it again after a redeploy.
A terminal: the agent waits for comments, receives one naming a file and line, edits one line, and resolves the comment in a commit.
Your agent picks it up over MCP. It waits for comments, reads each with its context, makes the change, and resolves it against the commit.
A pull request's checks: maple/visual-review fails with two comments open, they resolve, the check passes and the merge button wakes up.
A merge gate. maple/visual-review fails while a comment is open and, if you want, until the reviewer approves.
In developer mode, hovering the page shows each element's component name and its file, line and column.
The file and the line. A build-time tagger marks every JSX element with where it was written, so a comment arrives pointing at source.
A table of open reviews loads with real data, then empty, then failing, with a banner naming the state each time.
Any state, on request. Type empty, failing or a thousand rows, and the page's API calls return it. A model picks the state; code writes every byte.
A reviewer types a comment on a chart and it is scored as a request, rated on being specific, actionable, concise, standalone and placed.
A score, if you want one. The comment is judged as it is typed, against five pillars, by a classifier you supply.

Each package's README shows the rest: the CLI, the design lint, flags and roles in a mock and the connectors.

Why another one

Pincushion, Vercel Toolbar, Chromatic, BugHerd and Marker.io each do some of this. None combines all four of:

  • Open source, Apache-2.0, with no hosted service required.
  • Deployed previews. Maple runs on the preview URL your CI already builds, so anyone with the link can comment — a designer, a product manager, a client. Tools like Agentation run against localhost, which means the only person who can leave a comment is the person running the build.
  • A merge gate — CI blocks while a visual comment is unresolved, and optionally until somebody says they looked. See docs/gate.md.
  • An agent loop — the agent reads comments, fixes, and resolves them.

Pick a setup

The SDK route has no default store: without one, its comment endpoints answer 404. Smallest setup first:


Try it locally
No accounts and nothing wired in. Maple proxies your running app and keeps comments as files under .maple/.
maple review
review · CLI

Review a preview alone
Comments reach your machine through a loopback bridge. A guest cannot gate a merge.
maple solo <preview-url>
solo

Review as a team
Each reviewer signs in to GitHub through Device Flow, so every comment is written as its author. No secret sits in the preview.
maple setup app --owner=acme
GitHub auth · configuration · setup-maple-org

Let an agent fix it
The MCP server hands the agent each comment and resolves it against the commit. The Stop hook keeps the agent working while any are open.
/plugin install maple@maple-kit
agent loop · MCP · setup-maple-agent-loop

Gate the merge
maple/visual-review fails while a comment is open, and optionally until a reviewer approves.
maple setup ci --require-approval --write
gate

Review edge-case states
A reviewer names a state (empty, failing, a thousand rows), and the page's API calls return it.
import "@maple-kit/mock/install"
mock · package

Bring your own backend
A connector is plain Promise methods, and its capabilities are the methods it defines.
maple connectors
connectors · contribute-connector

Host a shared store
Not built yet. A file store in a preview pod loses its comments when the pod goes, so use GitHub or write a connector for now.
connectors

Add it to an app

The quickest wiring, for trying Maple on your own machine. Sharing a preview needs a store per reviewer: see Pick a setup.

npm install @maple-kit/core @maple-kit/ui @babel/core

@babel/core is an optional peer, and the tagger is what needs it: leave it out and tagger: true has nothing to transform with.

Mount the route and the tagger from the build, then render the overlay:

// vite.config.ts
import { createCommentStore } from "@maple-kit/core";
import { githubStore } from "@maple-kit/core/connectors";
import { maple } from "@maple-kit/core/vite";

const preview = process.env.MAPLE_PREVIEW === "1";

export default defineConfig({
  plugins: [
    react(),
    maple({
      tagger: preview,
      route: {
        // Local trial only: every comment is written as this one token.
        // The route has no default store; without one its comment endpoints 404.
        store: createCommentStore(
          githubStore({ owner: "acme", repo: "web", token: process.env.GITHUB_TOKEN! }),
        ),
      },
    }),
  ],
});
// main.tsx
import { Maple } from "@maple-kit/ui/maple";

createRoot(root).render(
  <>
    <App />
    <Maple branch={import.meta.env.VITE_MAPLE_BRANCH} />
  </>,
);

The shared GITHUB_TOKEN is for a local trial only. A preview other people review builds the store per request from each reviewer's own token, through the resolver in docs/github-auth.md, so a comment is authored by whoever wrote it and no GitHub secret sits in the preview.

Next.js uses withMaple from @maple-kit/core/next and a catch-all route at app/api/maple/[...maple]/route.ts; examples/next-app has both. The setup-maple-org skill walks through the GitHub App a reviewer signs in with.

Use with Claude Code

The Maple plugin bundles the skills that set Maple up and act on its comments, the MCP server that reads and resolves them, and the Stop hook that keeps an agent working while they are open:

/plugin marketplace add maple-kit/maple
/plugin install maple@maple-kit

The server and the hook read GITHUB_TOKEN, MAPLE_GITHUB_OWNER and MAPLE_GITHUB_REPO from the environment Claude Code starts in. In a project where the last two are unset, the hook lets every stop through.

To install only the skills, for Claude Code or any other agent the skills CLI supports, run npx skills add maple-kit/maple.

Vendor-agnostic by construction

Maple stores nothing itself. A connector is one file implementing plain Promise-returning methods:

import type { StoreConnector } from "@maple-kit/core/connectors";

export function myStore(options: MyOptions): StoreConnector {
  return {
    name: "my-store",
    async list(query) {
      /* … */
    },
    async append(comment) {
      /* … */
    },
  };
}

There are six kinds — store, media, observability, identity, gate and classifier — and a connector's capabilities are exactly the methods it defines. Run maple connectors to print the matrix from the code, or see docs/connectors.md.

Packages

PackageWhat it is
@maple-kit/coreServer SDK, overlay controller, connector contracts, build plugins.
@maple-kit/uiThe marks, the island and the composer: the overlay a reviewer uses.
@maple-kit/reactHooks over the controller, for an overlay in your own design system.
@maple-kit/mcpThe MCP server an agent talks to, and a Stop hook.
@maple-kit/cliThe maple command.
@maple-kit/mockRewrites a page's API responses, flags and role into a named state.
@maple-kit/classifierScores a comment as it is written, and plans a mock from a sentence.
@maple-kit/lintDesign-system rules read off the page the browser laid out.

Documentation

Getting started
Reviewing
Agent loop
Merge gate
  • The merge gate: what blocks a merge, what approving does, which App to pin
States and scoring
Connectors and configuration
Security and organisations
Contributing and internals

Security

Report a vulnerability privately, as SECURITY.md describes. To run Maple in an organisation, read docs/security.md: who trusts what, which secrets exist where, and a checklist to work through before a preview is shared.

See it running

The project site is maple-kit.org. To run the Vite example yourself:

nvm use
pnpm install
pnpm --filter @maple-kit/example-vite dev   # http://localhost:5173

A real Vite application with three comments already on it: marks on the page, the island in the corner, and all three picks working against an SDK route the dev server mounts. examples/vite-app says what it does and does not prove.

Development

nvm use          # or fnm use, mise install — .nvmrc pins the version
pnpm install
pnpm hooks
pnpm lint && pnpm typecheck && pnpm test

Requires Node 24, the active LTS, and pnpm 10. Switch before the install: pnpm 10 and 11 load node:sqlite, which Node 23 does not have, so on the wrong version pnpm crashes rather than telling you the version is wrong. See CONTRIBUTING.md.

Licence

Apache-2.0. Contributions are accepted under the Developer Certificate of Origin — sign off with git commit -s. There is no CLA.