Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ca95278
test(ddd): require MCP adapter bounded context
seonghobae Sep 1, 2026
9a2ad26
refactor(ddd): register MCP adapter crate
seonghobae Sep 1, 2026
ba8cb4f
refactor(ddd): add MCP adapter manifest
seonghobae Sep 1, 2026
89b5389
refactor(ddd): establish MCP adapter root
seonghobae Sep 1, 2026
b7d67a1
refactor(ddd): move MCP routing out of core
seonghobae Sep 1, 2026
4dceac1
refactor(ddd): remove MCP adapter from core
seonghobae Sep 1, 2026
20f8458
refactor(ddd): depend on MCP adapter boundary
seonghobae Sep 1, 2026
bcbed2c
refactor(ddd): bind policy to MCP adapter contract
seonghobae Sep 1, 2026
8be7521
refactor(ddd): move MCP routing tests
seonghobae Sep 1, 2026
03528ff
refactor(ddd): move MCP discovery tests
seonghobae Sep 1, 2026
2413f17
refactor(ddd): remove MCP test from core
seonghobae Sep 1, 2026
6d54e02
refactor(ddd): remove MCP discovery test from core
seonghobae Sep 1, 2026
480f6bb
refactor(ddd): consume MCP adapter contract
seonghobae Sep 1, 2026
84c3ae1
test(ddd): enforce MCP adapter ownership
seonghobae Sep 1, 2026
900a4d9
build: update lockfile for MCP adapter crate
seonghobae Sep 1, 2026
19ea16a
fix(build): restore Cargo registry lock integrity
seonghobae Sep 1, 2026
6562deb
test(ddd): consolidate MCP boundary fitness gate
seonghobae Sep 1, 2026
4c08dc8
test(ddd): reject policy-to-MCP dependency inversion
seonghobae Sep 1, 2026
f7d5057
fix(ddd): remove outward MCP dependency from policy
seonghobae Sep 1, 2026
3835480
fix(ddd): make MCP adapter depend inward on policy
seonghobae Sep 1, 2026
04f2ab6
fix(ddd): keep policy free of MCP adapter types
seonghobae Sep 1, 2026
24d8a3f
fix(ddd): keep MCP-to-policy bridge in adapter context
seonghobae Sep 1, 2026
7bc28a2
test(ddd): move MCP-policy binding evidence to adapter context
seonghobae Sep 1, 2026
a6c1fee
test(ddd): remove adapter-owned test from policy context
seonghobae Sep 1, 2026
8300d62
fix(ddd): align lockfile with adapter dependency direction
seonghobae Sep 1, 2026
b89d110
fix(lock): restore verified bit-vec checksum
seonghobae Sep 1, 2026
9e90c38
test(ddd): reject MCP vocabulary in policy context
seonghobae Sep 1, 2026
e8066ad
fix(ddd): keep MCP route rejection in adapter
seonghobae Sep 1, 2026
c79a4e9
style(ddd): apply canonical MCP route-test formatting
seonghobae Sep 1, 2026
b3595ef
fix(ddd): rely on Result must-use contract
seonghobae Sep 1, 2026
b1a9460
test(mcp): require modern per-request client metadata
seonghobae Sep 2, 2026
2552c56
test(mcp): align modern request RED with final spec
seonghobae Sep 2, 2026
8bbb2ee
docs(mcp): correct final per-request metadata contract
seonghobae Sep 2, 2026
34201b3
fix(mcp): require modern request metadata for tool calls
seonghobae Sep 2, 2026
2bea3e9
refactor(mcp): expose metadata-bound tool call adapter
seonghobae Sep 2, 2026
bff2a7f
test(mcp): cover mandatory per-request metadata
seonghobae Sep 2, 2026
24eefaf
test(mcp): bind policy tests to modern request envelope
seonghobae Sep 2, 2026
1aba0a6
test(mcp): preserve bounded routing under modern metadata
seonghobae Sep 2, 2026
db552ec
docs(mcp): trace metadata-bound tools call repair
seonghobae Sep 2, 2026
c450649
docs(changelog): record MCP request-envelope hardening
seonghobae Sep 2, 2026
bbe6b21
test(mcp): expose stdio transport binding gap
seonghobae Sep 2, 2026
09ffccc
fix(mcp): bind modern stdio requests without HTTP inputs
seonghobae Sep 3, 2026
80272f1
docs(mcp): trace stdio binding RED and repair
seonghobae Sep 3, 2026
eda00dc
test(mcp): remove prohibited metadata expect
seonghobae Sep 4, 2026
c0e587e
test(mcp): remove prohibited stdio expect
seonghobae Sep 4, 2026
cae3e02
test(mcp): cover private routing error diagnostics
seonghobae Sep 4, 2026
ab33144
Merge remote-tracking branch 'origin/main' into codex/reconcile-pr272
seonghobae Sep 4, 2026
fe124e4
test(mcp): apply canonical rustfmt diagnostics
seonghobae Sep 4, 2026
7763bd5
merge(main): adopt protected PR lifecycle controls
seonghobae Sep 4, 2026
55b2f06
test(ddd): reconcile MCP workspace contract with PR lifecycle
seonghobae Sep 4, 2026
975492b
test(ddd): isolate MCP adapter dependency-direction contract
seonghobae Sep 4, 2026
b1cae8a
docs(ddd): record protected-main MCP reconciliation
seonghobae Sep 4, 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 @@ -48,6 +48,7 @@ All notable changes to OriginWeave are documented in this file. The format follo

