<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mawyxx</title>
    <description>The latest articles on DEV Community by Mawyxx (@mawyxx).</description>
    <link>https://dev.to/mawyxx</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008380%2Ffa11b19b-d5cd-4bc1-ad9b-b1d364b04755.png</url>
      <title>DEV Community: Mawyxx</title>
      <link>https://dev.to/mawyxx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mawyxx"/>
    <language>en</language>
    <item>
      <title>Zero Human Auth for AI Agents: How to Replace OAuth with Cryptographic Passport</title>
      <dc:creator>Mawyxx</dc:creator>
      <pubDate>Fri, 03 Jul 2026 19:11:44 +0000</pubDate>
      <link>https://dev.to/mawyxx/zero-human-auth-for-ai-agents-how-to-replace-oauth-with-cryptographic-passport-45k7</link>
      <guid>https://dev.to/mawyxx/zero-human-auth-for-ai-agents-how-to-replace-oauth-with-cryptographic-passport-45k7</guid>
      <description>&lt;p&gt;Your AI agent needs to log into a customer's dashboard at 3 a.m. You cannot ask a human to click &lt;strong&gt;Allow&lt;/strong&gt;. The agent has no browser. It cannot complete an OAuth redirect.&lt;/p&gt;

&lt;p&gt;So you pick one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store an immortal API key in the worker (and hope it never leaks)&lt;/li&gt;
&lt;li&gt;Run headless Chrome with Playwright (and fix it every time the login UI changes)&lt;/li&gt;
&lt;li&gt;Use Device Authorization Grant (and wait for someone to scan a QR code)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are duct tape. They do not give you &lt;strong&gt;AI agent identity&lt;/strong&gt; — a signed, auditable proof of &lt;em&gt;which agent&lt;/em&gt; logged in, bound to a specific site session, with a separate lane for &lt;strong&gt;MCP OAuth&lt;/strong&gt; on external tool servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero Human Auth&lt;/strong&gt; means the human is not in the login path. It does not mean "no authentication."&lt;/p&gt;




&lt;h2&gt;
  
  
  How developers solve this today (and why it breaks)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern A: static API key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# agent_worker.py — what many teams ship first
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CUSTOMER_DASHBOARD_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# rot? lol
&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_orders&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://customer.example/api/orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works until: key rotation, per-customer scoping, audit ("which agent did this?"), or a compromised worker exfiltrating god-mode access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern B: Playwright + OAuth
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# brittle_login.py — do not ship this to prod
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.async_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;async_playwright&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;oauth_as_robot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;async_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headless&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://customer.example/oauth/authorize?...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;button[type=submit]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# breaks on: MFA, CAPTCHA, CSS rename, A/B test, ToS
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/callback*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# ... scrape cookies or localStorage ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;High maintenance. Often against the customer's terms. Impossible to reason about in CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern C: after — one LIME call
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;lime_agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LimeAgent&lt;/span&gt;

&lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lr_from_your_queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# from site.create_login_request()
&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;approve_login&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;LimeAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIME_AGENT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# APPROVED after successful approve (site receives JWT separately via SSE)
&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;approve_login&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The site backend receives the &lt;strong&gt;cryptographic passport&lt;/strong&gt; over SSE and verifies it with &lt;strong&gt;JWKS&lt;/strong&gt;. The agent never holds the user's session JWT.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why classic OAuth breaks for headless agents
&lt;/h2&gt;

&lt;p&gt;OAuth 2.0 was designed around &lt;strong&gt;human consent&lt;/strong&gt; in a browser:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Redirect user to IdP&lt;/li&gt;
&lt;li&gt;User authenticates&lt;/li&gt;
&lt;li&gt;User approves scopes&lt;/li&gt;
&lt;li&gt;Authorization code returns to the client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Headless agents fail at steps 1 and 3. Workarounds hurt:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workaround&lt;/th&gt;
&lt;th&gt;Why it fails at scale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Device Authorization Grant&lt;/td&gt;
&lt;td&gt;Still needs a human on another device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client Credentials only&lt;/td&gt;
&lt;td&gt;No per-login binding to a user session on &lt;em&gt;your&lt;/em&gt; site&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headless browser automation&lt;/td&gt;
&lt;td&gt;Breaks on MFA, CAPTCHA, UI changes; hard to audit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static API keys&lt;/td&gt;
&lt;td&gt;No short-lived, signed proof tied to a login request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For &lt;strong&gt;MCP&lt;/strong&gt; (Model Context Protocol) tool servers, the problem repeats: agents must call external &lt;strong&gt;MCP resource servers&lt;/strong&gt; with standard &lt;code&gt;Authorization: Bearer&lt;/code&gt; semantics — but you still should not mint long-lived secrets inside every agent process.&lt;/p&gt;

