OpenPencil — native canvas and MCP
OpenPencil gives a human member and an MCP client the same editable web canvas. The editor runs in your browser. A resident editor on the VPS maintains the project when you close the browser. Native changes synchronize through the project’s authenticated Yjs connection.
| Connection | Address or behavior |
|---|---|
| Web editor | openpencil.devfellowship.com |
| MCP endpoint | https://openpencil.devfellowship.com/mcp |
| Transport | Streamable HTTP |
| Authentication | Your current DFL Supabase user JWT, as a bearer token |
| Project access | The owner, or a current global IAM level of at least 80 |
| Tools | 23 tools, served by dfl-services/services/dfl-openpencil |
| Resident capacity | Two projects at once across the service |
| Durable project limits | 10 projects per owner; 100 projects across the service |
This endpoint lives with the OpenPencil service. It has its own address rather
than the <host>.mcp.devfellowship.com address used by this repository’s 14
package hosts. Its tools are discovered through its own MCP tools/list.
Prerequisite: the shared DFL fleet connection
Section titled “Prerequisite: the shared DFL fleet connection”Complete Auth & security before the first tool call. It explains the shared DFL identity and the global MCP client connection used across projects. OpenPencil reuses that DFL bearer JWT. It does not require a separate account.
Use your own DFL login in the web editor. OpenPencil Desktop is optional. One OpenPencil MCP connection is sufficient. No extra skill or Playwright MCP is required for your first design change. Your assistant still needs its own model access.
The same current global DFL credential authenticates this MCP endpoint. The shared auth guide covers that credential and its renewal. Keep that setup in the client’s global user scope, then use this page for the native design tool calls.
Project lifecycle
Section titled “Project lifecycle”A project is a durable document owned by a human DevFellowship member. Projects have separate documents, resident contexts and synchronization rooms. For parallel work, use a separate project or version for each person. A global administrator can access another owner’s project, so choose the intended project explicitly before each edit.
The public editor URL is stable:
https://openpencil.devfellowship.com/projects/PROJECT_UUIDUse that URL to return to the editor. Normal browser entry uses central DFL SSO.
get_attach_url is a separate private, one-use entry mechanism with a 60-second
lifetime. Consume it immediately when needed. Its fragment is a capability;
keep the full attach URL out of messages, logs, documents and screenshots.
Closing the browser leaves the resident available for MCP work. To release one
of the two resident slots, call close_project for your project. This preserves
the canonical FIG checkpoint and project metadata. Reopen the project when you
need it again. delete_project permanently removes the FIG and metadata;
use close_project to free resident capacity.
These limits count different resources. The two-resident limit is global and
counts active resident contexts. The durable document limits are 10 projects
per owner and 100 projects across the service. close_project preserves a
document, so it does not reduce those durable counts. At a document limit,
review existing projects with their owner and preserve the designs you need
before any planned permanent removal.
Tool calls: create, inspect, edit and save
Section titled “Tool calls: create, inspect, edit and save”The following examples show MCP tools/call parameters. An MCP client handles
initialization, the bearer header and the mcp-session-id returned by
initialization. A full JSON-RPC request wraps each parameter block like this:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_projects", "arguments": {} }}A successful response contains result.content text with a JSON payload. Parse
that payload to read the project’s id, version and runtime.generation,
or the native tool’s returned node data. HTTP success alone does not prove a
canvas change. Read the node back and inspect the native canvas.
1. List or create your project
Section titled “1. List or create your project”{ "name": "list_projects", "arguments": {} }Create a new project only when you need a separate document:
{ "name": "create_project", "arguments": { "name": "My first OpenPencil design" }}name is required and has a 120-character limit. The caller must be an active
human member. owner_id is optional; only a global administrator with IAM at
least 80 can assign a new project to another existing active human member.
Omit it to create your own project. Keep the id returned by the tool.
2. Read the project and current page
Section titled “2. Read the project and current page”Replace PROJECT_UUID in these examples with your project’s returned id:
{ "name": "get_project", "arguments": { "project_id": "PROJECT_UUID" }}{ "name": "get_page_tree", "arguments": { "project_id": "PROJECT_UUID", "depth": 2 }}get_project reads durable metadata. get_page_tree ensures a resident exists
and reads its current scene. If the resident was absent, call get_project
again after get_page_tree to obtain the current runtime.generation.
get_page_tree also accepts page_id, root_id, node_types and depth.
Use get_node for a specific node. Use find_nodes with a unique layer name
to find it again after a resident restart.
3. Supply current mutation controls
Section titled “3. Supply current mutation controls”Every document mutation requires these fields in addition to the native operation’s arguments:
| Field | Requirement |
|---|---|
project_id | The project UUID returned by create_project or list_projects. |
operation_id | A new identifier for each new operation: 8–100 letters, digits or hyphens. A UUID works. |
base_version | The current integer version returned by get_project. |
generation | Optional in the schema; supply the current runtime.generation to reject an obsolete resident. |
Read get_project before each new mutation. Human edits and MCP edits both
advance the checkpoint version. A version from an earlier call can be stale.
The mutation examples below use 42, 43, 44, 45 and 46 only as
illustrative versions. Replace each with the latest returned version.
Replace CURRENT_GENERATION, RECTANGLE_NODE_ID and TEXT_NODE_ID with
current returned values. Generate a new operation ID for each new call.
4. Create a rectangle and set its fill
Section titled “4. Create a rectangle and set its fill”{ "name": "create_shape", "arguments": { "project_id": "PROJECT_UUID", "operation_id": "first-rectangle-20261007", "base_version": 42, "generation": "CURRENT_GENERATION", "type": "RECTANGLE", "name": "FIRST_TEST_RECTANGLE", "x": 100, "y": 100, "width": 320, "height": 180 }}Keep the returned node ID. Re-read get_project, then set the fill:
{ "name": "set_fill", "arguments": { "project_id": "PROJECT_UUID", "operation_id": "first-fill-20261007", "base_version": 43, "generation": "CURRENT_GENERATION", "id": "RECTANGLE_NODE_ID", "color": "#2563EB" }}create_shape requires type, x, y, width and height.
Width and height must be at least 1. Optional fields include name,
parent_id and page_id. Supported types are FRAME, RECTANGLE, ELLIPSE,
TEXT, LINE, STAR, POLYGON and SECTION.
set_fill requires id and color. For a gradient, add color_end and
gradient: top-bottom, bottom-top, left-right or right-left.
5. Create and populate a text node
Section titled “5. Create and populate a text node”Re-read get_project before this call:
{ "name": "create_shape", "arguments": { "project_id": "PROJECT_UUID", "operation_id": "first-title-20261007", "base_version": 44, "generation": "CURRENT_GENERATION", "type": "TEXT", "name": "FIRST_TEST_TITLE", "x": 120, "y": 130, "width": 280, "height": 48 }}Keep its returned node ID. Re-read get_project, then set the text:
{ "name": "set_text", "arguments": { "project_id": "PROJECT_UUID", "operation_id": "first-text-20261007", "base_version": 45, "generation": "CURRENT_GENERATION", "id": "TEXT_NODE_ID", "text": "FIRST LIVE CANVAS TEST" }}set_text requires id and text. To adjust typography, use set_font after
another metadata read:
{ "name": "set_font", "arguments": { "project_id": "PROJECT_UUID", "operation_id": "first-font-20261007", "base_version": 46, "generation": "CURRENT_GENERATION", "id": "TEXT_NODE_ID", "size": 24 }}6. Confirm the visible result and save
Section titled “6. Confirm the visible result and save”Read get_node for both returned IDs, or get_page_tree for the project.
Check the actual editor: the rectangle and title must appear without a reload.
The human can select, drag and edit those native elements. Wait for the editor’s
save acknowledgment before the human closes the tab.
{ "name": "save_checkpoint", "arguments": { "project_id": "PROJECT_UUID" }}This call writes actual FIG bytes atomically and returns checkpoint metadata,
including a version and SHA-256 hash. Each successful document mutation also
checkpoints its result. save_checkpoint creates a new checkpoint version;
it does not use the replay-idempotent mutation contract.
7. Release the resident when finished
Section titled “7. Release the resident when finished”{ "name": "close_project", "arguments": { "project_id": "PROJECT_UUID" }}The result reports closed: true and deleted: false. The document survives.
Return through the stable project URL to restore it from the canonical FIG.
Retry and restart behavior
Section titled “Retry and restart behavior”After a lost mutation response, retry the same operation ID and exactly the same arguments, including the original base version and generation. The service stores the latest 32 completed operation results and returns a matching record without repeating the mutation. Do not change the arguments under the same operation ID.
An older retry outside that journal is refused by its stale base version.
A genuine new edit requires new metadata and a new operation ID. On
version_conflict, read the current state and decide whether the intended
change still applies. On generation_conflict, reopen the project and
rediscover its nodes by unique names. Native node IDs can change during FIG
import into a new resident.
A failed mutation or checkpoint can quarantine a resident. Close and reopen that project to recover from its last canonical checkpoint. Coordinate with any human who is currently editing it.
All 23 tools
Section titled “All 23 tools”Every project-specific tool requires project_id. Native document mutation
tools also require the controls described above. Optional native arguments are
available through this endpoint’s tools/list schemas.
| Tool | Purpose | Other required arguments |
|---|---|---|
create_project | Create a human-owned durable project. | name; no project_id on creation. |
list_projects | List projects the caller can access. | None; no project_id. |
get_project | Read canonical metadata and resident state. | None. |
save_checkpoint | Write a canonical FIG checkpoint. | None. |
get_attach_url | Issue a private, one-use attach URL. | None. |
close_project | Release the resident and preserve the document. | None. |
delete_project | Permanently remove the document and metadata. | None. |
get_node | Inspect a native node. | id. |
find_nodes | Find nodes by optional name or type filters. | None. |
get_page_tree | Read the current scene graph. | None. |
list_documents | Inspect the resident’s native document. | None. |
select_nodes | Set the resident selection. | ids. |
viewport_zoom_to_fit | Fit the resident viewport to nodes. | ids. |
create_shape | Create a native element. | type, x, y, width, height, mutation controls. |
update_node | Change native node properties. | id, mutation controls. |
set_fill | Apply a solid fill or gradient. | id, color, mutation controls. |
set_stroke | Apply an outline. | id, color, mutation controls. |
set_text | Replace text content. | id, text, mutation controls. |
set_font | Change typography. | id, mutation controls. |
set_layout | Change frame layout. | id, mutation controls. |
set_opacity | Set opacity. | id, value, mutation controls. |
clone_node | Duplicate a native node. | id, mutation controls. |
delete_node | Remove a native node. | id, mutation controls. |
The resident selection and viewport are separate from a human browser’s local selection and viewport. The shared scene graph carries document changes.
Troubleshoot the first interaction
Section titled “Troubleshoot the first interaction”| Symptom | Next step |
|---|---|
Browser shows Falha 401 with an old session. | Sign out of DFL, sign in again, then reopen the project. A private window can confirm a fresh session. |
MCP returns 401. | Obtain your current DFL JWT. Restart the client with the updated environment variable. |
MCP returns foreign_session. | Reconnect the MCP transport after a service restart or session expiry. |
| Project access is refused. | Confirm the caller and intended project. Access requires ownership or current global IAM at least 80. |
| The service reports resident capacity. | Two residents are shared across the service. Coordinate with another user or close your own idle project with close_project. |
| The service reports a durable project limit. | Review existing projects with the owner: 10 per owner, 100 across the service. Closing a resident does not reduce document counts. |
version_conflict or generation_conflict. | Re-read metadata and rediscover current node IDs as described above. |
| An open editor disconnects. | Reopen its stable project URL before you continue. Disconnected input is blocked. |
| A canvas change is missing. | Read the target nodes back. Check the correct project, native canvas and save acknowledgment. |
Report the failed step, operating system, client version and error in
#chapter-web3. Include a screenshot with private tokens and attach fragments
removed.
Evidence and source
Section titled “Evidence and source”The native editor acceptance report contains three staged screenshots and real public MCP/Playwright checks: native input, live MCP changes, and work without a human tab followed by return after a restart. Tainan accepted the human review on 7 October 2026.
This curated external-service guide uses the deployed 23-tool schema and
dfl-openpencil source at commit 50786b1.
It is separate from this repository’s generated package manifest. Use the live
endpoint’s tools/list as the argument authority when the service changes.