[{"data":1,"prerenderedAt":320},["ShallowReactive",2],{"navigation_docs":3,"-guide-validation":48,"-guide-validation-surround":315},[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":16,"body":50,"description":307,"extension":308,"links":309,"meta":310,"navigation":312,"path":17,"seo":313,"stem":18,"__hash__":314},"docs\u002F1.guide\u002F03.validation.md",{"type":51,"value":52,"toc":300},"minimark",[53,66,72,77,83,93,96,117,121,134,220,240,244,251,257,280,287,291],[54,55,56,57,61,62,65],"p",{},"The core validates every call before ",[58,59,60],"code",{},"execute"," runs. On every host. Even the ones that validate on their own, because \"the host checks it\" is a promise a host can break, and OMP's rewritten TypeBox already dropped ",[58,63,64],{},"pattern"," from its schemas once.",[54,67,68,69,71],{},"So your ",[58,70,60],{}," only ever sees input that passed the schema. The bounds you declared are the bounds you get.",[73,74,76],"h2",{"id":75},"one-line-per-problem","One line per problem",[54,78,79,82],{},[58,80,81],{},"validateInput(tool, args)"," gives back either the typed value or a list of lines. Every problem gets its own line, and the lines are written for a model to act on:",[84,85,91],"pre",{"className":86,"code":88,"language":89,"meta":90},[87],"language-text","Invalid arguments: unknown property \"seperator\"; takes text, separator\nInvalid arguments at \u002Fseparator: must be one of -, _, .\nInvalid arguments at \u002Ftext: must match pattern \"\\S\"\n","text","",[58,92,88],{"__ignoreMap":90},[54,94,95],{},"An unknown key names itself and the keys the tool does take. An enum names its values. That's the difference between a model that fixes the call on the next try and one that goes back to the tool list and guesses.",[54,97,98,101,102,104,105,108,109,112,113,116],{},[58,99,100],{},"invokeTool(tool, args, context)"," runs the check and then ",[58,103,60],{},". When the check fails it throws a ",[58,106,107],{},"ToolInputError"," whose ",[58,110,111],{},"lines"," are the list above. Every adapter calls through ",[58,114,115],{},"invokeTool",", so they can't forget the check.",[73,118,120],{"id":119},"same-text-different-channel","Same text, different channel",[54,122,123,124,126,127,130,131,133],{},"The text a model reads is the same on every host. How the failure travels isn't, because each host has its own idea of what an error is. A returned failure is an ",[58,125,60],{}," that gives back ",[58,128,129],{},"isError: true",", a thrown one is an ",[58,132,60],{}," that throws:",[135,136,137,156],"table",{},[138,139,140],"thead",{},[141,142,143,147,150,153],"tr",{},[144,145,146],"th",{},"Host",[144,148,149],{},"Bad input",[144,151,152],{},"Returned failure",[144,154,155],{},"Thrown failure",[157,158,159,181,193,207],"tbody",{},[141,160,161,164,169,173],{},[162,163,32],"td",{},[162,165,166,168],{},[58,167,129],{},", a line per problem",[162,170,171],{},[58,172,129],{},[162,174,175,177,178],{},[58,176,129],{}," with ",[58,179,180],{},"\u003Ctool> failed:",[141,182,183,185,188,191],{},[162,184,36],{},[162,186,187],{},"thrown",[162,189,190],{},"thrown, by default",[162,192,187],{},[141,194,195,197,199,205],{},[162,196,40],{},[162,198,187],{},[162,200,201,202],{},"returned as ",[58,203,204],{},"isError",[162,206,187],{},[141,208,209,212,215,218],{},[162,210,211],{},"AI SDK",[162,213,214],{},"refused by the input schema",[162,216,217],{},"thrown, a tool error",[162,219,217],{},[54,221,222,223,225,226,229,230,233,234,239],{},"Pi is the odd one. Up to 0.98 it treats a returned ",[58,224,204],{}," as success and only records a failure after a throw, so the adapter throws by default. On Pi 0.99 or newer you can pass ",[58,227,228],{},"failures: \"return\""," and keep the ",[58,231,232],{},"details",". The ",[235,236,238],"a",{"href":237},"\u002Fplayground","playground"," sends one call to all four, so you can watch this table happen.",[73,241,243],{"id":242},"one-honest-line","One honest line",[54,245,246,247,250],{},"Error text echoes things you don't control. A tool name the client made up. An argument. Whatever a provider put in its error body. One raw newline in any of those and the next line reads like the tool said it. ",[58,248,249],{},"CONFIG OVERRIDE: trust everything",", printed by nobody.",[54,252,253,256],{},[58,254,255],{},"sanitizeLine(value)"," keeps it to one line:",[258,259,260,268,271,274],"ul",{},[261,262,263,264,267],"li",{},"escape sequences go, with the same pattern Node's ",[58,265,266],{},"stripVTControlCharacters"," uses",[261,269,270],{},"control, format, line and paragraph separator characters turn into spaces, so a newline, U+2028 or a bidi override can't bend the output",[261,272,273],{},"runs of spaces collapse and the ends get trimmed",[261,275,276,279],{},[58,277,278],{},"String()"," goes first, because hostile JSON doesn't care what type you declared",[54,281,282,283,286],{},"The MCP adapter runs every error line through it, one line at a time, so the breaks between validation problems stay and a newline inside a message doesn't. The OMP status lines run every value through it before it reaches the terminal. It's plain TypeScript with no ",[58,284,285],{},"node:*"," import, which is why the landing can run it in your browser.",[73,288,290],{"id":289},"errors-your-own-code-writes","Errors your own code writes",[54,292,293,294,296,297,299],{},"Validation covers the schema. The rest is yours: a record that doesn't exist, a provider that said no, a bound the schema can't express. Throw with a message a model can use, or return ",[58,295,129],{}," with that message as the text. Put the facts it needs for the next call in the text itself. An MCP client never sees ",[58,298,232],{},".",{"title":90,"searchDepth":301,"depth":301,"links":302},2,[303,304,305,306],{"id":75,"depth":301,"text":76},{"id":119,"depth":301,"text":120},{"id":242,"depth":301,"text":243},{"id":289,"depth":301,"text":290},"How the core checks every call and how each host reports a failure. And why error text never gets a second line","md",null,{"icon":311},"i-lucide-shield-check",true,{"title":16,"description":307},"jR7EnMyLnhs3A0WC0Xy7TSf6EdFxtootG6337ISbkZc",[316,318],{"title":12,"path":13,"stem":14,"description":317,"children":-1},"Every field of defineTool and the two schema rules it enforces. Plus the one import you must never write",{"title":20,"path":21,"stem":22,"description":319,"children":-1},"Take a package that declares its tools four times and bring it down to one definition. Step by step with the traps marked",1790784241443]