[{"data":1,"prerenderedAt":676},["ShallowReactive",2],{"navigation_docs":3,"-guide":48,"-guide-surround":673},[4,24],{"title":5,"path":6,"stem":7,"children":8,"icon":23},"Guide","\u002Fguide","1.guide\u002F01.index",[9,11,15,19],{"title":10,"path":6,"stem":7},"Getting Started",{"title":12,"path":13,"stem":14},"Defining Tools","\u002Fguide\u002Fdefining-tools","1.guide\u002F02.defining-tools",{"title":16,"path":17,"stem":18},"Validation and Errors","\u002Fguide\u002Fvalidation","1.guide\u002F03.validation",{"title":20,"path":21,"stem":22},"Moving a Package Over","\u002Fguide\u002Fmigrating","1.guide\u002F04.migrating","i-lucide-book-open",{"title":25,"path":26,"stem":27,"children":28,"icon":47},"Hosts","\u002Fhosts","2.hosts\u002F00.index",[29,31,35,39,43],{"title":30,"path":26,"stem":27},"Every host",{"title":32,"path":33,"stem":34},"MCP","\u002Fhosts\u002Fmcp","2.hosts\u002F01.mcp",{"title":36,"path":37,"stem":38},"Pi","\u002Fhosts\u002Fpi","2.hosts\u002F02.pi",{"title":40,"path":41,"stem":42},"OMP","\u002Fhosts\u002Fomp","2.hosts\u002F03.omp",{"title":44,"path":45,"stem":46},"AI SDK","\u002Fhosts\u002Fai-sdk","2.hosts\u002F04.ai-sdk","i-lucide-table",{"id":49,"title":10,"body":50,"description":667,"extension":668,"links":669,"meta":670,"navigation":172,"path":6,"seo":671,"stem":7,"__hash__":672},"docs\u002F1.guide\u002F01.index.md",{"type":51,"value":52,"toc":660},"minimark",[53,58,63,67,79,89,93,118,136,140,427,435,439,541,630,636,640,656],[54,55,57],"warning",{"title":56},"Pre-1.0","The API can still move. Pin exact versions if you build on it now.",[59,60,62],"h2",{"id":61},"why-this-exists","Why this exists",[64,65,66],"p",{},"Every agntn package ships the same tools four times. An MCP server, a Pi extension, an OMP extension and an AI SDK tool set. Same names, same arguments, same answers. At least that's the plan.",[64,68,69,70,74,75,78],{},"In practice each host wants its own shape. MCP wants ",[71,72,73],"code",{},"inputSchema"," and annotations. Pi wants ",[71,76,77],{},"parameters"," plus a prompt snippet. OMP wants a schema built with its own TypeBox and an approval tier. The AI SDK wants a schema it can validate, which usually meant writing it again in Zod. So the schema lived in four places, and four places drift. A bound fixed in MCP stayed wrong in Pi. An error message sanitized on one surface leaked raw on another.",[64,80,81,84,85,88],{},[71,82,83],{},"@agntn\u002Ftools"," is the boring fix. You write the tool once with ",[71,86,87],{},"defineTool",". Four small adapters hand it to each host in the shape that host expects. The core validates every call before your code runs, on every host, with the same error text.",[59,90,92],{"id":91},"install","Install",[94,95,100],"pre",{"className":96,"code":97,"language":98,"meta":99,"style":99},"language-bash shiki shiki-themes tools tools tools","pnpm add @agntn\u002Ftools\n","bash","",[71,101,102],{"__ignoreMap":99},[103,104,107,111,115],"span",{"class":105,"line":106},"line",1,[103,108,110],{"class":109},"sK71F","pnpm",[103,112,114],{"class":113},"shU9J"," add",[103,116,117],{"class":113}," @agntn\u002Ftools\n",[64,119,120,121,124,125,124,128,131,132,135],{},"Node.js 24 or newer. TypeBox comes bundled, so there's nothing else to add for the core. The hosts are optional peers: install ",[71,122,123],{},"@modelcontextprotocol\u002Fserver",", ",[71,126,127],{},"@earendil-works\u002Fpi-coding-agent",[71,129,130],{},"@oh-my-pi\u002Fpi-coding-agent"," or ",[71,133,134],{},"ai"," only for the ones you actually serve.",[59,137,139],{"id":138},"first-tool","First tool",[94,141,146],{"className":142,"code":143,"filename":144,"language":145,"meta":99,"style":99},"language-ts shiki shiki-themes tools tools tools","import { defineTool, Type } from \"@agntn\u002Ftools\";\n\nexport const slugTool = defineTool({\n  name: \"text_slug\",\n  title: \"Text Slug\",\n  description: \"Turn a title into a lowercase URL slug.\",\n  effect: \"read\",\n  input: Type.Object(\n    {\n      text: Type.String({ minLength: 1, maxLength: 200, pattern: \"\\\\S\" }),\n      separator: Type.Optional(Type.Enum([\"-\", \"_\", \".\"])),\n    },\n    { additionalProperties: false },\n  ),\n  execute({ text, separator = \"-\" }) {\n    const slug = text.toLowerCase().match(\u002F[a-z0-9]+\u002Fg)?.join(separator) ?? \"\";\n    return { content: [{ type: \"text\", text: slug }], details: { slug } };\n  },\n});\n","tools.ts","ts",[71,147,148,167,174,195,207,218,229,240,252,258,276,310,316,322,328,345,400,415,421],{"__ignoreMap":99},[103,149,150,154,158,161,164],{"class":105,"line":106},[103,151,153],{"class":152},"skH_V","import",[103,155,157],{"class":156},"s38Sx"," { defineTool, Type } ",[103,159,160],{"class":152},"from",[103,162,163],{"class":113}," \"@agntn\u002Ftools\"",[103,165,166],{"class":156},";\n",[103,168,170],{"class":105,"line":169},2,[103,171,173],{"emptyLinePlaceholder":172},true,"\n",[103,175,177,180,183,186,189,192],{"class":105,"line":176},3,[103,178,179],{"class":152},"export",[103,181,182],{"class":152}," const",[103,184,185],{"class":156}," slugTool ",[103,187,188],{"class":152},"=",[103,190,191],{"class":109}," defineTool",[103,193,194],{"class":156},"({\n",[103,196,198,201,204],{"class":105,"line":197},4,[103,199,200],{"class":156},"  name: ",[103,202,203],{"class":113},"\"text_slug\"",[103,205,206],{"class":156},",\n",[103,208,210,213,216],{"class":105,"line":209},5,[103,211,212],{"class":156},"  title: ",[103,214,215],{"class":113},"\"Text Slug\"",[103,217,206],{"class":156},[103,219,221,224,227],{"class":105,"line":220},6,[103,222,223],{"class":156},"  description: ",[103,225,226],{"class":113},"\"Turn a title into a lowercase URL slug.\"",[103,228,206],{"class":156},[103,230,232,235,238],{"class":105,"line":231},7,[103,233,234],{"class":156},"  effect: ",[103,236,237],{"class":113},"\"read\"",[103,239,206],{"class":156},[103,241,243,246,249],{"class":105,"line":242},8,[103,244,245],{"class":156},"  input: Type.",[103,247,248],{"class":109},"Object",[103,250,251],{"class":156},"(\n",[103,253,255],{"class":105,"line":254},9,[103,256,257],{"class":156},"    {\n",[103,259,261,264,267,270,273],{"class":105,"line":260},10,[103,262,263],{"class":156},"      text: Type.",[103,265,266],{"class":109},"String",[103,268,269],{"class":156},"({ minLength: 1, maxLength: 200, pattern: ",[103,271,272],{"class":113},"\"\\\\S\"",[103,274,275],{"class":156}," }),\n",[103,277,279,282,285,288,291,294,297,299,302,304,307],{"class":105,"line":278},11,[103,280,281],{"class":156},"      separator: Type.",[103,283,284],{"class":109},"Optional",[103,286,287],{"class":156},"(Type.",[103,289,290],{"class":109},"Enum",[103,292,293],{"class":156},"([",[103,295,296],{"class":113},"\"-\"",[103,298,124],{"class":156},[103,300,301],{"class":113},"\"_\"",[103,303,124],{"class":156},[103,305,306],{"class":113},"\".\"",[103,308,309],{"class":156},"])),\n",[103,311,313],{"class":105,"line":312},12,[103,314,315],{"class":156},"    },\n",[103,317,319],{"class":105,"line":318},13,[103,320,321],{"class":156},"    { additionalProperties: false },\n",[103,323,325],{"class":105,"line":324},14,[103,326,327],{"class":156},"  ),\n",[103,329,331,334,337,339,342],{"class":105,"line":330},15,[103,332,333],{"class":109},"  execute",[103,335,336],{"class":156},"({ text, separator ",[103,338,188],{"class":152},[103,340,341],{"class":113}," \"-\"",[103,343,344],{"class":156}," }) {\n",[103,346,348,351,354,356,359,362,365,368,371,374,377,380,383,386,389,392,395,398],{"class":105,"line":347},16,[103,349,350],{"class":152},"    const",[103,352,353],{"class":156}," slug ",[103,355,188],{"class":152},[103,357,358],{"class":156}," text.",[103,360,361],{"class":109},"toLowerCase",[103,363,364],{"class":156},"().",[103,366,367],{"class":109},"match",[103,369,370],{"class":156},"(",[103,372,373],{"class":113},"\u002F[a-z0-9]",[103,375,376],{"class":152},"+",[103,378,379],{"class":113},"\u002F",[103,381,382],{"class":152},"g",[103,384,385],{"class":156},")?.",[103,387,388],{"class":109},"join",[103,390,391],{"class":156},"(separator) ",[103,393,394],{"class":152},"??",[103,396,397],{"class":113}," \"\"",[103,399,166],{"class":156},[103,401,403,406,409,412],{"class":105,"line":402},17,[103,404,405],{"class":152},"    return",[103,407,408],{"class":156}," { content: [{ type: ",[103,410,411],{"class":113},"\"text\"",[103,413,414],{"class":156},", text: slug }], details: { slug } };\n",[103,416,418],{"class":105,"line":417},18,[103,419,420],{"class":156},"  },\n",[103,422,424],{"class":105,"line":423},19,[103,425,426],{"class":156},"});\n",[64,428,429,430,434],{},"That's a complete tool. The one this site calls everywhere is the same idea with accents handled and an honest failure when nothing is left. Its file is on the ",[431,432,433],"a",{"href":379},"landing page",", and it really is the file.",[59,436,438],{"id":437},"hand-it-to-the-hosts","Hand it to the hosts",[94,440,443],{"className":142,"code":441,"filename":442,"language":145,"meta":99,"style":99},"import { createMcpServer } from \"@agntn\u002Ftools\u002Fmcp\";\nimport { StdioServerTransport } from \"@modelcontextprotocol\u002Fserver\u002Fstdio\";\nimport { slugTool } from \".\u002Ftools.ts\";\n\nconst server = createMcpServer({ name: \"slugs\", version: \"1.0.0\" }, [slugTool]);\nawait server.connect(new StdioServerTransport());\n","server.ts",[71,444,445,459,473,487,491,519],{"__ignoreMap":99},[103,446,447,449,452,454,457],{"class":105,"line":106},[103,448,153],{"class":152},[103,450,451],{"class":156}," { createMcpServer } ",[103,453,160],{"class":152},[103,455,456],{"class":113}," \"@agntn\u002Ftools\u002Fmcp\"",[103,458,166],{"class":156},[103,460,461,463,466,468,471],{"class":105,"line":169},[103,462,153],{"class":152},[103,464,465],{"class":156}," { StdioServerTransport } ",[103,467,160],{"class":152},[103,469,470],{"class":113}," \"@modelcontextprotocol\u002Fserver\u002Fstdio\"",[103,472,166],{"class":156},[103,474,475,477,480,482,485],{"class":105,"line":176},[103,476,153],{"class":152},[103,478,479],{"class":156}," { slugTool } ",[103,481,160],{"class":152},[103,483,484],{"class":113}," \".\u002Ftools.ts\"",[103,486,166],{"class":156},[103,488,489],{"class":105,"line":197},[103,490,173],{"emptyLinePlaceholder":172},[103,492,493,496,499,501,504,507,510,513,516],{"class":105,"line":209},[103,494,495],{"class":152},"const",[103,497,498],{"class":156}," server ",[103,500,188],{"class":152},[103,502,503],{"class":109}," createMcpServer",[103,505,506],{"class":156},"({ name: ",[103,508,509],{"class":113},"\"slugs\"",[103,511,512],{"class":156},", version: ",[103,514,515],{"class":113},"\"1.0.0\"",[103,517,518],{"class":156}," }, [slugTool]);\n",[103,520,521,524,527,530,532,535,538],{"class":105,"line":220},[103,522,523],{"class":152},"await",[103,525,526],{"class":156}," server.",[103,528,529],{"class":109},"connect",[103,531,370],{"class":156},[103,533,534],{"class":152},"new",[103,536,537],{"class":109}," StdioServerTransport",[103,539,540],{"class":156},"());\n",[94,542,545],{"className":142,"code":543,"filename":544,"language":145,"meta":99,"style":99},"import type { ExtensionAPI } from \"@earendil-works\u002Fpi-coding-agent\";\nimport { registerPiTools } from \"@agntn\u002Ftools\u002Fpi\";\nimport { slugTool } from \"..\u002F..\u002F..\u002Fsrc\u002Ftools.ts\";\n\nexport default function (pi: ExtensionAPI) {\n  registerPiTools(pi, [slugTool]);\n}\n","packages\u002Fpi\u002Fextensions\u002Fslugs.ts",[71,546,547,564,578,591,595,617,625],{"__ignoreMap":99},[103,548,549,551,554,557,559,562],{"class":105,"line":106},[103,550,153],{"class":152},[103,552,553],{"class":152}," type",[103,555,556],{"class":156}," { ExtensionAPI } ",[103,558,160],{"class":152},[103,560,561],{"class":113}," \"@earendil-works\u002Fpi-coding-agent\"",[103,563,166],{"class":156},[103,565,566,568,571,573,576],{"class":105,"line":169},[103,567,153],{"class":152},[103,569,570],{"class":156}," { registerPiTools } ",[103,572,160],{"class":152},[103,574,575],{"class":113}," \"@agntn\u002Ftools\u002Fpi\"",[103,577,166],{"class":156},[103,579,580,582,584,586,589],{"class":105,"line":176},[103,581,153],{"class":152},[103,583,479],{"class":156},[103,585,160],{"class":152},[103,587,588],{"class":113}," \"..\u002F..\u002F..\u002Fsrc\u002Ftools.ts\"",[103,590,166],{"class":156},[103,592,593],{"class":105,"line":197},[103,594,173],{"emptyLinePlaceholder":172},[103,596,597,599,602,605,608,611,614],{"class":105,"line":209},[103,598,179],{"class":152},[103,600,601],{"class":152}," default",[103,603,604],{"class":152}," function",[103,606,607],{"class":156}," (pi",[103,609,610],{"class":152},":",[103,612,613],{"class":109}," ExtensionAPI",[103,615,616],{"class":156},") {\n",[103,618,619,622],{"class":105,"line":220},[103,620,621],{"class":109},"  registerPiTools",[103,623,624],{"class":156},"(pi, [slugTool]);\n",[103,626,627],{"class":105,"line":231},[103,628,629],{"class":156},"}\n",[64,631,632,633,635],{},"OMP and the AI SDK look the same, one call each. ",[431,634,25],{"href":26}," has every adapter, what it gives its host and the gotchas that host brings along.",[59,637,639],{"id":638},"what-you-get-for-free","What you get for free",[641,642,643,647,650,653],"ul",{},[644,645,646],"li",{},"The core checks every call against the schema, even when a host skips its own check.",[644,648,649],{},"A misspelled key is an error that names itself, not a silently dropped argument.",[644,651,652],{},"Error text stays on one line. No forged lines from a hostile tool name, no escape codes in your terminal.",[644,654,655],{},"The OMP TypeBox trap is already handled. Honestly this one alone was worth the package.",[657,658,659],"style",{},"html pre.shiki code .skH_V, html code.shiki .skH_V{--shiki-light:var(--shiki-token-keyword);--shiki-default:var(--shiki-token-keyword);--shiki-dark:var(--shiki-token-keyword)}html pre.shiki code .s38Sx, html code.shiki .s38Sx{--shiki-light:var(--ui-text-highlighted);--shiki-default:var(--ui-text-highlighted);--shiki-dark:var(--ui-text-highlighted)}html pre.shiki code .shU9J, html code.shiki .shU9J{--shiki-light:var(--shiki-token-string);--shiki-default:var(--shiki-token-string);--shiki-dark:var(--shiki-token-string)}html pre.shiki code .sK71F, html code.shiki .sK71F{--shiki-light:var(--shiki-token-function);--shiki-default:var(--shiki-token-function);--shiki-dark:var(--shiki-token-function)}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":99,"searchDepth":169,"depth":169,"links":661},[662,663,664,665,666],{"id":61,"depth":169,"text":62},{"id":91,"depth":169,"text":92},{"id":138,"depth":169,"text":139},{"id":437,"depth":169,"text":438},{"id":638,"depth":169,"text":639},"Install the package and serve one tool to MCP and Pi and OMP and the AI SDK from a single definition","md",null,{"icon":23},{"title":10,"description":667},"BeW08a2PAXohT1OVLCb-b0elW26KyEeyMV6XWvjUJWU",[669,674],{"title":12,"path":13,"stem":14,"description":675,"children":-1},"Every field of defineTool and the two schema rules it enforces. Plus the one import you must never write",1790784241443]