use serde_json::{Value, json};
use sha2::{Digest as _, Sha256};
use std::fmt::Write as _;
use super::{AuthMdConfig, OAuthConfig};
pub const LLMS_TXT_PATH: &str = "/llms.txt";
pub const API_CATALOG_PATH: &str = "/.well-known/api-catalog";
pub const SERVER_CARD_PATH: &str = "/.well-known/mcp/server-card.json";
#[must_use]
pub fn server_card_path(mcp_path: &str) -> String {
format!("{}/server-card", mcp_path.trim_end_matches('/'))
}
pub const SKILLS_INDEX_PATH: &str = "/.well-known/agent-skills/index.json";
pub const ARD_PATH: &str = "/.well-known/ard.json";
pub const OAUTH_RESOURCE_PATH: &str = "/.well-known/oauth-protected-resource";
pub const OAUTH_SERVER_PATH: &str = "/.well-known/oauth-authorization-server";
pub const AUTH_MD_PATH: &str = "/auth.md";
pub const SITE_GUIDE_SKILL: &str = "site-guide";
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
#[allow(clippy::struct_excessive_bools)] pub struct SiteFacts {
pub name: Option<String>,
pub description: Option<String>,
pub mcp: Option<McpFacts>,
pub openapi: Option<OpenApiFacts>,
pub health_path: Option<String>,
pub pages: Vec<PageFacts>,
pub skills: Vec<AgentSkill>,
pub site_guide_skill: bool,
pub llms_txt: bool,
pub markdown: bool,
pub auth_md: AuthMdConfig,
pub oauth: OAuthConfig,
pub csrf_header: String,
pub csrf_required: bool,
pub webmcp_projected: Vec<String>,
pub web_bot_auth: Option<super::web_bot_auth::WebBotAuthKey>,
pub commerce: super::RegisteredCommerceDocs,
pub robots_txt: Option<String>,
pub sitemap: bool,
pub prefixed: bool,
pub home_paths: Vec<String>,
pub noindex_homes: Vec<String>,
pub no_home: bool,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct McpFacts {
pub path: String,
pub tools: Vec<ToolFacts>,
pub public_tools: bool,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct ToolFacts {
pub name: String,
pub description: Option<String>,
pub input_schema: Value,
pub annotations: Value,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct OpenApiFacts {
pub json_path: String,
pub docs_path: Option<String>,
pub version: String,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct PageFacts {
pub path: String,
pub title: String,
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentSkill {
name: String,
description: String,
extra: String,
body: String,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum AgentSkillError {
#[error("agent skill name {0:?} must be 1-64 chars of a-z, 0-9 and single inner hyphens")]
InvalidName(String),
#[error("agent skill {0:?} needs a description of 1-1024 characters")]
InvalidDescription(String),
#[error("SKILL.md needs YAML front matter with `name` and `description`")]
MissingFrontMatter,
}
impl AgentSkill {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
body: impl Into<String>,
) -> Result<Self, AgentSkillError> {
let name = name.into();
let description = description.into();
if !valid_skill_name(&name) {
return Err(AgentSkillError::InvalidName(name));
}
let len = description.chars().count();
if description.trim().is_empty() || len > 1024 {
return Err(AgentSkillError::InvalidDescription(name));
}
Ok(Self {
name,
description,
extra: String::new(),
body: body.into(),
})
}
pub fn parse(skill_md: &str) -> Result<Self, AgentSkillError> {
let rest = skill_md
.strip_prefix("---\n")
.or_else(|| skill_md.strip_prefix("---\r\n"))
.ok_or(AgentSkillError::MissingFrontMatter)?;
let (front, body) = rest
.split_once("\n---\n")
.or_else(|| rest.split_once("\r\n---\r\n"))
.ok_or(AgentSkillError::MissingFrontMatter)?;
let name = front_matter_field(front, "name").ok_or(AgentSkillError::MissingFrontMatter)?;
let description =
front_matter_field(front, "description").ok_or(AgentSkillError::MissingFrontMatter)?;
let body = body
.strip_prefix("\r\n")
.or_else(|| body.strip_prefix('\n'))
.unwrap_or(body);
let mut skill = Self::new(name, description, body)?;
skill.extra = other_front_matter(front);
Ok(skill)
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn description(&self) -> &str {
&self.description
}
#[must_use]
pub fn to_skill_md(&self) -> String {
format!(
"---\nname: {}\ndescription: {}\n{}---\n\n{}",
self.name,
yaml_quote(&self.description),
self.extra,
self.body
)
}
#[must_use]
pub fn path(&self) -> String {
format!("/.well-known/agent-skills/{}/SKILL.md", self.name)
}
}
pub fn request_authority<B>(req: &axum::http::Request<B>) -> (Option<String>, Option<String>) {
if let Some(identity) = req
.extensions()
.get::<crate::security::ResolvedClientIdentity>()
&& let Some(host) = identity.host.as_deref()
{
return (Some(host.to_owned()), identity.scheme.clone());
}
let host = req
.headers()
.get(axum::http::header::HOST)
.and_then(|v| v.to_str().ok())
.or_else(|| {
req.uri()
.authority()
.map(axum::http::uri::Authority::as_str)
});
(host.map(str::to_owned), None)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Origin {
pub base: String,
pub host: String,
pub configured: bool,
}
impl Origin {
#[must_use]
pub fn resolve(base_url: Option<&str>, host_header: Option<&str>) -> Self {
Self::resolve_with_scheme(base_url, host_header, None)
}
pub(crate) fn for_request<B>(
base_url: Option<&str>,
req: &axum::http::Request<B>,
) -> (Self, Option<String>) {
let (host, scheme) = request_authority(req);
(
Self::resolve_with_scheme(base_url, host.as_deref(), scheme.as_deref()),
host,
)
}
pub(crate) fn resolve_with_scheme(
base_url: Option<&str>,
host_header: Option<&str>,
scheme: Option<&str>,
) -> Self {
if let Some(raw) = base_url.map(|b| b.trim().trim_end_matches('/'))
&& let Ok(url) = url::Url::parse(raw)
&& let Some(host) = url.host_str()
&& matches!(url.scheme(), "http" | "https")
&& url.username().is_empty()
&& url.password().is_none()
&& url.query().is_none()
&& url.fragment().is_none()
{
let base = url.as_str().trim_end_matches('/').to_owned();
return Self {
base,
host: host.to_ascii_lowercase(),
configured: true,
};
}
let authority = host_header
.map(str::trim)
.filter(|h| valid_authority(h))
.map_or_else(|| "localhost".to_owned(), str::to_ascii_lowercase);
let host = authority_host(&authority).to_owned();
let loopback = host == "localhost"
|| host.ends_with(".localhost")
|| host == "[::1]"
|| host.starts_with("127.");
let scheme = match scheme {
Some(s) if s.eq_ignore_ascii_case("http") => "http",
Some(s) if s.eq_ignore_ascii_case("https") => "https",
_ if loopback => "http",
_ => "https",
};
Self {
base: format!("{scheme}://{authority}"),
host,
configured: false,
}
}
pub(crate) fn url(&self, path: &str) -> String {
format!("{}{path}", self.base)
}
#[must_use]
pub fn signing_authority(&self) -> String {
let authority = self.authority().to_ascii_lowercase();
let default_port = if self.base.starts_with("https://") {
":443"
} else {
":80"
};
authority
.strip_suffix(default_port)
.map_or_else(|| authority.clone(), str::to_owned)
}
#[must_use]
pub(crate) fn is_request_authority(&self, host: Option<&str>) -> bool {
let Some(host) = host.map(|h| h.trim().to_ascii_lowercase()) else {
return false;
};
let default_port = if self.base.starts_with("https://") {
":443"
} else {
":80"
};
host.strip_suffix(default_port).unwrap_or(&host) == self.signing_authority()
}
#[must_use]
pub fn authority(&self) -> &str {
let rest = self
.base
.split_once("://")
.map_or(self.base.as_str(), |(_, r)| r);
rest.split('/').next().unwrap_or(rest)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Document {
pub content_type: &'static str,
pub headers: Vec<(&'static str, String)>,
pub body: String,
}
#[must_use]
pub fn render(facts: &SiteFacts, origin: &Origin, path: &str) -> Option<Document> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
render_at(facts, origin, path, now)
}
#[must_use]
pub fn render_at(facts: &SiteFacts, origin: &Origin, path: &str, now: u64) -> Option<Document> {
let mut doc = render_inner(facts, origin, path, now)?;
if !origin.configured {
doc.headers.retain(|(name, _)| *name != "cache-control");
doc.headers.push(("cache-control", "no-store".to_owned()));
}
Some(doc)
}
#[must_use]
pub fn is_document_path(path: &str) -> bool {
path.starts_with("/.well-known/")
|| path.ends_with("/server-card")
|| matches!(
path,
LLMS_TXT_PATH
| AUTH_MD_PATH
| ROBOTS_TXT_PATH
| SITEMAP_PATH
| super::webmcp::WEBMCP_JS_PATH
)
}
pub const ROBOTS_TXT_PATH: &str = "/robots.txt";
pub const SITEMAP_PATH: &str = "/sitemap.xml";
fn sitemap(facts: &SiteFacts, origin: &Origin) -> Document {
use std::fmt::Write as _;
let mut body = String::from(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n",
);
let mut paths: Vec<&str> = if facts.no_home {
Vec::new()
} else if facts.home_paths.is_empty() {
vec!["/"]
} else {
facts.home_paths.iter().map(String::as_str).collect()
};
paths.retain(|p| !facts.noindex_homes.iter().any(|n| n == p));
for page in &facts.pages {
if !paths.contains(&page.path.as_str()) {
paths.push(&page.path);
}
}
for path in paths {
let loc = origin
.url(path)
.replace('&', "&")
.replace('<', "<")
.replace('>', ">");
let _ = writeln!(body, " <url><loc>{loc}</loc></url>");
}
body.push_str("</urlset>\n");
Document {
content_type: "application/xml",
headers: Vec::new(),
body,
}
}
fn render_inner(facts: &SiteFacts, origin: &Origin, path: &str, now: u64) -> Option<Document> {
match path {
SITEMAP_PATH => facts.sitemap.then(|| sitemap(facts, origin)),
ROBOTS_TXT_PATH => facts.robots_txt.as_ref().map(|body| Document {
content_type: "text/plain; charset=utf-8",
headers: Vec::new(),
body: body.clone(),
}),
super::webmcp::WEBMCP_JS_PATH => Some(Document {
content_type: "text/javascript; charset=utf-8",
headers: vec![("cache-control", "public, max-age=300".to_owned())],
body: super::webmcp::script(facts, &facts.csrf_header),
}),
super::commerce::UCP_PATH => facts.commerce.ucp.as_ref().map(|u| cors_json(u.as_json())),
super::commerce::ACP_PATH => facts.commerce.acp.as_ref().map(|a| cors_json(a.as_json())),
super::web_bot_auth::DIRECTORY_PATH => {
let key = facts.web_bot_auth.as_ref()?;
let body = super::web_bot_auth::directory_json(std::slice::from_ref(key));
let digest = super::web_bot_auth::content_digest(body.as_bytes());
let mut headers = vec![
("content-digest", digest.clone()),
("cache-control", "public, max-age=300".to_owned()),
];
if origin.configured {
let (input, signature) = super::web_bot_auth::sign_directory(
key,
&origin.signing_authority(),
&digest,
now,
);
headers.push(("signature-input", input));
headers.push(("signature", signature));
}
Some(Document {
content_type: super::web_bot_auth::DIRECTORY_MEDIA_TYPE,
headers,
body,
})
}
LLMS_TXT_PATH => llms_txt(facts, origin),
API_CATALOG_PATH => api_catalog(facts, origin),
SERVER_CARD_PATH => server_card(facts, origin),
SKILLS_INDEX_PATH => skills_index(facts, origin),
super::AI_CATALOG_PATH | ARD_PATH => Some(ard_manifest(facts, origin)),
OAUTH_RESOURCE_PATH => protected_resource(facts, origin),
OAUTH_SERVER_PATH => authorization_server(facts, origin),
AUTH_MD_PATH => auth_md(facts, origin),
_ => {
if let Some(name) = path
.strip_prefix("/.well-known/agent-skills/")
.and_then(|rest| rest.strip_suffix("/SKILL.md"))
{
return all_skills(facts, origin)
.into_iter()
.find(|s| s.name == name)
.map(|s| Document {
content_type: MARKDOWN,
headers: Vec::new(),
body: s.to_skill_md(),
});
}
let mcp = facts.mcp.as_ref()?;
let mut card = (path == server_card_path(&mcp.path))
.then(|| server_card(facts, origin))
.flatten()?;
card.content_type = "application/mcp-server-card+json";
Some(card)
}
}
}
#[must_use]
pub fn homepage_link_header(facts: &SiteFacts, home: &str, base_path: &str) -> String {
let at = |path: &str| format!("{base_path}{path}");
let mut links = Vec::new();
if has_api(facts) {
links.push(format!(
"<{}>; rel=\"api-catalog\"; type=\"application/linkset+json\"",
at(API_CATALOG_PATH)
));
}
if let Some(api) = &facts.openapi {
links.push(format!(
"<{}>; rel=\"service-desc\"; type=\"application/vnd.oai.openapi+json\"",
at(&api.json_path)
));
if let Some(docs) = &api.docs_path {
links.push(format!(
"<{}>; rel=\"service-doc\"; type=\"text/html\"",
at(docs)
));
}
}
let describedby = if facts.llms_txt {
(LLMS_TXT_PATH, "text/plain")
} else {
(super::AI_CATALOG_PATH, "application/ai-catalog+json")
};
links.push(format!(
"<{}>; rel=\"describedby\"; type=\"{}\"",
at(describedby.0),
describedby.1
));
links.push(format!(
"<{}>; rel=\"ai-catalog\"; type=\"application/ai-catalog+json\"",
at(super::AI_CATALOG_PATH)
));
links.push(format!(
"<{}>; rel=\"ard\"; type=\"application/ai-catalog+json\"",
at(ARD_PATH)
));
if facts.markdown {
links.push(format!(
"<{}>; rel=\"alternate\"; type=\"text/markdown\"",
at(home)
));
}
links.join(", ")
}
const MARKDOWN: &str = "text/markdown; charset=utf-8";
const JSON: &str = "application/json";
const fn has_api(facts: &SiteFacts) -> bool {
facts.mcp.is_some() || facts.openapi.is_some()
}
fn display_name(facts: &SiteFacts, origin: &Origin) -> String {
facts
.name
.clone()
.filter(|n| !n.trim().is_empty())
.unwrap_or_else(|| origin.host.clone())
}
fn json_body(value: &Value) -> String {
serde_json::to_string_pretty(value).unwrap_or_else(|_| "{}".to_owned())
}
fn cors_json(value: &Value) -> Document {
Document {
content_type: JSON,
headers: vec![
("access-control-allow-origin", "*".to_owned()),
("access-control-allow-methods", "GET, HEAD".to_owned()),
(
"access-control-allow-headers",
"Content-Type, If-None-Match".to_owned(),
),
("access-control-expose-headers", "ETag".to_owned()),
("cache-control", "public, max-age=3600".to_owned()),
],
body: json_body(value),
}
}
fn llms_txt(facts: &SiteFacts, origin: &Origin) -> Option<Document> {
if !facts.llms_txt {
return None;
}
let text = |s: &str| super::markdown::escape_inline(&one_line(s));
let dest = |path: &str| super::markdown::link_destination(&origin.url(path));
let mut out = format!("# {}\n", text(&display_name(facts, origin)));
if let Some(d) = facts
.description
.as_deref()
.filter(|d| !d.trim().is_empty())
{
let _ = writeln!(out, "\n> {}", text(d));
}
if facts.markdown {
out.push_str("\nSend `Accept: text/markdown` to get any page of this site as Markdown.\n");
}
if !facts.pages.is_empty() {
out.push_str("\n## Pages\n\n");
for page in &facts.pages {
let _ = write!(out, "- [{}]({})", text(&page.title), dest(&page.path));
if let Some(d) = page.description.as_deref().filter(|d| !d.trim().is_empty()) {
let _ = write!(out, ": {}", text(d));
}
out.push('\n');
}
}
out.push_str("\n## Agents\n\n");
if let Some(mcp) = &facts.mcp {
let _ = writeln!(
out,
"- [MCP server]({}): Streamable HTTP endpoint.",
dest(&mcp.path)
);
}
if has_api(facts) {
let _ = writeln!(
out,
"- [API catalog]({}): RFC 9727 list of APIs.",
dest(API_CATALOG_PATH)
);
}
if let Some(api) = &facts.openapi {
let _ = writeln!(
out,
"- [OpenAPI]({}): API description.",
dest(&api.json_path)
);
}
if !all_skills(facts, origin).is_empty() {
let _ = writeln!(
out,
"- [Agent skills]({}): skills for this site.",
dest(SKILLS_INDEX_PATH)
);
}
if auth_md_applies(facts, origin) {
let _ = writeln!(
out,
"- [auth.md]({}): how agents get credentials.",
dest(AUTH_MD_PATH)
);
}
let _ = writeln!(
out,
"- [Resource manifest]({}): ARD list of agent resources.",
dest(super::AI_CATALOG_PATH)
);
let _ = writeln!(out, "- [Sitemap]({}): all pages.", dest("/sitemap.xml"));
Some(Document {
content_type: "text/plain; charset=utf-8",
headers: Vec::new(),
body: out,
})
}
fn one_line(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ")
}
fn api_catalog(facts: &SiteFacts, origin: &Origin) -> Option<Document> {
if !has_api(facts) {
return None;
}
let status = facts
.health_path
.as_ref()
.map(|p| json!([{ "href": origin.url(p), "type": "application/json" }]));
let mut linkset = Vec::new();
if let Some(api) = &facts.openapi {
let mut entry = json!({
"anchor": origin.url("/"),
"service-desc": [{
"href": origin.url(&api.json_path),
"type": "application/vnd.oai.openapi+json",
}],
});
if let Some(docs) = &api.docs_path {
entry["service-doc"] = json!([{ "href": origin.url(docs), "type": "text/html" }]);
}
if let Some(status) = &status {
entry["status"] = status.clone();
}
linkset.push(entry);
}
if let Some(mcp) = &facts.mcp {
let mut entry = json!({
"anchor": origin.url(&mcp.path),
"service-desc": [{
"href": origin.url(&server_card_path(&mcp.path)),
"type": "application/mcp-server-card+json",
}],
});
if let Some(status) = &status {
entry["status"] = status.clone();
}
linkset.push(entry);
}
let mut doc = cors_json(&json!({ "linkset": linkset }));
doc.content_type =
"application/linkset+json; profile=\"https://www.rfc-editor.org/info/rfc9727\"";
doc.headers.push((
"link",
format!("<{}>; rel=\"api-catalog\"", origin.url(API_CATALOG_PATH)),
));
Some(doc)
}
const MCP_PROTOCOL_VERSIONS: &[&str] = &["2026-07-28", "2025-06-18", "2025-03-26", "2024-11-05"];
const MCP_DEFAULT_PROTOCOL_VERSION: &str = "2025-06-18";
fn server_card(facts: &SiteFacts, origin: &Origin) -> Option<Document> {
let mcp = facts.mcp.as_ref()?;
let display = display_name(facts, origin);
let name = card_name(&origin.host, &display);
let version = facts
.openapi
.as_ref()
.map_or_else(|| "1.0.0".to_owned(), |api| api.version.clone());
let description = truncate_chars(
facts
.description
.as_deref()
.filter(|d| !d.trim().is_empty())
.map_or_else(|| format!("MCP server for {display}"), one_line)
.as_str(),
100,
);
let endpoint = origin.url(&mcp.path);
let mut card = json!({
"$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
"name": name,
"title": truncate_chars(&display, 100),
"version": version,
"description": description,
"websiteUrl": origin.url("/"),
"remotes": [{
"type": "streamable-http",
"url": endpoint,
"supportedProtocolVersions": MCP_PROTOCOL_VERSIONS,
}],
"serverInfo": { "name": name, "title": display, "version": version },
"protocolVersion": MCP_DEFAULT_PROTOCOL_VERSION,
"transport": { "type": "streamable-http", "endpoint": endpoint },
"capabilities": { "tools": { "listChanged": false } },
});
if facts.no_home
&& let Some(card) = card.as_object_mut()
{
card.remove("websiteUrl");
}
if mcp.public_tools {
card["tools"] = mcp
.tools
.iter()
.map(|t| {
let mut tool = json!({
"name": t.name,
"inputSchema": t.input_schema,
"annotations": t.annotations,
});
if let Some(d) = &t.description {
tool["description"] = json!(d);
}
tool
})
.collect();
} else {
card["authentication"] = json!({ "required": true });
}
Some(cors_json(&card))
}
fn card_name(host: &str, display: &str) -> String {
let mut labels: Vec<&str> = host.split('.').filter(|l| !l.is_empty()).collect();
labels.reverse();
let mut ns: String = labels
.join(".")
.chars()
.filter(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-'))
.collect();
if ns.is_empty() {
"localhost".clone_into(&mut ns);
}
let mut slug: String = display
.to_ascii_lowercase()
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-') {
c
} else {
'-'
}
})
.collect();
slug = slug.trim_matches('-').to_owned();
if slug.is_empty() {
"app".clone_into(&mut slug);
}
let mut name = format!("{ns}/{slug}");
name.truncate(200);
name
}
fn truncate_chars(s: &str, max: usize) -> String {
if s.chars().count() <= max {
return s.to_owned();
}
let mut out: String = s.chars().take(max - 1).collect();
out.push('…');
out
}
fn all_skills(facts: &SiteFacts, origin: &Origin) -> Vec<AgentSkill> {
let mut skills: Vec<AgentSkill> = Vec::with_capacity(facts.skills.len() + 1);
for skill in &facts.skills {
if !skills.iter().any(|s| s.name == skill.name) {
skills.push(skill.clone());
}
}
if facts.site_guide_skill && !skills.iter().any(|s| s.name == SITE_GUIDE_SKILL) {
skills.push(site_guide(facts, origin));
}
skills
}
fn skills_index(facts: &SiteFacts, origin: &Origin) -> Option<Document> {
let skills = all_skills(facts, origin);
if skills.is_empty() {
return None;
}
let entries: Vec<Value> = skills
.iter()
.map(|s| {
json!({
"name": s.name,
"type": "skill-md",
"description": s.description,
"url": origin.url(&s.path()),
"digest": sha256_digest(s.to_skill_md().as_bytes()),
})
})
.collect();
Some(cors_json(&json!({
"$schema": "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
"skills": entries,
})))
}
fn site_guide(facts: &SiteFacts, origin: &Origin) -> AgentSkill {
let name = one_line(&display_name(facts, origin));
let text = |s: &str| super::markdown::escape_inline(&one_line(s));
let mut body = format!("# Use {}\n\n", text(&name));
if let Some(d) = facts
.description
.as_deref()
.filter(|d| !d.trim().is_empty())
{
let _ = write!(body, "{}\n\n", text(d));
}
body.push_str("## Read\n\n");
if facts.markdown {
body.push_str("- Send `Accept: text/markdown` to get any page as Markdown.\n");
}
let _ = writeln!(body, "- Page list: {}", origin.url("/sitemap.xml"));
if facts.llms_txt {
let _ = writeln!(body, "- Site overview: {}", origin.url(LLMS_TXT_PATH));
}
let _ = writeln!(
body,
"- Content rules: {} (`Content-Signal`). Obey them.",
origin.url("/robots.txt")
);
if has_api(facts) {
body.push_str("\n## Act\n\n");
}
if let Some(mcp) = &facts.mcp {
let _ = writeln!(
body,
"- MCP server (Streamable HTTP): {}",
origin.url(&mcp.path)
);
for tool in mcp.tools.iter().filter(|_| mcp.public_tools) {
match &tool.description {
Some(d) => {
let _ = writeln!(body, " - `{}`: {}", tool.name, text(d));
}
None => {
let _ = writeln!(body, " - `{}`", tool.name);
}
}
}
}
if let Some(api) = &facts.openapi {
let _ = writeln!(
body,
"- API description (OpenAPI): {}",
origin.url(&api.json_path)
);
}
if auth_md_applies(facts, origin) {
let _ = writeln!(
body,
"\n## Authenticate\n\n- Read {} before you call the API.",
origin.url(AUTH_MD_PATH)
);
}
let description = truncate_chars(
&format!("Read and use {name} as an AI agent: Markdown pages, tools, API, and auth."),
1024,
);
AgentSkill {
name: SITE_GUIDE_SKILL.to_owned(),
description,
extra: String::new(),
body,
}
}
fn valid_skill_name(name: &str) -> bool {
(1..=64).contains(&name.len())
&& name
.bytes()
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
&& !name.starts_with('-')
&& !name.ends_with('-')
&& !name.contains("--")
}
pub(super) fn yaml_quote(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\t' => out.push_str("\\t"),
'\r' => out.push_str("\\r"),
c if c.is_control() => {
let _ = write!(out, "\\u{:04X}", u32::from(c));
}
c => out.push(c),
}
}
out.push('"');
out
}
fn other_front_matter(front: &str) -> String {
let mut out = String::new();
let mut keep = true;
for line in front.lines() {
let starts_entry = line
.chars()
.next()
.is_some_and(|c| !c.is_whitespace() && c != '-' && c != '#');
if starts_entry {
let key = line.split_once(':').map_or(line, |(k, _)| k).trim_end();
keep = !matches!(key, "name" | "description");
}
if keep {
out.push_str(line);
out.push('\n');
}
}
let kept = out.trim_end_matches(['\n', ' ', '\t']).len();
out.truncate(kept);
if !out.is_empty() {
out.push('\n');
}
out
}
fn front_matter_field(front: &str, key: &str) -> Option<String> {
let lines: Vec<&str> = front.lines().collect();
let at = lines
.iter()
.position(|line| line.strip_prefix(key).is_some_and(|r| r.starts_with(':')))?;
let value = lines[at][key.len() + 1..].trim();
let mut more: Vec<&str> = lines[at + 1..]
.iter()
.take_while(|l| l.trim().is_empty() || l.starts_with([' ', '\t']))
.copied()
.collect();
if let Some((folded, chomp, indent)) = block_scalar_header(value) {
return Some(block_scalar(&more, folded, chomp, indent));
}
while more.last().is_some_and(|l| l.trim().is_empty()) {
more.pop();
}
let mut lines: Vec<String> = Vec::new();
let mut glue = false;
for line in std::iter::once(value).chain(more.iter().map(|l| l.trim())) {
match lines.last_mut() {
Some(last) if glue => last.push_str(line),
_ => lines.push(line.to_owned()),
}
glue = false;
if value.starts_with('"')
&& let Some(last) = lines.last_mut()
&& (last.len() - last.trim_end_matches('\\').len()) % 2 == 1
{
last.pop();
glue = true;
}
}
let joined = fold_lines(lines.iter().map(String::as_str));
if joined.starts_with(['"', '\'']) {
return Some(yaml_unquote(quoted_scalar(&joined)));
}
let plain = joined.find(" #").map_or(joined.as_str(), |n| &joined[..n]);
Some(plain.trim_end().to_owned())
}
fn quoted_scalar(s: &str) -> &str {
let bytes = s.as_bytes();
let quote = bytes[0];
let mut i = 1;
while i < bytes.len() {
match bytes[i] {
b'\\' if quote == b'"' => i += 2,
b'\'' if quote == b'\'' && bytes.get(i + 1) == Some(&b'\'') => i += 2,
b if b == quote => return &s[..=i],
_ => i += 1,
}
}
s
}
fn block_scalar_header(value: &str) -> Option<(bool, char, Option<usize>)> {
let value = value.split(" #").next().unwrap_or(value).trim_end();
let mut chars = value.chars();
let folded = match chars.next()? {
'>' => true,
'|' => false,
_ => return None,
};
let mut chomp = ' ';
let mut indent = None;
for c in chars {
match c {
'-' | '+' if chomp == ' ' => chomp = c,
'1'..='9' if indent.is_none() => indent = c.to_digit(10).map(|d| d as usize),
_ => return None,
}
}
Some((folded, chomp, indent))
}
fn block_scalar(lines: &[&str], folded: bool, chomp: char, declared: Option<usize>) -> String {
let indent = declared.unwrap_or_else(|| {
lines
.iter()
.find(|l| !l.trim().is_empty())
.map_or(0, |l| l.len() - l.trim_start().len())
});
let mut body: Vec<&str> = lines
.iter()
.map(|l| l.get(indent..).unwrap_or("").trim_end_matches('\r'))
.collect();
let mut trailing = 0;
while body.last().is_some_and(|l| l.trim().is_empty()) {
body.pop();
trailing += 1;
}
let mut text = if folded {
fold_block(&body)
} else {
body.join("\n")
};
if !text.is_empty() {
match chomp {
'-' => {}
'+' => text.push_str(&"\n".repeat(trailing + 1)),
_ => text.push('\n'),
}
}
text
}
fn fold_block(lines: &[&str]) -> String {
let mut out = String::new();
let mut prev_more: Option<bool> = None;
let mut blanks = 0;
for line in lines {
if line.is_empty() {
blanks += 1;
continue;
}
let more = line.starts_with([' ', '\t']);
match prev_more {
Some(prev) if prev || more => out.push_str(&"\n".repeat(blanks + 1)),
Some(_) if blanks == 0 => out.push(' '),
_ => out.push_str(&"\n".repeat(blanks)),
}
out.push_str(line);
prev_more = Some(more);
blanks = 0;
}
out
}
fn fold_lines<'a>(lines: impl Iterator<Item = &'a str>) -> String {
let mut out = String::new();
let mut after_text = false;
for line in lines {
if line.is_empty() {
out.push('\n');
after_text = false;
} else {
if after_text {
out.push(' ');
}
out.push_str(line);
after_text = true;
}
}
out
}
fn yaml_unquote(value: &str) -> String {
let quoted = value.len() >= 2
&& ((value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\'')));
if !quoted {
return value.to_owned();
}
let inner = &value[1..value.len() - 1];
if value.starts_with('\'') {
return inner.replace("''", "'");
}
let mut out = String::with_capacity(inner.len());
let mut chars = inner.chars();
while let Some(c) = chars.next() {
if c != '\\' {
out.push(c);
continue;
}
let decoded = match chars.next() {
None => None,
Some('0') => Some('\0'),
Some('a') => Some('\u{7}'),
Some('b') => Some('\u{8}'),
Some('t' | '\t') => Some('\t'),
Some('n') => Some('\n'),
Some('v') => Some('\u{b}'),
Some('f') => Some('\u{c}'),
Some('r') => Some('\r'),
Some('e') => Some('\u{1b}'),
Some('N') => Some('\u{85}'),
Some('_') => Some('\u{a0}'),
Some('L') => Some('\u{2028}'),
Some('P') => Some('\u{2029}'),
Some(e @ ('x' | 'u' | 'U')) => {
let digits = match e {
'x' => 2,
'u' => 4,
_ => 8,
};
let hex: String = chars.by_ref().take(digits).collect();
u32::from_str_radix(&hex, 16).ok().and_then(char::from_u32)
}
Some(other) => Some(other),
};
out.extend(decoded);
}
out
}
fn ard_manifest(facts: &SiteFacts, origin: &Origin) -> Document {
let display = display_name(facts, origin);
let urn = |ns: &str, name: &str| format!("urn:air:{}:{ns}:{name}", origin.host);
let mut entries = Vec::new();
if let Some(mcp) = &facts.mcp {
entries.push(json!({
"identifier": urn("mcp", "server"),
"displayName": format!("{display} MCP server"),
"type": "application/mcp-server-card+json",
"url": origin.url(&server_card_path(&mcp.path)),
}));
}
for skill in all_skills(facts, origin) {
entries.push(json!({
"identifier": urn("skill", &skill.name),
"displayName": skill.name,
"description": skill.description,
"type": "application/agent-skills+md",
"url": origin.url(&skill.path()),
}));
}
if has_api(facts) {
entries.push(json!({
"identifier": urn("api", "catalog"),
"displayName": format!("{display} API catalog"),
"type": "application/linkset+json",
"url": origin.url(API_CATALOG_PATH),
}));
}
if let Some(api) = &facts.openapi {
entries.push(json!({
"identifier": urn("api", "openapi"),
"displayName": format!("{display} OpenAPI"),
"type": "application/vnd.oai.openapi+json",
"url": origin.url(&api.json_path),
}));
}
if facts.llms_txt {
entries.push(json!({
"identifier": urn("doc", "llms-txt"),
"displayName": format!("{display} llms.txt"),
"type": "text/plain",
"url": origin.url(LLMS_TXT_PATH),
}));
}
if entries.is_empty() {
entries.push(json!({
"identifier": urn("doc", "sitemap"),
"displayName": format!("{display} sitemap"),
"type": "application/xml",
"url": origin.url("/sitemap.xml"),
}));
}
cors_json(&json!({
"specVersion": "1.0",
"host": {
"displayName": display,
},
"entries": entries,
}))
}
fn protected_resource(facts: &SiteFacts, origin: &Origin) -> Option<Document> {
let oauth = &facts.oauth;
if oauth.authorization_servers.is_empty() || facts.prefixed {
return None;
}
let mut prm = json!({
"resource": origin.base,
"resource_name": display_name(facts, origin),
"authorization_servers": oauth.authorization_servers,
"bearer_methods_supported": ["header"],
});
if !oauth.scopes_supported.is_empty() {
prm["scopes_supported"] = json!(oauth.scopes_supported);
}
if auth_md_applies(facts, origin) {
prm["resource_documentation"] = json!(origin.url(AUTH_MD_PATH));
}
Some(cors_json(&prm))
}
fn local_issuer<'a>(facts: &'a SiteFacts, origin: &Origin) -> Option<&'a str> {
if facts.prefixed {
return None;
}
let issuer = facts
.oauth
.authorization_server
.issuer
.as_deref()
.filter(|i| !i.trim().is_empty())?;
let url = url::Url::parse(issuer).ok()?;
let valid = url.scheme() == "https"
&& url.username().is_empty()
&& url.password().is_none()
&& url.query().is_none()
&& url.fragment().is_none();
let rooted = valid && matches!(url.path(), "" | "/");
let same_origin = url::Url::parse(&origin.base).is_ok_and(|base| base.origin() == url.origin());
(rooted && same_origin).then_some(issuer)
}
fn authorization_server(facts: &SiteFacts, origin: &Origin) -> Option<Document> {
let c = &facts.oauth.authorization_server;
let issuer = local_issuer(facts, origin)?;
let mut meta = serde_json::Map::new();
meta.insert("issuer".into(), json!(issuer));
let mut opt = |key: &str, value: &Option<String>| {
if let Some(v) = value.as_deref().filter(|v| !v.trim().is_empty()) {
meta.insert(key.into(), json!(v));
}
};
opt("authorization_endpoint", &c.authorization_endpoint);
opt("token_endpoint", &c.token_endpoint);
opt("jwks_uri", &c.jwks_uri);
opt("registration_endpoint", &c.registration_endpoint);
let response_types = if !c.response_types_supported.is_empty() {
c.response_types_supported.clone()
} else if c.authorization_endpoint.is_some() {
vec!["code".to_owned()]
} else {
Vec::new()
};
if !response_types.is_empty() {
meta.insert("response_types_supported".into(), json!(response_types));
}
for (key, list) in [
("scopes_supported", &c.scopes_supported),
("grant_types_supported", &c.grant_types_supported),
(
"code_challenge_methods_supported",
&c.code_challenge_methods_supported,
),
] {
if !list.is_empty() {
meta.insert(key.into(), json!(list));
}
}
if let Some(identity) = c.agent_identity_endpoint.as_deref() {
let mut agent = json!({
"skill": origin.url(AUTH_MD_PATH),
"identity_endpoint": identity,
"register_uri": identity,
});
if let Some(claim) = c.agent_claim_endpoint.as_deref() {
agent["claim_endpoint"] = json!(claim);
agent["claim_uri"] = json!(claim);
}
if !c.agent_identity_types.is_empty() {
agent["identity_types_supported"] = json!(c.agent_identity_types);
}
if !c.agent_credential_types.is_empty() {
agent["credential_types_supported"] = json!(c.agent_credential_types);
if c.agent_identity_types.iter().any(|t| t == "anonymous") {
agent["anonymous"] =
json!({ "credential_types_supported": c.agent_credential_types });
}
}
if !c.agent_assertion_types.is_empty() {
agent["identity_assertion"] = json!({
"assertion_types_supported": c.agent_assertion_types,
"credential_types_supported": c.agent_credential_types,
});
}
if let Some(revoke) = c.agent_revocation_endpoint.as_deref() {
agent["revocation_uri"] = json!(revoke);
}
if let Some(events) = c.agent_events_endpoint.as_deref() {
agent["events_endpoint"] = json!(events);
}
meta.insert("agent_auth".into(), agent);
}
Some(cors_json(&Value::Object(meta)))
}
fn auth_md_applies(facts: &SiteFacts, origin: &Origin) -> bool {
facts.auth_md.enabled
&& (facts.auth_md.registration_url.is_some()
|| !facts.oauth.authorization_servers.is_empty()
|| local_issuer(facts, origin).is_some())
}
fn auth_md(facts: &SiteFacts, origin: &Origin) -> Option<Document> {
if !auth_md_applies(facts, origin) {
return None;
}
let name = super::markdown::escape_inline(&one_line(&display_name(facts, origin)));
let oauth = &facts.oauth;
let mut md = format!(
"# {name} auth.md\n\nThis page tells AI agents how to get and use a credential for {name}.\n"
);
let _ = write!(
md,
"\n## Audience\n\nAI agents and scripts that call the {name} API"
);
if facts.mcp.is_some() {
md.push_str(" or its MCP server");
}
md.push_str(".\n\n## Discover\n\n");
if let Some(mcp) = &facts.mcp {
let _ = writeln!(md, "- MCP server: {}", origin.url(&mcp.path));
}
if has_api(facts) {
let _ = writeln!(md, "- API catalog: {}", origin.url(API_CATALOG_PATH));
}
if !oauth.authorization_servers.is_empty() && !facts.prefixed {
let _ = writeln!(
md,
"- OAuth protected resource metadata: {}",
origin.url(OAUTH_RESOURCE_PATH)
);
}
if local_issuer(facts, origin).is_some() {
let _ = writeln!(
md,
"- OAuth authorization server metadata: {}",
origin.url(OAUTH_SERVER_PATH)
);
}
let issuer = local_issuer(facts, origin);
let mut servers: Vec<&str> = oauth
.authorization_servers
.iter()
.map(String::as_str)
.collect();
if let Some(issuer) = issuer.filter(|i| !servers.contains(i)) {
servers.push(issuer);
}
let agent = &oauth.authorization_server;
let agent_types: &[String] = if issuer.is_some() {
&agent.agent_credential_types
} else {
&[]
};
let api_key = agent_types.iter().any(|t| t == "api_key")
|| (servers.is_empty() && facts.auth_md.registration_url.is_some());
md.push_str("\n## Pick a method\n\n");
if api_key {
md.push_str("- `api_key`: a bearer API token.\n");
}
if !servers.is_empty() {
let _ = write!(
md,
"- `oauth2`: an access token from {}.",
servers.join(", ")
);
if !oauth.scopes_supported.is_empty() {
let _ = write!(md, " Scopes: `{}`.", oauth.scopes_supported.join("`, `"));
}
md.push('\n');
}
md.push_str("\n## Register\n\n");
let identity = issuer.and(agent.agent_identity_endpoint.as_deref());
let client_registration = issuer.and(agent.registration_endpoint.as_deref());
if let Some(url) = &facts.auth_md.registration_url {
let _ = writeln!(md, "Get a credential at {url}.");
} else if let Some(url) = identity {
let _ = writeln!(md, "Register the agent at {url}.");
} else if let Some(url) = client_registration {
let _ = writeln!(md, "Register an OAuth client at {url}.");
} else if api_key {
md.push_str("Ask the site operator for an API token.\n");
} else {
md.push_str("Get an access token from the authorization server above.\n");
}
if let Some(extra) = facts.auth_md.instructions.as_deref() {
let _ = writeln!(md, "\n{}", extra.trim());
}
md.push_str(
"\n## Use the credential\n\nSend `Authorization: Bearer <token>` on each API request",
);
if facts.mcp.is_some() {
md.push_str(" and on each MCP request");
}
md.push_str(
".\n\n## Errors\n\n- `401`: the credential is missing, expired, or not valid.\n- `403`: the credential is valid but does not allow the action.\n",
);
Some(Document {
content_type: MARKDOWN,
headers: Vec::new(),
body: md,
})
}
fn valid_authority(h: &str) -> bool {
!h.is_empty()
&& h.len() <= 255
&& h.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'-' | b':' | b'[' | b']'))
&& !h.contains("..")
}
fn authority_host(authority: &str) -> &str {
if authority.starts_with('[') {
return authority
.find(']')
.map_or(authority, |end| &authority[..=end]);
}
authority.split(':').next().unwrap_or(authority)
}
#[must_use]
pub fn sha256_digest(bytes: &[u8]) -> String {
format!("sha256:{}", hex::encode(Sha256::digest(bytes)))
}
#[cfg(test)]
mod tests {
use super::*;
fn origin() -> Origin {
Origin::resolve(Some("https://shop.example.com/"), None)
}
fn content_site() -> SiteFacts {
SiteFacts {
name: Some("Shop".to_owned()),
description: Some("Hand-made things.".to_owned()),
health_path: Some("/health".to_owned()),
pages: vec![PageFacts {
path: "/about".to_owned(),
title: "About".to_owned(),
description: Some("Who we are".to_owned()),
}],
site_guide_skill: true,
llms_txt: true,
markdown: true,
auth_md: AuthMdConfig::default(),
..SiteFacts::default()
}
}
fn api_site() -> SiteFacts {
SiteFacts {
mcp: Some(McpFacts {
path: "/mcp".to_owned(),
tools: vec![ToolFacts {
name: "list_todos".to_owned(),
description: Some("List todos".to_owned()),
input_schema: json!({"type": "object"}),
annotations: json!({"readOnlyHint": true}),
}],
public_tools: true,
}),
openapi: Some(OpenApiFacts {
json_path: "/openapi.json".to_owned(),
docs_path: Some("/swagger-ui".to_owned()),
version: "2.1.0".to_owned(),
}),
..content_site()
}
}
fn json_doc(facts: &SiteFacts, path: &str) -> (Document, Value) {
let doc = render(facts, &origin(), path).unwrap_or_else(|| panic!("no {path}"));
let value = serde_json::from_str(&doc.body).expect("valid JSON");
(doc, value)
}
#[test]
fn origin_prefers_base_url() {
let o = Origin::resolve(Some("https://Shop.Example.com:8443/"), Some("evil.test"));
assert_eq!(o.base, "https://shop.example.com:8443");
assert_eq!(o.host, "shop.example.com");
}
#[test]
fn a_base_url_with_a_query_or_fragment_is_not_used() {
for base in [
"https://example.com?tenant=a",
"https://example.com/#top",
"ftp://example.com",
"https://deploy-token@example.com",
"https://user:pass@example.com",
] {
let o = Origin::resolve(Some(base), Some("app.example.com"));
assert!(!o.configured, "{base}");
assert_eq!(o.url("/llms.txt"), "https://app.example.com/llms.txt");
}
}
#[test]
fn origin_falls_back_to_host_header() {
assert_eq!(
Origin::resolve(None, Some("localhost:3000")).base,
"http://localhost:3000"
);
assert_eq!(
Origin::resolve(None, Some("127.0.0.1:3000")).base,
"http://127.0.0.1:3000"
);
let o = Origin::resolve(None, Some("App.Example.com"));
assert_eq!(o.base, "https://app.example.com");
assert_eq!(o.host, "app.example.com");
}
#[test]
fn origin_rejects_a_hostile_host_header() {
for bad in ["a b", "x/../y", "<script>", "a\"b", ""] {
assert_eq!(
Origin::resolve(None, Some(bad)).base,
"http://localhost",
"{bad}"
);
}
assert_eq!(Origin::resolve(None, None).host, "localhost");
assert_eq!(
Origin::resolve_with_scheme(None, Some("app.example"), Some("http")).base,
"http://app.example"
);
assert_eq!(
Origin::resolve_with_scheme(None, Some("localhost:3000"), Some("HTTPS")).base,
"https://localhost:3000"
);
assert_eq!(
Origin::resolve_with_scheme(None, Some("app.example"), Some("ftp")).base,
"https://app.example"
);
}
#[test]
fn skill_round_trips_through_skill_md() {
let skill = AgentSkill::new("refunds", "Draft refunds.", "# Refunds\n\nSteps.\n").unwrap();
let md = skill.to_skill_md();
assert_eq!(
md,
"---\nname: refunds\ndescription: \"Draft refunds.\"\n---\n\n# Refunds\n\nSteps.\n"
);
assert_eq!(AgentSkill::parse(&md).unwrap(), skill);
}
#[test]
fn skill_parse_keeps_the_other_front_matter() {
let skill = AgentSkill::parse(
"---\nlicense: MIT\nname: a\ndescription: >-\n Does\n things.\n\
compatibility: Needs git\nmetadata:\n author: me\nallowed-tools:\n\
- Bash\n- Read\n\n---\nBody\n",
)
.unwrap();
assert_eq!(
skill.to_skill_md(),
"---\nname: a\ndescription: \"Does things.\"\nlicense: MIT\n\
compatibility: Needs git\nmetadata:\n author: me\nallowed-tools:\n\
- Bash\n- Read\n---\n\nBody\n"
);
assert_eq!(AgentSkill::parse(&skill.to_skill_md()).unwrap(), skill);
}
#[test]
fn skill_parse_reads_plain_and_quoted_front_matter() {
let skill = AgentSkill::parse(
"---\nname: a-b\ndescription: Does a thing: well\nlicense: MIT\n---\nBody\n",
)
.unwrap();
assert_eq!(skill.name(), "a-b");
assert_eq!(skill.description(), "Does a thing: well");
assert!(AgentSkill::parse("no front matter").is_err());
}
#[test]
fn skill_parse_reads_block_and_multi_line_scalars() {
let parse = |front: &str| {
AgentSkill::parse(&format!("---\nname: a\n{front}\nlicense: MIT\n---\nBody\n"))
.unwrap()
.description()
.to_owned()
};
assert_eq!(
parse("description: >-\n Folded\n text.\n\n Next."),
"Folded text.\nNext."
);
assert_eq!(parse("description: |\n one\n two"), "one\ntwo\n");
assert_eq!(parse("description: |- # note\n kept"), "kept");
assert_eq!(
parse("description: >-\n first\n indented\n last"),
"first\n indented\nlast"
);
assert_eq!(
parse("description: |2-\n deep\n shallow"),
" deep\nshallow"
);
assert_eq!(
parse("description: A long\n plain value # note"),
"A long plain value"
);
assert_eq!(
parse("description: \"A long\n quoted one\""),
"A long quoted one"
);
assert_eq!(parse("description: Plain\n\n"), "Plain");
assert_eq!(
parse("description: \"Draft refunds.\" # note"),
"Draft refunds."
);
assert_eq!(
parse("description: \"Say \\\"hi\\\"\" # note"),
"Say \"hi\""
);
assert_eq!(parse("description: 'It''s here' # note"), "It's here");
assert_eq!(parse(r#"description: "First\tSecond""#), "First\tSecond");
assert_eq!(
parse("description: \"Draft\\\n refunds.\""),
"Draftrefunds."
);
assert_eq!(parse("description: \"Ends \\\\\n here\""), "Ends \\ here");
assert_eq!(
parse(r#"description: "\u263A \x41\U0001F600 \\ \/""#),
"\u{263A} A\u{1F600} \\ /"
);
for text in ["One\tTwo", "First\rSecond", "A\u{8}B\u{1b}C"] {
let skill = AgentSkill::new("a", text, "Body").unwrap();
assert_eq!(
AgentSkill::parse(&skill.to_skill_md()).unwrap(),
skill,
"{text:?}"
);
}
}
#[test]
fn skill_names_follow_the_rfc() {
for bad in ["", "A", "a_b", "-a", "a-", "a--b", &"a".repeat(65)] {
assert!(
matches!(
AgentSkill::new(bad, "d", ""),
Err(AgentSkillError::InvalidName(_))
),
"{bad:?}"
);
}
assert!(AgentSkill::new("a1-b2", "d", "").is_ok());
assert!(matches!(
AgentSkill::new("a", "", ""),
Err(AgentSkillError::InvalidDescription(_))
));
}
#[test]
fn skills_index_lists_digests_of_the_served_bytes() {
let mut facts = content_site();
facts.skills = vec![AgentSkill::new("refunds", "Draft refunds.", "Body").unwrap()];
let (doc, index) = json_doc(&facts, SKILLS_INDEX_PATH);
assert_eq!(doc.content_type, "application/json");
assert_eq!(
index["$schema"],
"https://schemas.agentskills.io/discovery/0.2.0/schema.json"
);
let skills = index["skills"].as_array().unwrap();
assert_eq!(skills.len(), 2, "user skill + site-guide: {index}");
for entry in skills {
let url = entry["url"].as_str().unwrap();
assert_eq!(entry["type"], "skill-md");
let path = url
.strip_prefix("https://shop.example.com")
.expect("absolute");
let served = render(&facts, &origin(), path).expect("artifact served");
assert_eq!(served.content_type, "text/markdown; charset=utf-8");
assert_eq!(entry["digest"], sha256_digest(served.body.as_bytes()));
}
}
#[test]
fn skill_urls_keep_the_base_url_path() {
let facts = content_site();
let shop = Origin::resolve(Some("https://example.com/shop"), None);
let doc = render(&facts, &shop, SKILLS_INDEX_PATH).unwrap();
let index: Value = serde_json::from_str(&doc.body).unwrap();
for entry in index["skills"].as_array().unwrap() {
let url = entry["url"].as_str().unwrap();
assert!(
url.starts_with("https://example.com/shop/.well-known/agent-skills/"),
"{url}"
);
}
}
#[test]
fn a_duplicate_skill_name_is_published_once() {
let mut facts = content_site();
facts.skills = vec![
AgentSkill::new("refunds", "First.", "One").unwrap(),
AgentSkill::new("refunds", "Second.", "Two").unwrap(),
];
let (_, index) = json_doc(&facts, SKILLS_INDEX_PATH);
let refunds: Vec<&Value> = index["skills"]
.as_array()
.unwrap()
.iter()
.filter(|e| e["name"] == "refunds")
.collect();
assert_eq!(refunds.len(), 1, "{index}");
let url = refunds[0]["url"].as_str().unwrap();
let path = url.strip_prefix("https://shop.example.com").unwrap();
let served = render(&facts, &origin(), path).unwrap();
assert_eq!(refunds[0]["digest"], sha256_digest(served.body.as_bytes()));
}
#[test]
fn a_trailing_slash_mcp_mount_gets_a_clean_card_path() {
assert_eq!(server_card_path("/"), "/server-card");
assert_eq!(server_card_path("/mcp/"), "/mcp/server-card");
let mut facts = api_site();
facts.mcp.as_mut().unwrap().path = "/mcp/".to_owned();
assert!(render(&facts, &origin(), "/mcp/server-card").is_some());
let (_, catalog) = json_doc(&facts, API_CATALOG_PATH);
assert!(!catalog.to_string().contains("//server-card"), "{catalog}");
}
#[test]
fn the_fallback_sitemap_lists_home_and_the_titled_pages() {
let mut facts = content_site();
assert!(render(&facts, &origin(), SITEMAP_PATH).is_none());
facts.sitemap = true;
let doc = render(&facts, &origin(), SITEMAP_PATH).unwrap();
assert_eq!(doc.content_type, "application/xml");
assert!(
doc.body.contains("<loc>https://shop.example.com/</loc>"),
"{}",
doc.body
);
assert!(
doc.body
.contains("<loc>https://shop.example.com/about</loc>"),
"{}",
doc.body
);
}
#[test]
fn a_locale_prefixed_sitemap_lists_each_locale_home() {
let mut facts = content_site();
facts.sitemap = true;
facts.home_paths = vec!["/en".to_owned(), "/fr".to_owned()];
let body = render(&facts, &origin(), SITEMAP_PATH).unwrap().body;
assert!(
body.contains("<loc>https://shop.example.com/en</loc>"),
"{body}"
);
assert!(
body.contains("<loc>https://shop.example.com/fr</loc>"),
"{body}"
);
assert!(
!body.contains("<loc>https://shop.example.com/</loc>"),
"{body}"
);
}
#[test]
fn site_guide_points_at_what_the_app_has() {
let mut facts = api_site();
facts.auth_md.registration_url = Some("https://shop.example.com/tokens".to_owned());
let doc = render(
&facts,
&origin(),
"/.well-known/agent-skills/site-guide/SKILL.md",
)
.unwrap();
let skill = AgentSkill::parse(&doc.body).unwrap();
assert_eq!(skill.name(), SITE_GUIDE_SKILL);
for want in [
"Accept: text/markdown",
"https://shop.example.com/mcp",
"`list_todos`",
"https://shop.example.com/openapi.json",
"https://shop.example.com/auth.md",
] {
assert!(doc.body.contains(want), "missing {want}:\n{}", doc.body);
}
}
#[test]
fn no_skills_means_no_index() {
let mut facts = content_site();
facts.site_guide_skill = false;
assert!(render(&facts, &origin(), SKILLS_INDEX_PATH).is_none());
}
#[test]
fn llms_txt_names_the_site_and_lists_pages_and_agent_entry_points() {
let doc = render(&api_site(), &origin(), LLMS_TXT_PATH).unwrap();
assert_eq!(doc.content_type, "text/plain; charset=utf-8");
assert!(
doc.body.starts_with("# Shop\n\n> Hand-made things.\n"),
"{}",
doc.body
);
for want in [
"- [About](https://shop.example.com/about): Who we are",
"(https://shop.example.com/mcp)",
"(https://shop.example.com/.well-known/api-catalog)",
"(https://shop.example.com/openapi.json)",
"Accept: text/markdown",
] {
assert!(doc.body.contains(want), "missing {want}:\n{}", doc.body);
}
}
#[test]
fn the_site_guide_escapes_metadata() {
let mut facts = api_site();
facts.name = Some("".to_owned());
let skill = site_guide(&facts, &origin());
let md = skill.to_skill_md();
assert!(
md.contains(r"# Use !\[logo\](https://tracker.example/x)"),
"{md}"
);
}
#[test]
fn llms_txt_titles_are_text() {
let mut facts = api_site();
facts.pages[0].title = "Docs](https://evil.example) [More".to_owned();
let doc = render(&facts, &origin(), LLMS_TXT_PATH).unwrap();
assert!(
doc.body
.contains(r"- [Docs\](https://evil.example) \[More](https://shop.example.com/"),
"{}",
doc.body
);
}
#[test]
fn llms_txt_links_reach_every_route() {
let mut facts = api_site();
facts.pages[0].path = "/docs)v2 x".to_owned();
let doc = render(&facts, &origin(), LLMS_TXT_PATH).unwrap();
assert!(
doc.body
.contains("](https://shop.example.com/docs%29v2%20x)"),
"{}",
doc.body
);
}
#[test]
fn llms_txt_uses_the_host_without_a_name_and_can_be_disabled() {
let mut facts = content_site();
facts.name = None;
let doc = render(&facts, &origin(), LLMS_TXT_PATH).unwrap();
assert!(doc.body.starts_with("# shop.example.com\n"), "{}", doc.body);
facts.llms_txt = false;
assert!(render(&facts, &origin(), LLMS_TXT_PATH).is_none());
}
#[test]
fn api_catalog_is_a_linkset_for_openapi_and_mcp() {
let (doc, catalog) = json_doc(&api_site(), API_CATALOG_PATH);
assert_eq!(
doc.content_type,
"application/linkset+json; profile=\"https://www.rfc-editor.org/info/rfc9727\""
);
assert!(
doc.headers.iter().any(|(k, v)| *k == "link"
&& v.contains(
"<https://shop.example.com/.well-known/api-catalog>; rel=\"api-catalog\""
)),
"HEAD needs the api-catalog Link: {:?}",
doc.headers
);
let set = catalog["linkset"].as_array().unwrap();
let api = set
.iter()
.find(|e| e["service-desc"][0]["href"] == "https://shop.example.com/openapi.json")
.unwrap_or_else(|| panic!("no OpenAPI entry: {catalog}"));
assert_eq!(
api["service-doc"][0]["href"],
"https://shop.example.com/swagger-ui"
);
assert_eq!(api["status"][0]["href"], "https://shop.example.com/health");
let mcp = set
.iter()
.find(|e| e["anchor"] == "https://shop.example.com/mcp")
.unwrap_or_else(|| panic!("no MCP entry: {catalog}"));
assert_eq!(
mcp["service-desc"][0]["href"],
"https://shop.example.com/mcp/server-card"
);
}
#[test]
fn content_site_has_no_api_catalog() {
assert!(render(&content_site(), &origin(), API_CATALOG_PATH).is_none());
}
#[test]
fn server_card_is_served_at_both_paths_in_hybrid_form() {
for (path, media) in [
(SERVER_CARD_PATH, "application/json"),
("/mcp/server-card", "application/mcp-server-card+json"),
] {
let (doc, card) = json_doc(&api_site(), path);
assert_eq!(doc.content_type, media, "{path}");
assert!(
doc.headers
.contains(&("access-control-allow-origin", "*".to_owned()))
);
assert_eq!(card["name"], "com.example.shop/shop");
assert_eq!(card["serverInfo"]["name"], "com.example.shop/shop");
assert_eq!(card["version"], "2.1.0");
assert_eq!(card["remotes"][0]["type"], "streamable-http");
assert_eq!(card["remotes"][0]["url"], "https://shop.example.com/mcp");
assert_eq!(
card["remotes"][0]["supportedProtocolVersions"][0],
"2026-07-28"
);
assert_eq!(card["protocolVersion"], "2025-06-18");
assert_eq!(
card["transport"]["endpoint"],
"https://shop.example.com/mcp"
);
assert_eq!(card["tools"][0]["name"], "list_todos");
assert!(card["description"].as_str().unwrap().chars().count() <= 100);
}
}
#[test]
fn public_json_documents_allow_cross_origin_revalidation() {
let mut facts = api_site();
facts.oauth.authorization_servers = vec!["https://auth.example.com".to_owned()];
for path in [
SERVER_CARD_PATH,
ARD_PATH,
SKILLS_INDEX_PATH,
OAUTH_RESOURCE_PATH,
API_CATALOG_PATH,
] {
let doc = render(&facts, &origin(), path).unwrap();
for want in [
("access-control-allow-origin", "*"),
("access-control-allow-methods", "GET, HEAD"),
(
"access-control-allow-headers",
"Content-Type, If-None-Match",
),
("access-control-expose-headers", "ETag"),
] {
let count = doc.headers.iter().filter(|(k, _)| *k == want.0).count();
assert_eq!(count, 1, "{path}: one {}", want.0);
assert!(
doc.headers.contains(&(want.0, want.1.to_owned())),
"{path}: {:?}",
doc.headers
);
}
}
}
#[test]
fn server_card_hides_tools_behind_secure_mcp() {
let mut facts = api_site();
let mcp = facts.mcp.as_mut().unwrap();
mcp.public_tools = false;
mcp.tools.clear();
let (_, card) = json_doc(&facts, SERVER_CARD_PATH);
assert!(card.get("tools").is_none(), "{card}");
assert_eq!(card["authentication"]["required"], true);
assert!(card["authentication"].get("schemes").is_none(), "{card}");
}
#[test]
fn no_mcp_means_no_server_card() {
assert!(render(&content_site(), &origin(), SERVER_CARD_PATH).is_none());
}
#[test]
fn ard_manifest_lists_every_resource_at_both_names() {
for path in [super::super::AI_CATALOG_PATH, ARD_PATH] {
let (doc, ard) = json_doc(&api_site(), path);
assert_eq!(doc.content_type, "application/json");
assert!(
doc.headers
.contains(&("access-control-allow-origin", "*".to_owned()))
);
assert_eq!(ard["specVersion"], "1.0");
assert_eq!(ard["host"]["displayName"], "Shop");
assert!(ard["host"].get("identifier").is_none(), "{ard}");
let entries = ard["entries"].as_array().unwrap();
let types: Vec<&str> = entries
.iter()
.map(|e| e["type"].as_str().unwrap())
.collect();
for want in [
"application/mcp-server-card+json",
"application/agent-skills+md",
"application/linkset+json",
"application/vnd.oai.openapi+json",
] {
assert!(types.contains(&want), "missing {want}: {types:?}");
}
for e in entries {
assert!(
e["identifier"]
.as_str()
.unwrap()
.starts_with("urn:air:shop.example.com:"),
"{e}"
);
assert!(e["displayName"].is_string(), "{e}");
assert!(e["url"].is_string() != e.get("data").is_some(), "{e}");
}
}
}
#[test]
fn content_site_ard_still_has_entries() {
let (_, ard) = json_doc(&content_site(), ARD_PATH);
assert!(!ard["entries"].as_array().unwrap().is_empty(), "{ard}");
}
#[test]
fn protected_resource_metadata_needs_authorization_servers() {
let mut facts = api_site();
assert!(render(&facts, &origin(), OAUTH_RESOURCE_PATH).is_none());
facts.oauth.authorization_servers = vec!["https://auth.example.com".to_owned()];
let (_, prm) = json_doc(&facts, OAUTH_RESOURCE_PATH);
assert_eq!(prm["resource"], "https://shop.example.com");
assert_eq!(prm["authorization_servers"][0], "https://auth.example.com");
assert_eq!(prm["bearer_methods_supported"][0], "header");
assert!(
prm.get("scopes_supported").is_none(),
"empty arrays are omitted"
);
}
#[test]
fn authorization_server_metadata_needs_an_issuer() {
let mut facts = api_site();
assert!(render(&facts, &origin(), OAUTH_SERVER_PATH).is_none());
let as_cfg = &mut facts.oauth.authorization_server;
as_cfg.issuer = Some("https://shop.example.com".to_owned());
as_cfg.token_endpoint = Some("https://shop.example.com/oauth/token".to_owned());
as_cfg.authorization_endpoint = Some("https://shop.example.com/oauth/authorize".to_owned());
as_cfg.agent_credential_types = vec!["api_key".to_owned()];
as_cfg.agent_identity_endpoint = Some("https://shop.example.com/agent/identity".to_owned());
as_cfg.agent_identity_types = vec!["anonymous".to_owned()];
let (_, meta) = json_doc(&facts, OAUTH_SERVER_PATH);
assert_eq!(meta["issuer"], "https://shop.example.com");
assert_eq!(meta["response_types_supported"][0], "code");
assert_eq!(
meta["agent_auth"]["skill"],
"https://shop.example.com/auth.md"
);
assert_eq!(
meta["agent_auth"]["identity_endpoint"],
"https://shop.example.com/agent/identity"
);
assert_eq!(
meta["agent_auth"]["register_uri"],
meta["agent_auth"]["identity_endpoint"]
);
assert_eq!(
meta["agent_auth"]["anonymous"]["credential_types_supported"][0],
"api_key"
);
assert!(meta.get("jwks_uri").is_none());
for bad in [
"https://shop.example.com?tenant=a",
"https://shop.example.com#x",
"https://token@shop.example.com",
"https://user:pass@shop.example.com",
] {
facts.oauth.authorization_server.issuer = Some(bad.to_owned());
assert!(
render(&facts, &origin(), OAUTH_SERVER_PATH).is_none(),
"{bad}"
);
}
facts.oauth.authorization_server.issuer = Some("http://localhost:3000".to_owned());
let local = Origin::resolve(Some("http://localhost:3000"), None);
assert!(render(&facts, &local, OAUTH_SERVER_PATH).is_none());
}
#[test]
fn the_request_authority_must_match_the_signed_one() {
let o = Origin::resolve(Some("https://Example.com"), None);
assert!(o.is_request_authority(Some("example.com")));
assert!(o.is_request_authority(Some("Example.com:443")));
assert!(!o.is_request_authority(Some("www.example.com")));
assert!(!o.is_request_authority(Some("example.com:8443")));
assert!(!o.is_request_authority(None));
let o = Origin::resolve(Some("HTTPS://example.com:443"), None);
assert_eq!(o.signing_authority(), "example.com");
assert!(o.is_request_authority(Some("example.com")));
let o = Origin::resolve(Some("https://bücher.example/shop/"), None);
assert_eq!(o.base, "https://xn--bcher-kva.example/shop");
assert_eq!(o.signing_authority(), "xn--bcher-kva.example");
assert!(o.is_request_authority(Some("xn--bcher-kva.example")));
}
#[test]
fn a_server_card_names_no_website_without_a_home() {
let mut facts = api_site();
let (_, card) = json_doc(&facts, "/.well-known/mcp/server-card.json");
assert_eq!(card["websiteUrl"], "https://shop.example.com/");
facts.no_home = true;
let (_, card) = json_doc(&facts, "/.well-known/mcp/server-card.json");
assert!(card.get("websiteUrl").is_none(), "{card}");
}
#[test]
fn a_site_without_a_home_route_lists_no_home() {
let mut facts = content_site();
facts.sitemap = true;
facts.no_home = true;
let doc = render(&facts, &origin(), SITEMAP_PATH).unwrap();
assert!(
!doc.body.contains("<loc>https://shop.example.com/</loc>"),
"{}",
doc.body
);
}
#[test]
fn only_the_noindex_home_leaves_the_sitemap() {
let mut facts = content_site();
facts.sitemap = true;
facts.home_paths = vec!["/en".to_owned(), "/fr".to_owned(), "/".to_owned()];
facts.noindex_homes = vec!["/".to_owned()];
let body = render(&facts, &origin(), SITEMAP_PATH).unwrap().body;
assert!(
body.contains("<loc>https://shop.example.com/en</loc>"),
"{body}"
);
assert!(
body.contains("<loc>https://shop.example.com/fr</loc>"),
"{body}"
);
assert!(
!body.contains("<loc>https://shop.example.com/</loc>"),
"{body}"
);
}
#[test]
fn a_noindex_home_is_not_in_the_fallback_sitemap() {
let mut facts = content_site();
facts.sitemap = true;
facts.noindex_homes = vec!["/".to_owned()];
let doc = render(&facts, &origin(), SITEMAP_PATH).unwrap();
assert!(
!doc.body.contains("<loc>https://shop.example.com/</loc>"),
"{}",
doc.body
);
assert!(doc.body.contains("/about</loc>"), "{}", doc.body);
}
#[test]
fn auth_md_needs_an_issuer_this_site_can_publish() {
let mut facts = api_site();
facts.oauth.authorization_server.issuer = Some("https://auth.elsewhere.example".to_owned());
assert!(render(&facts, &origin(), AUTH_MD_PATH).is_none());
facts.oauth.authorization_server.issuer = Some("https://shop.example.com".to_owned());
let doc = render(&facts, &origin(), AUTH_MD_PATH).unwrap();
assert!(
doc.body.contains("OAuth authorization server metadata"),
"{}",
doc.body
);
}
#[test]
fn auth_md_is_served_for_api_sites_with_an_auth_md_heading() {
let mut facts = api_site();
facts.oauth.authorization_servers = vec!["https://auth.example.com".to_owned()];
facts.auth_md.registration_url =
Some("https://shop.example.com/settings/tokens".to_owned());
let doc = render(&facts, &origin(), AUTH_MD_PATH).unwrap();
assert_eq!(doc.content_type, "text/markdown; charset=utf-8");
assert!(doc.body.starts_with("# Shop auth.md\n"), "{}", doc.body);
for want in [
"Authorization: Bearer",
"https://shop.example.com/settings/tokens",
"https://shop.example.com/.well-known/oauth-protected-resource",
"https://auth.example.com",
"https://shop.example.com/mcp",
] {
assert!(doc.body.contains(want), "missing {want}:\n{}", doc.body);
}
}
#[test]
fn content_site_has_no_auth_md() {
assert!(render(&content_site(), &origin(), AUTH_MD_PATH).is_none());
let mut facts = api_site();
facts.auth_md.enabled = false;
assert!(render(&facts, &origin(), AUTH_MD_PATH).is_none());
}
#[test]
fn authorization_server_metadata_stays_honest() {
let mut facts = api_site();
let c = &mut facts.oauth.authorization_server;
c.issuer = Some("https://shop.example.com".to_owned());
let (_, meta) = json_doc(&facts, OAUTH_SERVER_PATH);
assert!(
meta.get("response_types_supported").is_none(),
"no `code` without an authorization endpoint: {meta}"
);
for foreign in ["https://auth.other.com", "https://shop.example.com/tenant"] {
facts.oauth.authorization_server.issuer = Some(foreign.to_owned());
assert!(
render(&facts, &origin(), OAUTH_SERVER_PATH).is_none(),
"{foreign}"
);
}
facts.oauth.authorization_server.issuer = Some("https://auth.example".to_owned());
let api_host = Origin::resolve(None, Some("api.example"));
assert!(render(&facts, &api_host, OAUTH_SERVER_PATH).is_none());
let auth_host = Origin::resolve(None, Some("auth.example"));
assert!(render(&facts, &auth_host, OAUTH_SERVER_PATH).is_some());
}
#[test]
fn auth_md_offers_only_configured_methods() {
let mut facts = api_site();
facts.oauth.authorization_servers = vec!["https://auth.example.com".to_owned()];
let body = render(&facts, &origin(), AUTH_MD_PATH).unwrap().body;
assert!(!body.contains("api_key"), "{body}");
assert!(!body.contains("API token"), "{body}");
assert!(
body.contains("- `oauth2`: an access token from https://auth.example.com."),
"{body}"
);
let mut facts = api_site();
let c = &mut facts.oauth.authorization_server;
c.issuer = Some("https://shop.example.com".to_owned());
c.agent_identity_endpoint = Some("https://shop.example.com/agent/identity".to_owned());
c.agent_credential_types = vec!["api_key".to_owned()];
let body = render(&facts, &origin(), AUTH_MD_PATH).unwrap().body;
assert!(body.contains("- `api_key`"), "{body}");
assert!(
body.contains("- `oauth2`: an access token from https://shop.example.com."),
"{body}"
);
assert!(
body.contains("Register the agent at https://shop.example.com/agent/identity."),
"{body}"
);
let mut facts = api_site();
facts.auth_md.registration_url = Some("https://shop.example.com/tokens".to_owned());
let body = render(&facts, &origin(), AUTH_MD_PATH).unwrap().body;
assert!(body.contains("- `api_key`"), "{body}");
assert!(!body.contains("oauth2"), "{body}");
}
#[test]
fn auth_md_needs_a_concrete_way_to_get_a_credential() {
assert!(render(&api_site(), &origin(), AUTH_MD_PATH).is_none());
}
#[test]
fn host_derived_documents_are_never_publicly_cached() {
let host_origin = Origin::resolve(None, Some("evil.example"));
assert!(!host_origin.configured);
for path in [ARD_PATH, LLMS_TXT_PATH] {
let doc = render(&api_site(), &host_origin, path).unwrap();
let policies: Vec<&String> = doc
.headers
.iter()
.filter(|(n, _)| *n == "cache-control")
.map(|(_, v)| v)
.collect();
assert_eq!(policies, ["no-store"], "{path}: {:?}", doc.headers);
}
let doc = render(&api_site(), &origin(), ARD_PATH).unwrap();
assert!(
doc.headers
.contains(&("cache-control", "public, max-age=3600".to_owned()))
);
}
#[test]
fn robots_txt_is_served_only_when_aeo_owns_it() {
let mut facts = content_site();
assert!(render(&facts, &origin(), ROBOTS_TXT_PATH).is_none());
facts.robots_txt = Some("User-agent: *\nAllow: /\n".to_owned());
let doc = render(&facts, &origin(), ROBOTS_TXT_PATH).unwrap();
assert_eq!(doc.content_type, "text/plain; charset=utf-8");
}
#[test]
fn link_header_keeps_a_counted_relation_without_llms_txt() {
let mut facts = content_site();
facts.llms_txt = false;
let link = homepage_link_header(&facts, "/", "");
assert!(
link.contains("</.well-known/ai-catalog.json>; rel=\"describedby\""),
"{link}"
);
}
#[test]
fn document_paths_are_recognized_cheaply() {
for p in [
"/llms.txt",
"/auth.md",
"/robots.txt",
"/.well-known/ard.json",
"/mcp/server-card",
] {
assert!(is_document_path(p), "{p}");
}
for p in ["/", "/favicon.ico", "/about"] {
assert!(!is_document_path(p), "{p}");
}
}
#[test]
fn webmcp_script_is_always_served() {
let doc = render(&content_site(), &origin(), "/_autumn/webmcp.js").unwrap();
assert_eq!(doc.content_type, "text/javascript; charset=utf-8");
let doc = render(&api_site(), &origin(), "/_autumn/webmcp.js").unwrap();
assert!(doc.body.contains("\"list_todos\""), "{}", doc.body);
}
#[test]
fn key_directory_needs_a_key_and_is_signed() {
let path = super::super::web_bot_auth::DIRECTORY_PATH;
let mut facts = content_site();
assert!(render(&facts, &origin(), path).is_none());
facts.web_bot_auth = Some(
super::super::web_bot_auth::WebBotAuthKey::from_seed_b64(
"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
)
.unwrap(),
);
let unsigned = render_at(&facts, &Origin::resolve(None, Some("x.test")), path, 1).unwrap();
assert!(
!unsigned.headers.iter().any(|(k, _)| *k == "signature"),
"no signature for a request-derived host"
);
let doc = render_at(&facts, &origin(), path, 1_700_000_000).unwrap();
assert!(
doc.headers
.iter()
.any(|(k, v)| *k == "content-digest" && v.starts_with("sha-256=:"))
);
assert_eq!(
doc.content_type,
"application/http-message-signatures-directory+json"
);
let input = &doc
.headers
.iter()
.find(|(k, _)| *k == "signature-input")
.unwrap()
.1;
assert!(input.contains("created=1700000000"), "{input}");
assert!(doc.body.contains("\"OKP\""));
assert_eq!(origin().authority(), "shop.example.com");
}
#[test]
fn homepage_link_header_lists_agent_relations() {
let link = homepage_link_header(&api_site(), "/", "");
for want in [
"</.well-known/api-catalog>; rel=\"api-catalog\"",
"</openapi.json>; rel=\"service-desc\"",
"</swagger-ui>; rel=\"service-doc\"",
"</llms.txt>; rel=\"describedby\"",
"</.well-known/ai-catalog.json>; rel=\"ai-catalog\"",
"</>; rel=\"alternate\"; type=\"text/markdown\"",
] {
assert!(link.contains(want), "missing {want}: {link}");
}
let content = homepage_link_header(&content_site(), "/", "");
assert!(content.contains("rel=\"describedby\""), "{content}");
assert!(!content.contains("api-catalog"), "{content}");
let localized = homepage_link_header(&content_site(), "/fr", "");
assert!(
localized.contains("</fr>; rel=\"alternate\"; type=\"text/markdown\""),
"{localized}"
);
let shop = homepage_link_header(&api_site(), "/", "/shop");
for want in [
"</shop/.well-known/api-catalog>; rel=\"api-catalog\"",
"</shop/openapi.json>; rel=\"service-desc\"",
"</shop/llms.txt>; rel=\"describedby\"",
"</shop/>; rel=\"alternate\"; type=\"text/markdown\"",
] {
assert!(shop.contains(want), "missing {want}: {shop}");
}
}
#[test]
fn unknown_paths_render_nothing() {
for path in [
"/",
"/.well-known/agent-skills/nope/SKILL.md",
"/mcpx/server-card",
] {
assert!(render(&api_site(), &origin(), path).is_none(), "{path}");
}
}
}