tailscale_mcp/lib.rs
1//! MCP server for Tailscale.
2//!
3//! Two surfaces, deliberately kept apart. The *local* surface drives the node
4//! this server runs on through the `tailscale` command-line interface
5//! (ADR-0001). The *tailnet* surface acts on the whole tailnet through the
6//! control-plane REST API (ADR-0002). A tool belongs to exactly one of them.
7//!
8//! The crate is a library as well as a binary so that the tests can build a
9//! whole server in-process and drive it as a client, which is where nearly all
10//! of the behaviour is observable.
11//!
12//! That is the whole of why the library is public, and so it carries no
13//! stability guarantee: the compatible thing to depend on is the protocol this
14//! server speaks, which the contract tests pin, rather than the Rust surface
15//! the tests happen to reach through. A signature here may change in any
16//! release, and `cargo semver-checks` will say so — the question that answers
17//! is whether the change was meant, not whether it is allowed.
18
19// A panic in a test is a failed test, which is what these lints exist to
20// prevent elsewhere.
21#![cfg_attr(test, allow(clippy::expect_used, clippy::unwrap_used))]
22
23pub mod cli;
24pub mod completion;
25pub mod config;
26pub mod context;
27pub mod error;
28pub mod gating;
29pub mod http;
30pub mod instructions;
31pub mod meta;
32pub mod registry;
33pub mod resources;
34pub mod server;
35pub mod subcommands;
36pub mod tools;
37pub mod version;
38
39/// Test helpers, re-exported so that the unit tests and the integration tests
40/// reach for the same fake.
41#[cfg(test)]
42pub(crate) mod testing {
43 use std::sync::Arc;
44
45 #[allow(unused_imports)]
46 pub(crate) use tailscale_cli::stub::{Reply, StubBackend};
47
48 use crate::context::{Identity, PathPolicy, ToolContext};
49 use crate::error::Redactor;
50 use crate::meta::Tier;
51
52 /// A context wired to a scripted client and to nothing else.
53 ///
54 /// Every toolset's unit tests want the same one, and used to spell it out
55 /// six identical times. The tier is the most permissive: a handler called
56 /// directly has already passed the gate, so anything less would be testing
57 /// a check that is not this code's to make.
58 pub(crate) fn context(backend: Arc<StubBackend>) -> ToolContext {
59 ToolContext {
60 local: backend as Arc<dyn tailscale_cli::LocalBackend>,
61 // The tailnet surface has its own fake; a local handler
62 // never reaches for it.
63 tailnet: None,
64 redactor: Redactor::default(),
65 max_result_bytes: 1 << 20,
66 identity: Identity::default(),
67 cli_version: None,
68 paths: PathPolicy::default(),
69 devices: Default::default(),
70 max_tier: Tier::Destructive,
71 }
72 }
73}