Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e241631
test(mcp): require conservative tools list cache contract
seonghobae Aug 16, 2026
fb6ea61
feat(mcp): bind tools list to conservative cache hints
seonghobae Aug 16, 2026
54fb93c
docs(changelog): record MCP tools list cache contract
seonghobae Aug 16, 2026
7c8f69f
docs(doctoring): record MCP cache-hint boundary
seonghobae Aug 16, 2026
fde152c
test(mcp): require complete tools list result type
seonghobae Aug 16, 2026
ea61611
fix(mcp): bind mandatory tools list result type
seonghobae Aug 16, 2026
cb14dc1
docs(changelog): record complete MCP list result disposition
seonghobae Aug 16, 2026
88e6464
docs(mcp): record mandatory complete result disposition
seonghobae Aug 16, 2026
9c7981d
test(mcp): validate tool shape before route correlation
seonghobae Aug 16, 2026
1ff5d5f
fix(mcp): validate tool names before route correlation
seonghobae Aug 16, 2026
fc650d9
Merge live #168 head into MCP tools/list stack
seonghobae Aug 17, 2026
6d56cd0
docs: restore MCP tools/list changelog after stack sync
seonghobae Aug 17, 2026
7754f65
Merge live #168 repair into MCP tools/list stack
seonghobae Aug 17, 2026
a8b5b35
merge(mcp): reconcile tools list cache with current routing base
seonghobae Aug 17, 2026
3f7daa7
test(mcp): reject unissued tools list cursors
seonghobae Aug 17, 2026
927825c
feat(mcp): validate tools list request routing
seonghobae Aug 17, 2026
582eac4
style(mcp): apply canonical rustfmt
seonghobae Aug 17, 2026
43cbfcb
docs(mcp): record tools list request boundary
seonghobae Aug 17, 2026
e88550b
docs(mcp): keep active tools list boundary current
seonghobae Aug 17, 2026
3b0148b
test(mcp): require per-request metadata on tools list
seonghobae Aug 17, 2026
728e422
fix(mcp): bind tools list to per-request metadata
seonghobae Aug 17, 2026
552e0fa
style(mcp): apply canonical rustfmt
seonghobae Aug 17, 2026
530bb77
docs(mcp): record per-request metadata contract
seonghobae Aug 17, 2026
b8ffd57
docs(mcp): align changelog with request metadata
seonghobae Aug 17, 2026
8b83eaf
test(mcp): bound tools list method metadata
seonghobae Aug 24, 2026
0fade31
fix(mcp): bound tools list method metadata
seonghobae Aug 24, 2026
626c34f
merge(main): reconcile MCP tools list after #168
seonghobae Aug 24, 2026
972d97b
docs(changelog): reconcile merged MCP routing truth
seonghobae Aug 24, 2026
6242d6b
test(mcp): cover tools list invalid method error
seonghobae Aug 24, 2026
9d1cfd0
test(mcp): bound tools-list protocol metadata
seonghobae Aug 24, 2026
c25fb51
fix(mcp): bound protocol metadata before compare
seonghobae Aug 24, 2026
bc41f37
docs(changelog): record bounded MCP protocol metadata
seonghobae Aug 24, 2026
fcae054
docs(readme): align MCP shipped and active truth
seonghobae Aug 24, 2026
769ca7b
docs(traceability): reconcile merged MCP routing
seonghobae Aug 24, 2026
5bb5a7b
docs(adr): reconcile protected-main MCP routing
seonghobae Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules.
- Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors.
- Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes.
- Active PR #168 adds deterministic MCP `2026-07-28` stateless tool-routing foundations with bounded names, a single reviewed tool-to-action registry shared by routing and discovery metadata, and fail-closed policy binding that grants no ambient authority. This is active-PR evidence only; the complete MCP adapter, transport serialization, discovery response handling, OAuth, browser I/O, and persistence remain planned until separately integrated on protected `main`.
- Protected main now contains deterministic MCP `2026-07-28` stateless `tools/call` routing with bounded method/tool names, a single reviewed tool-to-action registry shared by routing and discovery metadata, and fail-closed policy binding that grants no ambient authority. The complete MCP adapter, transport serialization, discovery response handling, OAuth, browser I/O, and persistence remain planned.
- Active PR #170 adds conservative MCP `2026-07-28` `tools/list` discovery metadata derived from that protected-main catalog, with `resultType = complete`, zero freshness, private cache scope, no continuation cursor, per-request protocol/client-capability admission, and bounded protocol-version and method metadata validated before cross-field comparison. This remains active-PR evidence only and grants no browser, network, secret, approval, or Agent authority.
- Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets.
- Fail-closed resolved-destination policy with IPv4/IPv6 special-purpose and reviewed cloud-platform endpoint classification, IPv4-mapped canonicalization, explicit class grants, non-empty origin-bound DNS snapshots capped at 256 resolver addresses, concrete connection pinning, DNS-set expansion detection, and per-hop redirect reauthorization.
- Direct-only `originweave-network` TCP boundary with explicit canonical `SocketAddr` authority, zero IPv6 flow and scope metadata unless separately modeled, a non-cloneable single-use plan, a 30-second per-attempt timeout ceiling, at most four attempts, exact `peer_addr` verification before stream exposure, and no hostname re-resolution or ambient proxy inheritance.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

