OXYGENHUB
# 2d2dcb7a2778 spectra / tests / auto.rs
3.3 KiB · Rust · last changed spectra mcp --auto: one MCP entry for every folder 1 day ago source blame history raw
1
//! `spectra mcp --auto` (spec §15.1): one MCP entry for every folder. The
2
//! shim speaks for the agent whose project folder the client started in,
3
//! from any depth inside it; an agent home made before labels is labelled
4
//! on first use; a folder with no agent gets a quiet server, not a failure.
5
6
mod common;
7
8
use common::{hermetic, run, setup, text, Serving};
9
use serde_json::{json, Value};
10
use std::io::{BufRead, BufReader, Write};
11
use std::path::Path;
12
use std::process::Stdio;
13
14
/// Run the shim in `cwd` and ask it two things: the handshake and `brief`.
15
fn ask(d: &Path, cwd: &Path) -> (Value, Value) {
16
    let mut c = hermetic(d)
17
        .args(["mcp", "--auto"])
18
        .current_dir(cwd)
19
        .env("SPECTRA_AGENTS", d.join("agents"))
20
        .stdin(Stdio::piped())
21
        .stdout(Stdio::piped())
22
        .stderr(Stdio::piped())
23
        .spawn()
24
        .unwrap();
25
    let mut stdin = c.stdin.take().unwrap();
26
    for v in [
27
        json!({ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": { "name": "t", "version": "1" } } }),
28
        json!({ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "brief", "arguments": {} } }),
29
    ] {
30
        writeln!(stdin, "{v}").unwrap();
31
    }
32
    stdin.flush().unwrap();
33
    let mut lines = BufReader::new(c.stdout.take().unwrap()).lines();
34
    let mut got = std::collections::HashMap::new();
35
    while got.len() < 2 {
36
        let v: Value = serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap();
37
        got.insert(v["id"].as_u64().unwrap(), v);
38
    }
39
    drop(stdin);
40
    let _ = c.kill();
41
    let _ = c.wait();
42
    (got.remove(&1).unwrap(), got.remove(&2).unwrap())
43
}
44
45
#[test]
46
fn one_entry_serves_every_folder() {
47
    let dir = setup();
48
    let d = dir.path();
49
    for p in ["book", "web"] {
50
        std::fs::create_dir_all(d.join(p).join("chapters")).unwrap();
51
        assert!(run(d, &["project", "add", d.join(p).to_str().unwrap()]).status.success());
52
    }
53
    for (n, p) in [("claude-book", "book"), ("claude-web", "web")] {
54
        let home = d.join("agents").join(n);
55
        let o = run(d, &["agent", "add", n, "--kind", "claude-code", "--project", p, "--home", home.to_str().unwrap(), "--yes"]);
56
        assert!(o.status.success(), "{}", text(&o));
57
        assert!(home.join("agent.toml").exists(), "agent add labels the home");
58
    }
59
    // An agent home from before labels: labelled on first use.
60
    std::fs::remove_file(d.join("agents/claude-web/agent.toml")).unwrap();
61
    let srv = Serving::start(d);
62
63
    let (_, brief) = ask(d, &d.join("book/chapters"));
64
    assert!(brief["result"]["content"][0]["text"].as_str().unwrap().contains("You are claude-book"), "{brief}");
65
    let (_, brief) = ask(d, &d.join("web"));
66
    assert!(brief["result"]["content"][0]["text"].as_str().unwrap().contains("You are claude-web"), "{brief}");
67
    assert!(d.join("agents/claude-web/agent.toml").exists(), "labelled on first use");
68
69
    // Elsewhere: a quiet server that says how to join, not a failure.
70
    std::fs::create_dir_all(d.join("elsewhere")).unwrap();
71
    let (init, call) = ask(d, &d.join("elsewhere"));
72
    assert!(init["result"]["instructions"].as_str().unwrap().contains("has no Spectra agent"), "{init}");
73
    assert!(call["error"]["message"].as_str().unwrap().contains("Spectra: Add Agent"), "{call}");
74
    srv.stop();
75
}