~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
736 lines · 25.8 KB Raw History
//! MCP over HTTP against a reference PDS: an assistant registers, its user
//! signs in with their account, and it works as them with the token it gets.

#[path = "../../knotless/tests/common/mod.rs"]
mod common;
use common::*;

use knotless_mcp::http::{Event, Limits, Mcp, router};
use reqwest::blocking::{Client, Response};
use serde_json::{Value, json};
use std::collections::HashMap;
use std::io::{BufRead, BufReader, Write};
use std::sync::{Arc, Mutex, mpsc};

struct Server {
    /// Where tests reach it, and where PDSes and assistants do.
    base: String,
    url: String,
    _rt: tokio::runtime::Runtime,
    _proxy: Option<TlsProxy>,
    /// What it told its operator's counts, as `name subject ok did`.
    events: Arc<Mutex<Vec<String>>>,
}

/// The MCP server, turning away the accounts `refused` names and keeping
/// what it needs in `s`'s directories.
fn server(s: &Sandbox, refused: Vec<String>) -> Server {
    serve(|url: &str| {
        let mcp = Mcp::new(&ctx(s), url, Limits::default()).unwrap();
        mcp.refuse(Arc::new(move |did| refused.iter().any(|r| r == did)));
        mcp
    })
}

/// The MCP server `mcp` makes for its public URL, served.
fn serve(mcp: impl FnOnce(&str) -> Arc<Mcp>) -> Server {
    let rt = tokio::runtime::Runtime::new().unwrap();
    let listener = rt
        .block_on(tokio::net::TcpListener::bind("127.0.0.1:0"))
        .unwrap();
    let port = listener.local_addr().unwrap().port();
    // PDSes reach their clients over https.
    let proxy = tls_proxy(port);
    let mcp = mcp(&format!("{}/", proxy.url));
    let events = Arc::new(Mutex::new(Vec::new()));
    let told = events.clone();
    mcp.observe(Arc::new(move |e: Event| {
        let did = e.did.unwrap_or("-");
        let line = format!("{} {} {} {did}", e.name, e.subject, e.ok);
        told.lock().unwrap().push(line);
    }));
    rt.spawn(async move { axum::serve(listener, router(mcp)).await.unwrap() });
    Server {
        base: format!("http://127.0.0.1:{port}"),
        url: proxy.url.clone(),
        _rt: rt,
        _proxy: Some(proxy),
        events,
    }
}

fn client() -> Client {
    Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .build()
        .unwrap()
}

fn location(res: &Response) -> String {
    res.headers()["location"].to_str().unwrap().to_string()
}

fn query(url: &str) -> HashMap<String, String> {
    reqwest::Url::parse(url)
        .unwrap()
        .query_pairs()
        .into_owned()
        .collect()
}