OriginWeave is a Chromium-compatible, Rust-first control plane for governed AI agents on the web. It is designed to let an agent observe, extract, and act without turning untrusted page content into authority, exposing secrets to a model, connecting to an unapproved network destination, accepting an unauthenticated web service, or losing the evidence required to explain what happened.

> Project status: pre-alpha. The current protected repository contains independently reusable safety, resolved-destination, direct TCP peer-binding, and authenticated TLS service-identity kernels. Chromium, WebDriver BiDi, CDP, complete MCP, HTTP, proxy, WARC, and persistent provenance adapters are planned but not yet shipped. Active PR #168 implements only a bounded MCP `2026-07-28` stateless tool-routing and typed-action/policy foundation; it remains non-shipped active-PR evidence and does not make the complete MCP adapter available.
> Project status: pre-alpha. The current protected repository contains independently reusable safety, resolved-destination, direct TCP peer-binding, authenticated TLS service-identity, and bounded MCP `2026-07-28` stateless `tools/call` routing/policy foundations. Chromium, WebDriver BiDi, CDP, complete MCP, HTTP, proxy, WARC, and persistent provenance adapters are planned but not yet shipped. Active PR #170 implements only conservative `tools/list` discovery metadata on top of the protected-main MCP catalog; it remains non-shipped active-PR evidence and does not make the complete MCP adapter available.

## Why OriginWeave

Expand Down Expand Up @@ -40,7 +40,7 @@ The repository is organized as independently consumable Rust crates:
- `originweave-resource`: task-level RAM, VRAM, thread, and frame-time budgets with cumulative mitigation plans.
- `originweave-evidence`: universally value-redacted network evidence and source-bound provenance records.

Active PR #168 additionally carries a non-shipped `originweave-core` MCP routing registry and `originweave-policy` binding for the MCP `2026-07-28` `tools/call` boundary. That foundation validates and maps an explicit tool name to an existing typed action; it does not implement transport parsing, `tools/list`, OAuth, browser control, secret materialization, persistence, or ambient authority.
Protected main additionally contains an `originweave-core` MCP routing registry and `originweave-policy` binding for the MCP `2026-07-28` `tools/call` boundary. That shipped foundation validates and maps an explicit tool name to an existing typed action while preserving normal OriginWeave policy. Active PR #170 adds non-shipped conservative `tools/list` discovery metadata derived from the same reviewed catalog. Neither boundary implements transport parsing, OAuth, browser control, secret materialization, persistence, or ambient authority.

See [ARCHITECTURE.md](ARCHITECTURE.md) and the [architecture decision records](docs/adr/) for binding design decisions.

