[{"data":1,"prerenderedAt":378},["ShallowReactive",2],{"navigation_docs":3,"-guide-validation":52,"-guide-validation-surround":373},[4,28],{"title":5,"path":6,"stem":7,"children":8,"icon":27},"Guide","\u002Fguide","1.guide\u002F01.index",[9,11,15,19,23],{"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},"CLI","\u002Fguide\u002Fcli","1.guide\u002F04.cli",{"title":24,"path":25,"stem":26},"Moving a Package Over","\u002Fguide\u002Fmigrating","1.guide\u002F05.migrating","i-lucide-book-open",{"title":29,"path":30,"stem":31,"children":32,"icon":51},"Hosts","\u002Fhosts","2.hosts\u002F00.index",[33,35,39,43,47],{"title":34,"path":30,"stem":31},"Every host",{"title":36,"path":37,"stem":38},"MCP","\u002Fhosts\u002Fmcp","2.hosts\u002F01.mcp",{"title":40,"path":41,"stem":42},"Pi","\u002Fhosts\u002Fpi","2.hosts\u002F02.pi",{"title":44,"path":45,"stem":46},"OMP","\u002Fhosts\u002Fomp","2.hosts\u002F03.omp",{"title":48,"path":49,"stem":50},"AI SDK","\u002Fhosts\u002Fai-sdk","2.hosts\u002F04.ai-sdk","i-lucide-table",{"id":53,"title":16,"body":54,"description":365,"extension":366,"links":367,"meta":368,"navigation":370,"path":17,"seo":371,"stem":18,"__hash__":372},"docs\u002F1.guide\u002F03.validation.md",{"type":55,"value":56,"toc":357},"minimark",[57,70,76,81,87,97,116,137,141,154,240,260,264,271,277,300,307,311,314,320,338,344,348],[58,59,60,61,65,66,69],"p",{},"The core validates every call before ",[62,63,64],"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 ",[62,67,68],{},"pattern"," from its schemas once.",[58,71,72,73,75],{},"So your ",[62,74,64],{}," only ever sees input that passed the schema. The bounds you declared are the bounds you get.",[77,78,80],"h2",{"id":79},"one-line-per-problem","One line per problem",[58,82,83,86],{},[62,84,85],{},"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:",[88,89,95],"pre",{"className":90,"code":92,"language":93,"meta":94},[91],"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","",[62,96,92],{"__ignoreMap":94},[58,98,99,100,103,104,107,108,111,112,115],{},"An unknown key names itself and the keys the tool does take. Nested objects too, so a ",[62,101,102],{},"z"," slipped into ",[62,105,106],{},"point"," comes back as ",[62,109,110],{},"at \u002Fpoint: unknown property \"z\"; takes x, y",". TypeBox alone says ",[62,113,114],{},"schema is false"," there. Which key, though? 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.",[58,117,118,121,122,124,125,128,129,132,133,136],{},[62,119,120],{},"invokeTool(tool, args, context)"," runs the check and then ",[62,123,64],{},". When the check fails it throws a ",[62,126,127],{},"ToolInputError"," whose ",[62,130,131],{},"lines"," are the list above. Every adapter calls through ",[62,134,135],{},"invokeTool",", so they can't forget the check.",[77,138,140],{"id":139},"same-text-different-channel","Same text, different channel",[58,142,143,144,146,147,150,151,153],{},"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 ",[62,145,64],{}," that gives back ",[62,148,149],{},"isError: true",", a thrown one is an ",[62,152,64],{}," that throws:",[155,156,157,176],"table",{},[158,159,160],"thead",{},[161,162,163,167,170,173],"tr",{},[164,165,166],"th",{},"Host",[164,168,169],{},"Bad input",[164,171,172],{},"Returned failure",[164,174,175],{},"Thrown failure",[177,178,179,201,213,227],"tbody",{},[161,180,181,184,189,193],{},[182,183,36],"td",{},[182,185,186,188],{},[62,187,149],{},", a line per problem",[182,190,191],{},[62,192,149],{},[182,194,195,197,198],{},[62,196,149],{}," with ",[62,199,200],{},"\u003Ctool> failed:",[161,202,203,205,208,211],{},[182,204,40],{},[182,206,207],{},"thrown",[182,209,210],{},"thrown, by default",[182,212,207],{},[161,214,215,217,219,225],{},[182,216,44],{},[182,218,207],{},[182,220,221,222],{},"returned as ",[62,223,224],{},"isError",[182,226,207],{},[161,228,229,232,235,238],{},[182,230,231],{},"AI SDK",[182,233,234],{},"refused by the input schema",[182,236,237],{},"thrown, a tool error",[182,239,237],{},[58,241,242,243,245,246,249,250,253,254,259],{},"Pi is the odd one. Up to 0.98 it treats a returned ",[62,244,224],{}," 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 ",[62,247,248],{},"failures: \"return\""," and keep the ",[62,251,252],{},"details",". The ",[255,256,258],"a",{"href":257},"\u002Fplayground","playground"," sends one call to all four, so you can watch this table happen.",[77,261,263],{"id":262},"one-honest-line","One honest line",[58,265,266,267,270],{},"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. ",[62,268,269],{},"CONFIG OVERRIDE: trust everything",", printed by nobody.",[58,272,273,276],{},[62,274,275],{},"sanitizeLine(value)"," keeps it to one line:",[278,279,280,288,291,294],"ul",{},[281,282,283,284,287],"li",{},"escape sequences go, with the same pattern Node's ",[62,285,286],{},"stripVTControlCharacters"," uses",[281,289,290],{},"control, format, line and paragraph separator characters turn into spaces, so a newline, U+2028 or a bidi override can't bend the output",[281,292,293],{},"runs of spaces collapse and the ends get trimmed",[281,295,296,299],{},[62,297,298],{},"String()"," goes first, because hostile JSON doesn't care what type you declared",[58,301,302,303,306],{},"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 ",[62,304,305],{},"node:*"," import, which is why the landing can run it in your browser.",[77,308,310],{"id":309},"answers-that-keep-their-shape","Answers that keep their shape",[58,312,313],{},"One line is the wrong shape for an answer. Flatten a table or an indented tree and the model gets soup. So what do you run a whole answer through?",[58,315,316,319],{},[62,317,318],{},"sanitizeText(text)",", the multi-line twin. The CLI prints with it already:",[278,321,322,325,332,335],{},[281,323,324],{},"CRLF and a lone CR become LF, tabs and indents stay put",[281,326,327,328,331],{},"escape sequences go, in linear time, so a run of unclosed ",[62,329,330],{},"ESC ]"," can't stall the call",[281,333,334],{},"other C0 and C1 controls and the bidi marks go, U+2028 and U+2029 turn into spaces",[281,336,337],{},"ZWJ and ZWNJ stay, so an emoji glued from three code points stays one, and Persian still spells right",[58,339,340,341,343],{},"Run the text through it before ",[62,342,64],{}," returns. A provider's escape codes then never reach anyone's terminal. And nobody writes their own copy of the hard part again.",[77,345,347],{"id":346},"errors-your-own-code-writes","Errors your own code writes",[58,349,350,351,353,354,356],{},"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 ",[62,352,149],{}," with that message as the text. Put the facts it needs for the next call in the text itself. An MCP client never sees ",[62,355,252],{},".",{"title":94,"searchDepth":358,"depth":358,"links":359},2,[360,361,362,363,364],{"id":79,"depth":358,"text":80},{"id":139,"depth":358,"text":140},{"id":262,"depth":358,"text":263},{"id":309,"depth":358,"text":310},{"id":346,"depth":358,"text":347},"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":369},"i-lucide-shield-check",true,{"title":16,"description":365},"5-771aQmiOm3CGSKd5vRX-BDg9QN0qsfRWtfO-nl9Gs",[374,376],{"title":12,"path":13,"stem":14,"description":375,"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":377,"children":-1},"Turn the same tool list into a command line. Flags come from the schema and MCP rides along",1791469406375]