### Changed

- Active PR #272 hardens the MCP `2026-07-28` `tools/call` adapter so a validated call requires matching transport/request protocol versions and per-request client-capabilities presence; optional self-reported `clientInfo` is neither required nor retained as authority, and the former constructor shape now fails closed for otherwise valid legacy calls. This is active-PR evidence, not protected-main shipment.
- Aligned the hourly product-development branch-coverage toolchain and its one-shot materializer with the reviewed `nightly-2026-08-18` pin, and corrected the official Dependabot Rust-toolchain reference.
- Separated logical origin authority from resolved network destination authority; an origin grant no longer implies permission to connect to every resolver result.
- Separated resolved-address authorization from direct transport evidence; an approved IP now becomes a usable stream only after the operating system reports the exact requested IP and port.
Expand Down Expand Up @@ -102,4 +103,4 @@ All notable changes to OriginWeave are documented in this file. The format follo
- The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it.
- The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels.

[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
members = [
"crates/originweave-core",
"crates/originweave-bap",
"crates/originweave-mcp",
"crates/originweave-policy",
"crates/originweave-resource",
"crates/originweave-evidence",
Expand Down
2 changes: 0 additions & 2 deletions crates/originweave-core/src/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,5 @@ mod contracts;

pub use contracts::*;

/// Stateless MCP routing validation that maps only explicit tools to typed actions.
pub mod mcp;
/// Deterministic fail-closed release benchmark acceptance aggregation.
pub mod release_acceptance;
18 changes: 18 additions & 0 deletions crates/originweave-mcp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "originweave-mcp"
description = "OriginWeave MCP adapter contracts."
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
publish = false

[dependencies]
originweave-core = { path = "../originweave-core" }
originweave-policy = { path = "../originweave-policy" }

[lints]
workspace = true
92 changes: 92 additions & 0 deletions crates/originweave-mcp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//! Fail-closed MCP adapter contracts for OriginWeave.
//!
//! This crate owns MCP protocol-generation, discovery, and stateless tool-routing
//! contracts. It maps reviewed MCP protocol values into existing OriginWeave
//! action contracts but grants no policy, browser, network, secret, or evidence
//! authority.

#![forbid(unsafe_code)]
#![deny(missing_docs)]

use originweave_core::{ActionRequest, PolicyContext};
use originweave_policy::Decision;

pub(crate) use originweave_core::{ActionKind, Capability, RiskClass};

mod request;
mod routing;

pub use request::{McpToolBoundaryError, ValidatedMcpToolCall};
pub use routing::{
MAX_MCP_METHOD_NAME_BYTES, MAX_MCP_TOOL_NAME_BYTES, MCP_PROTOCOL_VERSION,
MCP_TOOLS_CALL_METHOD, MCP_TOOLS_LIST_METHOD, McpCacheScope, McpResultType,
McpToolCatalogEntry, McpToolsListBoundaryError, McpToolsListPage, ValidatedMcpToolsListRequest,
mcp_tools_list_page, supported_mcp_tools,
};

/// A fail-closed rejection owned by the MCP routing boundary rather than policy.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpRouteRejection {
/// The validated MCP route resolves to a different action than the typed request.
ActionMismatch,
}

/// Evaluate one validated MCP route through the ordinary OriginWeave policy boundary.
///
/// Route validation proves only protocol integrity. It grants no capability, origin, approval,
/// secret, browser, network, or evidence authority. A route/action mismatch is returned as an
/// MCP-owned rejection before the request reaches policy. Callers may execute only
/// `Ok(Decision::Allow)`; every other result remains non-authorizing.
pub fn evaluate_mcp(
call: &ValidatedMcpToolCall,
request: &ActionRequest,
context: &PolicyContext,
) -> Result<Decision, McpRouteRejection> {
if call.action_kind() != request.action() {
return Err(McpRouteRejection::ActionMismatch);
}

Ok(originweave_policy::evaluate(request, context))
}

#[cfg(test)]
mod tests {
use std::error::Error;

use super::routing::McpToolBoundaryError;

#[test]
fn private_routing_error_diagnostics_are_total_and_source_free() {
let cases = [
(
McpToolBoundaryError::UnsupportedProtocolVersion,
"unsupported MCP protocol version",
),
(
McpToolBoundaryError::HeaderBodyMismatch,
"MCP routing headers do not match the request body",
),
(
McpToolBoundaryError::InvalidMethod,
"MCP method violates the bounded ASCII routing syntax",
),
(
McpToolBoundaryError::UnsupportedMethod,
"only MCP tools/call requests can enter the typed action boundary",
),
(
McpToolBoundaryError::InvalidToolName,
"MCP tool name violates the bounded ASCII routing syntax",
),
(
McpToolBoundaryError::UnknownTool,
"MCP tool is not mapped to an OriginWeave typed action",
),
];

for (error, message) in cases {
assert_eq!(error.to_string(), message);
assert!(error.source().is_none());
}
}
}
239 changes: 239 additions & 0 deletions crates/originweave-mcp/src/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
//! MCP 2026-07-28 request-envelope validation for typed tool calls.
//!
//! This adapter layer binds transport protocol metadata to the existing bounded
//! tool-routing validator. It deliberately retains no client identity or
//! capability contents and grants no OriginWeave browser, policy, secret, or
//! evidence authority.

use std::fmt;

use crate::{ActionKind, MCP_PROTOCOL_VERSION, routing};

/// A deterministic failure while validating one MCP `tools/call` request.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpToolBoundaryError {
/// 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 adapter does not support.
UnsupportedProtocolVersion,
/// The structured request metadata omitted the required client-capabilities object.
MissingClientCapabilities,
/// MCP routing metadata disagrees with the method or tool name in the body.
HeaderBodyMismatch,
/// The request method violates the bounded ASCII MCP routing syntax.
InvalidMethod,
/// The request method is not the supported `tools/call` operation.
UnsupportedMethod,
/// The tool name violates the bounded ASCII MCP routing syntax.
InvalidToolName,
/// The tool name has no explicit mapping to an OriginWeave typed action.
UnknownTool,
}

