#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct OpenApiReport {
pub(crate) sources: Vec<LoadedSource>,
pub(crate) errors: Vec<String>,
pub(crate) warnings: Vec<String>,
}
const PROMPT_TAGS: usize = 6;
const PROMPT_TAG_CHARS: usize = 40;
impl OpenApiReport {
pub(crate) fn prompt_context(&self) -> String {
let sources: Vec<&LoadedSource> = self
.sources
.iter()
.filter(|source| source.read_operations > 0)
.collect();
if sources.is_empty() {
return String::new();
}
let mut text = String::from(
"\n\n# OpenAPI sources\n\nRead-only REST operations, found and called like MCP tools through `tool_search` or `codemode`:\n",
);
for source in sources {
text.push_str(&format!(
"- {}: {} read operations",
source.name, source.read_operations
));
if !source.tags.is_empty() {
let shown: Vec<String> = source
.tags
.iter()
.take(PROMPT_TAGS)
.map(|tag| prompt_tag(tag))
.collect();
let more = if source.tags.len() > PROMPT_TAGS {
", …"
} else {
""
};
text.push_str(&format!(
", {} tags ({}{more})",
source.tags.len(),
shown.join(", ")
));
}
text.push('\n');
}
text
}
}
fn prompt_tag(tag: &str) -> String {
let tag: String = tag
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
.chars()
.filter(|character| !character.is_control())
.collect();
match tag.char_indices().nth(PROMPT_TAG_CHARS) {
Some((cut, _)) => format!("{}…", &tag[..cut]),
None => tag,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct LoadedSource {
pub(crate) name: String,
pub(crate) read_operations: usize,
pub(crate) tags: Vec<String>,
}