Expand Down Expand Up @@ -99,7 +99,7 @@ isolated Chromium session
→ redacted provenance bundle
```

Subsequent work connects the live Chromium network service, adds explicit proxy and download policy, WARC/PROV persistence, completes the MCP and Browser Agent Protocol adapters beyond the active routing foundation, expands extension compatibility testing, adds GPU/RAM telemetry and prompt-injection benchmarks, and builds an accessible approval interface. See [docs/product-roadmap.md](docs/product-roadmap.md).
Subsequent work connects the live Chromium network service, adds explicit proxy and download policy, WARC/PROV persistence, completes the MCP and Browser Agent Protocol adapters beyond the protected-main `tools/call` foundation and active `tools/list` refinement, expands extension compatibility testing, adds GPU/RAM telemetry and prompt-injection benchmarks, and builds an accessible approval interface. See [docs/product-roadmap.md](docs/product-roadmap.md).

## Hourly product-development loop

Expand All @@ -111,4 +111,4 @@ Read [AGENTS.md](AGENTS.md), [CONTRIBUTING.md](CONTRIBUTING.md), and [SECURITY.m

## License

Apache License 2.0. See [LICENSE](LICENSE).
Apache License 2.0. See [LICENSE](LICENSE).
230 changes: 230 additions & 0 deletions crates/originweave-core/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub const MCP_PROTOCOL_VERSION: &str = "2026-07-28";
/// The only MCP method that can enter the typed action-routing boundary.
pub const MCP_TOOLS_CALL_METHOD: &str = "tools/call";

/// The MCP discovery method accepted by the typed tools-list boundary.
pub const MCP_TOOLS_LIST_METHOD: &str = "tools/list";

/// Maximum accepted MCP method-name length in bytes.
pub const MAX_MCP_METHOD_NAME_BYTES: usize = 64;

Expand Down Expand Up @@ -119,6 +122,233 @@ pub const fn supported_mcp_tools() -> &'static [McpToolCatalogEntry] {
MCP_TOOL_CATALOG
}

/// Protocol disposition carried by a typed MCP result.
///
/// OriginWeave currently constructs only terminal results at this boundary. A transport adapter
/// must serialize [`Self::Complete`] as MCP's `"complete"` result type and must not omit or
/// reinterpret the required protocol field.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpResultType {
/// The request completed and this value contains the final result.
Complete,
}

/// Cache-sharing scope for an MCP cacheable list result.
///
/// OriginWeave currently exposes only the conservative private scope. A transport adapter must
/// serialize this as MCP's `"private"` cache scope and must not widen it without a separately
/// reviewed policy that proves the returned catalog is safe to share across callers.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpCacheScope {
/// The result may be cached only for the current caller's private context.
Private,
}

/// One typed MCP `tools/list` page derived from the reviewed tool catalog.
///
/// This value is discovery metadata only. It does not grant any tool capability or action
/// authority. The initial contract is deliberately one complete private page with zero freshness
/// so adapters cannot omit MCP's required result disposition or accidentally share or reuse
/// discovery metadata beyond the current request.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct McpToolsListPage {
result_type: McpResultType,
tools: &'static [McpToolCatalogEntry],
ttl_ms: u64,
cache_scope: McpCacheScope,
next_cursor: Option<&'static str>,
}

impl McpToolsListPage {
/// Return the mandatory MCP result disposition for this list page.
#[must_use]
pub const fn result_type(&self) -> McpResultType {
self.result_type
}

/// Return the deterministic reviewed tool entries in this page.
#[must_use]
pub const fn tools(&self) -> &'static [McpToolCatalogEntry] {
self.tools
}

/// Return the MCP freshness lifetime in milliseconds.
///
/// The current conservative contract is zero, so clients must treat the result as
/// immediately stale rather than reusing it for a later request.
#[must_use]
pub const fn ttl_ms(&self) -> u64 {
self.ttl_ms
}

/// Return the MCP cache-sharing scope for this page.
#[must_use]
pub const fn cache_scope(&self) -> McpCacheScope {
self.cache_scope
}

/// Return the opaque continuation cursor when another page exists.
///
/// The current fixed catalog is emitted as one complete page, so this is always `None`.
#[must_use]
pub const fn next_cursor(&self) -> Option<&'static str> {
self.next_cursor
}
}

/// Build the conservative typed MCP `tools/list` result for the reviewed catalog.
///
/// This function does not perform transport serialization, authorization, or pagination. It
/// binds the catalog to the mandatory complete result disposition plus explicit zero-TTL/private
/// cache hints so adapters cannot invent broader protocol or cache semantics independently from
/// this reviewed boundary.
#[must_use]
pub const fn mcp_tools_list_page() -> McpToolsListPage {
McpToolsListPage {
result_type: McpResultType::Complete,
tools: MCP_TOOL_CATALOG,
ttl_ms: 0,
cache_scope: McpCacheScope::Private,
next_cursor: None,
}
}

/// A deterministic failure while validating one MCP `tools/list` request envelope.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpToolsListBoundaryError {
/// The transport request omitted the required MCP protocol-version header.
MissingProtocolVersionHeader,
/// The structured request metadata omitted the required MCP protocol version.
MissingProtocolVersionMetadata,
/// The transport protocol version disagrees with the structured request metadata.
ProtocolVersionHeaderBodyMismatch,
/// The request names an MCP protocol generation this boundary does not support.
UnsupportedProtocolVersion,
/// The structured request metadata omitted the required client-capabilities object.
MissingClientCapabilities,
/// The request method violates the bounded ASCII MCP routing syntax.
InvalidMethod,
/// MCP routing method metadata disagrees with the method in the request body.
MethodHeaderBodyMismatch,
/// The request method is not the supported `tools/list` operation.
UnsupportedMethod,
/// The request supplied a cursor that this fixed single-page catalog never issued.
UnsupportedCursor,
}

impl fmt::Display for McpToolsListBoundaryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingProtocolVersionHeader => {
formatter.write_str("MCP protocol version header is required")
}
Self::MissingProtocolVersionMetadata => {
formatter.write_str("MCP request metadata protocol version is required")
}
Self::ProtocolVersionHeaderBodyMismatch => {
formatter.write_str("MCP protocol version header does not match request metadata")
}
Self::UnsupportedProtocolVersion => {
formatter.write_str("unsupported MCP protocol version")
}
Self::MissingClientCapabilities => {
formatter.write_str("MCP request metadata client capabilities are required")
}
Self::InvalidMethod => {
formatter.write_str("MCP method violates the bounded ASCII routing syntax")
}
Self::MethodHeaderBodyMismatch => {
formatter.write_str("MCP method header does not match the request body")
}
Self::UnsupportedMethod => {
formatter.write_str("only MCP tools/list requests can enter the discovery boundary")
}
Self::UnsupportedCursor => {
formatter.write_str("MCP tools/list cursor was not issued by this fixed catalog")
}
}
}
}

impl std::error::Error for McpToolsListBoundaryError {}

/// An MCP `tools/list` request whose protocol, required metadata, and routing envelope were
/// validated.
///
/// This boundary is deliberately narrower than a general transport or pagination implementation.
/// A trusted structured parser must prove whether the required per-request client-capabilities
/// object was present; this type never accepts its contents as authority. The current reviewed
/// catalog returns one complete page and emits no continuation cursor, so no non-null cursor can
/// be a value previously issued by OriginWeave. A transport adapter must not silently ignore or
/// reinterpret a supplied cursor.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValidatedMcpToolsListRequest {
method: &'static str,
}

impl ValidatedMcpToolsListRequest {
/// Validate the stateless request envelope for the current fixed `tools/list` catalog.
///
/// Both the required transport protocol-version header and structured request `_meta`
/// protocol version must be present, individually bounded to the exact supported-version
/// length before cross-field comparison, equal, and exactly [`MCP_PROTOCOL_VERSION`]. A
/// trusted structured parser must also attest that the required `_meta` client-capabilities
/// object was present; its contents grant no OriginWeave authority. Each untrusted method
/// value is shape-validated before comparison. The routing/body method must then agree exactly.
/// Any supplied cursor fails closed because [`mcp_tools_list_page`] emits no continuation
/// cursor; accepting one would silently invent pagination state that OriginWeave never issued.
pub fn new(
protocol_version_header: Option<&str>,
protocol_version_metadata: Option<&str>,
client_capabilities_present: bool,
routing_method: &str,
body_method: &str,
cursor: Option<&str>,
) -> Result<Self, McpToolsListBoundaryError> {
let protocol_version_header = protocol_version_header
.ok_or(McpToolsListBoundaryError::MissingProtocolVersionHeader)?;
let protocol_version_metadata = protocol_version_metadata
.ok_or(McpToolsListBoundaryError::MissingProtocolVersionMetadata)?;

if protocol_version_header.len() > MCP_PROTOCOL_VERSION.len()
|| protocol_version_metadata.len() > MCP_PROTOCOL_VERSION.len()
{
return Err(McpToolsListBoundaryError::UnsupportedProtocolVersion);
}
if protocol_version_header != protocol_version_metadata {
return Err(McpToolsListBoundaryError::ProtocolVersionHeaderBodyMismatch);
}
if protocol_version_metadata != MCP_PROTOCOL_VERSION {
return Err(McpToolsListBoundaryError::UnsupportedProtocolVersion);
}
Comment thread
seonghobae marked this conversation as resolved.
if !client_capabilities_present {
return Err(McpToolsListBoundaryError::MissingClientCapabilities);
}
if !valid_method(routing_method) || !valid_method(body_method) {
return Err(McpToolsListBoundaryError::InvalidMethod);
}
if routing_method != body_method {
return Err(McpToolsListBoundaryError::MethodHeaderBodyMismatch);
}
if routing_method != MCP_TOOLS_LIST_METHOD {
return Err(McpToolsListBoundaryError::UnsupportedMethod);
}
if cursor.is_some() {
return Err(McpToolsListBoundaryError::UnsupportedCursor);
}

Ok(Self {
method: MCP_TOOLS_LIST_METHOD,
})
}

/// Return the canonical MCP method validated by this request.
#[must_use]
pub const fn method(&self) -> &'static str {
self.method
}
}

/// A deterministic failure while validating untrusted MCP routing metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpToolBoundaryError {
Expand Down
Loading
Loading