impl fmt::Display for McpToolBoundaryError {
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::HeaderBodyMismatch => {
formatter.write_str("MCP routing headers do not match the request body")
}
Self::InvalidMethod => {
formatter.write_str("MCP method violates the bounded ASCII routing syntax")
}
Self::UnsupportedMethod => formatter
.write_str("only MCP tools/call requests can enter the typed action boundary"),
Self::InvalidToolName => {
formatter.write_str("MCP tool name violates the bounded ASCII routing syntax")
}
Self::UnknownTool => {
formatter.write_str("MCP tool is not mapped to an OriginWeave typed action")
}
}
}
}

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

impl From<routing::McpToolBoundaryError> for McpToolBoundaryError {
fn from(error: routing::McpToolBoundaryError) -> Self {
match error {
routing::McpToolBoundaryError::UnsupportedProtocolVersion => {
Self::UnsupportedProtocolVersion
}
routing::McpToolBoundaryError::HeaderBodyMismatch => Self::HeaderBodyMismatch,
routing::McpToolBoundaryError::InvalidMethod => Self::InvalidMethod,
routing::McpToolBoundaryError::UnsupportedMethod => Self::UnsupportedMethod,
routing::McpToolBoundaryError::InvalidToolName => Self::InvalidToolName,
routing::McpToolBoundaryError::UnknownTool => Self::UnknownTool,
}
}
}

/// An MCP tool call whose required request metadata and routing envelope were validated.
///
/// The value proves protocol-envelope integrity only. Client capability contents and optional
/// `clientInfo` are deliberately not retained because self-reported client metadata is not an
/// OriginWeave authorization signal.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValidatedMcpToolCall {
routed: routing::ValidatedMcpToolCall,
}

