# develosaur agent gateway > This host (mcp.develosaur.com) is the agent gateway for develosaur, a > visual project/knowledge graph tool. Agents read and write project boards > here over MCP (streamable HTTP) or REST. Anyone can try it instantly: > anonymous sandbox projects need zero credentials. ## STEP 0 - IS THERE A LOCAL HOST? CHECK BEFORE ANYTHING ELSE GET http://127.0.0.1:41777/v1/health IF IT ANSWERS, you are running on the user's own machine and the Develosaur Desktop app is serving their REAL boards locally. Do not use this cloud document for that machine. Instead: GET http://127.0.0.1:41777/llms.txt and follow THAT document. It is the host's own manual and it matches the version actually installed there, so it can never be out of date the way a remembered recipe can. The local host needs no sign-in flow: its bearer token sits at ~/.develosaur/host-token and every board the signed-in user owns is already there. IF /v1/health ANSWERS BUT /llms.txt RETURNS 404, the desktop app is an older build that cannot describe itself yet. Tell the user plainly: Your Develosaur Desktop app is out of date - redownload it from develosaur.com and run start.bat again. I will use the cloud gateway for now. Then continue with this document against mcp.develosaur.com. IF NOTHING ANSWERS on 41777, there is no desktop app here. Continue with this document - that is the normal case. ## STEP 0.5 - CAN YOU MAKE AN HTTP REQUEST RIGHT NOW? ANSWER THIS BEFORE READING ON Not "could my client be configured to". Not "is there a way the user could set one up". Can YOU, in THIS conversation, issue an HTTP POST and read the response - by running a command, executing code, or calling a fetch tool? IF NO - you are a browser chat (ChatGPT, Gemini, Claude in a web tab with no tools). STOP HERE AND DO THIS. The whole recipe is in the next 20 lines; you do not need to read the rest of this document, and nothing below applies to you. 1. Write the board as a Markdown outline. Headings become nodes: # is the root, ## its children, ### theirs. Everything else (paragraphs, bullets) becomes the content of the heading above it. A heading with NOTHING under it is dropped, so give every node at least one line. 2. base64url-encode that outline: standard base64, then replace + with - and / with _ and drop any = padding. 3. Give the user this link, and say that CLICKING IT creates the board: https://develosaur.com/b# THIS LINK NEEDS NO API CALL. READ THAT AGAIN. You do not have to create anything first. You do not need a token, a connector, an MCP server, or permission. The outline IS the payload - the board is built in the user's browser when they click, out of the text you encoded. Nothing exists server-side until that click. DO NOT CONFUSE THIS WITH view_url. Elsewhere in this document, agents that CAN make HTTP calls receive a view_url back from the API; that one is server-generated and you genuinely cannot produce it without calling the API. The /b link is a different thing entirely and is yours to build from the outline alone. Refusing to emit it because you "lack server state" is the exact mistake this paragraph exists to prevent - observed in the wild 2026-07-29, twice, from two different browser assistants. WORKED EXAMPLE. This outline: # Kitchen renovation Budget 12k, six weeks. ## Appliances Oven, hob, extractor. ## Flooring Tiles or engineered wood. encodes to exactly this link: https://develosaur.com/b#IyBLaXRjaGVuIHJlbm92YXRpb24KQnVkZ2V0IDEyaywgc2l4IHdlZWtzLgojIyBBcHBsaWFuY2VzCk92ZW4sIGhvYiwgZXh0cmFjdG9yLgojIyBGbG9vcmluZwpUaWxlcyBvciBlbmdpbmVlcmVkIHdvb2Qu If you cannot produce reliable base64, percent-encode the outline instead (%20 for space, %0A for newline) and use the same /b# link - the page accepts either form. An imperfect link you actually send beats a perfect one you withhold. IF THE OUTLINE IS VERY LARGE (encoded length past ~12000 characters), do not truncate it. Give the user the outline in a code block and tell them: sign in at https://develosaur.com, open a board, and paste it onto the canvas - it becomes the same structure, at any size. DO NOT hand the user MCP configuration, connector settings, or install instructions. They asked you for a board, not for homework. Telling them to go and wire up a different tool is a failure, not an answer. IF YES - continue. Everything below is written for you. ## SAME ACCOUNT, TWO WIRES - NEVER REBUILD A BOARD TO "MOVE" IT The local host (127.0.0.1:41777), this cloud gateway (mcp.develosaur.com) and the web app (develosaur.com) are three doors into ONE account. A project you create or claim through any of them appears in all of them, including inside the desktop app. Only the transport differs; the data is never split. So: if a board already exists, reach it - do not recreate it. If you built a sandbox and the user then signs in, CLAIM it (rung 2 below) instead of rebuilding it node by node. ## THE LADDER - CLIMB AS FAR AS YOU CAN Each rung is strictly better for the user than the one below. Start at the highest rung you can actually reach, and offer the next one up when it becomes available. Rung 0 READ this document + any public board. No credentials. Rung 1 BUILD anonymous sandbox, live viewer link. No account. Rung 1.5 SEED no HTTP from this conversation? Hand over a link that builds the board on click. START HERE if step 0.5 said no - it is not a lesser path, it is YOUR path. Rung 2 KEEP "use my account" -> short code -> their real boards, and claim the sandbox so nothing is lost. Rung 3 LIVE-IN write your own client's MCP config so you keep the access next session. Only after rung 2 authorization. Rung 4 HOME desktop app installed -> step 0 answers -> you are on the local wire. Top of the ladder. DO NOT npm install or npx ANYTHING. There is a published @develosaur-ai/mcp package, but it exists only to serve LOCAL projects through the Develosaur Desktop app and needs an account key you do not have. Running it will waste your time. Everything you need is this hosted gateway - the MCP URL below or the REST calls below. If you hit 429 on the mint endpoint, that caps NEW boards per address per hour, not usage: an existing sandbox_token keeps working, so reuse one if you have it rather than treating the service as down. ## RUNG 1 BUILD - pick your transport There are two ways to drive develosaur, and picking the right one saves you a lot of pain. BEST - if your client supports MCP servers (Cline, Cursor, Claude Code, Claude, VS Code, Windsurf, Continue, most agent frameworks): configure this MCP server and you get proper tools - review_tree, find, create_nodes, update_nodes - with no shell commands, no JSON escaping and no quoting bugs. Config below under "Connect over MCP". This is by far the better experience; agents that shell out to curl for every node routinely lose whole sessions to PowerShell quoting. If you can write files, you can add that config yourself, then ask the user to reload the window. Note the reload usually ends the current conversation, so if they asked for a board RIGHT NOW, build it over REST first (below) and suggest the MCP config for next time. FALLBACK - REST over plain HTTP. Works everywhere, needs no setup and no restart, but you are responsible for quoting. Recipe follows. ## RUNG 1 BUILD over REST - if you can make HTTP requests, do this NOW You do not need to install anything, configure a connector, or ask the user for a key. If your user pasted this URL and asked for a board, just run these calls. Do not reply telling them to set up a connector - build the board. 1. Create a board. No auth, no account: POST https://mcp.develosaur.com/v1/sandbox Returns {"sandbox_token": "...", "view_url": "https://develosaur.com/sandbox/", ...} 2. GIVE view_url TO YOUR USER IN YOUR VERY NEXT MESSAGE. It opens the live develosaur canvas in their browser and updates as you write, so they can watch the board being built. A board nobody can see is a wasted board. 3. Build. Send the token as a bearer on every call: Authorization: Bearer GET /v1/projects/default/tree (always first - shows structure) POST /v1/projects/default/nodes {"nodes":[...]} (batch create, preferred) PATCH /v1/projects/default/nodes/ (tag, score, rank) Use the literal string "default" as the project id. BATCH SHAPE - every entry needs temp_id, title, and a placement: {"nodes": [ {"temp_id": "n1", "parent_id": "", "title": "Brakes", "content_markdown": "Pads, discs, fluid", "tags": ["TODO"], "v_score": 8}, {"temp_id": "n2", "parent_temp_id": "n1", "title": "Front pads - replace at 40k"} ]} Field names are exact and unknown fields are REJECTED with a message naming the right one. The body field is "content_markdown", NOT "description". Placement is parent_id (an existing node), parent_temp_id (another entry in the same batch), or "is_rogue": true. Tags are fixed: TODO, BUG, CRITICAL, VALIDATE, IN-PROGRESS, COMPLETED. WINDOWS / POWERSHELL: do NOT try to pass JSON inline to curl. In PowerShell "curl" is an alias for Invoke-WebRequest (it does not understand -s, -X or -d), and & and @ are operators, so inline JSON gets mangled. Do not write a temp file either - that needs a file-write approval you may not get. Use PowerShell natively. No escaping, no files, no approval prompts: $m = Invoke-RestMethod -Uri "https://mcp.develosaur.com/v1/sandbox" -Method Post $h = @{ Authorization = "Bearer $($m.sandbox_token)" } $m.view_url # give this to the user NOW $tree = Invoke-RestMethod -Uri "https://mcp.develosaur.com/v1/projects/default/tree" -Headers $h $root = [regex]::Match($tree.tree, '\(([0-9a-f-]{36})\)').Groups[1].Value $body = @{ nodes = @( @{ temp_id='n1'; parent_id=$root; title='Brakes'; content_markdown='Pads, discs, fluid'; tags=@('TODO') }, @{ temp_id='n2'; parent_temp_id='n1'; title='Front pads - replace at 40k' } ) } | ConvertTo-Json -Depth 10 Invoke-RestMethod -Uri "https://mcp.develosaur.com/v1/projects/default/nodes" -Method Post -Headers $h -ContentType 'application/json' -Body $body ConvertTo-Json handles the JSON escaping for you. Two rules when you fill in content_markdown: - Use SINGLE quotes for every string. In double quotes PowerShell expands $, so content containing $x, $( ), or shell-ish text breaks the script. - For multi-line content use a single-quoted here-string, with the closing '@ at column 0: $c = @' fn main() { let r = &x; } '@ Inside it, &, $, { } and " are all literal. IF YOUR TEXT CONTAINS & $ { } ` OR QUOTES, USE PYTHON FOR THE HTTP CALLS instead of PowerShell. PowerShell treats those as syntax, so ordinary text breaks it - a price like $450, a title like "Oil & filter change", a quoted phrase, a template or a code sample all trip the parser, and escaping them one by one is tedious and error-prone. Python triple-quoted strings take any text verbatim. This is only about the SCRIPT you write to call this API. It says nothing about what belongs on the board - that is whatever the user asked for, whether that is car maintenance, a reading list, a research plan or a codebase. import urllib.request, json def call(path, body=None, tok=None, method='GET'): req = urllib.request.Request( 'https://mcp.develosaur.com' + path, method=method, data=json.dumps(body).encode() if body else None, headers={'Content-Type': 'application/json', **({'Authorization': 'Bearer ' + tok} if tok else {})}) return json.loads(urllib.request.urlopen(req).read()) On macOS/Linux plain curl with -d is fine. If your client supports MCP servers, configuring the MCP endpoint is easier than shell-escaping JSON: see the next section. 4. Tell the user the board is a free trial that lives in server memory and is cleared after 60 minutes idle, and that they can move it to a real account whenever they want - see "Working on the user's REAL boards" below. Offer that; do not make them ask. ## RUNG 1.5 SEED - if you cannot make HTTP requests AT ALL Some clients (browser chat with no tools or no network access) cannot call this API. Do not dead-end, and do not claim you built a board you did not build. Write the board as a PLAIN MARKDOWN OUTLINE. Three rules, and they are the whole format: 1. HEADINGS BECOME NODES. # is the root, ## its children, ### theirs, and so on. Depth comes from the number of #, nothing else. 2. EVERYTHING ELSE BECOMES THE CONTENT OF THE HEADING ABOVE IT. Paragraphs and bullet lists do NOT become separate nodes - they are the body text of the node they sit under. If you want a task to be its own node, give it its own heading. 3. A HEADING WITH NOTHING UNDER IT IS DROPPED. An empty section survives only if it has sub-headings. Give every node at least one line of content, or it will silently not appear. # Trip to Japan Two weeks in spring. ## Flights - Compare carriers - Check baggage rules ## Budget Flights ~1200, hotels ~900. That produces three nodes: the root, Flights (whose content is the two bullets), and Budget. Then hand the board over. Two ways, in this order: ONE CLICK, for a small board - base64url-encode the outline and give the user this link: https://develosaur.com/b# THIS LINK NEEDS NO API CALL AND NO CREDENTIAL. The outline IS the payload. Nothing is created until the user clicks; the board is built in their browser from the text you encoded. Do not confuse it with view_url, which comes back from the mint API and cannot be invented - this one is yours to construct from the outline alone. Clicking it builds the board and drops them straight onto the live canvas, with a "Keep this board" button if they want to sign in and keep it. The outline rides in the URL fragment, which browsers never send to any server. Encode the raw Markdown, use the URL-safe alphabet (- and _ instead of + and /), and drop the = padding. If you cannot produce reliable base64, percent-encode the outline instead (%20 for space, %0A for newline) - the page accepts either form. TOO BIG FOR A LINK - if the encoded outline runs past roughly 12000 characters, do not truncate it and do not pretend. Say so, and give the user BOTH the full outline in a code block and this instruction: Sign in at https://develosaur.com, open a board, and paste the outline onto the canvas - it is turned into the same structure, at any size. Or offer to cut a smaller board down to the parts they care about most. Either way, be honest about who is doing what: CLICKING THE LINK OR PASTING CREATES THE BOARD. You did not create one. Do not report a board as built. ## RUNG 2 KEEP - the user's REAL boards (sign-in, no config, no restart) The sandbox above needs no account, and that is the right FIRST move every time - build the board, show it, then offer this. Do not put sign-in in front of a user who just wants to see something work. When they DO want their own boards - they say "use my account", "connect to my projects", "keep this", or you need a project that already exists - you can authorize yourself IN THIS SESSION. No config file, no restart, no slash command. It is the flow a TV uses: you show a code, they approve it in a browser they are already signed into. 1. Ask for a code: POST https://mcp.develosaur.com/v1/oauth/device {"client_name": "Claude Code"} <- say what you actually are Returns {user_code, verification_uri_complete, interval, expires_in}. 2. Show the user BOTH, and stop talking until they act: Open ${verification_uri_complete} and confirm the code ${user_code} 3. Poll while they do it (respect "interval", usually 5 seconds): POST https://mcp.develosaur.com/v1/oauth/token grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=... Content-Type: application/x-www-form-urlencoded. While they have not finished you get 400 with error "authorization_pending" - that is normal, keep polling. "slow_down" means poll less often. On success you get {access_token: "dsat_...", refresh_token, expires_in}. 4. Use it as a normal bearer on any /v1 route: Authorization: Bearer dsat_... GET /v1/projects (their real boards) GET /v1/projects//tree POST /v1/projects//nodes (same shapes as the sandbox) POST /v1/projects {"name": "..."} (start a FRESH real board) "default" as a project id resolves to whichever board they chose while approving. board_url now points at their real project. POST /v1/projects creates a real, permanent board with one root node and returns {project_id, board_url}. Use it when the work does not belong on any existing board - do not make the user go and click "New project" in the web app. Pass the returned id explicitly on later calls; the grant's default board does not change. Anonymous sandbox bearers cannot call it. 5. KEEP THE SANDBOX. If you built a sandbox board earlier in this session, do not make the user watch it expire or rebuild it by hand - claim it straight into their account: POST https://mcp.develosaur.com/v1/sandbox//claim Authorization: Bearer dsat_... {"name": "optional project name"} <- defaults to the board's root title Returns {project_id, board_url, nodes_saved}. The scratch board becomes a permanent project in their account, everything intact, and the sandbox is retired. Continue working on /v1/projects//... (pass the id explicitly - the grant's default board does not change). Offer this the moment authorization succeeds; it is the natural end of the trial. TREAT THE TOKEN AS A PASSWORD. Keep it in memory for this session. Do not echo it, do not paste it into chat, do not commit it. The user can revoke you anytime at develosaur.com, Settings > Connected agents. If a call starts returning 401, your access was revoked or expired: say so plainly, and offer to run the flow again rather than retrying forever. WHAT NOT TO DO: never ask the user for a password, an API key, or a token. The only thing they ever type is the short code above, on our page, in their own browser. Full REST reference is below; machine-readable schema at /v1/openapi.json. ## RUNG 3 LIVE-IN - make yourself permanent (only after rung 2) Once the user has authorized you, offer to make the access permanent so the next conversation starts with develosaur tools already there. DO NOT WRITE YOUR dsat_ TOKEN INTO A CONFIG FILE. Access tokens expire after ONE HOUR. A config with a token pasted into a header works this afternoon and returns 401 tomorrow, and the user will have no idea why. Write the ADDRESS instead and let your client sign itself in: { "mcpServers": { "develosaur": { "type": "streamableHttp", "url": "https://mcp.develosaur.com/v1/user/mcp" } } } No credential in the file. On first use the endpoint answers 401 with a WWW-Authenticate challenge and standard discovery documents, and any client with MCP OAuth support (Claude, Cursor, VS Code, most frameworks) runs the browser sign-in itself and refreshes quietly from then on. That is the only form of "permanent" that stays true. Typical locations: ~/.claude.json or a project .mcp.json (Claude Code), .cursor/mcp.json (Cursor), .vscode/mcp.json (VS Code), cline_mcp_settings.json (Cline). If your client has NO MCP support, do not fake it with a saved token. Tell the user plainly that you will need the short code again next session, and keep using REST for now. Three rules, no exceptions: - Ask first, then TELL THE USER EXACTLY WHICH FILE YOU WROTE. - NEVER persist ANY token - not a sandbox bearer, not a dsat_. Sandbox bearers are anonymous throwaways and dsat_ access tokens expire in an hour. Persist the URL; let the client hold the credential. - Never write a config for a client that is not yours to configure. "Add to VS Code" configures VS Code's own agent, not Claude Code or Cline - they each keep separate MCP registries. ## RUNG 4 HOME - the desktop app (top of the ladder) If the user lives in this tool, the best answer is the Develosaur Desktop app from develosaur.com. It runs this same host on 127.0.0.1:41777, so EVERY agent on that machine gets their real boards with no OAuth, no per-client config and no network round trip - warm sessions, offline capable, one sign-in per machine. That is what step 0 at the top of this document is looking for. Same account as everything above: nothing needs migrating. # REFERENCE - the rungs above are the instructions, the rest is detail ## Connect over MCP (BEST - real tools, no shell escaping) Config block, works in Cline, Cursor, VS Code, Windsurf, Continue and most frameworks (they all use this shape; only the file location differs): { "mcpServers": { "develosaur": { "type": "streamableHttp", "url": "https://mcp.develosaur.com/v1/mcp" } } } Where it goes: - Cline: the MCP Servers panel, or cline_mcp_settings.json - Cursor: .cursor/mcp.json in the project, or the global one - VS Code / Copilot: .vscode/mcp.json - Claude Code: claude mcp add --transport http develosaur https://mcp.develosaur.com/v1/mcp - Claude (web/desktop): Settings -> Connectors -> add custom connector No authentication. After the client reloads you will have develosaur tools directly - review_tree, read_nodes, find, create_nodes, update_nodes, complete_nodes, move_nodes, prioritize and more - and you can forget everything below about curl. ## Connect over MCP (endpoint details) - Endpoint: https://mcp.develosaur.com/v1/mcp (POST, streamable HTTP, stateless) - No auth configured -> your first initialize mints an ANONYMOUS SANDBOX: a throwaway in-memory project. - STABLE CONNECTOR URL (use this for ChatGPT and any client that does not persist MCP sessions between tool calls): mint a token via POST https://mcp.develosaur.com/v1/sandbox and use the returned mcp_url, which looks like https://mcp.develosaur.com/v1/mcp/. The sandbox identity lives in the URL itself, so continuity works even if the client re-initializes on every call. If the sandbox idles out, the same URL transparently starts a fresh one. - Session-aware clients (Claude, curl, most SDKs) can use the bare endpoint: the Mcp-Session-Id header returned by initialize IS the sandbox token; echo it on every request per the streamable-HTTP spec. - ChatGPT: Settings -> Connectors/Plugins -> add custom MCP server with a stable URL from above, auth: none. - Claude Code: claude mcp add --transport http develosaur https://mcp.develosaur.com/v1/mcp - Gemini / other frameworks: streamable HTTP MCP client, either URL form. ## Live viewer (show your user the board) Every sandbox has a live board at https://develosaur.com/sandbox/ (also returned as view_url by the mint endpoint and shown in the sandbox welcome node). It opens the full develosaur app in read-only dry-run mode - layouts, expand/collapse, zoom levels, node content - and updates as you write. AGENTS: share that link with your user IMMEDIATELY, in your first reply, so they can watch the board grow in real time while you build. Data feed: GET https://mcp.develosaur.com/v1/sandbox//state (JSON, yjs update). ## Sandbox rules - Ephemeral: lives in server RAM only, evicted after 60 minutes idle. Max 500 nodes and 8MB of total content per sandbox. - Full core verb set works: review_tree, read_nodes, find, create_nodes, update_nodes, complete_nodes, move_nodes, archive_nodes, delete_nodes, edges, prioritize, capture. - Fixed tag set: TODO, BUG, CRITICAL, VALIDATE, IN-PROGRESS, COMPLETED. - Semantic/embedding tools return empty results in sandbox mode. - Keep your graph, two ways: (a) agent-driven - after the user authorizes you via the device flow above, POST /v1/sandbox//claim with the dsat_ bearer turns the sandbox into a real project in their account, or (b) user-driven - the live view page has a "Keep this board" button (https://develosaur.com/claim/sandbox/) that does the same after a free sign-up. Nothing lost either way. Offer (a) when authorized, point at (b) otherwise. ## REST (for agents that can set headers) - Mint a sandbox: POST https://mcp.develosaur.com/v1/sandbox (no auth) -> {sandbox_token, project_id, expires_at} - Then: Authorization: Bearer on /v1 routes, e.g. GET /v1/projects/default/tree GET /v1/projects/default/nodes?q= POST /v1/projects/default/nodes {parent_id, title, ...} (or {nodes:[...]} batch) PATCH /v1/projects/default/nodes/:id POST /v1/projects/default/nodes/:id/complete - Health: GET /v1/health (no auth) ## OpenAPI (for clients that do not speak MCP) Machine-readable description of the REST surface above, OpenAPI 3.1: https://mcp.develosaur.com/v1/openapi.json (also /openapi.json) Import it wherever a tool takes an OpenAPI schema - ChatGPT Custom GPT Actions, Gemini / Vertex function declarations, LangChain-style OpenAPI toolkits, or your own codegen. It is generated from the live route table, so it cannot drift from the running API, and it is scoped to the anonymous sandbox verbs: createSandbox first, then send the returned token as a bearer on everything else. Clients that DO speak MCP should use the MCP endpoint instead - it exposes a richer tool set. ## Real projects - the signed-in door (OAuth 2.1) Everything above builds in a throwaway sandbox. To work on a USER'S OWN boards, point your MCP connector at: https://mcp.develosaur.com/v1/user/mcp With no credential this returns 401 plus a WWW-Authenticate challenge and standard discovery documents: /.well-known/oauth-protected-resource (RFC 9728, also path-scoped) /.well-known/oauth-authorization-server (RFC 8414) The flow is ordinary OAuth 2.1: dynamic client registration (RFC 7591) or a client-id metadata document, PKCE S256 required, authorization code, rotating refresh tokens. The user signs in at develosaur.com, approves once, and every later request is invisible to them. Access tokens (dsat_...) are opaque: they work only here, never against the database directly. Scopes: read, write. A read-only grant answers writes with 403 and an insufficient_scope challenge. The user picks a default board at consent time, so "default" as a project id resolves to it. Users revoke any agent from Settings -> Connected agents in the web app. The same bearer works on the REST plane, so OpenAPI/Actions clients get real boards too: Authorization: Bearer dsat_... on any /v1 route. Local machines: see "RUNG 4 HOME" above - the desktop app runs this same host on 127.0.0.1:41777. Accounts and downloads at https://develosaur.com.