/// An assistant's loopback redirect, and the query the browser brings it.
fn assistant() -> (String, mpsc::Receiver<HashMap<String, String>>) {
    let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let uri = format!(
        "http://127.0.0.1:{}/back",
        listener.local_addr().unwrap().port()
    );
    let (tx, rx) = mpsc::channel();
    std::thread::spawn(move || {
        for stream in listener.incoming() {
            let mut stream = stream.unwrap();
            let mut line = String::new();
            BufReader::new(&stream).read_line(&mut line).unwrap();
            let target = line.split(' ').nth(1).unwrap_or_default();
            stream
                .write_all(b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\nconnection: close\r\n\r\n")
                .unwrap();
            let _ = tx.send(query(&format!("http://127.0.0.1{target}")));
        }
    });
    (uri, rx)
}

/// Approves (or rejects) the sign-in at `url` as `account`, in a browser.
fn approve(account: &Account, extra: &str, url: &str) {
    let ok = std::process::Command::new("sh")
        .arg("-c")
        .arg(format!("{} '{url}'", browser(account, extra)))
        .status()
        .unwrap()
        .success();
    assert!(ok);
}

fn register(s: &Server, body: Value) -> (u16, Value) {
    let res = client()
        .post(format!("{}/mcp/register", s.base))
        .json(&body)
        .send()
        .unwrap();
    (res.status().as_u16(), res.json().unwrap())
}

fn token(s: &Server, form: &[(&str, &str)]) -> (u16, Value) {
    let res = client()
        .post(format!("{}/mcp/token", s.base))
        .form(form)
        .send()
        .unwrap();
    (res.status().as_u16(), res.json().unwrap())
}

/// The sign-in form's signed request, from its page.
fn ask(page: &str) -> String {
    let at = page.find("name=ask value=\"").unwrap() + 16;
    page[at..].split('"').next().unwrap().to_string()
}

/// The authorization page for `params`.
fn authorize(s: &Server, params: &[(&str, &str)]) -> Response {
    client()
        .get(format!("{}/mcp/authorize", s.base))
        .query(params)
        .send()
        .unwrap()
}

fn sign_in(s: &Server, ask: &str, handle: &str) -> Response {
    client()
        .post(format!("{}/mcp/authorize", s.base))
        .form(&[("ask", ask), ("handle", handle)])
        .send()
        .unwrap()
}

/// A JSON-RPC call to `/mcp` with `bearer`.
fn rpc(s: &Server, bearer: Option<&str>, method: &str, params: Value) -> (u16, Value) {
    let mut req = client()
        .post(format!("{}/mcp", s.base))
        .header("accept", "application/json, text/event-stream")
        .json(&json!({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}));
    if let Some(t) = bearer {
        req = req.bearer_auth(t);
    }
    let res = req.send().unwrap();
    let status = res.status().as_u16();
    (status, res.json().unwrap_or(Value::Null))
}

#[test]
fn an_assistant_signs_in_and_works_as_its_user() {
    let s = Sandbox::new();
    let allowed = account();
    let other = account();
    let srv = server(&s, vec![other.did.clone()]);

    // Discovery, from the 401 on.
    let (status, _) = rpc(&srv, None, "tools/list", json!({}));
    assert_eq!(status, 401);
    let res = client().post(format!("{}/mcp", srv.base)).send().unwrap();
    let challenge = res.headers()["www-authenticate"].to_str().unwrap();
    assert!(
        challenge.contains(&format!(
            "{}/.well-known/oauth-protected-resource/mcp",
            srv.url
        )),
        "{challenge}"
    );
    for path in ["oauth-protected-resource", "oauth-protected-resource/mcp"] {
        let meta: Value = client()
            .get(format!("{}/.well-known/{path}", srv.base))
            .send()
            .unwrap()
            .json()
            .unwrap();
        assert_eq!(meta["resource"], format!("{}/mcp", srv.url));
        assert_eq!(meta["authorization_servers"][0], srv.url);
    }
    let meta: Value = client()
        .get(format!(
            "{}/.well-known/oauth-authorization-server",
            srv.base
        ))
        .send()
        .unwrap()
        .json()
        .unwrap();
    assert_eq!(meta["token_endpoint"], format!("{}/mcp/token", srv.url));

    // Registration: https, or http on the assistant's own machine.
    let (redirect, back) = assistant();
    for bad in [
        json!({}),
        json!({"redirect_uris": ["http://elsewhere.test/back"]}),
        json!({"redirect_uris": ["https://ok.test/back", "https://ok.test/#frag"]}),
        json!({"redirect_uris": ["not a url"]}),
    ] {
        let (status, body) = register(&srv, bad);
        assert_eq!(status, 400);
        assert_eq!(body["error"], "invalid_redirect_uri");
    }
    let (status, reg) = register(
        &srv,
        json!({
            "redirect_uris": [redirect],
            "client_name": "Claude <b>",
            "token_endpoint_auth_method": "client_secret_post",
        }),
    );
    assert_eq!(status, 201, "{reg}");
    assert_eq!(reg["token_endpoint_auth_method"], "client_secret_post");
    let id = reg["client_id"].as_str().unwrap().to_string();
    let secret = reg["client_secret"].as_str().unwrap().to_string();

    // What the authorization page refuses: to the user when it cannot
    // trust where to send them back, else to the assistant.
    let verifier = "a-verifier-long-enough-for-pkce-0123456789";
    let challenge = knotless::oauth::sha256(verifier);
    let mut params = vec![
        ("client_id", id.as_str()),
        ("response_type", "code"),
        ("code_challenge", challenge.as_str()),
        ("code_challenge_method", "S256"),
        ("state", "st"),
    ];
    assert_eq!(authorize(&srv, &[("client_id", "nope")]).status(), 400);
    let res = authorize(
        &srv,
        &[("client_id", &id), ("redirect_uri", "https://x.test/")],
    );
    assert_eq!(res.status(), 400);
    let refused = |extra: (&str, &str)| {
        let mut p = params.clone();
        p.retain(|(k, _)| *k != extra.0);
        p.push(extra);
        let res = authorize(&srv, &p);
        assert_eq!(res.status(), 303);
        let q = query(&location(&res));
        assert_eq!(q["state"], "st");
        assert_eq!(q["iss"], srv.url);
        q["error"].clone()
    };
    assert_eq!(
        refused(("response_type", "token")),
        "unsupported_response_type"
    );
    assert_eq!(
        refused(("code_challenge_method", "plain")),
        "invalid_request"
    );
    assert_eq!(
        refused(("resource", "https://elsewhere.test/mcp")),
        "invalid_target"
    );
    let resource = format!("{}/mcp/", srv.url);
    params.push(("resource", &resource));

    // The user says who they are; one turned away goes no further.
    let page = authorize(&srv, &params).text().unwrap();
    assert!(
        page.contains("Connect Claude &lt;b&gt; to knotless"),
        "{page}"
    );
    let signed = ask(&page);
    assert_eq!(sign_in(&srv, "forged", &allowed.handle).status(), 400);
    let res = sign_in(&srv, &signed, "nobody.invalid");
    assert_eq!(res.status(), 400);
    assert!(
        res.text()
            .unwrap()
            .contains("No account answers to nobody.invalid")
    );
    let res = sign_in(&srv, &signed, &other.handle);
    assert_eq!(res.status(), 403);

    // A sign-in the user walks away from waits, among others.
    assert_eq!(sign_in(&srv, &signed, &allowed.handle).status(), 303);
    // A refusal at the PDS goes back to the assistant.
    let res = sign_in(&srv, &signed, &format!("@{}", allowed.handle));
    assert_eq!(res.status(), 303);
    approve(&allowed, "--reject", &location(&res));
    let q = back.recv().unwrap();
    assert_eq!(q["error"], "access_denied");
    assert_eq!(q["state"], "st");

    // Approved: the assistant gets a code, and trades it for tokens.
    let res = sign_in(&srv, &signed, &allowed.handle);
    approve(&allowed, "", &location(&res));
    let q = back.recv().unwrap();
    assert_eq!(q["state"], "st");
    assert_eq!(q["iss"], srv.url);
    let code = q["code"].clone();
    let form = |extra: &[(&'static str, &str)]| {
        let mut f: Vec<(&str, String)> = vec![
            ("grant_type", "authorization_code".into()),
            ("client_id", id.clone()),
            ("client_secret", secret.clone()),
            ("code", code.clone()),
            ("code_verifier", verifier.into()),
            ("redirect_uri", redirect.clone()),
        ];
        for (k, v) in extra {
            f.retain(|(key, _)| key != k);
            f.push((k, v.to_string()));
        }
        f
    };
    let send = |f: Vec<(&str, String)>| {
        let f: Vec<(&str, &str)> = f.iter().map(|(k, v)| (*k, v.as_str())).collect();
        token(&srv, &f)
    };
    let (status, body) = send(form(&[("client_secret", "bm9wZQ")]));
    assert_eq!(
        (status, body["error"].as_str()),
        (401, Some("invalid_client"))
    );
    let (status, _) = send(form(&[("client_id", "nope")]));
    assert_eq!(status, 401);
    let (status, body) = send(form(&[("grant_type", "password")]));
    assert_eq!(
        (status, body["error"].as_str()),
        (400, Some("unsupported_grant_type"))
    );
    let (status, tokens) = send(form(&[]));
    assert_eq!(status, 200, "{tokens}");
    assert_eq!(tokens["token_type"], "Bearer");
    // A code is good once.
    let (status, body) = send(form(&[]));
    assert_eq!(
        (status, body["error"].as_str()),
        (400, Some("invalid_grant"))
    );
    let access = tokens["access_token"].as_str().unwrap().to_string();
    let refresh = tokens["refresh_token"].as_str().unwrap().to_string();

    // The tools, as the user.
    let (status, init) = rpc(
        &srv,
        Some(&access),
        "initialize",
        json!({"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "t", "version": "1"}}),
    );
    assert_eq!(status, 200, "{init}");
    assert!(
        init["result"]["instructions"]
            .as_str()
            .unwrap()
            .contains("handle/name")
    );
    assert!(init["result"]["capabilities"]["resources"].is_object());
    let (_, list) = rpc(&srv, Some(&access), "tools/list", json!({}));
    assert!(list.to_string().contains("repo_create"), "{list}");
    let (_, made) = rpc(
        &srv,
        Some(&access),
        "tools/call",
        json!({"name": "repo_create", "arguments": {"name": "over-http"}}),
    );
    let text = made["result"]["content"][0]["text"].as_str().unwrap();
    assert!(
        text.contains(&format!("{}/over-http", allowed.handle)),
        "{made}"
    );
    // A newer client names the tool in headers too, which are checked
    // against its schema.
    let res = client()
        .post(format!("{}/mcp", srv.base))
        .bearer_auth(&access)
        .header("accept", "application/json, text/event-stream")
        .header("mcp-protocol-version", "2026-07-28")
        .header("mcp-method", "tools/call")
        .header("mcp-name", "repo_list")
        .json(
            &json!({"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {
                "name": "repo_list",
                "arguments": {"handle": allowed.handle},
                "_meta": {
                    "io.modelcontextprotocol/protocolVersion": "2026-07-28",
                    "io.modelcontextprotocol/clientCapabilities": {},
                    "io.modelcontextprotocol/clientInfo": {"name": "t", "version": "1"},
                },
            }}),
        )
        .send()
        .unwrap();
    let listed = res.text().unwrap();
    assert!(listed.contains("over-http"), "{listed}");
    // A tool that fails, as its result says.
    let (_, failed) = rpc(
        &srv,
        Some(&access),
        "tools/call",
        json!({"name": "repo_read", "arguments": {"repo": "!", "path": "a"}}),
    );
    assert_eq!(failed["result"]["isError"], true, "{failed}");
    // A call no tool takes, as an error.
    let (_, none) = rpc(
        &srv,
        Some(&access),
        "tools/call",
        json!({"name": "no_such_tool", "arguments": {}}),
    );
    assert!(none["error"].is_object(), "{none}");
    // Resources, read as the user too.
    let (_, templates) = rpc(&srv, Some(&access), "resources/templates/list", json!({}));
    let templates = templates["result"]["resourceTemplates"].as_array().unwrap();
    assert_eq!(templates.len(), 6, "{templates:?}");
    let repo = format!("{}/over-http", allowed.handle);
    let commit = json!({"name": "repo_commit", "arguments": {"repo": repo, "branch": "main", "message": "m", "changes": [{"path": "a.md", "content": "over\n"}]}});
    let (_, committed) = rpc(&srv, Some(&access), "tools/call", commit);
    assert_ne!(committed["result"]["isError"], true, "{committed}");
    let file = format!("knotless://{repo}/file/main/a.md");
    let (_, read) = rpc(&srv, Some(&access), "resources/read", json!({"uri": file}));
    assert_eq!(read["result"]["contents"][0]["text"], "over\n", "{read}");
    let (_, unknown) = rpc(
        &srv,
        Some(&access),
        "resources/read",
        json!({"uri": "knotless://nowhere"}),
    );
    assert!(
        unknown["error"]["message"]
            .as_str()
            .unwrap()
            .contains("expected one of")
    );
    // The operator's counts were told of the sign-ins, the tool calls and
    // the resources read, each read named for the tool that answers it.
    let me = &allowed.did;
    let told = srv.events.lock().unwrap().clone();
    let tools: Vec<&String> = told.iter().filter(|e| e.starts_with("mcp.tool")).collect();
    assert_eq!(
        tools,
        [
            &format!("mcp.tool repo_create true {me}"),
            &format!("mcp.tool repo_list true {me}"),
            &format!("mcp.tool repo_read false {me}"),
            &format!("mcp.tool no_such_tool false {me}"),
            &format!("mcp.tool repo_commit true {me}"),
        ]
    );
    let reads: Vec<&String> = told
        .iter()
        .filter(|e| e.starts_with("mcp.resource"))
        .collect();
    assert_eq!(
        reads,
        [
            &format!("mcp.resource repo_read true {me}"),
            &format!("mcp.resource  false {me}"),
        ]
    );
    assert!(
        told.contains(&"mcp.signin  false -".to_string()),
        "{told:?}"
    );
    assert!(told.contains(&format!("mcp.signin  true {me}")), "{told:?}");
    // A refresh token is the client's alone, and makes new tokens.
    let (status, _) = token(
        &srv,
        &[
            ("grant_type", "refresh_token"),
            ("client_id", &id),
            ("client_secret", &secret),
            ("refresh_token", &access),
        ],
    );
    assert_eq!(status, 400);
    let (status, fresh) = token(
        &srv,
        &[
            ("grant_type", "refresh_token"),
            ("client_id", &id),
            ("client_secret", &secret),
            ("refresh_token", &refresh),
        ],
    );
    assert_eq!(status, 200, "{fresh}");

    // A public client proves nothing but its PKCE; a code is for the
    // client it was issued to, with the verifier it was asked with.
    let (_, public) = register(&srv, json!({"redirect_uris": [redirect]}));
    assert_eq!(public["token_endpoint_auth_method"], "none");
    assert!(public.get("client_secret").is_none());
    let pid = public["client_id"].as_str().unwrap().to_string();
    let mut p = params.clone();
    p[0] = ("client_id", &pid);
    let page = authorize(&srv, &p).text().unwrap();
    assert!(page.contains("Connect An assistant to knotless"));
    // A code the assistant never trades waits, among others.
    let res = sign_in(&srv, &ask(&page), &allowed.handle);
    approve(&allowed, "", &location(&res));
    back.recv().unwrap();
    let res = sign_in(&srv, &ask(&page), &allowed.handle);
    approve(&allowed, "", &location(&res));
    let code = back.recv().unwrap()["code"].clone();
    let (status, _) = token(
        &srv,
        &[
            ("grant_type", "authorization_code"),
            ("client_id", &pid),
            ("code", &code),
            ("code_verifier", "wrong"),
        ],
    );
    assert_eq!(status, 400);

    // Nothing waits for a callback nobody started.
    let res = client()
        .get(format!("{}/mcp/oauth/callback?state=nope", srv.base))
        .send()
        .unwrap();
    assert_eq!(res.status(), 400);

    // Turned away, its tokens stop working.
    let off = server(&s, vec![allowed.did.clone()]);
    assert_eq!(rpc(&off, Some(&access), "tools/list", json!({})).0, 401);
    let (status, body) = token(
        &off,
        &[
            ("grant_type", "refresh_token"),
            ("client_id", &id),
            ("client_secret", &secret),
            ("refresh_token", &refresh),
        ],
    );
    assert_eq!(status, 400);
    assert!(
        body["error_description"]
            .as_str()
            .unwrap()
            .contains("no longer")
    );

    // The login it keeps with the PDS is refreshed as it runs out, and
    // waits out a PDS that cannot be reached.
    let dir = allowed.did.replace(':', "_");
    let kept = ctx(&s)
        .env
        .config_dir
        .join("mcp")
        .join(&dir)
        .join("sessions")
        .join(format!("{dir}.json"));
    let change = |what: &dyn Fn(&mut Value)| {
        let mut session: Value = serde_json::from_slice(&std::fs::read(&kept).unwrap()).unwrap();
        session["oauth"]["expiresAt"] = 0.into();
        what(&mut session);
        std::fs::write(&kept, session.to_string()).unwrap();
    };
    change(&|_| {});
    assert_eq!(rpc(&srv, Some(&access), "tools/list", json!({})).0, 200);
    let read: Value = serde_json::from_slice(&std::fs::read(&kept).unwrap()).unwrap();
    assert_ne!(read["oauth"]["expiresAt"], 0, "{read}");
    let endpoint = read["oauth"]["tokenEndpoint"].clone();
    change(&|s| s["oauth"]["tokenEndpoint"] = "http://127.0.0.1:1/token".into());
    assert_eq!(rpc(&srv, Some(&access), "tools/list", json!({})).0, 200);
    assert!(kept.exists());

    // Once the PDS turns it down the login is over: the assistant is sent
    // to sign in again, rather than working as no one.
    change(&|s| {
        s["oauth"]["tokenEndpoint"] = endpoint.clone();
        s["refreshJwt"] = format!("ref-{}", "0".repeat(64)).into();
    });
    let (status, _) = rpc(&srv, Some(&access), "tools/list", json!({}));
    assert_eq!(status, 401);
    assert!(!kept.exists());
    let (status, body) = token(
        &srv,
        &[
            ("grant_type", "refresh_token"),
            ("client_id", &id),
            ("client_secret", &secret),
            ("refresh_token", &refresh),
        ],
    );
    assert_eq!(
        (status, body["error"].as_str()),
        (400, Some("invalid_grant"))
    );
}

#[test]
fn a_pds_that_will_not_take_the_sign_in() {
    // A server whose public URL no PDS can reach is no client of theirs.
    let s = Sandbox::new();
    let allowed = account();
    let rt = tokio::runtime::Runtime::new().unwrap();
    let listener = rt
        .block_on(tokio::net::TcpListener::bind("127.0.0.1:0"))
        .unwrap();
    let base = format!("http://127.0.0.1:{}", listener.local_addr().unwrap().port());
    let mcp = Mcp::new(&ctx(&s), "https://nowhere.invalid", Limits::default()).unwrap();
    rt.spawn(async move { axum::serve(listener, router(mcp)).await.unwrap() });
    let srv = Server {
        base,
        url: String::new(),
        _rt: rt,
        _proxy: None,
        events: Arc::default(),
    };
    let meta: Value = client()
        .get(format!("{}/mcp/oauth-client-metadata.json", srv.base))
        .send()
        .unwrap()
        .json()
        .unwrap();
    assert_eq!(
        meta["client_id"],
        "https://nowhere.invalid/mcp/oauth-client-metadata.json"
    );
    let (_, reg) = register(&srv, json!({"redirect_uris": ["https://claude.test/back"]}));
    let id = reg["client_id"].as_str().unwrap();
    let page = authorize(
        &srv,
        &[
            ("client_id", id),
            ("response_type", "code"),
            ("code_challenge", "c"),
            ("code_challenge_method", "S256"),
        ],
    )
    .text()
    .unwrap();
    let res = sign_in(&srv, &ask(&page), &allowed.handle);
    assert_eq!(res.status(), 502);
    assert!(res.text().unwrap().contains("didn't take the sign-in"));
}

#[test]
fn open_to_anyone_but_those_turned_away() {
    let s = Sandbox::new();
    let anyone = account();
    let away = account();
    let refused = away.did.clone();
    let srv = serve(|url| {
        let limits = Limits {
            signing_in: 1,
            running: 8,
            waiting: 2,
            per_minute: 3,
        };
        let mcp = Mcp::new(&ctx(&s), url, limits).unwrap();
        mcp.refuse(Arc::new(move |did| did == refused));
        mcp
    });
    let (redirect, back) = assistant();
    let (_, reg) = register(&srv, json!({"redirect_uris": [redirect]}));
    let id = reg["client_id"].as_str().unwrap().to_string();
    let verifier = "a-verifier-long-enough-for-pkce-0123456789";
    let challenge = knotless::oauth::sha256(verifier);
    let page = authorize(
        &srv,
        &[
            ("client_id", &id),
            ("response_type", "code"),
            ("code_challenge", &challenge),
            ("code_challenge_method", "S256"),
        ],
    )
    .text()
    .unwrap();
    let signed = ask(&page);

    // Turned away.
    let res = sign_in(&srv, &signed, &away.handle);
    assert_eq!(res.status(), 403);
    assert!(res.text().unwrap().contains("taken down here"));
    // Any other account goes on, as many at once as the server lets.
    let res = sign_in(&srv, &signed, &anyone.handle);
    assert_eq!(res.status(), 303);
    let crowded = sign_in(&srv, &signed, &anyone.handle);
    assert_eq!(crowded.status(), 503);
    assert!(crowded.text().unwrap().contains("Too many sign-ins"));
    approve(&anyone, "", &location(&res));
    let code = back.recv().unwrap()["code"].clone();
    let (status, tokens) = token(
        &srv,
        &[
            ("grant_type", "authorization_code"),
            ("client_id", &id),
            ("code", &code),
            ("code_verifier", verifier),
        ],
    );
    assert_eq!(status, 200, "{tokens}");
    let access = tokens["access_token"].as_str().unwrap();

    // A public repository is read from the server's own mirror, not one
    // of the account's.
    let create = json!({"name": "repo_create", "arguments": {"name": "shared"}});
    let (_, made) = rpc(&srv, Some(access), "tools/call", create);
    assert_ne!(made["result"]["isError"], true, "{made}");
    let repo = format!("{}/shared", anyone.handle);
    let tree = json!({"name": "repo_tree", "arguments": {"repo": repo}});
    rpc(&srv, Some(access), "tools/call", tree);
    let cache = ctx(&s).env.cache_dir;
    let own = anyone.did.replace(':', "_");
    assert!(cache.join("mirrors").join(&own).exists());
    assert!(!cache.join("mcp").join(&own).join("mirrors").exists());

    // So many tool calls a minute, and then a result that says to wait.
    let list = json!({"name": "repo_list", "arguments": {"handle": anyone.handle}});
    let (_, listed) = rpc(&srv, Some(access), "tools/call", list.clone());
    assert_ne!(listed["result"]["isError"], true, "{listed}");
    let (_, waits) = rpc(&srv, Some(access), "tools/call", list);
    assert_eq!(waits["result"]["isError"], true, "{waits}");
    let said = waits["result"]["content"][0]["text"].as_str().unwrap();
    assert!(said.contains("this minute"), "{said}");
    // A resource read is a call as a tool's is, and waits the same.
    let file = json!({"uri": format!("knotless://{repo}/file/main/a")});
    let (_, refused) = rpc(&srv, Some(access), "resources/read", file);
    let said = refused["error"]["message"].as_str().unwrap();
    assert!(said.contains("this minute"), "{said}");
}