framer-mcp

tmcpro/framer-mcp

3.1

If you are the rightful owner of framer-mcp and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.

The Framer Mission Control Protocol (MCP) server is a local development tool designed to enable programmatic interaction with the Framer canvas through a secure API.

Tools
5
Resources
0
Prompts
0

Framer Mission Control Protocol (MCP) - Local MVP

Project Overview

This project combines a local MCP server with a companion Framer plugin so developers and external clients can interact with the Framer canvas programmatically. By exposing a secure Tool API, Framer users can automate page generation, update content, and manipulate components directly from scripts or other services.

This MVP is built with Node.js and targets local development and testing.


Architecture

The system consists of three main components:

  1. Framer Plugin:

    • A React-based plugin that runs directly inside the Framer application.
    • It is responsible for executing commands using Framer's official Plugin API.
    • It establishes and maintains a secure WebSocket connection to the MCP Server.
  2. MCP Server:

    • A Node.js server built with Express and WebSocket support.
    • It acts as a secure bridge between API clients (CLI tools, services) and the Framer Plugin.
    • It exposes a REST API (/v1/invoke) for sending commands and manages a per-user request queue to ensure operations are executed sequentially.
  3. Shared Logic:

    • A shared directory contains common code for both the server and plugin, including type definitions, message protocols, and Zod schemas for validation, ensuring consistency across the system.

Data Flow

  1. The user opens the plugin in Framer.
  2. The plugin requests a unique, hashed User ID from the server.
  3. The plugin generates a secret and establishes a WebSocket connection to the server using the User ID and secret.
  4. The plugin UI displays the full MCP URL, including the User ID and secret, for the user to copy.
  5. An API client makes a POST request to the server's /v1/invoke endpoint with the URL, a tool name, and parameters.
  6. The server authenticates the request, validates the parameters, and forwards the command to the correct plugin over the WebSocket connection.
  7. The plugin executes the command using the Framer API and sends the result back to the server.
  8. The server relays the result back to the API client as the HTTP response.

Getting Started

Prerequisites

Steps

  1. Clone the repository:

    git clone https://github.com/tmcpro/framer-mcp
    cd framer-mcp
    
  2. Install dependencies:

    npm install
    
  3. Run the plugin:

    npm run dev --prefix plugin
    
  4. Run the server:

    npm run dev --prefix server
    

    The server starts on http://localhost:3000 and the plugin dev server prints an install URL.

    To run both simultaneously, use npm run dev from the project root.

Auth Modes

The server supports configurable auth for local vs. cloud environments via AUTH_PROVIDER:

  • legacy (default): requires ?id=<USER_ID>&secret=<SECRET> on requests and the plugin WS query. Compatible with existing plugin flows.
  • none: disables server-side auth for local development. Protect with your local network boundary.
  • cf-access: assumes Cloudflare Access is enforcing auth in front of the server. No custom auth logic runs in the app.

Set in server/.env:

AUTH_PROVIDER=none            # local dev
# AUTH_PROVIDER=cf-access     # when deployed behind Cloudflare Access

Configuration

Environment variables control both the plugin and server. The server uses dotenv to load settings from a local .env file (server/.env) during development. For production, configure variables via your hosting platform's secret manager and never commit .env files. Rotate SERVER_SALT regularly in deployed environments.

Plugin
VariablePurposeDefaultSecurity Notes
VITE_SERVER_BASE_URLBase URL of the MCP server that the plugin connects to.http://localhost:3000Exposed in browser; do not put secrets here.
  • Development: create plugin/.env with VITE_SERVER_BASE_URL=http://localhost:3000 to match the default server started by npm run dev.
  • Production: set VITE_SERVER_BASE_URL to your deployed server's URL before building the plugin. For example:
    VITE_SERVER_BASE_URL=https://mcp.example.com npm run build --prefix plugin
    
Server
VariablePurposeDefaultSecurity Notes
PORTPort for the HTTP server to listen on.3000Non-sensitive.
REQUEST_TIMEOUT_MSTime to wait for plugin responses before timing out.5000Non-sensitive.
QUEUE_MAXMaximum number of pending requests allowed per user.16Non-sensitive.
SERVER_SALTSalt used to hash user IDs.dev-salt-change-me-in-prodTreat as secret; rotate in production.
IDEMPOTENCY_TTL_MSDuration to cache responses for idempotency.600000 (10 minutes)Non-sensitive.
NODE_ENVNode.js environment mode.developmentSet to production in deployments; avoid test.

