Feature chore bug 005 #19
+80
-40
@@ -14,10 +14,28 @@
|
||||
use crate::models::{FeatureBrief, MeetingId};
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub mod scope;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
pub mod handler;
|
||||
#[cfg(feature = "mcp")]
|
||||
pub mod http_transport;
|
||||
#[cfg(feature = "mcp")]
|
||||
pub mod server;
|
||||
#[cfg(feature = "mcp")]
|
||||
pub mod stdio_transport;
|
||||
#[cfg(feature = "mcp")]
|
||||
pub mod token;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
pub use server::RmcpServer;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum McpError {
|
||||
#[error("refusing to bind non-loopback address")]
|
||||
NonLoopback,
|
||||
#[error("unauthorized: missing or invalid token")]
|
||||
Unauthorized,
|
||||
#[error("server error: {0}")]
|
||||
Server(String),
|
||||
}
|
||||
@@ -38,7 +56,7 @@ pub struct McpConfig {
|
||||
pub expose_recordings: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum McpTransport {
|
||||
/// Streamable HTTP on http://127.0.0.1:<port>/mcp (loopback only).
|
||||
Http,
|
||||
@@ -46,13 +64,51 @@ pub enum McpTransport {
|
||||
Stdio,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
impl McpTransport {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
McpTransport::Http => "http",
|
||||
McpTransport::Stdio => "stdio",
|
||||
}
|
||||
}
|
||||
|
||||
/// Unknown/missing values fall back to `Http` — the safer default to
|
||||
/// document to the user (stdio requires a client that spawns a process).
|
||||
pub fn parse(s: &str) -> Self {
|
||||
match s {
|
||||
"stdio" => McpTransport::Stdio,
|
||||
_ => McpTransport::Http,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExposeScope {
|
||||
None,
|
||||
Selected,
|
||||
All,
|
||||
}
|
||||
|
||||
impl ExposeScope {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
ExposeScope::None => "none",
|
||||
ExposeScope::Selected => "selected",
|
||||
ExposeScope::All => "all",
|
||||
}
|
||||
}
|
||||
|
||||
/// Unknown values fall back to `None` — scope-control is a privacy
|
||||
/// control, so an unparsed value must never silently become permissive.
|
||||
pub fn parse(s: &str) -> Self {
|
||||
match s {
|
||||
"selected" => ExposeScope::Selected,
|
||||
"all" => ExposeScope::All,
|
||||
_ => ExposeScope::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned on start: where to point the agent + the token it must present.
|
||||
pub struct McpHandle {
|
||||
pub endpoint: String,
|
||||
@@ -64,6 +120,28 @@ pub struct McpToolDescriptor {
|
||||
pub description: &'static str,
|
||||
}
|
||||
|
||||
/// The four tools-first-surface descriptors (FR-MCP-2), shared by the trait's
|
||||
/// default listing and anything else that needs to enumerate them without a
|
||||
/// running server (e.g. the settings/privacy UI).
|
||||
pub const TOOL_DESCRIPTORS: [McpToolDescriptor; 4] = [
|
||||
McpToolDescriptor {
|
||||
name: "list_recent_meetings",
|
||||
description: "Recent meetings (scoped).",
|
||||
},
|
||||
McpToolDescriptor {
|
||||
name: "get_transcript",
|
||||
description: "Transcript for a meeting (scoped).",
|
||||
},
|
||||
McpToolDescriptor {
|
||||
name: "get_action_items",
|
||||
description: "Action items for a meeting.",
|
||||
},
|
||||
McpToolDescriptor {
|
||||
name: "get_feature_brief",
|
||||
description: "Agent-ready spec distilled from a meeting.",
|
||||
},
|
||||
];
|
||||
|
||||
/// The MCP server. Built on the official Rust SDK (`rmcp`, feature `mcp`).
|
||||
#[async_trait]
|
||||
pub trait McpServer: Send + Sync {
|
||||
@@ -82,41 +160,3 @@ pub trait FeatureBriefBuilder: Send + Sync {
|
||||
target_repo: Option<&str>,
|
||||
) -> Result<FeatureBrief, BriefError>;
|
||||
}
|
||||
|
||||
/// Default rmcp-backed server (feature `mcp`).
|
||||
#[cfg(feature = "mcp")]
|
||||
pub struct RmcpServer;
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
#[async_trait]
|
||||
impl McpServer for RmcpServer {
|
||||
async fn start(&self, _cfg: McpConfig) -> Result<McpHandle, McpError> {
|
||||
// T10.4: bind loopback ONLY (reject non-loopback), mint a token, register tools,
|
||||
// serve over Streamable HTTP (/mcp) or stdio. Never opens an outbound socket.
|
||||
todo!("Phase 10b — start MCP server (loopback, token)")
|
||||
}
|
||||
async fn stop(&self, _handle: McpHandle) -> Result<(), McpError> {
|
||||
todo!("Phase 10b — stop MCP server")
|
||||
}
|
||||
fn tools(&self) -> Vec<McpToolDescriptor> {
|
||||
// T10.5: the tools an agent can call. Tools-first for Copilot compatibility.
|
||||
vec![
|
||||
McpToolDescriptor {
|
||||
name: "list_recent_meetings",
|
||||
description: "Recent meetings (scoped).",
|
||||
},
|
||||
McpToolDescriptor {
|
||||
name: "get_transcript",
|
||||
description: "Transcript for a meeting (scoped).",
|
||||
},
|
||||
McpToolDescriptor {
|
||||
name: "get_action_items",
|
||||
description: "Action items for a meeting.",
|
||||
},
|
||||
McpToolDescriptor {
|
||||
name: "get_feature_brief",
|
||||
description: "Agent-ready spec distilled from a meeting.",
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user