ctlsurf MCP server
Official hosted ctlsurf MCP server: connected coding agents (Claude Code, Cursor, Codex, Copilot, ChatGPT, Windsurf, VS Code) read and write ctlsurf pages, blocks, folders, files, dashboards, datastores, graphs, presentations and skills.
One-click install for Cursor, VS Code, Claude, and 20+ other MCP clients, powered by API Commons MCP Install — visit install.apicommons.org for more information.
Endpoint
Hosted endpoint · transport
Connect
This is a remote MCP server — point an MCP client at the endpoint URL. Clients with native remote support (Claude, Cursor, VS Code, …):
For stdio-only clients (older Claude Desktop, etc.), bridge with mcp-remote:
If the server requires authentication, add the provider's token/header (e.g. an X-API-Key) per its docs. A quick reachability check:
Tools
add_checklist_item— Add an item to a checklist blockadd_ctlsurf_task_item— Add a new task to a ctlsurf_task block. Use this to create work items for yourself. Example: add_ctlsurf_task_item(block_id="", text="Implement user authentication with JWT") add_ctlsurf_task_item(block_id=" ", text="Debug API endpoint", skill_id=" ") archive_folder— Archive a folder. Archived folders are hidden by default and don't count toward your project quota.archive_group— Archive a whole folder group: every active project in it is archived at once. Nothing is deleted; the group's dashboard stays readable, and restore_group brings all the projects back. Confirm with the user first.confirm_upload— Confirm a completed file upload and create the database record. Call this after successfully uploading a file to the presigned URL from get_upload_url. This verifies the file exists in storage and creates a file block on the page.create_block— Create a new block on a page. Block types and their props: - **text**: Rich markdown content. Props: {"content": "markdown text"} - **checklist**: Task list with checkboxes. Props: {"items": [{"id": "item-0", "text": "Task", "done": false}]} or use add_checklist_item after creation - **ctlsurf_task**: Agent task assignments with notes. Props: {"items": [{"id": "task-0", "text": "Task", "done": false, "notes": []}]} or use add_ctlsurf_task_item after creation - **table**: Structured data grid. Props: {"columns": ["Col1", "Col2"], "rows": [["val1", "val2"]]} - **tree**: Hierarchical tree display. Props: {"nodes": [{"id": "1", "label": "Root", "children": [{"id": "2", "label": "Child"}]}]} - **react_flow**: Interactive node graph (auto-creates Graph). Props: {"graph_id": "uuid"} or empty to auto-create - **page_summary**: Page metadata for organization. Props: {"summary": "...", "purpose": "...", "status": "active", "tags": [], "key_topics": []} - **file**: File/image attachment. Props: {"file_id": "uuid", "filename": "name.ext", "mime_type": "image/png", "size_bytes": 12345}. To upload, use get_upload_url + curl PUT + confirm_upload — this creates the file block for you. - **mindmap**: Interactive mind map with visual editor. Props: {"content": "..."} where content can be: **Option 1 - Markdown format** (recommended for agents): ``` # Central Topic ## Branch 1 - Leaf item A - Leaf item B ## Branch 2 ### Sub-branch - [[Linked Page|page-uuid]] ``` Use # for root, ## for branches, ### for sub-branches, - for leaf items. Link to pages with [[Display Text|page-uuid]] syntax. **Option 2 - JSON tree format**: ```json {"id": "root", "text": "Central Topic", "children": [ {"id": "b1", "text": "Branch 1", "children": [ {"id": "l1", "text": "Leaf", "children": [], "link": "/page/uuid"} ]} ]} ``` **Dataspace blocks** (dataspace pages only — use dedicated tools instead of create_block): - **datastore**: (dataspace pages only) Structured data table with typed columns and row-level operations. Use `create_datastore` tool instead. - **state**: (dataspace pages only) Key-value store for agent config, counters, flags. Props: {"data": {"key": "value"}}. Use `get_state`/`set_state` tools. - **log**: (dataspace pages only) Append-only timestamped event stream. Props: {"entries": [], "max_entries": 1000}. Use `append_log`/`query_log` tools. These block types can ONLY be created on pages with type="dataspace" (e.g., the "Agent Datastore" page under your project root). - **presentation**: Slide deck (960x540 canvas, .pptx export). Use the `create_presentation` tool, then `add_slide_from_template` / `add_structured_slide` for rich structured slides. Markdown `content` slides render as flat text (no tables/bold/links) — avoid for real decks. Props: {"slides": [...], "config": {"theme": "dark"}}. Use checklist for simple task lists, ctlsurf_task for agent-assigned work with progress notes.create_page— Create a new page. The page is automatically assigned to a folder based on: 1. Explicit folder_id (if provided) 2. Parent page's folder (if parent_id provided) 3. Working directory (if cwd provided) - auto-creates folder if needed For a project with an organization template, first call get_project_organization(project_root_id). Choose a section using its purpose and the page content, then pass organization_placement: {mode: "section", project_root_id: "...", expected_binding_revision: 1, section_key: "..."} Omit parent_id: core resolves the section and creates only missing section pages on that path. To reuse an existing section page, explicitly include it in anchor_page_ids. Only if the user requests an exception, use mode: "user_override" with their reason and a parent_id inside the same project (omit parent_id to use the project root). This workflow makes no server-side LLM call. Placement is the agent's decision. IMPORTANT: For root pages (no parent_id), this tool automatically checks if a page already exists with the same root_path or repo_url. If found, it returns the existing page instead of creating a duplicate. Example - simple page creation with auto-folder: create_page(title="Bug Notes", cwd="/path/to/project") -> Creates page, auto-creates "project" folder if needed Example - sub-page (inherits folder from parent): create_page(title="API Docs", parent_id="") create_page_link— Create a properly formatted page link for use in markdown content. Returns the wiki-style link format: [[Display Text|page_uuid]] Use this to generate links when creating or updating text blocks. The link format ensures the link remains valid even if the page is renamed. Example: create_page_link(page_id="abc-123", display_text="Architecture Overview") -> Returns: {"link": "[[Architecture Overview|abc-123]]", ...} If display_text is not provided, the page's current title will be used. Usage in text block: update_block(block_id, props={"content": "See the " + link + " for details."})ctlsurf_call— Invoke a ctlsurf tool from a deferred toolset by name. Use load_toolset first to get the tool's documentation and input schema, then pass its name and arguments here. Listed tools can also be invoked this way, but calling them directly is preferred.delete_block— Delete a block from a pagedelete_folder— Delete a folder. Pages in the folder will be moved to root level.delete_page— Delete a page and all its blocksfind_folder_by_path— Find a folder by its codebase root pathfind_linkable_pages— Search for pages to link to. Use this before creating links to find the right page. Returns pages matching the search query with their IDs for use with create_page_link. Examples: find_linkable_pages(query="architecture") -> Search by title find_linkable_pages(folder_id="...") -> List pages in folder find_linkable_pages(query="api", limit=5) -> Limit resultsfind_page_by_root_path— Find a control plane page for a codebase. Use this to locate your project's page on session start. Examples: - find_page_by_root_path(repo_url="https://github.com/user/repo") - by repo URL (preferred, cross-machine) - find_page_by_root_path(root_path="/path/to/project") - by local path (auto-detects repo URL) Returns matching pages with match_type: "repo_match" (preferred), "exact" (path match), or "parent" (subdirectory).get_backlinks— Get all pages that link to a specific page. Returns the source page title, block ID, and link text for each incoming link. Use this to understand how pages are connected and discover related content. Example: get_backlinks(page_id="abc-123") -> Returns list of pages that contain [[...|abc-123]] linksget_block_history— Get version history for a block. Shows previous versions that can be restored.get_block_slice— Fetch a slice of a block's content (for large blocks). Returns paging metadata. Use offset/limit to paginate through large blocks.get_ctlsurf_instructions— Initialize a project with ctlsurf or get ctlsurf integration instructions. Use this when the user says "initialize project with ctlsurf" or "set up ctlsurf for this project". Returns instructions to add to your project memory/config file (CLAUDE.md, AGENTS.md, .cursorrules, etc.): - ctlsurf as source of truth - Checking pending tasks on session start - Documenting assumptions, shortcuts, decisions - Task workflow (claim, progress, complete, follow-up) - Block types guide (text, checklist, table, tree, react_flow, ctlsurf_task, datastore, state, log) - When to update documentation Usage: 1. Detect the git remote URL: run `git remote get-url origin` 2. Detect the hostname: run `hostname` 3. Call this tool with root_path, repo_url, and hostname 4. Add the returned 'instructions' to your project memory/config file Examples: - get_ctlsurf_instructions(root_path="/path/to/project", repo_url="https://github.com/user/repo", hostname="macbook-pro") - get_ctlsurf_instructions(root_path="/path/to/project", format="text")get_file_text— Get text content from a text-based file (txt, md, json, etc.).get_folder— Get a folder by ID with its pagesget_image— Get an image as base64 data for vision model analysis. Returns base64-encoded image + metadata. IMPORTANT: Only use this when you need to ANALYZE image content (e.g., read a chart, describe a screenshot, extract text from an image). This sends the full image data through MCP which is expensive for large images. For all other cases, use get_image_url instead — it returns a lightweight presigned URL.get_image_url— Get a temporary presigned URL for an image/file. The URL is valid for 1 hour, requires no authentication, and costs almost zero tokens. Use this as your DEFAULT way to access images. Use cases: - View/check what an image looks like (open the URL) - Reference in presentations, markdown, or HTML:  or- Share with users or embed in documentation - Check image metadata (dimensions, size, type) Only use get_image instead when you need to ANALYZE image content with vision (e.g., read a chart, OCR text from a screenshot).
get_outlinks— Get all pages that a specific page links to. Returns the target page title, block ID, and link text for each outgoing link. Example: get_outlinks(page_id="abc-123") -> Returns list of pages linked from page abc-123get_page— Get a specific page by ID, including all its blocksget_page_summary— Get the page summary block for a page. Returns summary, purpose, status, tags, key topics.get_page_tree— Get the hierarchical tree structure of all pagesget_pending_agent_tasks— Get all pending ctlsurf_task items assigned to you. Call this on session start to check for work. Examples: - get_pending_agent_tasks(repo_url="https://github.com/user/repo") - by repo URL (preferred) - get_pending_agent_tasks(root_path="/path/to/project") - by local path (auto-detects repo URL) - get_pending_agent_tasks() - get ALL pending tasks across all projects Tasks may include a skill (playbook) with workflow steps and guards to follow.get_project_organization— Read a project's versioned filing structure before creating a page: section keys, purposes, paths, examples, rules and existing section-page IDs. Choose placement yourself from the page content. Pass the chosen section and returned binding revision in create_page.organization_placement. When the user explicitly requests another location, use mode=user_override with their reason. Core validates permissions/project boundaries; no server-side LLM or semantic verdict is involved.get_upload_url— Get a presigned URL for direct file upload. This avoids sending file data through MCP, saving tokens. Flow: 1. Call get_upload_url(page_id, filename, mime_type) → returns upload_url, file_id, s3_key 2. Use Bash to upload: curl -X PUT -H 'Content-Type:' --upload-file /path/to/file ' ' 3. Call confirm_upload(file_id, s3_key, filename, mime_type, page_id) → creates file record and block The upload_url expires in 15 minutes. Supported types: images (png, jpg, gif, webp), PDFs, Office docs, text files, and email exports (.eml, .emlx, .msg, .mbox). Use the returned mime_type for the PUT Content-Type header and confirm_upload. list_blocks— List all blocks for a specific pagelist_blocks_meta— List lightweight block metadata (no props) for a page, including size hints. Use this to discover blocks without pulling full content.list_folders— List all folders (codebase workspaces). Returns folder metadata only by default. Use include_pages=true to get nested page trees (warning: can be very large). Prefer get_folder(folder_id) for a single folder's pages.list_page_files— List all files/images attached to a page. Returns metadata for each file including id, filename, mime_type, size, and dimensions for images.list_pages— List owned, enterprise-managed and shared pages. Active Business owners have full control within enterprise projects; ordinary shares do not reveal private pages. Returns slim metadata by default. Use folder_id to filter by project and include_shared=false to omit ordinary shared pages.list_trash— List all deleted pages in the trash. Deleted pages can be restored.load_toolset— Load the documentation and input schemas for a deferred toolset. Most ctlsurf tools are listed directly, but specialty toolsets are deferred to keep the tool list small. Call this when you need one of them, then invoke the returned tools via ctlsurf_call(tool_name, arguments). Available toolsets: - dashboards (18 tools): Business: group dashboards with change-order, proposal/SOW and ticket cards, documents, rollups and pinned live blocks - datastore (14 tools): Agent working data: typed-column data tables with row operations, key-value state blocks, append-only logs - graphs (12 tools): Diagrams: react-flow node graphs, ER diagrams (DB schemas, API contracts, domain models), tree nodes - presentations (7 tools): Slide decks: create presentations, add structured slides from templates, export to .pptx - skills (12 tools): Agent playbooks: create, search, fork and share reusable workflow definitionspermanent_delete_page— Permanently delete a page. This cannot be undone!restore_block_version— Restore a block to a previous version.restore_folder— Restore an archived folder back to active status.restore_group— Restore every archived project in a folder group, undoing archive_group.restore_page— Restore a deleted page from trash.search_blocks— Search block content and return snippets. Use get_block_slice for full content after finding matches.search_folder— Search for a folder by path, git URL, or name. Examples: - search_folder(query="https://github.com/user/repo", type="url") - by git repository URL - search_folder(query="/path/to/project", type="path") - by local filesystem path - search_folder(query="myproject", type="name") - by folder name (partial match) Returns the matching folder(s) with match_type indicator.search_pages— Search pages by title using full-text search with fuzzy matching. Supports stemming (run/running/ran match) and typo tolerance (threshold 0.3).update_block— Update a block's properties, title, or orderupdate_checklist_item— Update a checklist itemupdate_ctlsurf_task_item— Update a ctlsurf_task item. Use to add progress notes, mark complete, or reopen with feedback. Examples: - Add progress note: update_ctlsurf_task_item(block_id, item_id, notes=["Implemented login endpoint"]) - Mark complete with structured completion (REQUIRED when done=true): update_ctlsurf_task_item( block_id="...", item_id="...", done=true, completion={ "summary": "Implemented user authentication with JWT", "assumptions": ["Database schema already supports user sessions"], "attempted_but_failed": ["Tried OAuth but API rate limited"], "simplified_or_skipped": [] } ) - Reopen a completed task with feedback: update_ctlsurf_task_item( block_id="...", item_id="...", done=false, reopen_feedback="Please implement the rate limiting properly, don't skip it" ) - Attach skill: update_ctlsurf_task_item(block_id, item_id, skill_id="") IMPORTANT: When marking done=true, the completion object is REQUIRED with structured documentation of what was done, assumptions made, and any simplifications. update_folder— Update a folder's name, root_path, git_remote, hostname, or groupupdate_page— Update a page's properties. Use this to link pages to codebases. Examples: - update_page(page_id, repo_url="https://github.com/user/repo") - set repo URL for cross-machine support - update_page(page_id, root_path="/path/to/project") - set local path - update_page(page_id, title="New Title") - rename page - update_page(page_id, parent_id="") - move under another page - update_page(page_id, folder_id=" ") - assign to a folder - update_page(page_id, tags=["architecture", "documentation"]) - set page tags update_page_summary— Create or update a page's summary block with metadata like summary, purpose, status, tags, and key topics.attach_to_card— Attach a document (proposal, SOW, signed copy, redline, CO backup) to a card. Upload first with get_upload_url + curl PUT + confirm_upload, then pass the file_id. Documents with the same label are versioned (v1, v2...). role: draft | sent | client_redline | signed | amendment | backup | otherbackfill_dashboard— Import existing change-order tables and proposal/SOW pages in the group as cards. ALWAYS run with dry_run=true first and show the plan to the user; only run dry_run=false after they approve. Re-running never duplicates.comment_on_card— Add a progress note to a card's activity feed (use this instead of appending dated text blocks to pages).create_card— Create a card. type: change_order (stages pending, quoted, approved, delivered, billed, reclassified, goodwill), proposal / SOW (proposal, sold, in_progress, delivered, lost) or ticket (todo, in_progress, blocked, done). Example: create_card(folder_id="", type="change_order", title="Timberline export", stage="quoted", hours_agent=80, hours_unaided=110, rate=190, fields={"why_co": "SOW Part 1 explicitly deferred"}) find_dashboard_blocks— Search blocks on the group's pages (tables, datastores, charts, checklists...) to pin to the dashboard or link to a card. Returns block id, type, label, page and project. type filters by block type, comma separated (e.g. "table,datastore"); page_id lists one page's blocks.get_card— Full card: fields, links, documents (current + versions) and recent activity.get_dashboard_summary— KPIs (unbilled, delivered-not-billed, pipeline, open tickets, overdue), needs-attention list, per-project rollup and recent activity.link_card— Link evidence or related items to a card. relation: evidence | related | follow_up | parent | supersedes target_kind: commit (target_ref=sha) | url (target_ref) | page (target_id) | block (target_id=block UUID, e.g. the CO table) | card (target_ref=ref like CO-2, or target_id) Example after deploying: link_card(card_id, relation="evidence", target_kind="commit", target_ref="77c1995", label="RDI tracking")list_card_files— Documents on a card: the current document and every label with its version history. Read a document's text with get_file_text(file_id).list_cards— List cards (slim). Filter by type (change_order, proposal, ticket), stage, project or text. Ticket results also include task items from pages as virtual tickets.list_dashboard_pins— List the live blocks pinned to a dashboard's Views tab (id, title, type, source page, status).list_dashboards— List your folder groups, whether each has a dashboard, and whether you have Business access.move_card— Move a card to another stage (e.g. a change order to delivered after deploying), with an optional reason.pin_block_to_dashboard— Pin a live block (e.g. the CO table or a tracking datastore) to the dashboard's Views tab. The dashboard shows the block itself, so edits there and on the page are the same data. Pass block_id for one block, or block_ids to pin several (e.g. every table on a page; already pinned ones are skipped). width: full | half. label (single block only) overrides the shown title.snapshot_card_document— Export the card's body page to docx or pdf and attach it as a new version, e.g. to freeze exactly what was sent to the client.unpin_dashboard_block— Remove a pinned view from the dashboard. The block and its page are not changed.update_card— Update card fields. `fields` is merged (set a key to null to remove it). Pass stage to move it. Add `note` to explain the change.update_dashboard_ticket— Move a task item shown on the dashboard as a ticket (stage todo, in_progress, blocked, done). Writes back to the source ctlsurf_task/checklist item; done on a ctlsurf_task needs a completion object.add_column— Add a column to a datastore block's schema. Example: add_column(block_id="...", name="Priority", type="select", options=[ {"value": "high", "color": "red"}, {"value": "medium", "color": "yellow"}, {"value": "low", "color": "green"} ])add_row— Add a row to a datastore block. Use column names as keys. Example: add_row(block_id="...", data={"Post": "How to automate X", "Status": "scouted", "Score": 85}) Returns the new row with its UUID id for future reference.append_log— Append an entry to a log block (timestamped, append-only event stream). Example: append_log(block_id="...", action="scout_complete", message="Found 36 posts across 5 subreddits", data={"post_count": 36}) Auto-adds timestamp and unique ID. Trims oldest entries if exceeding max_entries (default: 1000).create_datastore— Create a datastore block on a page — a structured data table with typed columns for agent working data. IMPORTANT: Can only be created on dataspace pages (type="dataspace"). The "Agent Datastore" page under your project root is a dataspace. Create child pages under it for additional datastores. Dataspaces are pages where agents store structured, queryable data that humans can see and edit. Column types: - text: Plain text strings - number: Numeric values (sortable, filterable with $gt/$lt) - select: Single value from predefined options (renders as colored pills) - url: Clickable links - date: ISO date strings (renders as relative time) - checkbox: Boolean toggles Example — Reddit tracker: create_datastore(page_id="...", title="Post Tracker", columns=[ {"name": "Post", "type": "text"}, {"name": "Status", "type": "select", "options": [ {"value": "scouted", "color": "blue"}, {"value": "engaged", "color": "yellow"}, {"value": "plugged", "color": "green"} ]}, {"name": "Score", "type": "number"}, {"name": "URL", "type": "url"}, {"name": "Scouted At", "type": "date"} ])delete_column— Delete a column from a datastore block. Removes the column from schema and from all rows. Example: delete_column(block_id="...", column="Priority")delete_row— Delete a row from a datastore block.delete_rows— Bulk-delete datastore rows by filter or explicit ids (set-based, cheap). Provide either `where` (same operators as query_rows) or `row_ids` — one is required. Examples: delete_rows(block_id="...", where={"Status": "rejected"}) delete_rows(block_id="...", row_ids=["", " "]) get_datastore_ingest_url— Get a short-lived signed URL to BULK-load rows into a datastore by uploading a local file — instead of sending row data through this conversation (token-expensive). Use for bulk writes (say >20 rows). For a few rows use add_row / update_rows / delete_rows. Flow (row data never enters your context — your code writes it to a temp file): 1. url = get_datastore_ingest_url(block_id, mode="insert") 2. Write rows to /tmp/ingest.ndjson — one JSON object per line, keys = column names: {"Post": "How to X", "Status": "scouted", "Score": 85} 3. curl -X POST --data-binary @/tmp/ingest.ndjson '' Modes (full table control): - insert: append rows - upsert: match existing rows on key_column (required) -> replace matched row, else insert - replace: table becomes exactly the uploaded rows (all-or-nothing; aborts if any row is invalid) - delete: body is row ids one per line (or key_column values if key_column is set) A JSON array is accepted instead of NDJSON. URL expires in 15 minutes. Returns {url, instructions}. get_state— Get values from a state block (key-value store for agent config, counters, flags). Example: get_state(block_id="...", keys=["last_scout_run", "budget_remaining"]) Returns only the requested keys, or all keys if none specified.query_log— Query entries from a log block with optional filters. Example: query_log(block_id="...", since="2026-02-25T10:00:00Z", action="scout_complete", limit=50) Returns entries newest-first.query_rows— Query rows from a datastore with filtering, sorting, and pagination. Returns only matching rows. Filtering — use column names: Simple equality: where={"Status": "scouted"} Operators: where={"Score": {"$gt": 50}} Multiple filters: where={"Status": "scouted", "Score": {"$gte": 80}} Change detection: where={"_updated_at": {"$gt": "2026-02-25T10:00:00Z"}} Operators: $gt, $gte, $lt, $lte, $neq, $in, $contains Sorting: order_by="Score", order="desc" Pagination: limit=10, offset=0 Column selection: select=["Post", "Status"] — only return these columns Returns {rows, total_count, has_more} for pagination.set_state— Set values in a state block. Merges with existing data (doesn't replace). Example: set_state(block_id="...", data={"last_scout_run": "2026-02-25", "budget_remaining": 2}) Use null to delete a key: set_state(block_id="...", data={"old_key": null})update_row— Update a row in a datastore block. Only provided fields are updated (merge). Example: update_row(block_id="...", row_id="...", data={"Status": "engaged"}) Updates updated_at timestamp for change detection.update_rows— Bulk-update datastore rows matching a filter (set-based, cheap — no file needed). Only the provided fields are merged into each matched row. Filter uses the same operators as query_rows ($gt/$gte/$lt/$lte/$neq/$in/$contains). Omitting `where` updates ALL rows. Example: update_rows(block_id="...", where={"Status": "scouted"}, data={"Status": "engaged"})add_graph_edge— Add an edge (connection) between two nodes in a graphadd_graph_node— Add a node to an existing graphadd_tree_node— Add a node to a tree blockcreate_er_diagram— Create an entity-relationship diagram from Mermaid-like syntax. Returns a graph with entity nodes. Each entity has a header and supports multi-line typed fields - use this for any structured model, not just databases. Great for: - **Database schemas** - tables, columns, foreign keys - **API contracts** - request/response shapes with typed fields - **Domain models** - business entities and their relationships - **System architecture** - services with their interfaces and dependencies - **Config schemas** - settings groups with typed options - **Feature specs** - components with properties and connections **Quick Start - Database:** ``` User ||--o{ Post : writes User { int id PK string email UK } Post { int id PK int user_id FK string title } ``` **Quick Start - API Contract:** ``` Client ||--o{ APIGateway : calls APIGateway ||--|| AuthService : validates Client { header Authorization body JSON payload } APIGateway { POST /api/users GET /api/users/:id PUT /api/users/:id } AuthService { method validate_token method refresh_token returns JWT claims } ``` **Quick Start - Domain Model:** ``` Customer ||--o{ Order : places Order ||--|{ LineItem : contains LineItem }o--|| Product : references Customer { string name string tier %% gold, silver, bronze date member_since } Order { enum status %% pending, shipped, delivered decimal total date placed_at } Product { string sku UK decimal price int stock_qty } ``` **Relationship Syntax:** `EntityA-- EntityB : label` Cardinality symbols (read from entity outward): - `||` = exactly one (1) - `o|` or `|o` = zero or one (0..1) - `|{` or `{|` = one or more (1..n) - `o{` or `{o` = zero or more (0..n) - `}|` or `|}` = many (n) Examples: - `User ||--o{ Post` = one user has zero-or-many posts - `Post }o--|| Category` = many posts belong to one category - `User ||--|| Profile` = one-to-one relationship **Field Syntax:** `type name [PK|FK|UK] [?]` - Fields are freeform text - use any format that fits your model - `PK` = Primary Key, `FK` = Foreign Key, `UK` = Unique Key - `?` = Nullable/optional (e.g., `string bio?`) **Comments:** Use `%%` for inline comments (e.g., `int id PK %% auto-increment`) **Notes:** - The `erDiagram` keyword is optional - Entities referenced in relationships are auto-created if not defined - Entities are auto-positioned hierarchically (parents above children) Returns: `{graph_id, block_id, entity_count, relationship_count, message}` create_graph— Create a new graph for react_flow blocks. Use this for architecture diagrams, flowcharts, and system designs. **Node structure**: Each node needs id, position {x, y}, and data {label}. Type defaults to 'default'. **Edge structure**: Each edge needs id, source (node id), and target (node id). **Meta**: Optional metadata like title and description. Example - System Architecture: { "nodes": [ {"id": "frontend", "position": {"x": 250, "y": 0}, "data": {"label": "React Frontend"}}, {"id": "api", "position": {"x": 250, "y": 100}, "data": {"label": "FastAPI Backend"}}, {"id": "db", "position": {"x": 250, "y": 200}, "data": {"label": "PostgreSQL"}} ], "edges": [ {"id": "e1", "source": "frontend", "target": "api"}, {"id": "e2", "source": "api", "target": "db"} ], "meta": {"title": "System Architecture"} } After creating, use the returned graph_id with create_block(type='react_flow', props={'graph_id': ''}). delete_graph_edge— Delete an edge from a graphdelete_graph_node— Delete a node and its connected edges from a graphget_graph— Get a graph by ID with its nodes and edgesget_react_flow_block_graph— Get the graph associated with a ReactFlow blocklist_graphs— List all graphsupdate_graph— Update an existing graph's nodes, edges, or metadata. Useful for modifying architecture diagrams.update_graph_node— Update a node's label or positionadd_slide— Append a markdown-based (legacy v1) slide to a presentation. LIMITED — prefer add_slide_from_template or add_structured_slide for anything you want to look good. On render this collapses to a plain title + text box. Rich markdown (tables, **bold**, *italic*, links, nested lists) is NOT rendered — it shows as literal characters. Only headings, plain lines, simple bullets, code fences, and  are picked up. Examples: add_slide(block_id="...", content="## New Topic\n- Point 1\n- Point 2") add_slide(block_id="...", content="## Inserted", title="Middle Slide", position=2)add_slide_from_template— Add a slide built from a named template — the easiest way for agents to create well-laid-out slides without calculating coordinates. Slides use a 960x540 logical canvas (16:9 wide). All x/y/w/h values are in these logical units. Templates and required fields: - **title**: cover slide fields: { title: str, subtitle?: str, notes?: str } - **title_bullets**: title plus bulleted body (most common) fields: { title: str, bullets: [str], notes?: str } - **two_column**: title plus two side-by-side text blocks fields: { title: str, left: str | [str], right: str | [str], notes?: str } (arrays render as bullets) - **image_left_text_right**: title plus image on left, text on right fields: { title: str, image_url: str, text: str | [str], notes?: str } (image_url can be /api/files/{id}/content) - **section_divider**: large centered title on colored background fields: { title: str, background_color?: str (default #1e293b), notes?: str } Example: add_slide_from_template( block_id="...", template="title_bullets", fields={ "title": "What shipped this sprint", "bullets": ["Auth refactor", "Billing page", "Docs rewrite"] } )add_structured_slide— Add a slide with precisely-positioned shapes to a presentation block. Prefer this or add_slide_from_template over add_slide(markdown) when you need layout control (position, size, styling). Slides use a 960x540 logical canvas (16:9 wide). All x/y/w/h values are in these logical units. Example — two text blocks and an accent line: add_structured_slide( block_id="...", shapes=[ {"type": "text", "text": "Q1 Results", "x": 40, "y": 40, "w": 880, "h": 70, "fontSize": 36, "bold": True}, {"type": "text", "text": "Revenue\nGrowth\nARR", "x": 60, "y": 140, "w": 840, "h": 300, "fontSize": 22, "bullet": True}, {"type": "line", "x": 40, "y": 120, "w": 880, "h": 2, "stroke": "#3b82f6", "strokeWidth": 2} ], background={"color": "#0f172a"} )create_presentation— Create a presentation (slide deck) block on a page. Supports .pptx export. Slides render on a 960x540 logical canvas (16:9) and export to PowerPoint. >>> Build real decks with STRUCTURED (v2) slides. <<< Create the block, then add slides with add_slide_from_template (named layouts) or add_structured_slide (custom shapes). That is how good-looking decks are made — titles, two-column layouts, metric cards, accent bars, images, etc. Recommended flow: block = create_presentation(page_id="...", title="Sprint Review", slides=[]) add_slide_from_template(block_id, template="title", fields={"title": "...", "subtitle": "..."}) add_slide_from_template(block_id, template="title_bullets", fields={"title": "...", "bullets": [...]}) # ...or add_structured_slide(block_id, shapes=[...]) for full control Markdown slides (v1) are accepted but DISCOURAGED: on render they collapse to a plain title + text box. Rich markdown — tables, **bold**, *italic*, links, nested lists — is NOT rendered; it shows as literal characters. Use markdown only for a quick text-only slide, e.g. slides=[{"title": "Overview", "content": "- A\n- B"}]. Companion tools: - add_slide_from_template() — named layouts: title, title_bullets, two_column, image_left_text_right, section_divider (easiest) - add_structured_slide(shapes) — fully custom layout - update_structured_slide / delete_slide - add_slide(markdown) / update_slide — legacy v1 text-only slides Images: - Upload via get_upload_url + confirm_upload, then reference /api/files/{file_id}/content as an image shape url or background.imageUrl. - Keep uploaded images on a sub-page titled "Images" under the presentation page. Config options: - theme: "dark" (default), "light", "dracula" - controls / progress / transition: deprecated (carried in props, no longer rendered)delete_slide— Remove a slide from a presentation block by slide ID.update_slide— Update a legacy markdown (v1) slide by slide ID. For v2 structured slides (those with version=2 and a shapes array), use update_structured_slide instead. Example: update_slide(block_id="...", slide_id="slide-0", content="## Updated Content", notes="New notes")update_structured_slide— Update shapes, background, or notes on an existing structured (v2) slide. Pass a full `shapes` array to replace the slide's shape list. To tweak a single shape, fetch the slide first, mutate the array, and send it back. Works on v2 slides only. For legacy markdown slides, use update_slide.create_skill— Create a new skill (agent playbook/workflow). Skills are reusable workflows that guide agents through complex tasks with: - Inputs: Variables that need to be collected before starting - Workflow: Step-by-step instructions with optional checkpoints - Guards: Safety rules that must not be violated Example - Create an API debugging skill: ``` create_skill( name="API Debug Workflow", description="Systematic approach to debugging API issues", category="debugging", icon="🔧", scope="personal", definition={ "version": "1.0", "inputs": [ {"name": "endpoint", "type": "text", "description": "API endpoint URL", "required": True}, {"name": "error_message", "type": "text", "description": "Error encountered", "required": True} ], "workflow": [ {"type": "step", "name": "analyze", "title": "Analyze the error", "template": "Review the error: {error_message}"}, {"type": "step", "name": "check_logs", "title": "Check logs", "template": "Look for related errors in logs"}, {"type": "checkpoint", "name": "confirm_fix", "title": "Confirm approach", "description": "Verify fix approach with user"}, {"type": "step", "name": "implement", "title": "Implement fix", "template": "Apply the fix"} ], "guards": ["Never modify production database directly", "Always backup before changes"] } ) ```delete_skill— Delete a skill from your library. You can only delete skills you own. This cannot be undone. Example: ``` delete_skill(skill_id="") ``` fork_skill— Fork a public skill to your library for customization. Like Canva templates - copy a skill to your library, then modify it. Examples: - fork_skill(skill_id="") - fork to personal library - fork_skill(skill_id=" ", target_scope="project", folder_id=" ") - fork to a project - fork_skill(skill_id=" ", new_name="My Custom Debug Workflow") - fork with custom name get_my_skills— Get skills from your library (personal and project skills). Examples: - get_my_skills() - get all your skills - get_my_skills(folder_id="") - get skills for a specific project Use this to check what skills are available before starting a task. get_skill— Get a skill's full definition including inputs, workflow steps, and guards. Use this to understand how to execute a skill attached to a task.get_skills_shared_with_me— Get all skills that have been shared with you by other users. Returns skills with the sharer's email and your permission level. Example: ``` get_skills_shared_with_me() ```list_skill_shares— List all users a skill is shared with. You must be the owner or have admin permission to view shares. Example: ``` list_skill_shares(skill_id="") ``` revoke_skill_share— Remove a user's access to a shared skill. You must be the owner or have admin permission to revoke shares. Example: ``` revoke_skill_share(skill_id="", share_id=" ") ``` search_skills— Search the skill marketplace for agent playbooks. Examples: - search_skills(query="API debugging") - search by keywords - search_skills(query="code review", category="review") - filter by category Returns matching public skills that can be forked to your library.share_skill— Share a skill with another user by email. You can only share skills you own. Sharing gives the user access to view, edit, or admin the skill. Permission levels: - view: Can see and use the skill - edit: Can modify the skill definition - admin: Can manage shares (add/remove other users) Examples: - share_skill(skill_id="", email="user@example.com") - share with view access - share_skill(skill_id=" ", email="user@example.com", permission="edit") - share with edit access update_skill— Update an existing skill's metadata or definition. You can only update skills you own. Example - Update workflow steps: ``` update_skill( skill_id="", definition={ "version": "1.0", "inputs": [...], "workflow": [...updated steps...], "guards": [...] } ) ``` Example - Just update name and description: ``` update_skill( skill_id=" ", name="New Name", description="Updated description" ) ``` update_skill_share— Update the permission level of an existing skill share. You must be the owner or have admin permission to update shares. Example: ``` update_skill_share(skill_id="", share_id=" ", permission="edit") ```
About MCP
The Model Context Protocol (MCP) is an open protocol Anthropic introduced for connecting LLM-based agents to external tools and data sources. Providers publish MCP servers that expose their API surface as structured, discoverable tools — an MCP-compatible client (Claude Desktop, Cursor, Cline, Continue, etc.) can connect to the server and call its tools without any per-provider integration code.
Browse every MCP server on the APIs.io network or compare with the broader Agent Skill surfaces of the same providers.
Work with this as data
Every MCP server here is available over the APIs.io API and to AI agents over MCP.