Create server/.env to override these defaults. For example:

PORT=4000
SERVER_SALT=prod-secret-salt

Install the plugin in Framer:

-   Open the printed Vite URL in your browser and follow the prompt to add the plugin to Framer.
-   In your Framer project, open the plugin from the **Plugins** panel. The small UI should appear in the bottom-right corner.

Verify the connection

-   When the plugin successfully connects to the server, the UI shows `Status: online`.
-   In legacy mode, the UI may display a URL containing a user ID and secret for convenience. In non-legacy modes, you can call the API directly without query parameters.

Send a test request

Confirm everything is working with one of the following, depending on auth mode:
- Legacy:
  ```bash
  curl -X POST "http://localhost:3000/v1/invoke?id=<USER_ID>&secret=<SECRET>" \
    -H 'Content-Type: application/json' \
    -d '{"tool":"project.getInfo","params":{}}'
  ```
- None / Cloudflare Access:
  ```bash
  curl -X POST "http://localhost:3000/v1/invoke" \
    -H 'Content-Type: application/json' \
    -d '{"tool":"project.getInfo","params":{}}'
  ```
A JSON response with project details means the server and plugin are communicating correctly.

Running Tests

This project includes a test suite for the server to ensure its core logic is working correctly.

To run the tests, use the following command:

npm test --prefix server

This will execute all Jest tests located in the server/src/__tests__ directory.


API Reference

The MCP API exposes a single POST /v1/invoke?id=<USER_ID>&secret=<SECRET> endpoint used to run tools in a connected Framer project.

Core capabilities include:

  • Creating, updating, and removing nodes on the canvas.
  • Managing selection and interacting with the editor UI.
  • Inserting components and working with project files and code.

For the formal specification, see the .
Additional REST and WebSocket details live in .

curl -X POST "<MCP_URL>/v1/invoke?id=USER_ID&secret=SECRET" -H 'Content-Type: application/json' -d '{"tool":"project.getInfo","params":{}}'

Tool Manifest

GET /v1/tools returns metadata for every available tool. The response begins with a version string that increments whenever tool schemas change so clients know when to refresh their cached manifest.

Each tool object contains:

  • name – unique identifier for the tool.

    "name": "nodes.createFrame"
    
  • description – natural-language summary of the tool.

    "description": "Creates a new frame node."
    
  • paramsSchema – JSON Schema describing required parameters. This structure helps clients construct valid requests.

    "paramsSchema": {
      "type": "object",
      "properties": {
        "attrs": {
          "type": "object",
          "properties": { "name": { "type": "string" } }
        }
      }
    }
    
  • resultSchema – JSON Schema describing the result shape.

    "resultSchema": {
      "type": "object",
      "properties": {
        "node": {
          "type": "object",
          "properties": {
            "id": { "type": "string" },
            "type": { "type": "string" }
          }
        }
      }
    }
    
  • examples – Array of sample {params, result} pairs showing real usage.

    "examples": [
      {
        "params": { "attrs": { "name": "Landing" } },
        "result": { "node": { "id": "123", "type": "FrameNode" } }
      }
    ]
    
  • errors – Array of objects describing possible failures.

    "errors": [
      { "code": "BAD_REQUEST", "message": "Invalid parameters" },
      { "code": "INTERNAL", "message": "Unexpected server error" }
    ]
    

Schemas and examples provide explicit contracts for generating requests and interpreting responses.

Tool Surface

All API calls are made via POST /v1/invoke?id=<USER_ID>&secret=<SECRET>. The body of the request is a JSON object with tool and params keys.

Nodes

  • nodes.createFrame: Creates a new Frame.
    • params: { parentId?: string, attrs?: NodeAttributes }
  • nodes.addText: Adds a new Text layer.
    • params: { parentId?: string, text: string, attrs?: NodeAttributes }
  • nodes.setText: Updates the content of a Text layer.
    • params: { nodeId: string, text: string }
  • nodes.addImageFromUrl: Fetches an image from a URL and adds it to the canvas.
    • params: { parentId?: string, url: string, attrs?: NodeAttributes, fit?: 'cover'|'contain'|'fill' }
  • nodes.addSvg: Adds an SVG as a VectorNode.
    • params: { parentId?: string, svg: string, attrs?: NodeAttributes }
  • nodes.setAttributes: Applies a set of attributes to any node.
    • params: { nodeId: string, attrs: NodeAttributes }
  • nodes.getNode: Retrieves the properties of a single node.
    • params: { nodeId: string }
  • nodes.remove: Deletes a node.
    • params: { nodeId: string }
  • nodes.duplicate: Duplicates a node.
    • params: { nodeId: string, parentId?: string }

