framer-mcp

framer-mcp

Enables programmatic interaction with Framer canvas via a local MCP server and companion plugin, allowing automation of page generation, content updates, and component manipulation.

Category
Visit Server

README

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

Variable Purpose Default Security Notes
VITE_SERVER_BASE_URL Base URL of the MCP server that the plugin connects to. http://localhost:3000 Exposed 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

Variable Purpose Default Security Notes
PORT Port for the HTTP server to listen on. 3000 Non-sensitive.
REQUEST_TIMEOUT_MS Time to wait for plugin responses before timing out. 5000 Non-sensitive.
QUEUE_MAX Maximum number of pending requests allowed per user. 16 Non-sensitive.
SERVER_SALT Salt used to hash user IDs. dev-salt-change-me-in-prod Treat as secret; rotate in production.
IDEMPOTENCY_TTL_MS Duration to cache responses for idempotency. 600000 (10 minutes) Non-sensitive.
NODE_ENV Node.js environment mode. development Set 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 OpenAPI spec.
Additional REST and WebSocket details live in docs/endpoints.md.

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.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured