PageSourceSearch

https://joplinapp.org/assets/js/ba5bb86c.db6c6cc8.js

js joplinapp.org collected 2026-10-01 09:23:53 UTC 21,513 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunk_joplin_doc_builder=self.webpackChunk_joplin_doc_builder||[]).push([[1973],{3905:function(e,t,n){n.d(t,{Zo:function(){return c},kt:function(){return m}});var o=n(67294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function r(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?i(Object(n),!0).forEach((function(t){a(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):i(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,o,a=function(e,t){if(null==e)return{};var n,o,a={},i=Object.keys(e);for(o=0;o<i.length;o++)n=i[o],t.indexOf(n)>=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(o=0;o<i.length;o++)n=i[o],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=o.createContext({}),p=function(e){var t=o.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},c=function(e){var t=p(e.components);return o.createElement(l.Provider,{value:t},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},h=o.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),d=p(n),h=a,m=d["".concat(l,".").concat(h)]||d[h]||u[h]||i;return n?o.createElement(m,r(r({ref:t},c),{},{components:n})):o.createElement(m,r({ref:t},c))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,r=new Array(i);r[0]=h;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[d]="string"==typeof e?e:a,r[1]=s;for(var p=2;p<i;p++)r[p]=n[p];return o.createElement.apply(null,r)}return o.createElement.apply(null,n)}h.displayName="MDXCreateElement"},72674:function(e,t,n){n.r(t),n.d(t,{assets:function(){return l},contentTitle:function(){return r},default:function(){return u},frontMatter:function(){return i},metadata:function(){return s},toc:function(){return p}});var o=n(87462),a=(n(67294),n(3905));const i={sidebar_label:"AI sidebar: tool-using architecture",title:"AI sidebar: tool-using architecture",description:"How the built-in AI chat sidebar evolves from a single-shot structured-output call into a tool-using agent, with a two-tier toolset and a plugin extension point. The underlying joplin.ai.chat() pri..."},r="AI sidebar: tool-using architecture",s={unversionedId:"dev/spec/ai_sidebar_tools",id:"dev/spec/ai_sidebar_tools",title:"AI sidebar: tool-using architecture",description:"How the built-in AI chat sidebar evolves from a single-shot structured-output call into a tool-using agent, with a two-tier toolset and a plugin extension point. The underlying joplin.ai.chat() pri...",source:"@site/help/dev/spec/ai_sidebar_tools.md",sourceDirName:"dev/spec",slug:"/dev/spec/ai_sidebar_tools",permalink:"/help/dev/spec/ai_sidebar_tools",draft:!1,editUrl:"https://github.com/laurent22/joplin/tree/dev/readme/dev/spec/ai_sidebar_tools.md",tags:[],version:"current",frontMatter:{sidebar_label:"AI sidebar: tool-using architecture",title:"AI sidebar: tool-using architecture",description:"How the built-in AI chat sidebar evolves from a single-shot structured-output call into a tool-using agent, with a two-tier toolset and a plugin extension point. The underlying joplin.ai.chat() pri..."},sidebar:"helpSidebar",previous:{title:"AI primitives",permalink:"/help/dev/spec/ai_primitives"},next:{title:"Joplin architecture",permalink:"/help/dev/spec/architecture"}},l={},p=[{value:"Motivating use cases",id:"motivating-use-cases",level:2},{value:"Motivation",id:"motivation",level:2},{value:"Design principle revision",id:"design-principle-revision",level:2},{value:"Architecture",id:"architecture",level:2},{value:"Two tiers, one interface",id:"two-tiers-one-interface",level:2},{value:"Tier 1 \u2014 Session tools",id:"tier-1--session-tools",level:3}
1,{value:"Tier 2 \u2014 Workspace tools (MCP)",id:"tier-2--workspace-tools-mcp",level:3},{value:"Tier 3 \u2014 Plugin-registered tools",id:"tier-3--plugin-registered-tools",level:3},{value:"How the model chooses which tool to call",id:"how-the-model-chooses-which-tool-to-call",level:2},{value:"Agent loop",id:"agent-loop",level:2},{value:"Why session tools are not MCP tools",id:"why-session-tools-are-not-mcp-tools",level:2}],c={toc:p},d="wrapper";function u(e){let{components:t,...n}=e;return(0,a.kt)(d,(0,o.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"ai-sidebar-tool-using-architecture"},"AI sidebar: tool-using architecture"),(0,a.kt)("div",{className:"donate-links"},(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://www.paypal.com/donate/?hosted_button_id=WQCERTSSLCC7U"},(0,a.kt)("img",{parentName:"a",src:"https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg",alt:"Donate using PayPal"}))," ",(0,a.kt)("a",{parentName:"p",href:"https://github.com/sponsors/laurent22/"},(0,a.kt)("img",{parentName:"a",src:"https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/GitHub-Badge.svg",alt:"Sponsor on GitHub"}))," ",(0,a.kt)("a",{parentName:"p",href:"https://www.patreon.com/joplin"},(0,a.kt)("img",{parentName:"a",src:"https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Patreon-Badge.svg",alt:"Become a patron"}))," ",(0,a.kt)("a",{parentName:"p",href:"https://joplinapp.org/donate/#donations"},(0,a.kt)("img",{parentName:"a",src:"https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-IBAN.svg",alt:"Donate using IBAN"})))),(0,a.kt)("p",null,"How the built-in AI chat sidebar evolves from a single-shot structured-output call into a tool-using agent, with a two-tier toolset and a plugin extension point."),(0,a.kt)("p",null,"The underlying ",(0,a.kt)("inlineCode",{parentName:"p"},"joplin.ai.chat()")," primitive described in ",(0,a.kt)("a",{parentName:"p",href:"/help/dev/spec/ai_chat"},"ai_chat.md")," is unchanged; this spec only covers the sidebar and adjacent internals."),(0,a.kt)("h2",{id:"motivating-use-cases"},"Motivating use cases"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"Cross-workspace operations from the sidebar."),' "Find my last packing list and add socks to it" \u2014 today impossible because the sidebar is scoped to the focused note.'),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"Plugin-owned chat behaviours.")," ",(0,a.kt)("em",{parentName:"li"},"Vacation-list plugin"),': user types "what do I need for 5 days in the Lake District with my dog"; the plugin\'s registered ',(0,a.kt)("inlineCode",{parentName:"li"},"plan_trip_packing")," tool produces a structured list, fetches reference images for each item, and its custom renderer displays them inline in the chat. The plugin owns the domain (destination, weather, companions, image fetching); the model routes to it on intent.")),(0,a.kt)("h2",{id:"motivation"},"Motivation"),(0,a.kt)("p",null,"The sidebar today (see ",(0,a.kt)("inlineCode",{parentName:"p"},"noteChat.ts"),") makes one LLM call per user turn. The response is constrained by a JSON schema to ",(0,a.kt)("inlineCode",{parentName:"p"},"{ reply, edits[] }")," where ",(0,a.kt)("inlineCode",{parentName:"p"},"edits[]")," is a hard-coded enum of operations. Three consequences push us toward tool calling:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"The sidebar can only touch the focused note. Anything cross-workspace is impossible without leaving structured-output mode."),(0,a.kt)("li",{parentName:"ul"},"Plugins have no seam to extend chat behaviour."),(0,a.kt)("li",{parentName:"ul"},"Joplin already ships an MCP server (",(0,a.kt)("a",{parentName:"li",href:"/help/dev/spec/ai_mcp"},"ai_mcp.md"),") whose tools are the exact same operations we'd want the sidebar to perform (search, create, tag, link). Today they're consumed only by external hosts \u2014 duplicated capability.")),(0,a.kt)("h2",{id:"design-principle-revision"},"Design principle revision"),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"/help/dev/spec/ai_primitives"},"ai_primitives.md")," states ",(0,a.kt)("em",{parentName:"p"},'"MCP is a peer, not a client. Joplin itself is not an MCP client and does not host conversations."')," Revised: ",(0,a.kt)("strong",{parentName:"p"},"the built-in sidebar is an in-process consumer of the MCP tool implementations"),", calling the same handlers as the MCP endpoint without going through JSON-RPC. External MCP clients remain the only network-facing consumers. Other primitives principles stand."),(0,a.kt)("h2",{id:"architecture"},"Architecture"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 ChatPanel (desktop) \u2014 conversation state, UI    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                   \u2502 runNoteChat(context, history, msg)\n                   \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 noteChat \u2014 per-turn tool list, agent loop,      \u2502\n\u2502           dispatch to tier handlers             \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n          \u25bc                              \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510        \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Session tools       \u2502        \u2502 Worksp
1ace tools        \u2502\n\u2502 (in-process,        \u2502        \u2502 (MCP registry \u2014        \u2502\n\u2502  editor-coupled)    \u2502        \u2502  built-in + plugin)    \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518        \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n")),(0,a.kt)("p",null,"To the model, both tiers are opaque tools. Tier is a runtime-only concern: the dispatcher routes by tool ID."),(0,a.kt)("h2",{id:"two-tiers-one-interface"},"Two tiers, one interface"),(0,a.kt)("h3",{id:"tier-1--session-tools"},"Tier 1 \u2014 Session tools"),(0,a.kt)("p",null,"In-process handlers with direct access to editor state (selection, cursor, note body, pending-diff queue, undo stack). They participate in the editor's command pipeline so AI edits coalesce into single undo steps and appear as streaming diffs the user can accept or reject. Not in the MCP registry, not exposed to external clients, not pluggable \u2014 see ",(0,a.kt)("a",{parentName:"p",href:"#why-session-tools-are-not-mcp-tools"},"Why session tools are not MCP tools"),"."),(0,a.kt)("p",null,"Initial set (mapping from current ",(0,a.kt)("inlineCode",{parentName:"p"},"EditOp")," operations):"),(0,a.kt)("table",null,(0,a.kt)("thead",{parentName:"table"},(0,a.kt)("tr",{parentName:"thead"},(0,a.kt)("th",{parentName:"tr",align:null},"Tool"),(0,a.kt)("th",{parentName:"tr",align:null},"Replaces"),(0,a.kt)("th",{parentName:"tr",align:null},"Availability"))),(0,a.kt)("tbody",{parentName:"table"},(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"edit_selection(text)")),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"replaceSelection")),(0,a.kt)("td",{parentName:"tr",align:null},"Selection present")),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"rewrite_paragraph(anchor, text)")),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"replaceRange")),(0,a.kt)("td",{parentName:"tr",align:null},"Note focused, no selection")),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"insert_before_anchor(anchor, text)")),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"insertBefore")),(0,a.kt)("td",{parentName:"tr",align:null},"Note focused, no selection")),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"insert_after_anchor(anchor, text)")),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"insertAfter")),(0,a.kt)("td",{parentName:"tr",align:null},"Note focused, no selection")),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"append_to_note(text)")),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"appendToNote")),(0,a.kt)("td",{parentName:"tr",align:null},"Note focused, no selection")),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"replace_structured_block(tag, text)")),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("inlineCode",{parentName:"td"},"replaceFencedBlock")),(0,a.kt)("td",{parentName:"tr",align:null},"Note focused, block present")))),(0,a.kt)("p",null,"Selection scope enforcement (currently ",(0,a.kt)("inlineCode",{parentName:"p"},"enforceSelectionScope")," in ",(0,a.kt)("inlineCode",{parentName:"p"},"noteChat.ts")," moves from post-hoc filter to tool-list gate: when a selection exists, only ",(0,a.kt)("inlineCode",{parentName:"p"},"edit_selection")," is offered. The dispatcher additionally rejects any call to a tool not in the current turn's list."),(0,a.kt)("h3",{id:"tier-2--workspace-tools-mcp"}
1,"Tier 2 \u2014 Workspace tools (MCP)"),(0,a.kt)("p",null,"The existing MCP tool registry (",(0,a.kt)("a",{parentName:"p",href:"/help/dev/spec/ai_mcp"},"ai_mcp.md"),") \u2014 ",(0,a.kt)("inlineCode",{parentName:"p"},"search_notes"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"semantic_search_notes"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"read_note"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"list_notebooks"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"list_tags"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"create_note"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"update_note"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"delete_note"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"manage_tags"),", ",(0,a.kt)("inlineCode",{parentName:"p"},"create_notebook"),". Called in-process by the sidebar, bypassing JSON-RPC. One implementation, two consumers."),(0,a.kt)("h3",{id:"tier-3--plugin-registered-tools"},"Tier 3 \u2014 Plugin-registered tools"),(0,a.kt)("p",null,"Plugins register additional workspace tools:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-typescript"},"joplin.ai.tools.register({\n    id: string;\n    description: string;\n    inputSchema: JsonSchema;\n    handler: (input) => Promise<unknown>;\n    render?: (input, output) => RenderSpec;   // optional custom renderer\n});\n")),(0,a.kt)("p",null,"Registered tools appear alongside built-in workspace tools in the sidebar ",(0,a.kt)("em",{parentName:"p"},"and")," through the MCP endpoint \u2014 a plugin tool works identically for the built-in sidebar and for Claude Desktop over HTTP. That symmetry is why plugin tools live at Tier 2, not their own tier."),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"render")," field lets a plugin format its tool output (e.g. list-with-thumbnails for the vacation-list case). Absent, the result renders as collapsed JSON. Plugins never need to detect intent \u2014 the model routes on tool description."),(0,a.kt)("p",null,"Plugin tools run in the plugin sandbox with the usual message-passing overhead."),(0,a.kt)("h2",{id:"how-the-model-chooses-which-tool-to-call"},"How the model chooses which tool to call"),(0,a.kt)("p",null,"Three mechanisms combine:"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("strong",{parentName:"li"},"The tool list.")," Only tools present in the per-turn tools array are callable. The sidebar builds this list from context:",(0,a.kt)("ul",{parentName:"li"},(0,a.kt)("li",{parentName:"ul"},"No note focused \u2192 workspace tools only."),(0,a.kt)("li",{parentName:"ul"},"Note focused, no selection \u2192 workspace tools + anchor / append session tools."),(0,a.kt)("li",{parentName:"ul"},"Note focused, selection present \u2192 workspace tools + ",(0,a.kt)("inlineCode",{parentName:"li"},"edit_selection")," only.Same context-dependent logic today in ",(0,a.kt)("inlineCode",{parentName:"li"},"noteChat.ts"),', lifted from "which JSON ops are legal" to "which tools appear."'))),(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("strong",{parentName:"li"},"Tool descriptions.")," The model routes on semantic intent match. No routing code, no intent classifier."),(0,a.kt)("li",{parentName:"ol"},(0,a.kt)("strong",{parentName:"li"},"The system prompt.")," Frames Joplin context, current note title/body/selection (when focused), Joplin-Markdown rules (current ",(0,a.kt)("inlineCode",{parentName:"li"},"joplinMarkdownNotes")," block survives verbatim), and instructs the model to scope edits to selection when one exists.")),(0,a.kt)("h2",{id:"agent-loop"},"Agent loop"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre"},"runNoteChat(context, history, userMessage):\n    tools = buildToolList(context)\n    messages = [systemPrompt(context), ...history, {user, userMessage}]\n    for step in 1..MAX_STEPS:\n        result = AiService.chat(messages, {tools})\n        if result.stopReason == 'tool_use':\n            for call in result.toolCalls:\n                output = dispatch(call)   // tier 1 handler or MCP tool\n                messages.push({assistant, toolCalls: [call]})\n                messages.push({tool, toolCallId: call.id, content: serialize(output)})\n            continue\n        return { reply: result.text, edits: <tier 1 calls this turn> }\n    throw 'agent loop exceeded max steps'\n")),(0,a.kt)("p",null,(0,a.kt)("inlineCode",{parentName:"p"},"MAX_STEPS = 8"),". ",(0,a.kt)("inlineCode",{parentName:"p"},"buildToolList")," is a pure function of ",(0,a.kt)("inlineCode",{parentName:"p"},"NoteContext"),"; ",(0,a.kt)("inlineCode",{parentName:"p"}
1,"dispatch")," is a switch between the session-tools table and ",(0,a.kt)("inlineCode",{parentName:"p"},"mcpRegistry.findTool(id)"),". The ",(0,a.kt)("inlineCode",{parentName:"p"},"{ reply, edits }")," shape returned to ChatPanel is unchanged, so the existing ",(0,a.kt)("inlineCode",{parentName:"p"},"applyNoteEdits")," pipeline keeps working \u2014 ",(0,a.kt)("inlineCode",{parentName:"p"},"edits")," is now the accumulated tier-1 calls. Workspace tool calls appear as tool-use transcript entries, not edits."),(0,a.kt)("h2",{id:"why-session-tools-are-not-mcp-tools"},"Why session tools are not MCP tools"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"Live context."),' Selection/cursor change as the user clicks around. MCP is stateless \u2014 you\'d either re-send full editor state on every call (wasteful, racy) or reach into shared UI state from the "server" (no longer stateless, unsafe for the network transport).'),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"Undo integration.")," An AI edit should be a single undoable step in the editor's history stack \u2014 requires going through the command pipeline, not applying an arbitrary diff."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},"Diff preview / accept-reject.")," The existing UX is a ",(0,a.kt)("em",{parentName:"li"},"session")," between LLM and editor, not a single call.")),(0,a.kt)("p",null,"If we ever expose editor operations to external MCP clients, they'll be different tools \u2014 coarser, stateless, caller-supplied context on every call."))}u.isMDXComponent=!0}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.