Selection

  • selection.get: Gets the currently selected node IDs.
    • params: {}
  • selection.set: Sets the current selection.
    • params: { nodeIds: string[] }

Editor

  • editor.zoomIntoView: Zooms the editor viewport to fit a specific node.
    • params: { nodeId: string }
  • editor.notify: Shows a notification toast in the Framer UI.
    • params: { message: string, durationMs?: number }

Components

  • components.list: Lists all available components in the project.
    • params: {}
  • components.insertByUrl: Inserts a component using its import URL.
    • params: { insertUrl: string, parentId?: string, attrs?: NodeAttributes, props?: object }
  • components.insertByName: Inserts a component by its name.
    • params: { name: string, parentId?: string, attrs?: NodeAttributes, props?: object }

Project & Code

  • project.getInfo: Retrieves the current project's ID and name.
    • params: {}
  • code.createFile: Creates a new code file (e.g., a React component).
    • params: { path: string, template?: 'component'|'override'|'empty' }
  • code.readFile: Reads the content of a code file.
    • params: { fileId?: string, path?: string }
  • code.updateFile: Updates the content of a code file.
    • params: { fileId?: string, path?: string, code: string }

Project Structure

framer-mcp/
├─ .gitignore
├─ package.json              # Root dependencies and scripts
├─ README.md                 # This file
├─ shared/                   # Shared types, schemas, and protocols
│  ├─ protocol.ts
│  ├─ schemas.ts
│  └─ tools.ts
├─ server/                   # The Node.js MCP Server
│  ├─ package.json
│  ├─ jest.config.js
│  ├─ tsconfig.json
│  └─ src/
│     ├─ __tests__/          # Server-side tests
│     ├─ index.ts            # Express app setup and server entrypoint
│     ├─ router.invoke.ts    # Handles the main /invoke endpoint
│     └─ ws.hub.ts           # Manages WebSocket connections
└─ plugin/                   # The Framer Plugin
   ├─ package.json
   ├─ vite.config.ts
   ├─ tsconfig.json
   └─ src/
      ├─ main.tsx            # Plugin entrypoint, connects to server
      ├─ ui/App.tsx          # The React component for the plugin UI
      └─ handlers/           # Logic for executing Tool API commands

Local Usage Examples

  • Start plugin + server: npm run dev
  • With AUTH_PROVIDER=none, you can invoke without id/secret once the plugin shows “online”:
curl -X POST "http://localhost:3000/v1/invoke" \
  -H 'Content-Type: application/json' \
  -d '{"tool":"project.getInfo","params":{}}'
  • JSON-RPC (MCP-style) over HTTP (useful for adapters):
curl -X POST "http://localhost:3000/v1/mcp" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"1","method":"tool/list","params":{}}'

Cloudflare Agents (Remote MCP)

Recommended for production: deploy behind Cloudflare and use Cloudflare Access instead of custom auth.

  • Protect the origin with Cloudflare Access (Application type: Web → include WS). Set AUTH_PROVIDER=cf-access on the server.
  • Point the plugin VITE_SERVER_BASE_URL to your Access-protected origin.
  • For Cloudflare Agents: use the Remote MCP Server guide to register an MCP server that proxies to this API so external agents can call tools. Implement a small Worker that translates MCP JSON-RPC methods (tool/list, tool_code/invoke) to this server’s REST endpoints.

Local dev remains unchanged (AUTH_PROVIDER=none).

Configuration Summary

  • Server: see server/.env.example for all supported settings. Copy to server/.env and adjust as needed.
  • Plugin: copy plugin/.env.example to plugin/.env and set VITE_SERVER_BASE_URL.
  • Typical local setup: AUTH_PROVIDER=none, VITE_SERVER_BASE_URL=http://localhost:3000.