<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Leuria tutorials</title>
        <link>https://leuria.dev/tutorials</link>
        <description>Add AI to your website on each visitor's own AI: Ollama, LM Studio, oMLX, LiteLLM and more.</description>
        <lastBuildDate>Tue, 06 Oct 2026 16:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>Leuria · Apache-2.0</copyright>
        <item>
            <title><![CDATA[Add Ask AI to Docusaurus in 5 minutes, with no API keys]]></title>
            <link>https://leuria.dev/tutorials/docusaurus-ask-ai</link>
            <guid>https://leuria.dev/tutorials/docusaurus-ask-ai</guid>
            <pubDate>Tue, 06 Oct 2026 16:00:00 GMT</pubDate>
            <description><![CDATA[Add Ask AI to Docusaurus with one plugin: answers from your pages, with links, on each reader's own AI. No API keys, nothing to pay per question.]]></description>
            <content:encoded><![CDATA[<p>Add one plugin to your Docusaurus config and your docs get an <strong>Ask AI</strong> button. The assistant searches your pages, reads the ones it needs and answers with links to them. It runs on each reader's own AI (ChatGPT, Claude, or a model in Ollama or LM Studio), so you hold no API key and pay nothing per question.</p>
<!-- -->
<p><strong>In short:</strong> <code>npm install @leuria/docusaurus</code>, add <code>"@leuria/docusaurus"</code> to <code>plugins</code>, run your site. That's the whole setup. The rest of this tutorial shows what readers see, the options worth turning on, and how to add a fallback for readers who have no AI of their own.</p>
<p>You can try the result on this page: the <strong>Ask AI</strong> button at the top answers from the Leuria docs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="what-you-need">What you need<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#what-you-need" class="hash-link" aria-label="Direct link to What you need" title="Direct link to What you need" translate="no">​</a></h2>
<ul>
<li class="">A Docusaurus 3 site (3.6 or later).</li>
<li class="">Five minutes.</li>
<li class="">To try it with your own AI: the <a href="https://leuria.eu/download" target="_blank" rel="noopener noreferrer" class="">Leuria app</a> (Mac or Windows), or <code>npx @leuria/cli</code> in a terminal.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-1-install-the-plugin">Step 1: install the plugin<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#step-1-install-the-plugin" class="hash-link" aria-label="Direct link to Step 1: install the plugin" title="Direct link to Step 1: install the plugin" translate="no">​</a></h2>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> @leuria/docusaurus</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-2-add-it-to-your-config">Step 2: add it to your config<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#step-2-add-it-to-your-config" class="hash-link" aria-label="Direct link to Step 2: add it to your config" title="Direct link to Step 2: add it to your config" translate="no">​</a></h2>
<div class="language-js codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_yl8C">docusaurus.config.js</div><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-js codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string" style="color:#e3116c">"@leuria/docusaurus"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">suggestions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"How do I get started?"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"How do I deploy?"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p><code>suggestions</code> are the questions the panel offers before the first one. Write the two or three questions your readers ask most.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-3-run-your-site">Step 3: run your site<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#step-3-run-your-site" class="hash-link" aria-label="Direct link to Step 3: run your site" title="Direct link to Step 3: run your site" translate="no">​</a></h2>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> start</span><br></div></code></pre></div></div>
<p>The navbar now has an <strong>Ask AI</strong> button. At build time the plugin read the Markdown of every docs page (drafts and unlisted pages left out) and cut it into sections. Readers fetch that file the first time they ask.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-4-ask-with-your-own-ai">Step 4: ask with your own AI<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#step-4-ask-with-your-own-ai" class="hash-link" aria-label="Direct link to Step 4: ask with your own AI" title="Direct link to Step 4: ask with your own AI" translate="no">​</a></h2>
<ol>
<li class="">Open the Leuria app. The first time, it asks which AI to use: an app you already pay for (ChatGPT, Claude), a model on your computer (LM Studio, Ollama), or an AI service with a key.</li>
<li class="">On your site, click <strong>Ask AI</strong>, then <strong>Connect your AI</strong>.</li>
<li class="">Allow your site in the window Leuria opens.</li>
</ol>
<p>Ask a question. The assistant calls the docs tools (<code>search_docs</code>, <code>read_page</code>, <code>list_pages</code>, <code>open_page</code>), reads the pages it needs, and answers with links. It knows which page the reader has open and the text they selected. The conversation stays open while the reader moves between pages.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-5-turn-on-the-options-worth-having">Step 5: turn on the options worth having<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#step-5-turn-on-the-options-worth-having" class="hash-link" aria-label="Direct link to Step 5: turn on the options worth having" title="Direct link to Step 5: turn on the options worth having" translate="no">​</a></h2>
<div class="language-js codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_yl8C">docusaurus.config.js</div><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-js codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"@leuria/docusaurus"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">app</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Acme docs"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic">// the name readers see when they connect your site</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">suggestions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"How do I get started?"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">search</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">              </span><span class="token comment" style="color:#999988;font-style:italic">// a ⌘K search, by words and by meaning (for sites without one)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">connectButton</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic">// "Connect your AI" in the navbar</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">pageEmbeddings</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic">// search by meaning for readers whose AI can't embed (npm install @leuria/web-embed)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<ul>
<li class=""><strong>Already on Algolia DocSearch or a local search plugin?</strong> Leave <code>search</code> off. The plugin replaces no theme component, so it works next to your search.</li>
<li class=""><strong><code>pageEmbeddings</code></strong> downloads a small embedding model (about 40 MB) only when the reader agrees.</li>
<li class=""><strong>Versioned docs</strong> work as they are: the assistant stays in the version the reader is on.</li>
</ul>
<p>Every option is in the <a class="" href="https://leuria.dev/docs/developers/docusaurus#options">plugin reference</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-6-optional-a-fallback-for-readers-with-no-ai">Step 6 (optional): a fallback for readers with no AI<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#step-6-optional-a-fallback-for-readers-with-no-ai" class="hash-link" aria-label="Direct link to Step 6 (optional): a fallback for readers with no AI" title="Direct link to Step 6 (optional): a fallback for readers with no AI" translate="no">​</a></h2>
<p>Readers without Leuria are offered it first. Below, in small, they can pick another AI for the page:</p>
<ul>
<li class=""><strong>The browser's built-in model</strong>, where there is one (Chrome with its AI features on). It can't use tools, so the panel searches your docs itself and gives the model the best passages.</li>
<li class=""><strong>Your own server</strong>, if you set <code>server</code>. It must speak the OpenAI Chat Completions API, and add the API key on the server side:</li>
</ul>
<div class="language-js codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-js codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"@leuria/docusaurus"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">server</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://ai.example.com/v1/chat/completions"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">model</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"docs-fallback"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Acme's AI"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<p>Everything in <code>server</code> reaches the page, so never put a key there. The <a class="" href="https://leuria.dev/tutorials/litellm-docs-fallback">LiteLLM tutorial</a> builds that endpoint, with a budget and a rate limit.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-7-optional-llmstxt-for-coding-agents">Step 7 (optional): llms.txt for coding agents<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#step-7-optional-llmstxt-for-coding-agents" class="hash-link" aria-label="Direct link to Step 7 (optional): llms.txt for coding agents" title="Direct link to Step 7 (optional): llms.txt for coding agents" translate="no">​</a></h2>
<p>The Ask panel serves people. Coding agents (Cursor, Claude Code, Codex) read your docs better as Markdown. Add <a href="https://github.com/rachfop/docusaurus-plugin-llms" target="_blank" rel="noopener noreferrer" class=""><code>docusaurus-plugin-llms</code></a> next to it:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> docusaurus-plugin-llms</span><br></div></code></pre></div></div>
<div class="language-js codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-js codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"@leuria/docusaurus"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">suggestions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"How do I get started?"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"docusaurus-plugin-llms"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">generateMarkdownFiles</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<p>It writes <code>/llms.txt</code>, <code>/llms-full.txt</code> and a Markdown copy of every page at build time. This site does the same: see <a href="https://leuria.dev/llms.txt" target="_blank" rel="noopener noreferrer" class="">leuria.dev/llms.txt</a>.</p>
<p>The docs tools also go to the agents built into the browser, through <a class="" href="https://leuria.dev/docs/developers/guides/tools#webmcp-the-same-tools-for-the-browsers-agents">WebMCP</a>. That's on by default (<code>webmcp: true</code>).</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="how-it-works">How it works<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<!-- -->
<ul>
<li class="">Your site stays static. There is no server of yours in the loop, unless you set <code>server</code>.</li>
<li class="">The docs index stays in the reader's browser.</li>
<li class="">The reader's AI only gets your docs tools. Through Leuria it runs with its own tools switched off, in an empty folder.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="hosted-ask-ai-or-the-readers-own-ai">Hosted Ask AI or the reader's own AI?<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#hosted-ask-ai-or-the-readers-own-ai" class="hash-link" aria-label="Direct link to Hosted Ask AI or the reader's own AI?" title="Direct link to Hosted Ask AI or the reader's own AI?" translate="no">​</a></h2>
<p>A hosted docs assistant answers with an API key the site pays for, so every question costs you something. With Leuria, the reader's AI answers: a ChatGPT or Claude plan they already pay for, or a model on their own computer that costs nobody anything. You can still keep a hosted model as the last resort with <code>server</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="faq">FAQ<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#faq" class="hash-link" aria-label="Direct link to FAQ" title="Direct link to FAQ" translate="no">​</a></h2>
<p><strong>Does it work for readers who don't have Leuria?</strong>
Yes. They're offered Leuria first, and can pick the browser's built-in model or your <code>server</code> instead, for the page.</p>
<p><strong>Which AIs can readers use?</strong>
ChatGPT (through Codex), Claude (through Claude Code), any model in LM Studio or Ollama, and any OpenAI-compatible API they have a key for.</p>
<p><strong>Does my content go to a third party?</strong>
Only to the AI the reader chose. Questions never reach your server unless you set <code>server</code> and nothing else can answer.</p>
<p><strong>Does it replace my search?</strong>
No. It sits next to Algolia DocSearch or a local search. Turn on <code>search: true</code> only if your site has none.</p>
<p><strong>Do I need to change my Markdown?</strong>
No. The plugin reads your docs as they are.</p>
<p><strong>What does it cost?</strong>
Nothing per question. The plugin is open source (Apache-2.0).</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="next">Next<a href="https://leuria.dev/tutorials/docusaurus-ask-ai#next" class="hash-link" aria-label="Direct link to Next" title="Direct link to Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://leuria.dev/tutorials/ollama-web-app">Use Ollama in a web app, without CORS or API keys</a>: the reader side, with a model on their computer.</li>
<li class=""><a class="" href="https://leuria.dev/tutorials/litellm-docs-fallback">LiteLLM as your docs assistant's fallback</a>: the server side.</li>
<li class=""><a class="" href="https://leuria.dev/docs/developers/docusaurus">Plugin reference</a>: every option.</li>
</ul>
<p><em>Written for <code>@leuria/docusaurus</code> 0.1.2 and Docusaurus 3.10.</em></p>]]></content:encoded>
            <category>Docusaurus</category>
            <category>Ask AI</category>
            <category>WebMCP</category>
        </item>
        <item>
            <title><![CDATA[Use Ollama in a web app, without CORS or API keys]]></title>
            <link>https://leuria.dev/tutorials/ollama-web-app</link>
            <guid>https://leuria.dev/tutorials/ollama-web-app</guid>
            <pubDate>Tue, 06 Oct 2026 15:00:00 GMT</pubDate>
            <description><![CDATA[Let your website use the Ollama model on each visitor's computer: no OLLAMA_ORIGINS, no CORS setup, no API key, and no cost to you.]]></description>
            <content:encoded><![CDATA[<p>Your visitors already run models in Ollama. With Leuria, your website can use them: the visitor's own Ollama model answers on your site, with your page's tools, and you pay nothing per request. Nobody touches <code>OLLAMA_ORIGINS</code>, opens a port or pastes a key.</p>
<!-- -->
<p><strong>In short:</strong> add Leuria to your page (one script from a CDN). A visitor with Ollama installs the Leuria app, picks an Ollama model, and allows your site once. From then on, your AI features run on their model, on their computer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="why-not-call-ollama-from-the-page">Why not call Ollama from the page?<a href="https://leuria.dev/tutorials/ollama-web-app#why-not-call-ollama-from-the-page" class="hash-link" aria-label="Direct link to Why not call Ollama from the page?" title="Direct link to Why not call Ollama from the page?" translate="no">​</a></h2>
<p>The usual way is to <code>fetch("http://localhost:11434/api/chat")</code> from the browser. That breaks for real visitors:</p>
<ul>
<li class=""><strong>CORS.</strong> Ollama refuses browser requests from origins it doesn't know. Each visitor would have to set <code>OLLAMA_ORIGINS</code> for your site and restart Ollama.</li>
<li class=""><strong>No consent.</strong> Any page the visitor opens could use their model the same way.</li>
<li class=""><strong>One AI only.</strong> Visitors who use ChatGPT, Claude or LM Studio instead get nothing.</li>
</ul>
<p>With Leuria, the browser never calls Ollama. The Leuria app, on the visitor's computer, does. It answers only the sites the visitor allowed, and gives the model only the tools your page offers.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="part-1-for-site-builders-add-leuria-to-your-page">Part 1, for site builders: add Leuria to your page<a href="https://leuria.dev/tutorials/ollama-web-app#part-1-for-site-builders-add-leuria-to-your-page" class="hash-link" aria-label="Direct link to Part 1, for site builders: add Leuria to your page" title="Direct link to Part 1, for site builders: add Leuria to your page" translate="no">​</a></h2>
<p>One file, no build step:</p>
<div class="language-html codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_yl8C">index.html</div><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-html codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token doctype punctuation" style="color:#393A34;font-style:italic">&lt;!</span><span class="token doctype doctype-tag" style="color:#999988;font-style:italic">doctype</span><span class="token doctype" style="color:#999988;font-style:italic"> </span><span class="token doctype name" style="color:#999988;font-style:italic">html</span><span class="token doctype punctuation" style="color:#393A34;font-style:italic">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">leuria-connect-button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">leuria-connect-button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">question</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">placeholder</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">Ask something</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">ask</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">Ask</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">answer</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">script</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">module</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token script language-javascript keyword module" style="color:#00009f">import</span><span class="token script language-javascript"> </span><span class="token script language-javascript imports punctuation" style="color:#393A34">{</span><span class="token script language-javascript imports"> leuria</span><span class="token script language-javascript imports punctuation" style="color:#393A34">,</span><span class="token script language-javascript imports"> promptAPI</span><span class="token script language-javascript imports punctuation" style="color:#393A34">,</span><span class="token script language-javascript imports"> createAI</span><span class="token script language-javascript imports punctuation" style="color:#393A34">,</span><span class="token script language-javascript imports"> setDefaultClient </span><span class="token script language-javascript imports punctuation" style="color:#393A34">}</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword module" style="color:#00009f">from</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">"https://cdn.jsdelivr.net/npm/@leuria/connect/dist/standalone.js"</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token script language-javascript comment" style="color:#999988;font-style:italic">// The visitor's own AI first (their Ollama model, or another AI they chose), then the browser's built-in model.</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token script language-javascript keyword" style="color:#00009f">const</span><span class="token script language-javascript"> ai </span><span class="token script language-javascript operator" style="color:#393A34">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript function" style="color:#d73a49">createAI</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript punctuation" style="color:#393A34">{</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript literal-property property" style="color:#36acaa">providers</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">[</span><span class="token script language-javascript function" style="color:#d73a49">leuria</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript punctuation" style="color:#393A34">{</span><span class="token script language-javascript"> </span><span class="token script language-javascript literal-property property" style="color:#36acaa">app</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">"My site"</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"> </span><span class="token script language-javascript literal-property property" style="color:#36acaa">needs</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">{</span><span class="token script language-javascript"> </span><span class="token script language-javascript literal-property property" style="color:#36acaa">effort</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">"light"</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">}</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">}</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"> </span><span class="token script language-javascript function" style="color:#d73a49">promptAPI</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript punctuation" style="color:#393A34">]</span><span class="token script language-javascript punctuation" style="color:#393A34">,</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token script language-javascript punctuation" style="color:#393A34">}</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token script language-javascript function" style="color:#d73a49">setDefaultClient</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript">ai</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token script language-javascript dom variable" style="color:#36acaa">document</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript method function property-access" style="color:#d73a49">querySelector</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript string" style="color:#e3116c">"#ask"</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript method-variable function-variable method function property-access" style="color:#d73a49">onclick</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator" style="color:#393A34">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword" style="color:#00009f">async</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript"> </span><span class="token script language-javascript arrow operator" style="color:#393A34">=&gt;</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">{</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript keyword" style="color:#00009f">const</span><span class="token script language-javascript"> answer </span><span class="token script language-javascript operator" style="color:#393A34">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript dom variable" style="color:#36acaa">document</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript method function property-access" style="color:#d73a49">querySelector</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript string" style="color:#e3116c">"#answer"</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    answer</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">textContent</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator" style="color:#393A34">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">""</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript keyword control-flow" style="color:#00009f">for</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword control-flow" style="color:#00009f">await</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript keyword" style="color:#00009f">const</span><span class="token script language-javascript"> event </span><span class="token script language-javascript keyword" style="color:#00009f">of</span><span class="token script language-javascript"> ai</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript method function property-access" style="color:#d73a49">chat</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript punctuation" style="color:#393A34">{</span><span class="token script language-javascript"> </span><span class="token script language-javascript literal-property property" style="color:#36acaa">prompt</span><span class="token script language-javascript operator" style="color:#393A34">:</span><span class="token script language-javascript"> </span><span class="token script language-javascript dom variable" style="color:#36acaa">document</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript method function property-access" style="color:#d73a49">querySelector</span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript string" style="color:#e3116c">"#question"</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">value</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">}</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">{</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">      </span><span class="token script language-javascript keyword control-flow" style="color:#00009f">if</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:#393A34">(</span><span class="token script language-javascript">event</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">type</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator" style="color:#393A34">===</span><span class="token script language-javascript"> </span><span class="token script language-javascript string" style="color:#e3116c">"text-delta"</span><span class="token script language-javascript punctuation" style="color:#393A34">)</span><span class="token script language-javascript"> answer</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">textContent</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator" style="color:#393A34">+=</span><span class="token script language-javascript"> event</span><span class="token script language-javascript punctuation" style="color:#393A34">.</span><span class="token script language-javascript property-access">text</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">    </span><span class="token script language-javascript punctuation" style="color:#393A34">}</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript">  </span><span class="token script language-javascript punctuation" style="color:#393A34">}</span><span class="token script language-javascript"></span><br></div><div class="token-line" style="color:#393A34"><span class="token script language-javascript"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">script</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></div></code></pre></div></div>
<p><code>&lt;leuria-connect-button&gt;</code> handles the whole connect flow. <code>needs</code> tells Leuria what your feature needs, so it recommends the smallest of the visitor's models that is enough: a short answer doesn't need a 70B model.</p>
<p>On Docusaurus, the <a class="" href="https://leuria.dev/tutorials/docusaurus-ask-ai">Ask AI plugin</a> does all this for you.</p>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="give-the-model-tools">Give the model tools<a href="https://leuria.dev/tutorials/ollama-web-app#give-the-model-tools" class="hash-link" aria-label="Direct link to Give the model tools" title="Direct link to Give the model tools" translate="no">​</a></h3>
<p>Ollama models trained for tool calling (Qwen 3, Llama 3.1, gpt-oss…) can call functions in your page:</p>
<div class="language-js codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-js codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> listMugs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"list_mugs"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">description</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"List the mugs for sale, with their price in euros."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">inputSchema</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"object"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">properties</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">execute</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Celadon"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">price</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">24</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Tenmoku"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">price</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">31</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> answer </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">chat</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">prompt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Which mug is cheapest?"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">tools</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">listMugs</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">text</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>The tool runs in the page. The model only sees what it returns. Declare <code>needs: { tools: true }</code> so Leuria recommends a model that can call tools. See <a class="" href="https://leuria.dev/docs/developers/guides/tools">Page tools</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="part-2-for-ollama-users-let-your-model-answer-on-websites">Part 2, for Ollama users: let your model answer on websites<a href="https://leuria.dev/tutorials/ollama-web-app#part-2-for-ollama-users-let-your-model-answer-on-websites" class="hash-link" aria-label="Direct link to Part 2, for Ollama users: let your model answer on websites" title="Direct link to Part 2, for Ollama users: let your model answer on websites" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="1-pull-a-chat-model">1. Pull a chat model<a href="https://leuria.dev/tutorials/ollama-web-app#1-pull-a-chat-model" class="hash-link" aria-label="Direct link to 1. Pull a chat model" title="Direct link to 1. Pull a chat model" translate="no">​</a></h3>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">ollama pull qwen3:8b</span><br></div></code></pre></div></div>
<p>Any chat model works. For sites with tools (Ask AI on docs, shops), pick one trained for tool calling. 4B to 8B models handle light tasks well.</p>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="2-install-leuria">2. Install Leuria<a href="https://leuria.dev/tutorials/ollama-web-app#2-install-leuria" class="hash-link" aria-label="Direct link to 2. Install Leuria" title="Direct link to 2. Install Leuria" translate="no">​</a></h3>
<p>Download it from <a href="https://leuria.eu/download" target="_blank" rel="noopener noreferrer" class="">leuria.eu/download</a> (Mac and Windows), or with Homebrew:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">brew tap leur-ia/leuria https://github.com/leur-ia/leuria</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">brew </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--cask</span><span class="token plain"> leuria</span><br></div></code></pre></div></div>
<p>On Linux, run the engine from a terminal: <code>npx @leuria/cli@latest</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="3-choose-your-ollama-model">3. Choose your Ollama model<a href="https://leuria.dev/tutorials/ollama-web-app#3-choose-your-ollama-model" class="hash-link" aria-label="Direct link to 3. Choose your Ollama model" title="Direct link to 3. Choose your Ollama model" translate="no">​</a></h3>
<p>Open Leuria. Under <strong>Add an AI</strong>, choose <strong>A model on this computer</strong>. Leuria finds Ollama at its usual address and lists your models. Pick one. You can change it later, for every site or for one site.</p>
<p>If Ollama isn't listed, make sure it runs (the Ollama app is open, or <code>ollama serve</code>), then click <strong>Look again</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="4-allow-a-site">4. Allow a site<a href="https://leuria.dev/tutorials/ollama-web-app#4-allow-a-site" class="hash-link" aria-label="Direct link to 4. Allow a site" title="Direct link to 4. Allow a site" translate="no">​</a></h3>
<p>On a site that uses Leuria (try <a href="https://demo.leuria.dev/" target="_blank" rel="noopener noreferrer" class="">demo.leuria.dev</a>), click <strong>Connect your AI</strong> and allow the site in Leuria's window. Its AI features now run on your model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="search-by-meaning-add-an-embedding-model">Search by meaning: add an embedding model<a href="https://leuria.dev/tutorials/ollama-web-app#search-by-meaning-add-an-embedding-model" class="hash-link" aria-label="Direct link to Search by meaning: add an embedding model" title="Direct link to Search by meaning: add an embedding model" translate="no">​</a></h2>
<p>Sites that search by meaning (the Docusaurus plugin's search, for one) use an embedding model on your computer when you have one:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">ollama pull nomic-embed-text</span><br></div></code></pre></div></div>
<p>Leuria finds it on its own. Embeddings only ever use a model on your computer, never a service on the network.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="ollama-cloud-models">Ollama cloud models<a href="https://leuria.dev/tutorials/ollama-web-app#ollama-cloud-models" class="hash-link" aria-label="Direct link to Ollama cloud models" title="Direct link to Ollama cloud models" translate="no">​</a></h2>
<p>Ollama also lists the cloud models you signed in to, which run on ollama.com. Leuria shows them, but doesn't count them as on this computer when it recommends a model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="troubleshooting">Troubleshooting<a href="https://leuria.dev/tutorials/ollama-web-app#troubleshooting" class="hash-link" aria-label="Direct link to Troubleshooting" title="Direct link to Troubleshooting" translate="no">​</a></h2>
<p><strong>Leuria says "No model found on this computer".</strong>
Ollama isn't running, or has no chat model yet. Start Ollama, run <code>ollama list</code> to check, then click <strong>Look again</strong>.</p>
<p><strong>Do I need <code>OLLAMA_ORIGINS</code> or <code>OLLAMA_HOST</code>?</strong>
No. The browser never calls Ollama. Leuria calls it from the same computer, at <code>127.0.0.1:11434</code>.</p>
<p><strong>Ollama runs on another port or machine.</strong>
Add it as a service with its address: in Leuria, <strong>Add an AI</strong> → <strong>An AI service with a key</strong> → <strong>Another service</strong>, address <code>http://&lt;host&gt;:&lt;port&gt;/v1</code>. Or from a terminal: <code>npx @leuria/cli providers add "My Ollama" http://&lt;host&gt;:&lt;port&gt;/v1</code>.</p>
<p><strong>Answers say the model can't use the site's tools.</strong>
Pick a model trained for tool calling, such as <code>qwen3:8b</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="faq">FAQ<a href="https://leuria.dev/tutorials/ollama-web-app#faq" class="hash-link" aria-label="Direct link to FAQ" title="Direct link to FAQ" translate="no">​</a></h2>
<p><strong>Does my data leave my computer?</strong>
With an Ollama model on your computer, no: the site's page sends the question to Leuria, Leuria to Ollama, both on your computer.</p>
<p><strong>Can a website use my model without asking?</strong>
No. A site must ask, and you allow it in Leuria's own window. You can disconnect it at any time.</p>
<p><strong>What does it cost the website?</strong>
Nothing. The visitor's model answers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="next">Next<a href="https://leuria.dev/tutorials/ollama-web-app#next" class="hash-link" aria-label="Direct link to Next" title="Direct link to Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://leuria.dev/tutorials/litellm-docs-fallback">LiteLLM as your docs assistant's fallback</a>: for visitors with no AI of their own.</li>
<li class=""><a class="" href="https://leuria.dev/tutorials/lm-studio-websites">Use LM Studio models on any website</a>.</li>
<li class=""><a class="" href="https://leuria.dev/docs/developers/guides/providers">Providers and the cascade</a>: who answers, and when.</li>
</ul>
<p><em>Written for Leuria 0.1.4 and <code>@leuria/client</code> 0.1.4.</em></p>]]></content:encoded>
            <category>Ollama</category>
            <category>Local AI</category>
            <category>Page tools</category>
        </item>
        <item>
            <title><![CDATA[LiteLLM as the fallback for your site's AI assistant]]></title>
            <link>https://leuria.dev/tutorials/litellm-docs-fallback</link>
            <guid>https://leuria.dev/tutorials/litellm-docs-fallback</guid>
            <pubDate>Tue, 06 Oct 2026 14:00:00 GMT</pubDate>
            <description><![CDATA[Use LiteLLM as your site's last-resort AI: no API key in the page, a budget and a rate limit. Visitors with their own AI still cost you nothing.]]></description>
            <content:encoded><![CDATA[<p>Leuria lets your site's AI features run on each visitor's own AI. Some visitors have none. For them, you can keep a model of your own as the last resort: a LiteLLM gateway, with the API key on your server, a monthly budget and a rate limit. This tutorial sets it up for a static site (Docusaurus on GitHub Pages, say) in about 20 minutes.</p>
<!-- -->
<p><strong>In short:</strong> run LiteLLM with one model alias and a virtual key capped by budget and rate. Put a tiny proxy in front that adds the key and allows only your site. Point <code>server()</code> (or the Docusaurus plugin's <code>server</code> option) at the proxy, with <code>model</code> set.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="the-cascade">The cascade<a href="https://leuria.dev/tutorials/litellm-docs-fallback#the-cascade" class="hash-link" aria-label="Direct link to The cascade" title="Direct link to The cascade" translate="no">​</a></h2>
<!-- -->
<p>Your gateway answers only when the visitor has no AI of their own and picks yours. So the bill stays small, and the cap keeps it that way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="what-you-need">What you need<a href="https://leuria.dev/tutorials/litellm-docs-fallback#what-you-need" class="hash-link" aria-label="Direct link to What you need" title="Direct link to What you need" translate="no">​</a></h2>
<ul>
<li class="">A server that can run Docker, reachable over HTTPS (any small VPS).</li>
<li class="">An API key for a model provider (OpenAI, Anthropic, Mistral…), or your own vLLM or Ollama server.</li>
<li class="">A free Cloudflare account for the proxy, or any place that runs a small function.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-1-run-litellm-with-one-model-alias">Step 1: run LiteLLM with one model alias<a href="https://leuria.dev/tutorials/litellm-docs-fallback#step-1-run-litellm-with-one-model-alias" class="hash-link" aria-label="Direct link to Step 1: run LiteLLM with one model alias" title="Direct link to Step 1: run LiteLLM with one model alias" translate="no">​</a></h2>
<p>Expose one alias, <code>docs-fallback</code>, so you can swap the model behind it without touching your site.</p>
<div class="language-yaml codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_yl8C">litellm_config.yaml</div><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-yaml codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">model_list</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">model_name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> docs</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">fallback</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">litellm_params</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> mistral/mistral</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">small</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">latest </span><span class="token comment" style="color:#999988;font-style:italic"># or openai/…, anthropic/…, hosted_vllm/…</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">api_key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> os.environ/MISTRAL_API_KEY</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">general_settings</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">master_key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> os.environ/LITELLM_MASTER_KEY</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">database_url</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> os.environ/DATABASE_URL </span><span class="token comment" style="color:#999988;font-style:italic"># virtual keys and budgets need a database</span><br></div></code></pre></div></div>
<div class="language-yaml codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_yl8C">docker-compose.yml</div><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-yaml codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">litellm</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> docker.litellm.ai/berriai/litellm</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">latest</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">command</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"--config"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/app/config.yaml"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"./litellm_config.yaml:/app/config.yaml:ro"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">ports</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"4000:4000"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">environment</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">MISTRAL_API_KEY</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">MISTRAL_API_KEY</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">LITELLM_MASTER_KEY</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">LITELLM_MASTER_KEY</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">DATABASE_URL</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> postgresql</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">//litellm</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">litellm@db</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">5432/litellm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">depends_on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">db</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">db</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> postgres</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">16</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">environment</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">POSTGRES_USER</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> litellm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">POSTGRES_PASSWORD</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> litellm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">POSTGRES_DB</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> litellm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"pgdata:/var/lib/postgresql/data"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  pgdata</span><span class="token punctuation" style="color:#393A34">:</span><br></div></code></pre></div></div>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token builtin class-name">export</span><span class="token plain"> </span><span class="token assign-left variable" style="color:#36acaa">MISTRAL_API_KEY</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">..</span><span class="token plain">. </span><span class="token assign-left variable" style="color:#36acaa">LITELLM_MASTER_KEY</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">sk-</span><span class="token variable" style="color:#36acaa">$(</span><span class="token variable" style="color:#36acaa">openssl rand </span><span class="token variable parameter variable" style="color:#36acaa">-hex</span><span class="token variable" style="color:#36acaa"> </span><span class="token variable number" style="color:#36acaa">16</span><span class="token variable" style="color:#36acaa">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">docker</span><span class="token plain"> compose up </span><span class="token parameter variable" style="color:#36acaa">-d</span><br></div></code></pre></div></div>
<p>Put LiteLLM behind HTTPS (Caddy, nginx, or your host's load balancer).</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-2-create-a-capped-key-for-your-site">Step 2: create a capped key for your site<a href="https://leuria.dev/tutorials/litellm-docs-fallback#step-2-create-a-capped-key-for-your-site" class="hash-link" aria-label="Direct link to Step 2: create a capped key for your site" title="Direct link to Step 2: create a capped key for your site" translate="no">​</a></h2>
<p>The master key never leaves your server. Make a virtual key that can only use <code>docs-fallback</code>, with a budget and a rate limit:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">-X</span><span class="token plain"> POST https://llm.example.com/key/generate </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">-H</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Authorization: Bearer </span><span class="token string variable" style="color:#36acaa">$LITELLM_MASTER_KEY</span><span class="token string" style="color:#e3116c">"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">-H</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Content-Type: application/json"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token parameter variable" style="color:#36acaa">-d</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'{</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    "key_alias": "docs-fallback",</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    "models": ["docs-fallback"],</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    "max_budget": 10,</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    "budget_duration": "30d",</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">    "rpm_limit": 30</span><br></div><div class="token-line" style="color:#393A34"><span class="token string" style="color:#e3116c">  }'</span><br></div></code></pre></div></div>
<p>That's $10 a month (LiteLLM counts in US dollars) and 30 requests a minute, at most. When the budget is spent, the gateway refuses, and readers are offered Leuria and the browser's model as before.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-3-a-proxy-that-adds-the-key">Step 3: a proxy that adds the key<a href="https://leuria.dev/tutorials/litellm-docs-fallback#step-3-a-proxy-that-adds-the-key" class="hash-link" aria-label="Direct link to Step 3: a proxy that adds the key" title="Direct link to Step 3: a proxy that adds the key" translate="no">​</a></h2>
<p>Everything you give <code>server()</code> reaches the page, so the key can't go there. A Cloudflare Worker sits between the page and LiteLLM. It allows only your site's origin, forces the model and caps the answer's length:</p>
<div class="language-js codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_yl8C">worker.js</div><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-js codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">request</span><span class="token parameter punctuation" style="color:#393A34">,</span><span class="token parameter"> env</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> cors </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-property property" style="color:#36acaa">"Access-Control-Allow-Origin"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">SITE_ORIGIN</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-property property" style="color:#36acaa">"Access-Control-Allow-Methods"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"POST, OPTIONS"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string-property property" style="color:#36acaa">"Access-Control-Allow-Headers"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Content-Type"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">Vary</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Origin"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> allowed </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Origin"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> env</span><span class="token punctuation" style="color:#393A34">.</span><span class="token constant" style="color:#36acaa">SITE_ORIGIN</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">method</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">===</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"OPTIONS"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Response</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword null nil" style="color:#00009f">null</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> allowed </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">204</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">403</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> cors </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">method</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"POST"</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">||</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">!</span><span class="token plain">allowed</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Response</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Forbidden"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">403</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> body </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    body</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">model</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"docs-fallback"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    body</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">max_tokens</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token known-class-name class-name">Math</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">min</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">max_tokens</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">??</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> upstream </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">fetch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">env</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation constant" style="color:#36acaa">LITELLM_URL</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string string" style="color:#e3116c">/v1/chat/completions</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">method</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"POST"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token string-property property" style="color:#36acaa">"Content-Type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"application/json"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">Authorization</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token template-string string" style="color:#e3116c">Bearer </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">${</span><span class="token template-string interpolation">env</span><span class="token template-string interpolation punctuation" style="color:#393A34">.</span><span class="token template-string interpolation constant" style="color:#36acaa">LITELLM_KEY</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:#393A34">}</span><span class="token template-string template-punctuation string" style="color:#e3116c">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">body</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token known-class-name class-name">JSON</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">stringify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">body</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Response</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">upstream</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">body</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">status</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> upstream</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">status</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">headers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain">cors</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string-property property" style="color:#36acaa">"Content-Type"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> upstream</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">headers</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Content-Type"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">??</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"text/event-stream"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">npx wrangler deploy worker.js </span><span class="token parameter variable" style="color:#36acaa">--name</span><span class="token plain"> docs-ai</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx wrangler secret put LITELLM_KEY        </span><span class="token comment" style="color:#999988;font-style:italic"># the virtual key from step 2</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx wrangler secret put LITELLM_URL        </span><span class="token comment" style="color:#999988;font-style:italic"># https://llm.example.com</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npx wrangler secret put SITE_ORIGIN        </span><span class="token comment" style="color:#999988;font-style:italic"># https://docs.example.com</span><br></div></code></pre></div></div>
<p>The origin check stops other websites from using your gateway from their pages. It doesn't stop a script that fakes the header: the budget and the rate limit on the key do.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-4-point-your-site-at-it">Step 4: point your site at it<a href="https://leuria.dev/tutorials/litellm-docs-fallback#step-4-point-your-site-at-it" class="hash-link" aria-label="Direct link to Step 4: point your site at it" title="Direct link to Step 4: point your site at it" translate="no">​</a></h2>
<p><strong>Docusaurus</strong>, with the <a class="" href="https://leuria.dev/tutorials/docusaurus-ask-ai">Ask AI plugin</a>:</p>
<div class="language-js codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_yl8C">docusaurus.config.js</div><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-js codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token literal-property property" style="color:#36acaa">plugins</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"@leuria/docusaurus"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">suggestions</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"How do I get started?"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token literal-property property" style="color:#36acaa">server</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://docs-ai.&lt;you&gt;.workers.dev"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">model</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"docs-fallback"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token literal-property property" style="color:#36acaa">label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Acme's AI"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><br></div></code></pre></div></div>
<p><strong>Any other site</strong>, with the SDK:</p>
<div class="language-ts codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-ts codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> createAI</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> leuria</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> promptAPI</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> server </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@leuria/client"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> ai </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createAI</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  providers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">leuria</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> app</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Acme docs"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">                       </span><span class="token comment" style="color:#999988;font-style:italic">// the visitor's own AI first</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">promptAPI</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">                                         </span><span class="token comment" style="color:#999988;font-style:italic">// the browser's built-in model</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">server</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://docs-ai.&lt;you&gt;.workers.dev"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> model</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"docs-fallback"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Acme's AI"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Set <code>model</code>: LiteLLM doesn't pick one for you. (<code>@leuria/docusaurus</code> sends it from 0.1.2.)</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-5-try-it">Step 5: try it<a href="https://leuria.dev/tutorials/litellm-docs-fallback#step-5-try-it" class="hash-link" aria-label="Direct link to Step 5: try it" title="Direct link to Step 5: try it" translate="no">​</a></h2>
<ol>
<li class="">Open your site in a browser without Leuria.</li>
<li class="">Click <strong>Ask AI</strong>. The panel proposes Leuria first.</li>
<li class="">Pick <strong>Acme's AI</strong> below. Ask a question.</li>
</ol>
<p>Each request shows up in LiteLLM's spend logs under the <code>docs-fallback</code> key.</p>
<p>To make your gateway answer at once for readers without Leuria, instead of after they pick it, set <code>fallback: "auto"</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="options-worth-knowing">Options worth knowing<a href="https://leuria.dev/tutorials/litellm-docs-fallback#options-worth-knowing" class="hash-link" aria-label="Direct link to Options worth knowing" title="Direct link to Options worth knowing" translate="no">​</a></h2>
<ul>
<li class=""><strong>Tools.</strong> The docs tools (<code>search_docs</code>, <code>read_page</code>) still run in the page: your gateway only relays the model's tool calls. Choose a model that calls tools well. If yours can't, set <code>tools: false</code>.</li>
<li class=""><strong>Structured output.</strong> If your model supports <code>response_format: { type: "json_schema" }</code>, set <code>jsonSchema: true</code>.</li>
<li class=""><strong>Images.</strong> Set <code>images: true</code> if the model reads images.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="the-same-with-vllm-ollama-or-omlx-on-your-server">The same with vLLM, Ollama or oMLX on your server<a href="https://leuria.dev/tutorials/litellm-docs-fallback#the-same-with-vllm-ollama-or-omlx-on-your-server" class="hash-link" aria-label="Direct link to The same with vLLM, Ollama or oMLX on your server" title="Direct link to The same with vLLM, Ollama or oMLX on your server" translate="no">​</a></h2>
<p>Anything that speaks the OpenAI Chat Completions API with streaming works behind the proxy:</p>
<ul>
<li class=""><strong>vLLM</strong>: <code>vllm serve &lt;model&gt; --served-model-name docs-fallback --enable-auto-tool-choice --tool-call-parser &lt;parser&gt;</code>, then add it to LiteLLM's <code>model_list</code> with <code>model: hosted_vllm/docs-fallback</code> and <code>api_base: http://&lt;vllm-host&gt;:8000/v1</code>.</li>
<li class=""><strong>Ollama or oMLX</strong> on a server: same, with their <code>/v1</code> address. Keep them behind the proxy too: neither should face the internet without a key.</li>
</ul>
<p>Going through LiteLLM keeps the budget, the rate limit and the spend logs in one place.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="faq">FAQ<a href="https://leuria.dev/tutorials/litellm-docs-fallback#faq" class="hash-link" aria-label="Direct link to FAQ" title="Direct link to FAQ" translate="no">​</a></h2>
<p><strong>Why not call LiteLLM straight from the page with the virtual key?</strong>
The key would be in your site's JavaScript, for anyone to copy and use anywhere. The proxy keeps it on the server.</p>
<p><strong>Does my gateway see questions from visitors who use their own AI?</strong>
No. Those go from the page to Leuria on the visitor's computer, then to their AI.</p>
<p><strong>What happens when the budget runs out?</strong>
The gateway refuses. Readers can still connect their own AI or use the browser's model.</p>
<p><strong>Can I use OpenRouter instead of LiteLLM?</strong>
Yes. The proxy is the same: put your OpenRouter key in the Worker and pick the model there.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="next">Next<a href="https://leuria.dev/tutorials/litellm-docs-fallback#next" class="hash-link" aria-label="Direct link to Next" title="Direct link to Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://leuria.dev/tutorials/ollama-web-app">Use Ollama in a web app, without CORS or API keys</a>: the visitor's side.</li>
<li class=""><a class="" href="https://leuria.dev/docs/developers/production">Going to production</a>: limits, privacy, Content-Security-Policy.</li>
</ul>
<p><em>Written for <code>@leuria/docusaurus</code> 0.1.2, <code>@leuria/client</code> 0.1.4 and LiteLLM's <code>latest</code> image.</em></p>]]></content:encoded>
            <category>LiteLLM</category>
            <category>vLLM</category>
            <category>AI gateway</category>
            <category>Docusaurus</category>
        </item>
        <item>
            <title><![CDATA[Use LM Studio models on any website, without CORS]]></title>
            <link>https://leuria.dev/tutorials/lm-studio-websites</link>
            <guid>https://leuria.dev/tutorials/lm-studio-websites</guid>
            <pubDate>Tue, 06 Oct 2026 13:00:00 GMT</pubDate>
            <description><![CDATA[Let websites use the model in LM Studio on the visitor's computer, with their consent: no Enable CORS switch, no API key, no cost to the site.]]></description>
            <content:encoded><![CDATA[<p>LM Studio users have capable models loaded on their own machine. With Leuria, those models can answer on your website, with your page's tools, at no cost to you. The visitor allows your site once. Nobody turns on CORS or exposes LM Studio's server.</p>
<!-- -->
<p><strong>In short:</strong> add Leuria to your site. A visitor with LM Studio starts its local server, installs the Leuria app and picks a model. Your AI features then run on that model, on their computer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="why-not-call-lm-studio-from-the-page">Why not call LM Studio from the page?<a href="https://leuria.dev/tutorials/lm-studio-websites#why-not-call-lm-studio-from-the-page" class="hash-link" aria-label="Direct link to Why not call LM Studio from the page?" title="Direct link to Why not call LM Studio from the page?" translate="no">​</a></h2>
<p>You could turn on <strong>Enable CORS</strong> in LM Studio and <code>fetch("http://localhost:1234/v1/chat/completions")</code> from your page. It works on your machine. For real visitors:</p>
<ul>
<li class="">each one has to find that switch, and then <strong>every</strong> page they open can use their model, without asking;</li>
<li class="">visitors who use Ollama, ChatGPT or Claude get nothing.</li>
</ul>
<p>With Leuria, the browser never calls LM Studio. The Leuria app does, from the same computer, for the sites the visitor allowed, with only the tools your page offers. LM Studio's settings stay as they are.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="part-1-for-site-builders">Part 1, for site builders<a href="https://leuria.dev/tutorials/lm-studio-websites#part-1-for-site-builders" class="hash-link" aria-label="Direct link to Part 1, for site builders" title="Direct link to Part 1, for site builders" translate="no">​</a></h2>
<p>Add Leuria to your page. With a bundler:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">npm</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> @leuria/client @leuria/connect</span><br></div></code></pre></div></div>
<div class="language-ts codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-ts codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> createAI</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> leuria</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> promptAPI</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> defineTool </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@leuria/client"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> setDefaultClient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@leuria/connect"</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic">// defines &lt;leuria-connect-button&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> ai </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createAI</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  providers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">leuria</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> app</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Recipe box"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> needs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> tools</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> effort</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"light"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">promptAPI</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">setDefaultClient</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ai</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> findRecipes </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">defineTool</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">{</span><span class="token generic-function generic class-name"> ingredient</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">string</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name punctuation" style="color:#393A34">}</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"find_recipes"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  description</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Find recipes that use an ingredient. Returns titles and links."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  inputSchema</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"object"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> properties</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> ingredient</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"string"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> required</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"ingredient"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">execute</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> ingredient </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> recipes</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">filter</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">r</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> r</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ingredients</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ingredient</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> answer </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">chat</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> prompt</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"What can I cook with leeks?"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> tools</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">findRecipes</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">text</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Put <code>&lt;leuria-connect-button&gt;&lt;/leuria-connect-button&gt;</code> in your page. Without a bundler, the <a class="" href="https://leuria.dev/tutorials/ollama-web-app#part-1-for-site-builders-add-leuria-to-your-page">Ollama tutorial</a> has a one-file version from a CDN.</p>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="say-what-your-feature-needs">Say what your feature needs<a href="https://leuria.dev/tutorials/lm-studio-websites#say-what-your-feature-needs" class="hash-link" aria-label="Direct link to Say what your feature needs" title="Direct link to Say what your feature needs" translate="no">​</a></h3>
<p><code>needs</code> matters more with LM Studio than anywhere else. Leuria reads what LM Studio reports about each model (loaded or not, trained for tool use, reads images, context length, size) and recommends the smallest one that fits your site. A visitor with a 70B model and a 4B model loaded gets the 4B one recommended for a light task, and their machine stays responsive.</p>
<ul>
<li class=""><code>tools: true</code> if your feature uses tools.</li>
<li class=""><code>effort</code>: <code>light</code> (answer, extract, summarize), <code>standard</code> (several steps with tools), <code>deep</code> (long reasoning).</li>
</ul>
<p>See <a class="" href="https://leuria.dev/docs/developers/guides/providers#declare-what-your-features-need">Declare what your features need</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="part-2-for-lm-studio-users">Part 2, for LM Studio users<a href="https://leuria.dev/tutorials/lm-studio-websites#part-2-for-lm-studio-users" class="hash-link" aria-label="Direct link to Part 2, for LM Studio users" title="Direct link to Part 2, for LM Studio users" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="1-start-lm-studios-local-server">1. Start LM Studio's local server<a href="https://leuria.dev/tutorials/lm-studio-websites#1-start-lm-studios-local-server" class="hash-link" aria-label="Direct link to 1. Start LM Studio's local server" title="Direct link to 1. Start LM Studio's local server" translate="no">​</a></h3>
<p>In LM Studio, open the <strong>Developer</strong> tab and start the server. Or from a terminal:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">lms server start</span><br></div></code></pre></div></div>
<p>Leave the port at 1234. Download a chat model if you have none yet: for sites with tools, one trained for tool use (LM Studio marks them).</p>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="2-install-leuria">2. Install Leuria<a href="https://leuria.dev/tutorials/lm-studio-websites#2-install-leuria" class="hash-link" aria-label="Direct link to 2. Install Leuria" title="Direct link to 2. Install Leuria" translate="no">​</a></h3>
<p>From <a href="https://leuria.eu/download" target="_blank" rel="noopener noreferrer" class="">leuria.eu/download</a> (Mac and Windows), or with Homebrew:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">brew tap leur-ia/leuria https://github.com/leur-ia/leuria</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">brew </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--cask</span><span class="token plain"> leuria</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="3-choose-your-lm-studio-model">3. Choose your LM Studio model<a href="https://leuria.dev/tutorials/lm-studio-websites#3-choose-your-lm-studio-model" class="hash-link" aria-label="Direct link to 3. Choose your LM Studio model" title="Direct link to 3. Choose your LM Studio model" translate="no">​</a></h3>
<p>Open Leuria, <strong>Add an AI</strong>, <strong>A model on this computer</strong>. Leuria lists your LM Studio models and suggests one that is already loaded. Pick one. When a site asks, Leuria shows which model it recommends for that site, and why.</p>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="4-allow-a-site">4. Allow a site<a href="https://leuria.dev/tutorials/lm-studio-websites#4-allow-a-site" class="hash-link" aria-label="Direct link to 4. Allow a site" title="Direct link to 4. Allow a site" translate="no">​</a></h3>
<p>On a site that uses Leuria (try <a href="https://demo.leuria.dev/" target="_blank" rel="noopener noreferrer" class="">demo.leuria.dev</a>), click <strong>Connect your AI</strong> and allow it in Leuria's window.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="search-by-meaning-with-an-lm-studio-embedding-model">Search by meaning with an LM Studio embedding model<a href="https://leuria.dev/tutorials/lm-studio-websites#search-by-meaning-with-an-lm-studio-embedding-model" class="hash-link" aria-label="Direct link to Search by meaning with an LM Studio embedding model" title="Direct link to Search by meaning with an LM Studio embedding model" translate="no">​</a></h2>
<p>Download an embedding model in LM Studio (for example a <code>nomic-embed-text</code> build). Leuria finds it, and sites that search by meaning use it on your computer. Embeddings only ever use a model on your computer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="troubleshooting">Troubleshooting<a href="https://leuria.dev/tutorials/lm-studio-websites#troubleshooting" class="hash-link" aria-label="Direct link to Troubleshooting" title="Direct link to Troubleshooting" translate="no">​</a></h2>
<p><strong>Leuria says "No model found on this computer".</strong>
LM Studio's server isn't running. Start it in the Developer tab or with <code>lms server start</code>, then click <strong>Look again</strong>.</p>
<p><strong>Do I need to turn on "Enable CORS" or "Serve on local network"?</strong>
No. Leuria calls LM Studio from the same computer, at <code>127.0.0.1:1234</code>.</p>
<p><strong>LM Studio runs on another port or another machine.</strong>
Add it as a service: <strong>Add an AI</strong> → <strong>An AI service with a key</strong> → <strong>Another service</strong>, with its address (<code>http://&lt;host&gt;:&lt;port&gt;/v1</code>).</p>
<p><strong>The first answer is slow.</strong>
The model is loading. Leuria prefers models that are already loaded: load the one you use most in LM Studio.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="faq">FAQ<a href="https://leuria.dev/tutorials/lm-studio-websites#faq" class="hash-link" aria-label="Direct link to FAQ" title="Direct link to FAQ" translate="no">​</a></h2>
<p><strong>Does anything leave my computer?</strong>
No. The page sends the question to Leuria, Leuria to LM Studio, both on your computer.</p>
<p><strong>Can sites use my model without asking?</strong>
No. Each site must ask, and you allow it in Leuria's own window. You can disconnect it at any time.</p>
<p><strong>Can I use a different model per site?</strong>
Yes. In Leuria's Websites tab, change the AI for one site.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="next">Next<a href="https://leuria.dev/tutorials/lm-studio-websites#next" class="hash-link" aria-label="Direct link to Next" title="Direct link to Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://leuria.dev/tutorials/ollama-web-app">Use Ollama in a web app, without CORS or API keys</a>.</li>
<li class=""><a class="" href="https://leuria.dev/tutorials/omlx-apple-silicon">Run oMLX models on websites, on Apple Silicon</a>.</li>
<li class=""><a class="" href="https://leuria.dev/docs/developers/guides/tools">Page tools</a>.</li>
</ul>
<p><em>Written for Leuria 0.1.4 and <code>@leuria/client</code> 0.1.4.</em></p>]]></content:encoded>
            <category>LM Studio</category>
            <category>Local AI</category>
            <category>Page tools</category>
        </item>
        <item>
            <title><![CDATA[Run oMLX models on websites, on Apple Silicon]]></title>
            <link>https://leuria.dev/tutorials/omlx-apple-silicon</link>
            <guid>https://leuria.dev/tutorials/omlx-apple-silicon</guid>
            <pubDate>Tue, 06 Oct 2026 12:00:00 GMT</pubDate>
            <description><![CDATA[Use oMLX, the MLX model server for Apple Silicon, to answer on the websites you allow, on your Mac. Plus oMLX as a site's own fallback AI.]]></description>
            <content:encoded><![CDATA[<p><a href="https://github.com/jundot/omlx" target="_blank" rel="noopener noreferrer" class="">oMLX</a> serves MLX models on Apple Silicon through an OpenAI-compatible API, from a menu bar app. Add it to Leuria once, and the websites you allow can use your oMLX model for their AI features, on your Mac.</p>
<!-- -->
<p><strong>In short:</strong> start oMLX (it listens on <code>http://localhost:8000/v1</code>), then in Leuria add it as a service with that address. Sites you allow then answer with your MLX model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="why-mlx-for-this">Why MLX for this<a href="https://leuria.dev/tutorials/omlx-apple-silicon#why-mlx-for-this" class="hash-link" aria-label="Direct link to Why MLX for this" title="Direct link to Why MLX for this" translate="no">​</a></h2>
<p>MLX runs models on the Mac's unified memory and GPU, and is often the fastest way to run a model on an M-series Mac. oMLX adds what a website's AI features need: streaming, tool calling, and continuous batching, so two sites can ask at once.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="part-1-start-omlx">Part 1: start oMLX<a href="https://leuria.dev/tutorials/omlx-apple-silicon#part-1-start-omlx" class="hash-link" aria-label="Direct link to Part 1: start oMLX" title="Direct link to Part 1: start oMLX" translate="no">​</a></h2>
<p>Install the app from <a href="https://github.com/jundot/omlx/releases" target="_blank" rel="noopener noreferrer" class="">oMLX's releases</a>, or with Homebrew:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">brew tap jundot/omlx https://github.com/jundot/omlx</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">brew </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> jundot/omlx/omlx</span><br></div></code></pre></div></div>
<p>Start the server with a folder of MLX models:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">omlx serve --model-dir ~/models</span><br></div></code></pre></div></div>
<p>Or start it from the menu bar app. Download a model from its dashboard (<code>http://localhost:8000/admin</code>). For sites with tools, pick one trained for tool calling, such as a Qwen 3 MLX build.</p>
<p>Check that it answers:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> http://localhost:8000/v1/models</span><br></div></code></pre></div></div>
<p>oMLX needs macOS 15 or later on Apple Silicon.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="part-2-add-omlx-to-leuria">Part 2: add oMLX to Leuria<a href="https://leuria.dev/tutorials/omlx-apple-silicon#part-2-add-omlx-to-leuria" class="hash-link" aria-label="Direct link to Part 2: add oMLX to Leuria" title="Direct link to Part 2: add oMLX to Leuria" translate="no">​</a></h2>
<p>Install Leuria from <a href="https://leuria.eu/download" target="_blank" rel="noopener noreferrer" class="">leuria.eu/download</a>, or:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">brew tap leur-ia/leuria https://github.com/leur-ia/leuria</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">brew </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--cask</span><span class="token plain"> leuria</span><br></div></code></pre></div></div>
<p>Then open Leuria:</p>
<ol>
<li class=""><strong>Add an AI</strong> → <strong>An AI service with a key</strong>.</li>
<li class=""><strong>Service</strong>: <strong>Another service (your company's AI…)</strong>.</li>
<li class=""><strong>Name</strong>: <code>oMLX</code>. <strong>Address</strong>: <code>http://localhost:8000/v1</code>. Leave <strong>API key</strong> empty, unless you set one in oMLX.</li>
<li class=""><strong>Connect this service</strong>, then pick a model.</li>
</ol>
<p>From a terminal instead:</p>
<div class="language-sh codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-sh codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token plain">npx @leuria/cli providers </span><span class="token function" style="color:#d73a49">add</span><span class="token plain"> oMLX http://localhost:8000/v1</span><br></div></code></pre></div></div>
<p>Leuria lists oMLX with your AI services. You can make it your default, or use it for some sites only.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="part-3-allow-a-site">Part 3: allow a site<a href="https://leuria.dev/tutorials/omlx-apple-silicon#part-3-allow-a-site" class="hash-link" aria-label="Direct link to Part 3: allow a site" title="Direct link to Part 3: allow a site" translate="no">​</a></h2>
<p>On a site that uses Leuria (try <a href="https://demo.leuria.dev/" target="_blank" rel="noopener noreferrer" class="">demo.leuria.dev</a>), click <strong>Connect your AI</strong> and allow it. Leuria asks oMLX, on your Mac. Nothing leaves it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="for-site-builders-omlx-as-your-sites-fallback">For site builders: oMLX as your site's fallback<a href="https://leuria.dev/tutorials/omlx-apple-silicon#for-site-builders-omlx-as-your-sites-fallback" class="hash-link" aria-label="Direct link to For site builders: oMLX as your site's fallback" title="Direct link to For site builders: oMLX as your site's fallback" translate="no">​</a></h2>
<p>A Mac mini running oMLX can also be your site's own AI, for visitors who have none. Put it behind the proxy from the <a class="" href="https://leuria.dev/tutorials/litellm-docs-fallback">LiteLLM tutorial</a>, never straight on the internet: the proxy holds the key, allows only your site and caps usage. oMLX itself refuses to listen beyond your Mac without an API key.</p>
<div class="language-ts codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-ts codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">server</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://docs-ai.&lt;you&gt;.workers.dev"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> model</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;your oMLX model id&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> label</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Acme's AI"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Set <code>model</code> to the id <code>curl http://localhost:8000/v1/models</code> returns.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="troubleshooting">Troubleshooting<a href="https://leuria.dev/tutorials/omlx-apple-silicon#troubleshooting" class="hash-link" aria-label="Direct link to Troubleshooting" title="Direct link to Troubleshooting" translate="no">​</a></h2>
<p><strong>"Connected, but this service lists no chat model."</strong>
oMLX found no model in its folder. Download one from its dashboard, or check <code>--model-dir</code>.</p>
<p><strong>Leuria can't reach oMLX.</strong>
Check that <code>curl http://localhost:8000/v1/models</code> answers. If you started oMLX on another port, use that port in the address.</p>
<p><strong>Search by meaning doesn't use my oMLX embedding model.</strong>
Leuria uses embedding models from LM Studio or Ollama. Sites that search by meaning still work by words, or with their own in-page model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="faq">FAQ<a href="https://leuria.dev/tutorials/omlx-apple-silicon#faq" class="hash-link" aria-label="Direct link to FAQ" title="Direct link to FAQ" translate="no">​</a></h2>
<p><strong>Is oMLX better than Ollama or LM Studio for this?</strong>
Use the one you already have. Leuria works with all three, and you can switch per site.</p>
<p><strong>Does it work on Intel Macs?</strong>
No: MLX needs Apple Silicon. Use Ollama or LM Studio there.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="next">Next<a href="https://leuria.dev/tutorials/omlx-apple-silicon#next" class="hash-link" aria-label="Direct link to Next" title="Direct link to Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://leuria.dev/tutorials/lm-studio-websites">Use LM Studio models on any website</a>.</li>
<li class=""><a class="" href="https://leuria.dev/tutorials/tools-webmcp-skills">Page tools, WebMCP and skills</a>: what your site can give the visitor's AI.</li>
</ul>
<p><em>Written for Leuria 0.1.4.</em></p>]]></content:encoded>
            <category>oMLX</category>
            <category>Apple Silicon</category>
            <category>Local AI</category>
        </item>
        <item>
            <title><![CDATA[Page tools, WebMCP and agent skills for every AI]]></title>
            <link>https://leuria.dev/tutorials/tools-webmcp-skills</link>
            <guid>https://leuria.dev/tutorials/tools-webmcp-skills</guid>
            <pubDate>Tue, 06 Oct 2026 11:00:00 GMT</pubDate>
            <description><![CDATA[Write page tools once: the visitor's own AI uses them through Leuria, the browser's agents through WebMCP. Then guide the AI with agent skills.]]></description>
            <content:encoded><![CDATA[<p>AI on a website is more than a chat box when the AI can act on the page: search the catalog, fill the form, open the right doc. This tutorial writes those actions once, as page tools, and offers them to two kinds of AI: the visitor's own AI through Leuria, and the agents built into the browser through WebMCP. Then it adds agent skills, so the AI knows how your site works.</p>
<!-- -->
<p><strong>In short:</strong> define tools with <code>defineTool</code>. Pass them to <code>ai.chat()</code> or a conversation for the visitor's AI, and to <code>exposeTools()</code> for the browser's agents. Add <code>skills</code> to <code>leuria()</code> to give the visitor's AI your site's know-how.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-1-define-a-tool">Step 1: define a tool<a href="https://leuria.dev/tutorials/tools-webmcp-skills#step-1-define-a-tool" class="hash-link" aria-label="Direct link to Step 1: define a tool" title="Direct link to Step 1: define a tool" translate="no">​</a></h2>
<p>A tool is a name, a description, a JSON Schema for its arguments, and a function that runs in the page:</p>
<div class="language-ts codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-ts codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> defineTool </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@leuria/client"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> searchProducts </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">defineTool</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">{</span><span class="token generic-function generic class-name"> query</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">string</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name punctuation" style="color:#393A34">}</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"search_products"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  description</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Search the catalog. Returns name, price in euros and stock."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  inputSchema</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"object"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> properties</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> query</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"string"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> required</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"query"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  annotations</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> readOnlyHint</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">execute</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> query </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> signal </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> catalog</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">search</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">query</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> signal </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> addToCart </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">defineTool</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">{</span><span class="token generic-function generic class-name"> sku</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">string</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">;</span><span class="token generic-function generic class-name"> quantity</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">number</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name punctuation" style="color:#393A34">}</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"add_to_cart"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  description</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Add a product to the visitor's cart."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  inputSchema</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"object"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    properties</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> sku</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"string"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> quantity</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"integer"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> minimum</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    required</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"sku"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"quantity"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  annotations</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> consequentialHint</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function-variable function" style="color:#d73a49">execute</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> sku</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> quantity </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> cart</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">add</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">sku</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> quantity</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>The AI never runs code of its own on your page. It asks for a tool call, your function runs with the page's rights, and the AI sees only what it returns. Validate arguments as you would a form.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-2-the-visitors-own-ai-uses-them">Step 2: the visitor's own AI uses them<a href="https://leuria.dev/tutorials/tools-webmcp-skills#step-2-the-visitors-own-ai-uses-them" class="hash-link" aria-label="Direct link to Step 2: the visitor's own AI uses them" title="Direct link to Step 2: the visitor's own AI uses them" translate="no">​</a></h2>
<div class="language-ts codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-ts codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> createAI</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> leuria</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> promptAPI </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@leuria/client"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> ai </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createAI</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  providers</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token function" style="color:#d73a49">leuria</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> app</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Kiln &amp; Co."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> needs</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> tools</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> effort</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"standard"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">promptAPI</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> chat </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ai</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">conversation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  system</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"You are the shop's assistant. Use the tools; never invent a product."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  tools</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">searchProducts</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> addToCart</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> chat</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">send</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Add two of your cheapest blue mugs to my cart"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">result</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>The visitor's AI (ChatGPT, Claude, an Ollama or LM Studio model) gets these tools and only these: through Leuria it runs with its own tools off, in an empty folder.</p>
<h3 class="anchor anchorTargetStickyNavbar_Yhiu" id="ask-before-anything-that-matters">Ask before anything that matters<a href="https://leuria.dev/tutorials/tools-webmcp-skills#ask-before-anything-that-matters" class="hash-link" aria-label="Direct link to Ask before anything that matters" title="Direct link to Ask before anything that matters" translate="no">​</a></h3>
<p>Leave out <code>execute</code>, and the call waits for your UI. The visitor confirms, then you answer the call:</p>
<div class="language-ts codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-ts codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> placeOrder </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token generic-function function" style="color:#d73a49">defineTool</span><span class="token generic-function generic class-name operator" style="color:#393A34">&lt;</span><span class="token generic-function generic class-name punctuation" style="color:#393A34">{</span><span class="token generic-function generic class-name"> total</span><span class="token generic-function generic class-name operator" style="color:#393A34">:</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name builtin">number</span><span class="token generic-function generic class-name"> </span><span class="token generic-function generic class-name punctuation" style="color:#393A34">}</span><span class="token generic-function generic class-name operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"place_order"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  description</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Place the order. The visitor confirms first."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  inputSchema</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"object"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> properties</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> total</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"number"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> required</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"total"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chat</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">subscribe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> input </span><span class="token keyword" style="color:#00009f">of</span><span class="token plain"> chat</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">getState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pendingInputs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">confirmDialog</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ok</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ok </span><span class="token operator" style="color:#393A34">?</span><span class="token plain"> chat</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">submitToolResult</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">callId</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> placed</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> chat</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">rejectToolCall</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">input</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">callId</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-3-the-browsers-agents-use-them-too-webmcp">Step 3: the browser's agents use them too (WebMCP)<a href="https://leuria.dev/tutorials/tools-webmcp-skills#step-3-the-browsers-agents-use-them-too-webmcp" class="hash-link" aria-label="Direct link to Step 3: the browser's agents use them too (WebMCP)" title="Direct link to Step 3: the browser's agents use them too (WebMCP)" translate="no">​</a></h2>
<p>WebMCP lets a page offer tools to the agent built into the browser. The same tool objects work there:</p>
<div class="language-ts codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-ts codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> exposeTools </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@leuria/client"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> takeBack </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">exposeTools</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">searchProducts</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> addToCart</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// later, to withdraw them: takeBack()</span><br></div></code></pre></div></div>
<ul>
<li class="">Where the browser has no WebMCP, nothing happens.</li>
<li class="">The <code>annotations</code> tell the browser's agent which tools only read (<code>readOnlyHint</code>), which do something that matters (<code>consequentialHint</code>), and which return text you don't control (<code>untrustedContentHint</code>).</li>
<li class="">Tools without <code>execute</code> are left out: they need your UI.</li>
<li class="">In React: <code>useExposedTools(tools)</code> from <code>@leuria/react</code>.</li>
</ul>
<p>To try it, Chrome ships WebMCP behind <code>chrome://flags/#enable-webmcp-testing</code>, and to sites through an origin trial from Chrome 149.</p>
<p>On Docusaurus, the <a class="" href="https://leuria.dev/tutorials/docusaurus-ask-ai">Ask AI plugin</a> already offers its docs tools through WebMCP (<code>webmcp: true</code> by default).</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="step-4-guide-the-ai-with-skills">Step 4: guide the AI with skills<a href="https://leuria.dev/tutorials/tools-webmcp-skills#step-4-guide-the-ai-with-skills" class="hash-link" aria-label="Direct link to Step 4: guide the AI with skills" title="Direct link to Step 4: guide the AI with skills" translate="no">​</a></h2>
<p>Tools say what the AI can do. Skills say how your site works: how returns go, what to ask first, which tool to call when. A skill is a folder with a <code>SKILL.md</code>, in the <a href="https://agentskills.io/" target="_blank" rel="noopener noreferrer" class="">Agent Skills</a> format:</p>
<div class="language-markdown codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_yl8C">static/.well-known/agent-skills/returns/SKILL.md</div><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-markdown codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token front-matter-block punctuation" style="color:#393A34">---</span><span class="token front-matter-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token front-matter-block"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">name</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> returns</span><br></div><div class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">description</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> </span><span class="token front-matter-block front-matter yaml language-yaml string" style="color:#e3116c">"How to prepare a return request, from the order number to the label."</span><span class="token front-matter-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token front-matter-block"></span><span class="token front-matter-block punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Ask for the order number, then call </span><span class="token code-snippet code keyword" style="color:#00009f">`find_order`</span><span class="token plain">. Returns are free within 30 days.</span><br></div></code></pre></div></div>
<p>Then list your skills in <code>.well-known/agent-skills/index.json</code> (see the <a href="https://agentskills.io/" target="_blank" rel="noopener noreferrer" class="">discovery index</a>), and pass them to <code>leuria()</code>:</p>
<div class="language-ts codeBlockContainer_lHVw theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_Ab13"><pre tabindex="0" class="prism-code language-ts codeBlock_OvnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_NTO7"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">leuria</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  app</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Kiln &amp; Co."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  skills</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"/"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">                                                       </span><span class="token comment" style="color:#999988;font-style:italic">// your own, from /.well-known/agent-skills</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"vercel-labs/agent-skills@web-design-guidelines#9f2c1e4"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// a shared skill, pinned to a commit</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>On Docusaurus: <code>["@leuria/docusaurus", { skills: ["/"] }]</code>.</p>
<p>What happens next:</p>
<ul>
<li class="">When your site asks to connect, Leuria fetches the skills and shows them to the visitor ("This site uses 2 skills to guide your AI", with <strong>See details</strong>).</li>
<li class="">The AI gets each skill's name and description, and a <code>read_skill</code> tool to read the full instructions when a request matches. Long skills cost nothing until they're needed.</li>
<li class="">Pin shared skills to a commit (<code>#&lt;sha&gt;</code>): a branch can change under you.</li>
</ul>
<p>Skills only reach the visitor's own AI, through Leuria. For the browser's model and your server, put what they need in <code>system</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="which-ai-gets-what">Which AI gets what<a href="https://leuria.dev/tutorials/tools-webmcp-skills#which-ai-gets-what" class="hash-link" aria-label="Direct link to Which AI gets what" title="Direct link to Which AI gets what" translate="no">​</a></h2>








































<table><thead><tr><th></th><th>Visitor's AI (Leuria)</th><th>Browser's agent (WebMCP)</th><th>Browser's model</th><th>Your server</th></tr></thead><tbody><tr><td>Page tools</td><td>yes</td><td>yes, through <code>exposeTools</code></td><td>with <code>promptAPI({ tools: true })</code></td><td>yes</td></tr><tr><td>Tools the visitor answers</td><td>yes</td><td>no</td><td>no</td><td>yes</td></tr><tr><td>Skills</td><td>yes</td><td>no</td><td>no</td><td>no</td></tr><tr><td>Cost to you</td><td>none</td><td>none</td><td>none</td><td>per request</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="faq">FAQ<a href="https://leuria.dev/tutorials/tools-webmcp-skills#faq" class="hash-link" aria-label="Direct link to FAQ" title="Direct link to FAQ" translate="no">​</a></h2>
<p><strong>What's the difference between WebMCP and MCP?</strong>
MCP connects an AI to tools on a server. WebMCP lets a web page offer tools to an AI in the browser, running in the page with the visitor's session.</p>
<p><strong>Can the AI call my tools with any arguments?</strong>
Yes. Treat arguments as untrusted input. Take the account or the page from the turn's context (<code>send(text, { context })</code>), never from the model's arguments.</p>
<p><strong>Do skills give the AI new permissions?</strong>
No. Skills are instructions. The AI can still only call your page's tools.</p>
<h2 class="anchor anchorTargetStickyNavbar_Yhiu" id="next">Next<a href="https://leuria.dev/tutorials/tools-webmcp-skills#next" class="hash-link" aria-label="Direct link to Next" title="Direct link to Next" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://leuria.dev/docs/developers/guides/tools">Page tools</a>: middleware, terminal tools, limits.</li>
<li class=""><a class="" href="https://leuria.dev/docs/developers/guides/skills">Skills</a>: refs, the discovery index, caching.</li>
<li class=""><a class="" href="https://leuria.dev/tool-builder">Tool builder</a>: write a tool's schema in the browser.</li>
</ul>
<p><em>Written for <code>@leuria/client</code> 0.1.4.</em></p>]]></content:encoded>
            <category>WebMCP</category>
            <category>Page tools</category>
            <category>Agent skills</category>
        </item>
    </channel>
</rss>