mapnotes
Summary: The MapNotes MCP server lets an AI agent read, search, and edit a MapNotes graph file (YAML/JSON) on disk, with every change saved immediately.
Load a graph file —
load_graphopens a YAML/JSON file by path and makes it the current file (missing file → empty graph).Save the graph —
save_graphwrites to the current file or saves a copy to a new path (.json→ JSON, otherwise YAML).Read the whole graph —
get_graphreturns properties, styles, nodes and edges as YAML or JSON.Inspect one node —
get_nodereturns a node with its properties and all connected edges.Search/list nodes —
find_nodesfilters by text query, style, property key/value, with paging (limit up to 200, cursor).Manage nodes —
add_node,edit_node(relabel, rename id, move, set/remove/replace properties, change style),remove_node(also drops its edges).Manage edges —
add_edge,edit_edge(endpoints, label, style, id, properties),remove_edge.Edit graph-level properties —
edit_graph_propertiessets/removes/replaces key/value pairs such astitleandsubtitle.Manage reusable styles —
set_stylecreates/replaces node or edge styles (color, shape, size, border, arrow, line style, curve, icon, icon size/color) andremove_styledeletes them.Stay in sync with the web app — when a file is open, changes are written immediately and the file is re-read before each operation, so browser or text-editor edits aren't overwritten.
Serves at runtime the icon assets referenced by node styles (Lucide line icons and full-color brand logos), so styled graphs render their icons without the project shipping or storing any icon files.
Node styles created with the set_style tool can embed Lucide line icons via the "lucide:" icon field (e.g. lucide:folder, lucide:bug), with configurable icon color and icon size. The icons are drawn inside the node's shape on the canvas.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mapnotesadd a node for the login service and connect it to the user database"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MapNotes
A render engine for your own graphs. Model anything as nodes and edges (pull requests → issues → projects, services → dependencies, notes → topics…), attach key/value properties, style them, and save everything as a readable YAML file.
Web app: interactive canvas (drag, click to inspect, hover to preview), editing of nodes, edges, graph properties and styles (including full-color brand icons), automatic layouts, search, undo/redo, dark theme by default with a light theme toggle.
MCP server: lets an AI assistant (Claude Code, Claude Desktop, …) load, query and edit a graph file. Open the same file in the web app (Chrome/Edge) to see its edits live.
Quick start
npm install
npm run dev # dev server with hot reload at http://localhost:5173
npm run build # static site in dist/
npm test # regression tests (vitest)The web app is a static site with no backend. How it saves depends on the browser:
Chrome, Edge and other Chromium browsers can edit files on disk directly (File System Access API). Open a YAML/JSON file, or Save to create one, and every change is written back to that file. The file is also checked every second, so edits made by the MCP server or a text editor show up live. After a page reload the browser remembers the file but asks for permission again: click Reconnect in the status pill. Close file in the ▾ menu unlinks it; New also unlinks it so they never overwrite your file.
Firefox and Safari don't support that API, so work is kept in the browser (
localStorage). Open loads a file's contents, and Download saves a copy.
In every browser the current graph is also kept in localStorage, and the ▾ menu can download YAML/JSON or export a PNG.
Live site
Every push to master is built and published to GitHub Pages by .github/workflows/deploy.yml: https://joecabezas.github.io/mapnotes/
Using the app
Action | How |
Add node |
|
Connect nodes |
|
Inspect / edit | Click a node or edge; edit label, id, style, properties on the right |
Select several |
|
Delete |
|
Clusters | Select a node and click Convert to cluster in the inspector: it is drawn as a zone around every node connected to it (in either direction), and its own edges are hidden. The zone bends around nodes that aren't in it, so it never covers them. Click the zone to select it, drag it to move its members, and Convert to node to bring the node and its edges back as they were. The node only gets |
Hide / show |
|
Arrange | Drag nodes, or click Layout and pick one (it applies immediately; ↻ runs it again). Smart (layered) puts parents above their children and minimises edge crossings; Force-directed spreads nodes out like springs. Evolved (genetic) starts from the other layouts and the current one and evolves them with a genetic algorithm towards fewer edge crossings, animating the best layout of each generation; besides crossings it keeps edges clear of the nodes they don't connect. It explores first: whenever it gets stuck it keeps the best layout of that try and starts over, so the whole layout changes; then the best of those breed and get refined. Its settings (time, explore, population, crossover, mutations, repair, spacing, edge clearance) show in the panel while it's picked, and Stop keeps the best so far. Grown (insertion) builds a layout from scratch, inserting the nodes one at a time (busiest first, then whichever has the most neighbours already in), each at the spot that adds the fewest crossings; where nothing fits, it pushes the nodes around a spot outwards to make room, and the nodes around each new one move to better spots if they can. Then it keeps taking out a patch of the layout around a crossing and inserting it again, keeping the result when it's no worse. You watch it grow, then improve; its settings (time, patch size, lookahead, crowding, spacing, edge clearance, make room) show in the panel while it's picked. The status bar shows the graph's edge crossings, edges drawn through or closer than the edge clearance to other nodes, and overlapping nodes. The Repel button next to it is a switch: while it is on, the selected nodes (or all of them, if fewer than two are selected) float apart until there is room between them, held together by their edges like the atoms of a molecule: dragging one pushes the others out of its way and pulls along the ones it is connected to. The rest of the graph fades meanwhile. |
Styles | 🎨 Styles: node color/border/shape/size/icon, edge color/width/line/arrow/curve |
Views | Views opens a panel to switch between saved views, create one (+) or edit the current one (✎). While a view is active, × next to Views returns to the full graph. See "Filters and saved views" below |
Search |
|
Undo / redo |
|
Open / save |
|
Help |
|
Filters and saved views
A saved view shows part of the graph: the nodes that match its filter. Nodes, edges and properties stay shared, so an edit made in a view changes the same graph you see everywhere else.
Create: click Views, then +. Give the view a name and choose what it shows: entity types, node styles, text, exact property values, nodes within 1–3 connections of a node, and edge labels. Values within one list are alternatives; different filters must all match. None matches nothing, and All removes that restriction. A node's entity type is its
typeproperty, or its style if it has none (PR styles are grouped aspr).Switch: pick a view from the dropdown in the Views panel. The Views button is highlighted while a view is active, and the panel shows how many nodes are visible (e.g.
12/40 nodes).Back to the full graph: click × next to Views.
Edit: ✎ in the panel lets you rename the view, change its filter, reset the filter or delete the view.
Each view keeps its own layout: dragging nodes or running a layout in a view doesn't move them anywhere else. A view only stores positions for the nodes it shows, so a node that stops matching the filter, or is removed, loses its position in that view. Selection, search and layout act on the visible nodes. Saving or downloading YAML/JSON always keeps the full graph, while PNG exports what is on the canvas.
Views are stored under the optional top-level views field of the graph file and in the browser copy. They store the filter, not a list of nodes, so new nodes that match appear in the view automatically. Files without views still work. MCP server versions from before views were added drop this field when they save; restart your MCP client to update it.
views:
- id: people-prs
name: People & PRs
filters:
types: [person, pr]
edgeLabels: [authors]
positions:
alice: { x: 0, y: 0 }Icons
Node styles can show an icon inside the node, from two open icon sets. Both are fetched at runtime from jsDelivr, so nothing is stored in this repo. The canvas needs network access to show them; if an icon can't be loaded, the node is simply drawn without it.
Lucide (pinned to
lucide-static1.x): ~1,850 general-purpose line icons, writtenlucide:<name>, e.g.lucide:folder,lucide:bug. They're drawn in one color: black or white to suit the node's fill, or Icon color to choose.svgl (pinned to svgl
5.0.0): full-color brand logos, written as the svgl file name, e.g.slack,linear,obsidian. Logos with light and dark variants have a suffix:github_lightis the dark logo for light fills,github_darkis the white logo for dark fills.
In 🎨 Styles, Browse… next to Icon opens a picker with a tab per set and a search box (Lucide matches names and tags, e.g. "money" finds lucide:banknote). You can also type a name directly. Icon size (%) sets how much of the node the icon fills (default 70).
Icons are drawn over the node's shape and fill. A circle (ellipse) works best; pointed shapes such as diamond or star clip the icon. For brand logos, a white fill with the brand color as the border works for most.
The MCP set_style tool accepts the same icon, iconColor and iconSize fields.
Related MCP server: Neo4j MCP Server
MCP server
The MCP server lets an AI agent read and edit a graph file. The GitHub Pages site is static, so the server runs locally on your computer over stdio. There is no connection between the website and the MCP server: Chrome or Edge reads and writes a file you choose with its file picker, while the MCP server reads and writes a file by its path. If both use the same file, the browser notices changes made by the agent when it checks the file.
Install without cloning
Install Node.js 20.6 or newer and Git, then register the server in your MCP client. You do not need a graph file yet. npm downloads the repository and dependencies into its cache on first use; you do not need a local clone or an npm account.
For Claude Code:
claude mcp add --scope user mapnotes -- npm exec --yes --package=github:joecabezas/mapnotes -- mapnotes-mcpFor another stdio MCP client, use this configuration (adjust it to your client's configuration format):
{
"mcpServers": {
"mapnotes": {
"command": "npm",
"args": ["exec", "--yes", "--package=github:joecabezas/mapnotes", "--", "mapnotes-mcp"]
}
}
}Restart or reload your MCP client and call get_graph to check the connection. Without a file, the server starts with an empty graph in memory. To see edits on the web app, ask the agent to call save_graph with an absolute .yaml or .json path, then use Open in Chrome or Edge to choose that file. Or save a graph in the web app first and ask the agent to call load_graph with its path. The website's file picker does not pass the file path to the MCP server; you must give the agent that path separately. Once both use the same file, the browser checks it for changes and the MCP server re-reads it before operations. Save before restarting the MCP client: unsaved in-memory changes are lost when it stops. You can also pass the file path after mapnotes-mcp in the command above to open it at startup.
Each time the client starts the server, npm checks the latest commit on the repository's default branch and downloads it if it changed, so new tools and fixes arrive after you restart or reload the MCP client. A running server keeps its current version until then. Starting the server needs access to GitHub.
Ask your agent to install it
Paste this into Claude Code, Codex, Cursor or any agent that can run commands:
Install the MapNotes MCP server for me:
1. Register a stdio MCP server named "mapnotes" with command "npm" and arguments:
exec --yes --package=github:joecabezas/mapnotes -- mapnotes-mcp
In Claude Code: claude mcp add --scope user mapnotes -- npm exec --yes --package=github:joecabezas/mapnotes -- mapnotes-mcp
2. Tell me how to reload MCP servers in this client, then call mapnotes get_graph to check it works.
3. When I want to see edits in https://joecabezas.github.io/mapnotes/, ask me where to save the graph, call save_graph with that absolute path, and tell me to open the same file in Chrome or Edge. If I already have a graph file, call load_graph with its path instead.Run from a local clone
If you are developing MapNotes, you can also run the server from this repository:
git clone https://github.com/joecabezas/mapnotes && cd mapnotes && npm install
npx tsx mcp/server.ts # optionally add an absolute graph file pathTool | Purpose |
| Open a YAML/JSON file; it becomes the current file |
| Save to the current file, or "save as" to a new path ( |
| Read the graph, or one node with its edges |
| Search nodes by id, label, property or style; paged id/label summaries for large graphs |
| Node CRUD (rename via |
| Edge CRUD (label, endpoints, style, properties) |
| Set/remove/replace graph-level key/value properties |
| Create/replace/remove reusable node and edge styles (including |
When a file is open, every change is written to it immediately, and the file is re-read before each operation, so edits made to the file by other tools are not overwritten.
File format
properties: # GraphProperty[]
- key: title # shown as the graph's name
value: My graph
- key: subtitle # optional line shown under the title
value: What this graph is about
styles: # reusable looks, referenced by id
- id: pr
target: node # node | edge
color: "#7aa2f7"
borderColor: "#c9d8ff"
borderWidth: 2 # optional, outline width in px, 0-20 (default 2)
textColor: "#ffffff"
shape: ellipse # ellipse, rectangle, round-rectangle, triangle, diamond, pentagon, hexagon, octagon, star, tag, barrel
size: 34
icon: github_dark # optional, svgl logo name or lucide:<name> (see "Icons" above)
iconColor: "#ffffff" # optional, color of Lucide icons
iconSize: 70 # optional, icon size as % of the node (default 70)
- id: fixes
target: edge
color: "#7dcfff"
width: 2
lineStyle: dashed # solid | dashed | dotted
arrow: triangle # triangle, vee, circle, square, diamond, tee, none
curve: bezier # bezier | straight | taxi
nodes:
- id: pr-101
label: PR 101
style: pr # optional
position: { x: 0, y: 120 } # optional; saved when you drag
cluster: true # optional; draw it as a zone around the nodes connected to it
properties:
- key: author
value: sam
edges:
- id: e1
source: pr-101
target: issue-12
label: fixes # optional
style: fixes # optional
properties: []
views: # optional saved views (see "Filters and saved views")
- id: open-prs
name: Open PRs
filters: # every filter is optional
types: [pr] # `type` property, or style when there is none
styles: [pr]
query: login # text in ids, labels and properties
properties: # exact key/value matches, all required
- key: author
value: sam
relatedTo: [issue-12] # only nodes within `depth` connections of these
depth: 2 # 1-3, default 2
edgeLabels: [fixes] # only edges with these labels
positions: # this view's layout, only for the nodes it shows
pr-101: { x: 0, y: 0 }For hand-written files, properties may also be a plain mapping (properties: { author: sam }), and edge ids may be left out (they're generated). JSON with the same structure is accepted too. See examples/layering-100.yaml.
Project layout
shared/ data model, graph operations, YAML/JSON (used by everything)
mcp/ MCP server (stdio)
web/ React + Cytoscape.js frontend
examples/ sample graphsContributing
See CONTRIBUTING.md. Commits in pull requests must be signed off (git commit -s) under the Developer Certificate of Origin.
License
Available Tools
14 toolsadd_edgeAdd edgeB
Connect two existing nodes. The id is generated if omitted.
Inputs: source (required), target (required), id, label, +2 more. Returns: edge, file
add_nodeAdd nodeB
Add a node. The id is generated if omitted; the label defaults to the id.
Inputs: id, label, style, position, +1 more. Returns: file, node
edit_edgeEdit edgeB
Edit an edge: endpoints, label, style, id or properties. Only the given fields change; "" clears label/style.
Inputs: id (required), label, newId, style, +5 more. Returns: edge, file
edit_graph_propertiesEdit graph propertiesA
Edit the key/value properties of the graph itself. set adds/overwrites keys, remove deletes keys, properties replaces the whole list. "title" names the graph (keep it short) and "subtitle" is shown under it.
Inputs: set, remove, properties. Returns: file, properties
edit_nodeEdit nodeA
Edit a node. Only the given fields change. setProperties adds/overwrites keys, removeProperties deletes keys, properties replaces the whole list. Renaming (newId) updates connected edges. style "" clears the style.
Inputs: id (required), label, newId, style, +4 more. Returns: file, node
find_nodesFind nodesARead-only
Search or list nodes without fetching the whole graph. Returns ids with brief summaries (label, style, property and edge counts, matched fields), sorted by id; call get_node for details. All filters are optional and combine with AND; with none, lists every node. If nextCursor is returned, pass it as cursor to get the next page.
Inputs: limit, query, style, cursor, +2 more. Returns: nodes, total, nextCursor
get_graphGet graphARead-only
Return the whole current graph (properties, styles, nodes, edges). YAML output starts with a comment naming the current file; JSON output is plain JSON.
Inputs: format. Returns: file, graph, issues
get_nodeGet nodeBRead-only
Return one node with its properties and all edges connected to it.
Inputs: id (required). Returns: node, edges
load_graphLoad graphA
Open a graph file (YAML, or JSON if it ends in .json). It becomes the current file: all later changes are saved to it automatically. A missing file starts an empty graph.
Inputs: path (required). Returns: file, counts, issues
remove_edgeRemove edgeCDestructive
Remove an edge by id.
Inputs: id (required). Returns: file, removedEdge
remove_nodeRemove nodeBDestructive
Remove a node and every edge connected to it.
Inputs: id (required). Returns: file, removedNode, removedEdges
remove_styleRemove styleADestructive
Remove a style; nodes/edges using it fall back to the default look.
Inputs: id (required). Returns: file, removedStyle
save_graphSave graphA
Save the current graph. With a path, saves a copy there (format from the extension: .json → JSON, otherwise YAML) and makes it the current file.
Inputs: path. Returns: file, counts, format, dropped
set_styleCreate or replace styleA
Create or replace (by id) a reusable style in the graph styles. Assign it via the "style" field of nodes/edges. Colors are CSS colors, e.g. "#7aa2f7". Fields marked "Nodes only" / "Edges only" are rejected for the other target.
Inputs: id (required), target (required), icon, name, +12 more. Returns: file, style
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
TDQS
Scored across 14 tools
Each tool targets a distinct resource/action pair: graph file I/O (load/save), whole-graph retrieval, node/edge CRUD, graph property editing, and style management. get_node vs find_nodes are clearly differentiated (detail vs search), and edit_node/edit_edge/edit_graph_properties have non-overlapping targets.
All tool names use a consistent snake_case verb_noun pattern (load_graph, save_graph, get_node, add_edge, edit_node, remove_style, etc.). There are no deviations into camelCase or vague single-word names.
The 14 tools precisely cover the graph-notes domain without redundancy. Each tool earns its place by handling a specific operation on nodes, edges, graph properties, or styles.
Core lifecycle coverage is strong: nodes have add/get/list/edit/remove, edges have add/edit/remove, and graph properties/styles are editable. Minor gaps exist: no direct get_edge or list_styles/get_style, though these can be inferred from get_graph or get_node.
Maintenance
Related MCP Connectors
Create, validate, edit, export (markdown/svg/png/mermaid), and search JSON Canvas files.
Draw, shape, and edit Neo4j graph data models in toro4n with your AI assistant.
Create, read and live-edit visual boards, Kanban plans, Gantt timelines and diagrams with AI agents.
Persistent knowledge graph for AI agents. Remember, recall, and forget facts.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with Gremlin-compatible graph databases through natural language, supporting schema discovery, complex graph queries, relationship analysis, and data import/export operations.12 npm9MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to interact with Neo4j graph databases through natural language, supporting Cypher queries, schema management, data manipulation, and graph algorithms.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to create, edit, and export flowcharts through a local web-based editor with visual drag-and-drop, real-time sync, and 14 MCP tools for full node/edge CRUD.2Apache 2.0
- AlicenseAqualityDmaintenanceEnables AI assistants to interact with LightRAG knowledge graphs, supporting smart upsert for Obsidian vaults, semantic queries, and document/graph management.1816 PyPI8Apache 2.0