CodePen MCP is a Model Context Protocol server for CodePen that uses a Chrome extension bridge instead of browser automation. The MCP server runs locally over stdio, the bridge exposes a lightweight HTTP hub on http://127.0.0.1:3456, and the extension executes commands directly against the live CodePen DOM.
This approach is built for reliable editor automation against the modern CodePen UI: file trees, editor tabs, share links, assets, settings panels, project metadata, and local folder mirroring.
- Exposes CodePen as an MCP server for tools and agents.
- Connects to a Chrome extension through a local SignalR-style long-polling bridge.
- Automates the current CodePen editor surface without Playwright.
- Supports classic pen CRUD flows and modern file-aware editor workflows.
- Adds local-to-CodePen mirroring so a folder on disk can push and pull against a pen.
MCP Client
<-> stdio
Node.js MCP Server (src/)
<-> local bridge on http://127.0.0.1:3456
Chrome Extension (extension/)
<-> content script on codepen.io
CodePen Editor DOM
Core pieces:
src/index.ts: MCP server entry point.src/bridge.ts: local bridge server plus proxy reuse mode when port3456is already occupied by another compatible bridge instance.src/tools/: MCP tool registration grouped by domain.src/mirror/: local folder mapping, hashing, watch mode, lock handling, and console helpers.extension/background.js: extension-side bridge client and message router.extension/content.js: CodePen DOM operations.extension/popup.*: extension status UI, toggle, and logs.
The server currently registers 70 MCP tools.
statuscloseget_log
list_tabsswitch_tab
list_fileslist_open_tabsget_active_fileset_active_fileopen_file_tabread_fileupdate_filetoggle_panelget_preview_urlcopy_share_links
close_file_tabcreate_filedelete_filerename_filecreate_foldermove_filesearch_fileslist_assetsupload_assetdelete_assetcopy_asset_urllist_blockssearch_blocksinsert_blocklist_templatesuse_templateget_privacy_settingsset_privacy_settingslist_collaboratorsinvite_collaboratorremove_collaboratorget_pen_detailsset_pen_detailslist_commentsadd_commentreply_to_commentget_deployment_statusdeploy_projectsearch_workspacerun_editor_command
create_penopen_penread_penupdate_pensave_penget_url
runfork_penexport_penscreenshotconsole_output
set_preprocessorsadd_external_resourcesset_pen_settingschange_layout
oembedprefillsearch_pens
mirror_initmirror_pushmirror_pullmirror_startmirror_stopmirror_statusmirror_config
This repo does not drive CodePen through generic browser automation. Instead, it uses an unpacked Chrome extension that runs on CodePen pages and interacts with the editor directly.
Benefits:
- Better access to the live editor DOM.
- Less fragile than remote-control browser scripting for this use case.
- Cleaner routing between MCP tools and the active CodePen tab.
- Easier support for UI-only workflows like file trees, asset panels, and share links.
Tradeoffs:
- The extension must be installed and enabled.
- DOM selectors still depend on CodePen's current UI.
- Some actions are gated by account tier or the currently visible editor state.
- Node.js
18+ - npm
- Google Chrome or a Chromium browser that supports unpacked extensions
- A CodePen tab open at
https://codepen.io/editor/penor another editor/details page
npm installnpm run build:allThis does two things:
- Compiles the TypeScript server into
dist/ - Copies the browser SignalR client into
extension/lib/signalr.min.js
If you only want the server build:
npm run buildIf you only want the extension-side asset refresh:
npm run build:extThe extension manifest metadata is synced from package.json during that build step.
If you want a distributable extension archive:
npm run pack:extThat command refreshes the extension assets and writes a zip archive to artifacts/codepen-mcp-bridge-<version>.zip with manifest.json at the archive root.
If you want both the server build and the extension package in one step:
npm run pack:allIf you want to bump versions cleanly, use npm's version command:
npm version patchThat updates package.json and package-lock.json, then automatically syncs the Chrome extension version in extension/manifest.json through the version script.
- Open
chrome://extensions/ - Enable Developer mode
- Click Load unpacked
- Select the
extension/folder from this repo - Pin the extension if you want easier access to its popup
The popup provides:
- An ON/OFF connection toggle
- Connection state feedback
- A scrolling event log for bridge activity
Run:
npm run pack:extThis produces a zip archive under artifacts/ that can be unpacked for distribution or uploaded where a Chrome extension zip is required. The archive excludes the local icon-generation helper page and includes the built SignalR client plus build.json metadata.
The repo now includes a GitHub Actions workflow at .github/workflows/release-artifacts.yml.
workflow_dispatchincrements the version, commits it, creates a tag, and starts the tagged release flow- pushing a
v*tag builds artifacts and attaches them to a GitHub release
When you dispatch the workflow, choose patch, minor, or major. The workflow runs npm version, which updates:
package.jsonpackage-lock.jsonextension/manifest.jsonthrough the existing sync hook
The workflow publishes:
- the extension zip from
artifacts/ - the npm package tarball from
npm pack
Start the compiled server:
npm startExpected behavior:
- The MCP server starts on stdio for your MCP client.
- The local bridge listens on
http://127.0.0.1:3456/codepen-hub. - If another compatible bridge is already listening on
3456, the server reuses it in proxy mode instead of failing.
For VS Code workspace usage, this repo now includes .vscode/mcp.json. After dependencies are installed, VS Code can start the server directly from the workspace configuration.
That workspace config runs the TypeScript entrypoint through npx tsx, so it does not depend on a prebuilt dist/ folder.
Example MCP configuration:
{
"mcpServers": {
"codepen": {
"command": "node",
"args": ["d:/CodePenMCP/dist/index.js"]
}
}
}If you publish or install the package globally, you can also use the package binary:
{
"mcpServers": {
"codepen": {
"command": "codepen-mcp"
}
}
}- Build the repo with
npm run build:all. - Load the unpacked extension from
extension/. - Start the MCP server with
npm start. - Open a CodePen editor tab in Chrome.
- Turn the extension ON in the popup.
- Call
statusto confirm the bridge is connected.
Common next calls:
list_tabsto see which browser tab will be targeted.switch_tabif multiple CodePen tabs are open.list_filesandget_active_filefor file-aware editing.read_penorupdate_penfor classic panel workflows. These work on both classic pens at/pen/<id>and modern Project Pens at/editor/<user>/pen/<uuid>— for the latter, panel content is read and written via achrome.scripting.executeScript({ world: "MAIN" })round-trip into the page-world CodeMirror 6 instances, since those JS objects are not visible from the content script's isolated world.
The mirror subsystem lets you bind a local folder to a CodePen pen and keep HTML, CSS, and JS synchronized.
Typical flow:
- Initialize a folder and mapping with
mirror_init. - Start continuous sync with
mirror_start— it performs an initial push of the current local files before it begins watching, so the pen reflects disk from the moment watching begins. - (Optional) Push or pull on demand with
mirror_push/mirror_pullfor one-shot syncs outside the watch loop. - Inspect state with
mirror_status. - Stop watch mode with
mirror_stop.
mirror_init writes a .codepenmcp.json file into the target folder. That config stores:
- The CodePen URL and pen id, if known
- The HTML/CSS/JS file mapping
- Detected preprocessors based on file extensions
- Last sync timestamps and content hashes
- Existing pen to local folder: initialize with
penUrl, then pull the pen into files on disk. - Local folder to new pen: initialize without
penUrl, optionally use a template, push local content, save, and persist the resulting pen URL. - Continuous sync: watch local files and auto-push on save.
mirror_startruns an initial best-effort push before the watcher begins, so the pen is in sync from t=0. If that push fails (no pen yet, bridge offline, empty folder), watching still starts and the failure reason is included in the response.- Watch mode uses a lock file to prevent duplicate watchers.
- The watcher observes the parent directories of mapped files (not individual files), which keeps it reliable across editor save modes that write a temp file then rename — including VS Code's atomic save on Windows.
- Concatenated mappings are supported for panels that should be assembled from multiple files. The JS/CSS panel content is delimited by
/* === filename === */boundary comments, whichmirror_pulluses to round-trip the panel back into separate files. - HTML content pulled from CodePen is wrapped into a fuller local-file form when needed.
npm run build
npm run build:ext
npm run build:all
npm run dev
npm start
npm run test:list
npm run test:client
npm run test:call -- statusCommand summary:
build: compile TypeScript intodist/build:ext: refresh extension-side build metadata and bundled SignalR clientbuild:all: run both buildsdev: TypeScript watch modestart: run the compiled MCP servertest:list: list registered MCP tools through the included test clienttest:client: interactive REPL client for manual testingtest:call -- <tool> [json]: invoke one tool from the command line
The repo includes test/client.ts, an interactive MCP client for local validation.
Useful checks:
npm run test:listconfirms tool registration.npm run test:call -- statusverifies the bridge and extension connection.get_logreturns timestamped bridge and extension logs for reconnect issues.- The extension popup log helps track command flow and UI-side failures.
- In the REPL (
npm run test:client), if you mistype a tool's JSON arguments the client prints the expected schema (required/optional fields, types, descriptions) plus a paste-ready example call, so you don't have to re-runlistto remember the shape.
There are also VS Code tasks for build:all and build:ext in this workspace, and a Build Extension launch entry that runs npm run build:ext in the integrated terminal without launching a browser.
.
├── extension/
│ ├── background.js
│ ├── content.js
│ ├── manifest.json
│ ├── popup.css
│ ├── popup.html
│ ├── popup.js
│ ├── lib/
│ └── icons/
├── src/
│ ├── bridge.ts
│ ├── index.ts
│ ├── types.ts
│ ├── mirror/
│ └── tools/
├── test/
├── test-output/
├── FEATURE_ROADMAP.md
├── REQUIREMENTS.md
├── package.json
└── tsconfig.json
- The extension must be connected to the same bridge instance the MCP server is using.
- If multiple CodePen tabs are open, explicit tab routing may be required.
- Several tools depend on whatever controls CodePen currently exposes in the active UI.
- Asset upload, privacy changes, deployment, and collaborator actions may be limited by account tier or editor state.
- Settings-oriented tools are partly assistive: some open a dialog and rely on CodePen's current DOM rather than fully deterministic structured APIs.
- Screenshot capture is browser-tab based, not a semantic export of the preview frame.
- Confirm
npm startis running. - Open the extension popup and toggle it OFF, then ON.
- Make sure the active page is a supported CodePen page.
- Use
get_logto inspect bridge and extension event history.
- Run
list_tabs. - Use
switch_tabwith the returned Chrome tab id.
This is expected if another compatible bridge is already running. The server will switch into proxy mode and reuse the existing bridge if its health endpoint responds correctly.
That usually means the relevant control is hidden, renamed, gated by account state, or no longer matches the selector assumptions in the content script. Re-check the current CodePen UI before changing server-side logic.
See CONTRIBUTING.md for local setup, contribution guidelines, and how to raise issues and pull requests.
This project is licensed under the MIT License. See LICENSE.
An attribution request is also included in NOTICE. The notice does not change the license terms.