&lt;p&gt;You need two lanes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Site login&lt;/strong&gt; — agent proves identity; &lt;strong&gt;your backend&lt;/strong&gt; receives a verifiable session artifact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP OAuth&lt;/strong&gt; — short-lived JWT for external tool servers, separate from site session&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://lime.pics" rel="noopener noreferrer"&gt;LIME&lt;/a&gt; implements both with &lt;strong&gt;cryptographic passports&lt;/strong&gt; (RS256 JWTs) and official Python SDKs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The fix: cryptographic passport + one agent call
&lt;/h2&gt;

&lt;p&gt;Instead of redirecting a browser, LIME runs a &lt;strong&gt;headless login protocol&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Site backend                LIME Core                 Agent worker
     |                          |                          |
     |-- create login request -&amp;gt;|                          |
     |&amp;lt;- request_id ------------|                          |
     |                          |                          |
     |--- request_id -------------------------------------&amp;gt;|
     |                          |&amp;lt;- PoW solve + approve ---|
     |                          |   (X-Agent-Token)        |
     |&amp;lt;- SSE: passport JWT -----|                          |
     |   (aud=lime-site-login)  |                          |
     |-- verify (JWKS cache) ---|                          |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographic passport&lt;/strong&gt; — RS256 JWT signed by LIME Core (&lt;code&gt;aud=lime-site-login&lt;/code&gt;, TTL ~60s); sites verify via &lt;strong&gt;JWKS verification&lt;/strong&gt; (&lt;code&gt;GET /api/v1/core/.well-known/jwks.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Human Auth&lt;/strong&gt; — agent calls &lt;code&gt;login(request_id)&lt;/code&gt; once; no OAuth redirect, no QR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proof-of-Work&lt;/strong&gt; — SHA-256 PoW challenge instead of CAPTCHA (bots pay compute, humans do not)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passport goes to the site, not the agent&lt;/strong&gt; — even if the worker is compromised, it never holds the user's site session JWT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not "replace all OAuth everywhere." It is &lt;strong&gt;replace browser OAuth for autonomous agents&lt;/strong&gt; on integrations you control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Python: secure login for AI agents (site flow)
&lt;/h2&gt;

&lt;p&gt;Install the SDKs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;lime-agents-sdk lime-sites-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set secrets &lt;strong&gt;server-side only&lt;/strong&gt; (never in prompts, never in frontend JS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LIME_SITE_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"st_..."&lt;/span&gt;    &lt;span class="c"&gt;# site backend&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LIME_AGENT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"at_..."&lt;/span&gt;   &lt;span class="c"&gt;# agent worker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Site backend — create request, receive passport over SSE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asynccontextmanager&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;lime_sites&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InvalidPassportError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LimeSite&lt;/span&gt;

&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LimeSite&lt;/span&gt;


&lt;span class="nd"&gt;@asynccontextmanager&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lifespan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;
    &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LimeSite&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# reads LIME_SITE_TOKEN
&lt;/span&gt;
    &lt;span class="nd"&gt;@site.on_login&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;passport&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="c1"&gt;# expired
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;verified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify_passport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;passport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;expected_request_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;InvalidPassportError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="c1"&gt;# Bind verified.claims["agent_id"] to your user session
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent logged in:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aclose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lifespan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lifespan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/login/start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_login&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_login_request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;verify_passport()&lt;/code&gt; checks signature (JWKS), &lt;code&gt;aud == "lime-site-login"&lt;/code&gt;, expiry (platform TTL ≤120s), optional &lt;code&gt;request_id&lt;/code&gt; binding, and exposes claims such as &lt;code&gt;agent_id&lt;/code&gt;, &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;agent_reputation&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent worker — one call, PoW included
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;lime_agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApiError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LimeAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PowTimeoutError&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lr_from_your_queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# from site / job orchestrator
&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;LimeAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIME_AGENT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# APPROVED after successful approve (site receives JWT separately via SSE)
&lt;/span&gt;            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;approved_agent_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;PowTimeoutError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PoW timeout — retry or increase pow_timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ApiError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;login()&lt;/code&gt; fetches the PoW challenge, solves it in a thread pool, and approves with &lt;code&gt;X-Agent-Token&lt;/code&gt;. The &lt;strong&gt;cryptographic passport&lt;/strong&gt; is delivered to your site via SSE — not returned to the agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  MCP OAuth: tools without mixing credentials
&lt;/h2&gt;

&lt;p&gt;Agents also call &lt;strong&gt;external MCP resource servers&lt;/strong&gt;. That uses a &lt;strong&gt;different JWT&lt;/strong&gt; (&lt;code&gt;aud=mcp&lt;/code&gt;, ~5 minute TTL). Do not reuse the site passport for MCP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;lime_agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CallToolResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LimeAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;

&lt;span class="n"&gt;MCP_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://mcp.example.com/mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# streamable HTTP path
&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;LimeAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIME_AGENT_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MCP_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No tools at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MCP_ENDPOINT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CallToolResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MCP_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool error:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Credential lanes (swapping them breaks security):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lane&lt;/th&gt;
&lt;th&gt;Header&lt;/th&gt;
&lt;th&gt;Used for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LIME platform&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-Agent-Token&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;login()&lt;/code&gt;, &lt;code&gt;get_mcp_access_token()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External MCP RS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Authorization: Bearer &amp;lt;mcp_jwt&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;list_tools&lt;/code&gt;, &lt;code&gt;call_tool&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the MCP server side, verify Bearer tokens with &lt;a href="https://pypi.org/project/lime-mcp-server-sdk/" rel="noopener noreferrer"&gt;&lt;code&gt;lime-mcp-server-sdk&lt;/code&gt;&lt;/a&gt; — &lt;strong&gt;JWKS + RS256&lt;/strong&gt;, cached keys, async-friendly.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try it now.&lt;/strong&gt; If this matches your problem: install takes about two minutes.&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;lime-agents-sdk lime-sites-sdk
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Docs: &lt;a href="https://lime.pics/docs" rel="noopener noreferrer"&gt;https://lime.pics/docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How LIME compares to alternatives
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Agent-native?&lt;/th&gt;
&lt;th&gt;MCP OAuth?&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Roll-your-own API key&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright + OAuth&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Free (until it isn't)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent Passport (protocol + IETF draft)&lt;/td&gt;
&lt;td&gt;Very High (delegation chains, 7 constraints)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Freemium (OSS + enterprise)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth0 for AI Agents&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Freemium (free tier + paid plans)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LIME&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Low&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Free for identity&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;LIME is free for &lt;strong&gt;AI agent identity&lt;/strong&gt; and headless site login today. The business model is commission on future agent payments — similar to Stripe taking a cut on transactions, not charging you to issue passports.&lt;/p&gt;

&lt;p&gt;Agent Passport is a full protocol (IETF draft) with delegation chains and seven normative constraints — powerful, but heavy to implement. Auth0 offers a freemium path with paid plans at scale. LIME optimizes for a smaller surface: one Python call on the agent, SSE + &lt;strong&gt;JWKS verification&lt;/strong&gt; on the site, and &lt;strong&gt;MCP OAuth&lt;/strong&gt; JWTs for external resource servers out of the box.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security notes (read this before production)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this model gets right&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short-lived, signed artifacts instead of immortal API keys in agent memory&lt;/li&gt;
&lt;li&gt;Sites verify JWTs against cached Core JWKS — no LIME API round-trip on every request after keys are warm&lt;/li&gt;
&lt;li&gt;PoW rate-limits brute approval spam without UX friction for legitimate agents&lt;/li&gt;
&lt;li&gt;Separation of site passport vs MCP token limits blast radius&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What you must still do&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store &lt;code&gt;LIME_AGENT_TOKEN&lt;/code&gt; and &lt;code&gt;LIME_SITE_TOKEN&lt;/code&gt; as secrets (Vault, K8s secrets, CI vars) — never in client-side code or LLM context&lt;/li&gt;
&lt;li&gt;Treat &lt;code&gt;request_id&lt;/code&gt; as single-use and bind it in &lt;code&gt;verify_passport(expected_request_id=...)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run one &lt;code&gt;LimeSite&lt;/code&gt; per process with a perpetual SSE connection; do not create per HTTP request&lt;/li&gt;
&lt;li&gt;Rotate agent tokens from the LIME owner portal if a worker is compromised&lt;/li&gt;
&lt;li&gt;PoW is abuse resistance, not a malware detector — pair with your own agent allowlists and rate limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zero Human Auth removes the human from the authorization flow — it does not remove your responsibility for security. LIME gives you the tools (JWT, JWKS, PoW). Managing secrets, rotating tokens, and setting rate limits is still on you.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use LIME vs rolling your own
&lt;/h2&gt;

&lt;p&gt;Build it yourself if you only need a single static API key between two services you own.&lt;/p&gt;

&lt;p&gt;Reach for an &lt;strong&gt;AI agent identity layer&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple agents must log into &lt;strong&gt;customer sites&lt;/strong&gt; with auditable identity&lt;/li&gt;
&lt;li&gt;You need &lt;strong&gt;JWKS verification&lt;/strong&gt; and standard JWT claims (&lt;code&gt;agent_id&lt;/code&gt;, &lt;code&gt;request_id&lt;/code&gt;, &lt;code&gt;agent_reputation&lt;/code&gt;, &lt;code&gt;user_kyc_level&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;You participate in the &lt;strong&gt;MCP&lt;/strong&gt; ecosystem and want &lt;strong&gt;MCP OAuth&lt;/strong&gt; without hand-rolling PyJWT + metadata fetch per request&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stop fighting OAuth. Ship the passport.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;lime-agents-sdk lime-sites-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PyPI:&lt;/strong&gt; &lt;a href="https://pypi.org/project/lime-agents-sdk/" rel="noopener noreferrer"&gt;lime-agents-sdk&lt;/a&gt; · &lt;a href="https://pypi.org/project/lime-sites-sdk/" rel="noopener noreferrer"&gt;lime-sites-sdk&lt;/a&gt; · &lt;a href="https://pypi.org/project/lime-mcp-server-sdk/" rel="noopener noreferrer"&gt;lime-mcp-server-sdk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Mawyxx/lime-agents-sdk" rel="noopener noreferrer"&gt;lime-agents-sdk&lt;/a&gt; · &lt;a href="https://github.com/Mawyxx/lime-site-sdk" rel="noopener noreferrer"&gt;lime-site-sdk&lt;/a&gt; · &lt;a href="https://github.com/Mawyxx/lime-mcp-server-sdk" rel="noopener noreferrer"&gt;lime-mcp-server-sdk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://lime.pics/docs" rel="noopener noreferrer"&gt;https://lime.pics/docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your agents a cryptographic passport. Verify it with JWKS. Keep MCP on its own OAuth lane.&lt;/p&gt;

&lt;p&gt;Questions? Open an issue on GitHub or ping &lt;a href="https://twitter.com/MawyxxMC" rel="noopener noreferrer"&gt;@MawyxxMC&lt;/a&gt; on X.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags for Dev.to: &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;ai&lt;/code&gt;, &lt;code&gt;security&lt;/code&gt;, &lt;code&gt;jwt&lt;/code&gt;, &lt;code&gt;mcp&lt;/code&gt;, &lt;code&gt;oauth&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>security</category>
      <category>jwt</category>
    </item>
    <item>
      <title>I built a cryptographic passport for AI agents — here's how it works</title>
      <dc:creator>Mawyxx</dc:creator>
      <pubDate>Mon, 29 Jun 2026 15:09:28 +0000</pubDate>
      <link>https://dev.to/mawyxx/i-built-a-cryptographic-passport-for-ai-agents-heres-how-it-works-5b73</link>
      <guid>https://dev.to/mawyxx/i-built-a-cryptographic-passport-for-ai-agents-heres-how-it-works-5b73</guid>
      <description>&lt;h2&gt;
  
  
  The problem nobody is solving
&lt;/h2&gt;

&lt;p&gt;AI agents can write code, browse the web, send emails. But ask a simple question: &lt;strong&gt;"How does this agent prove its identity to another service?"&lt;/strong&gt; — and you'll hit a wall.&lt;/p&gt;

&lt;p&gt;Modern identity systems (OAuth, SSO, API keys) are built for humans who click buttons in a browser. An autonomous agent needs something different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Portable credentials&lt;/strong&gt; that can be carried across platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A way to prove identity&lt;/strong&gt; to other agents and services without human intervention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization flows&lt;/strong&gt; that don't require "click this link in your browser".&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Agents don't need "accounts" on platforms. They need portable identity — like a passport in the real world.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Solution: a cryptographic passport for agents
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;LIME&lt;/strong&gt; — a cryptographic identity layer for AI agents. Every agent gets a &lt;strong&gt;signed JWT passport (RS256)&lt;/strong&gt; that any website can verify locally via JWKS in &lt;strong&gt;&amp;lt;10ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key advantage:&lt;/strong&gt; verification happens without external calls to our API. The site fetches the public key once and verifies all subsequent passports independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works (5 steps)
&lt;/h2&gt;

&lt;p&gt;The flow is designed for &lt;strong&gt;fully headless scenarios&lt;/strong&gt; — no browsers, no redirects:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Site creates a login request&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;POST /api/v1/modules/agent-login/requests&lt;/code&gt; with &lt;code&gt;X-Site-Token&lt;/code&gt; → receives &lt;code&gt;login_request_id&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agent fetches the PoW challenge&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;GET /api/v1/auth/requests/{id}&lt;/code&gt; → receives cryptographic challenge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agent solves Proof-of-Work&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Instead of a captcha — SHA-256 with difficulty=15 (~50ms CPU). SDK solves it automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agent approves login&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;POST .../approve&lt;/code&gt; with &lt;code&gt;X-Agent-Token&lt;/code&gt; and &lt;code&gt;pow_nonce&lt;/code&gt; → site receives JWT via SSE stream.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Site verifies passport locally&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Via JWKS endpoint — &lt;strong&gt;zero latency, no external calls&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Architecture &amp;amp; stack
&lt;/h2&gt;

&lt;p&gt;├── Core — identity, JWT, JWKS, PassportContext&lt;br&gt;
├── Foundation — owner registration, sessions, KYC&lt;br&gt;
├── Modules — site_login, MCP OAuth&lt;br&gt;
├── Infrastructure — PostgreSQL, Redis, crypto adapters&lt;br&gt;
└── Composition — single DI assembly point&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Python 3.11, FastAPI, asyncpg&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptography:&lt;/strong&gt; Rust (PyO3) — JWT, HMAC, PoW&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL (single DB with logical separation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache/queues:&lt;/strong&gt; Redis (SSE long-poll, rate limits)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Next.js (App Router), TypeScript, Tailwind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why Rust for crypto?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT signing: &lt;strong&gt;&amp;lt;1ms&lt;/strong&gt; vs 12ms in Python&lt;/li&gt;
&lt;li&gt;HMAC and PoW — native, no GIL&lt;/li&gt;
&lt;li&gt;Auditability and security for critical code&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's already working
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Cryptographic passport (RS256 JWT)&lt;/strong&gt; — agent receives a signed identity.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;JWKS endpoint&lt;/strong&gt; — public key for zero-latency verification.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;MCP OAuth provider&lt;/strong&gt; — OAuth 2.1 Authorization Server for Anthropic MCP.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Site Login API&lt;/strong&gt; — headless auth with PoW and SSE.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Python SDK&lt;/strong&gt; — &lt;code&gt;lime-agents-sdk&lt;/code&gt; and &lt;code&gt;lime-sites-sdk&lt;/code&gt; on PyPI.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;100% test coverage&lt;/strong&gt; + 40+ merge-blocking quality gates.&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Rust-first crypto core&lt;/strong&gt; — all critical ops via PyO3.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The LIME ecosystem is growing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent reputation module&lt;/strong&gt; — trust scoring so sites can trust agents based on history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crypto wallet + payments&lt;/strong&gt; — agents can pay for services, monetization via fees (like Stripe). Identity stays &lt;strong&gt;free forever&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"By 2027, every AI agent will have a cryptographic passport. API keys don't scale. Security requires verifiable identity."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LIME gives agents what OAuth gave humans — but without browsers, redirects, or human involvement. &lt;strong&gt;It's the infrastructure layer for the agent economy.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://lime.pics" rel="noopener noreferrer"&gt;lime.pics&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://lime.pics/docs" rel="noopener noreferrer"&gt;lime.pics/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Mawyxx" rel="noopener noreferrer"&gt;github.com/Mawyxx&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;We're in Early Access and looking for pilot partners. If you're building AI agents or want to accept them on your site — reach out via DM or email.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>python</category>
      <category>security</category>
    </item>
  </channel>
</rss>