impl ValidatedMcpToolCall {
/// Fail closed for the pre-2026-07-28 constructor shape.
///
/// This compatibility surface preserves deterministic routing diagnostics for malformed
/// legacy callers, but a syntactically valid route is rejected because this signature cannot
/// prove the required per-request protocol metadata or client-capabilities presence. New
/// adapters must use [`Self::new_with_request_metadata`].
pub fn new(
protocol_version: &str,
routing_method: &str,
routing_tool_name: &str,
body_method: &str,
body_tool_name: &str,
) -> Result<Self, McpToolBoundaryError> {
let _ = routing::ValidatedMcpToolCall::new(
protocol_version,
routing_method,
routing_tool_name,
body_method,
body_tool_name,
)
.map_err(McpToolBoundaryError::from)?;

Err(McpToolBoundaryError::MissingProtocolVersionMetadata)
}

/// Validate one MCP 2026-07-28 `tools/call` request envelope.
///
/// The transport protocol-version header and structured request `_meta` protocol version are
/// both mandatory, are bounded before comparison, must agree exactly, and must equal
/// [`MCP_PROTOCOL_VERSION`]. A trusted structured parser must also attest that the request's
/// client-capabilities object was present. Capability contents and optional `clientInfo` grant
/// no OriginWeave authority and are not retained. After metadata validation, the existing
/// bounded method/tool validator performs the explicit tool-to-action mapping.
pub fn new_with_request_metadata(
protocol_version_header: Option<&str>,
protocol_version_metadata: Option<&str>,
client_capabilities_present: bool,
routing_method: &str,
routing_tool_name: &str,
body_method: &str,
body_tool_name: &str,
) -> Result<Self, McpToolBoundaryError> {
let protocol_version_header =
protocol_version_header.ok_or(McpToolBoundaryError::MissingProtocolVersionHeader)?;
let protocol_version_metadata = protocol_version_metadata
.ok_or(McpToolBoundaryError::MissingProtocolVersionMetadata)?;

if protocol_version_header.len() > MCP_PROTOCOL_VERSION.len()
|| protocol_version_metadata.len() > MCP_PROTOCOL_VERSION.len()
{
return Err(McpToolBoundaryError::UnsupportedProtocolVersion);
}
if protocol_version_header != protocol_version_metadata {
return Err(McpToolBoundaryError::ProtocolVersionHeaderBodyMismatch);
}
if protocol_version_metadata != MCP_PROTOCOL_VERSION {
return Err(McpToolBoundaryError::UnsupportedProtocolVersion);
}
if !client_capabilities_present {
return Err(McpToolBoundaryError::MissingClientCapabilities);
}

let routed = routing::ValidatedMcpToolCall::new(
protocol_version_metadata,
routing_method,
routing_tool_name,
body_method,
body_tool_name,
)
.map_err(McpToolBoundaryError::from)?;

Ok(Self { routed })
}

/// Validate one MCP 2026-07-28 stdio `tools/call` request envelope.
///
/// Stdio has no HTTP routing headers, so callers provide only request-body protocol metadata,
/// capability presence, method, and tool name. The body values are correlated with themselves
/// inside the existing pure envelope validator only to reuse its bounds and catalog checks; no
/// HTTP header value is accepted, retained, or surfaced as evidence by this constructor.
pub fn new_for_stdio(
protocol_version_metadata: Option<&str>,
client_capabilities_present: bool,
body_method: &str,
body_tool_name: &str,
) -> Result<Self, McpToolBoundaryError> {
let protocol_version_metadata = protocol_version_metadata
.ok_or(McpToolBoundaryError::MissingProtocolVersionMetadata)?;

Self::new_with_request_metadata(
Some(protocol_version_metadata),
Some(protocol_version_metadata),
client_capabilities_present,
body_method,
body_tool_name,
body_method,
body_tool_name,
)
}

/// Return the canonical static tool name selected by the explicit mapping.
#[must_use]
pub const fn tool_name(&self) -> &'static str {
self.routed.tool_name()
}

/// Return the existing OriginWeave typed action selected by this tool.
#[must_use]
pub const fn action_kind(&self) -> ActionKind {
self.routed.action_kind()
}
}

impl routing::ValidatedMcpToolsListRequest {
/// Validate one MCP 2026-07-28 stdio `tools/list` request envelope.
///
/// Stdio carries the protocol metadata and method in the JSON-RPC request body and has no HTTP
/// routing headers. The body method/version are correlated with themselves inside the existing
/// pure list validator only to reuse its bounded syntax, cache, and cursor checks; callers cannot
/// supply or obtain fabricated HTTP header evidence through this constructor.
pub fn new_for_stdio(
protocol_version_metadata: Option<&str>,
client_capabilities_present: bool,
body_method: &str,
cursor: Option<&str>,
) -> Result<Self, routing::McpToolsListBoundaryError> {
let protocol_version_metadata = protocol_version_metadata
.ok_or(routing::McpToolsListBoundaryError::MissingProtocolVersionMetadata)?;

Self::new(
Some(protocol_version_metadata),
Some(protocol_version_metadata),
client_capabilities_present,
body_method,
body_method,
cursor,
)
}
}
Loading
Loading