bctx-nexus 0.1.6

bctx-nexus — MCP/Nexus gateway with permission enforcement and tool registry
Documentation
use serde_json::Value;
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub struct ToolManifest {
    pub name: String,
    pub description: String,
    pub input_schema: Value,
    pub scope: ToolScope,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolScope {
    ReadOnly,
    FileSystem,
    Shell,
    Network,
    Memory,
    Cloud,
}

pub struct ToolRegistry {
    tools: HashMap<String, ToolManifest>,
}

impl ToolRegistry {
    pub fn new() -> Self {
        Self {
            tools: HashMap::new(),
        }
    }

    pub fn register(&mut self, manifest: ToolManifest) {
        self.tools.insert(manifest.name.clone(), manifest);
    }

    pub fn get(&self, name: &str) -> Option<&ToolManifest> {
        self.tools.get(name)
    }

    pub fn all(&self) -> Vec<&ToolManifest> {
        self.tools.values().collect()
    }

    pub fn default_registry() -> Self {
        let mut r = Self::new();
        for (name, desc, scope) in BUILT_IN_TOOLS {
            r.register(ToolManifest {
                name: name.to_string(),
                description: desc.to_string(),
                input_schema: skill_schema(name),
                scope: *scope,
            });
        }
        r
    }
}

impl Default for ToolRegistry {
    fn default() -> Self {
        Self::default_registry()
    }
}

const BUILT_IN_TOOLS: &[(&str, &str, ToolScope)] = &[
    (
        "sieve",
        "Filter raw output to task-relevant lines",
        ToolScope::ReadOnly,
    ),
    (
        "cartograph",
        "Scan directory → structured project map",
        ToolScope::FileSystem,
    ),
    (
        "chisel",
        "Extract AST symbols from files",
        ToolScope::FileSystem,
    ),
    ("sediment", "Persist facts into Vault", ToolScope::Memory),
    (
        "prism",
        "Full AST parse + index of project directory",
        ToolScope::FileSystem,
    ),
    (
        "compass",
        "Fuse BM25 + graph + Vault into ranked results",
        ToolScope::FileSystem,
    ),
    (
        "condenser",
        "Compress content via Lens stack",
        ToolScope::ReadOnly,
    ),
    (
        "archivist",
        "Query Vault for facts (read-only)",
        ToolScope::Memory,
    ),
    (
        "scout",
        "Execute shell command with output compression",
        ToolScope::Shell,
    ),
    (
        "chronicler",
        "Generate session narrative (read-only)",
        ToolScope::ReadOnly,
    ),
    (
        "alchemist",
        "Transform noisy content → structured JSON",
        ToolScope::ReadOnly,
    ),
    (
        "sentinel",
        "Static security risk assessment",
        ToolScope::ReadOnly,
    ),
    (
        "cartridge",
        "Package current context into portable bundle",
        ToolScope::ReadOnly,
    ),
    (
        "resonator",
        "Dense-vector semantic search over ProjectIndex",
        ToolScope::FileSystem,
    ),
    (
        "surveyor",
        "Compute dependency topology and impact analysis",
        ToolScope::FileSystem,
    ),
];

fn skill_schema(name: &str) -> Value {
    match name {
        "scout" => serde_json::json!({
            "type": "object",
            "properties": {
                "command":      { "type": "string", "description": "Shell command to execute" },
                "working_dir":  { "type": "string", "description": "Working directory (default: cwd)" },
                "timeout_secs": { "type": "number", "description": "Timeout in seconds (default: 30)" },
                "dry_run":      { "type": "boolean", "description": "Print command without executing" }
            },
            "required": ["command"]
        }),
        "cartograph" => serde_json::json!({
            "type": "object",
            "properties": {
                "root":  { "type": "string", "description": "Directory to map (default: cwd)" },
                "depth": { "type": "number", "description": "Traversal depth (default: 3)" }
            }
        }),
        "chisel" => serde_json::json!({
            "type": "object",
            "properties": {
                "paths":           { "type": "array", "items": { "type": "string" }, "description": "Files to extract symbols from" },
                "filter_exported": { "type": "boolean", "description": "Only return exported symbols" }
            },
            "required": ["paths"]
        }),
        "sieve" => serde_json::json!({
            "type": "object",
            "properties": {
                "content":      { "type": "string", "description": "Raw output to filter" },
                "task_hint":    { "type": "string", "description": "Task context for relevance scoring" },
                "budget_tokens":{ "type": "number", "description": "Token budget (default: 2000)" }
            },
            "required": ["content"]
        }),
        "condenser" => serde_json::json!({
            "type": "object",
            "properties": {
                "content":      { "type": "string", "description": "Text to compress (mutually exclusive with path)" },
                "path":         { "type": "string", "description": "File path to compress" },
                "lens":         { "type": "string", "description": "Lens to apply: clarity|narrow|focus|auto (default: auto)" },
                "task_hint":    { "type": "string" },
                "budget_tokens":{ "type": "number", "description": "Token budget (default: 2000)" }
            }
        }),
        "compass" => serde_json::json!({
            "type": "object",
            "properties": {
                "query": { "type": "string", "description": "Search query" },
                "root":  { "type": "string", "description": "Project root (default: cwd)" },
                "top_k": { "type": "number", "description": "Max results (default: 10)" }
            },
            "required": ["query"]
        }),
        "prism" => serde_json::json!({
            "type": "object",
            "properties": {
                "root":      { "type": "string", "description": "Project root to index (default: cwd)" },
                "max_files": { "type": "number", "description": "Max files to parse (default: 500)" }
            }
        }),
        "archivist" => serde_json::json!({
            "type": "object",
            "properties": {
                "query": { "type": "string", "description": "Search query across Vault tiers" },
                "top_k": { "type": "number", "description": "Max results (default: 10)" }
            }
        }),
        "sediment" => serde_json::json!({
            "type": "object",
            "properties": {
                "facts": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "key":        { "type": "string" },
                            "value":      { "type": "string" },
                            "category":   { "type": "string" },
                            "confidence": { "type": "number" }
                        },
                        "required": ["key", "value"]
                    }
                },
                "target_tier": { "type": "string", "description": "resonant|crystallized (default: resonant)" }
            },
            "required": ["facts"]
        }),
        "chronicler" => serde_json::json!({
            "type": "object",
            "properties": {
                "session_id": { "type": "string" },
                "format":     { "type": "string", "description": "markdown|json (default: markdown)" },
                "limit":      { "type": "number", "description": "Max events (default: 20)" }
            }
        }),
        "alchemist" => serde_json::json!({
            "type": "object",
            "properties": {
                "content":     { "type": "string", "description": "Text to extract structure from" },
                "domain_hint": { "type": "string", "description": "Domain hint for extraction (default: freetext)" }
            },
            "required": ["content"]
        }),
        "sentinel" => serde_json::json!({
            "type": "object",
            "properties": {
                "subject": { "type": "string", "description": "Command, path, or code to assess" }
            },
            "required": ["subject"]
        }),
        "cartridge" => serde_json::json!({
            "type": "object",
            "properties": {
                "max_tokens":             { "type": "number", "description": "Token budget for bundle (default: 4000)" },
                "session_id":             { "type": "string" },
                "include_recent_commands":{ "type": "boolean", "description": "Include recent shell commands (default: true)" }
            }
        }),
        "resonator" => serde_json::json!({
            "type": "object",
            "properties": {
                "query":      { "type": "string", "description": "Semantic search query" },
                "root":       { "type": "string", "description": "Project root (default: cwd)" },
                "top_k":      { "type": "number", "description": "Max results (default: 15)" },
                "auto_index": { "type": "boolean", "description": "Auto-run prism if no index found" }
            },
            "required": ["query"]
        }),
        "surveyor" => serde_json::json!({
            "type": "object",
            "properties": {
                "root":          { "type": "string", "description": "Project root (default: cwd)" },
                "output_format": { "type": "string", "description": "dot|json|summary (default: dot)" }
            }
        }),
        _ => serde_json::json!({
            "type": "object",
            "properties": {
                "input": { "type": "string" }
            }
        }),
    }
}