Skip to main content

fastmcp_protocol/
lib.rs

1//! MCP protocol types and JSON-RPC implementation.
2//!
3//! This crate provides:
4//! - JSON-RPC 2.0 message types
5//! - MCP-specific method types (tools, resources, prompts)
6//! - Protocol version negotiation
7//! - Message serialization/deserialization
8//!
9//! MCP 2026-07-28 support is under implementation and remains unverified.
10//! Its core vocabulary is always available. Optional legacy, Tasks, and Apps
11//! wire surfaces require their matching crate features and are not aggregate
12//! conformance or release evidence.
13//!
14//! # MCP Protocol Overview
15//!
16//! MCP (Model Context Protocol) uses JSON-RPC 2.0 over various transports.
17//! The protocol defines:
18//!
19//! - **Tools**: Executable functions the client can invoke
20//! - **Resources**: Data sources the client can read
21//! - **Prompts**: Template prompts for the client to use
22//!
23//! # Wire Format
24//!
25//! Protocol values serialize as JSON-RPC. Framing is transport-specific; the
26//! stdio transport uses newline-delimited JSON (NDJSON).
27//!
28//! # Role in the System
29//!
30//! `fastmcp-protocol` is the **shared vocabulary** for FastMCP:
31//! - The server uses these types to validate and serialize responses.
32//! - The client uses the same types to construct requests and parse replies.
33//! - Transports carry these messages without needing to know business logic.
34//!
35//! If you are integrating FastMCP with a custom runtime or embedding it into
36//! another system, depend on this crate to use FastMCP's current JSON-RPC and
37//! MCP data models. The modernization disclaimer above still applies.
38
39#![forbid(unsafe_code)]
40#![allow(dead_code)]
41
42pub mod common_types;
43pub mod extensions;
44#[cfg(feature = "jose")]
45pub mod jose;
46mod jsonrpc;
47#[cfg(feature = "apps")]
48pub mod mcp_apps_bridge;
49mod messages;
50pub mod methods;
51pub mod protocol_policy;
52pub mod protocol_version;
53mod result;
54pub mod schema;
55pub mod server_discovery;
56#[cfg(feature = "tasks")]
57pub mod tasks_extension;
58mod types;
59pub mod uri_template;
60
61pub use common_types::JsonInteger;
62pub use extensions::{
63    ClientExtensionDiscovery, ExtensionDescriptor, ExtensionDescriptorRegistry, ExtensionDirection,
64    ExtensionDiscovery, ExtensionFallbackPolicy, ExtensionHttpEraDisposition, ExtensionId,
65    ExtensionMethodDescriptor, ExtensionNegotiationResolver, ExtensionNotificationDescriptor,
66    ExtensionRegistryError, ExtensionRegistryReceipt, ExtensionRoutingHeaderDescriptor,
67    ExtensionSettings, ExtensionSettingsCompatibilityResolver, ExtensionSettingsResolution,
68    ExtensionSettingsSchema, MAX_EXTENSION_DESCRIPTORS, MAX_EXTENSION_ID_BYTES,
69    MAX_EXTENSION_REGISTRY_CANONICAL_BYTES, MAX_EXTENSION_SETTINGS_ENTRIES,
70    MAX_EXTENSION_SETTINGS_KEY_BYTES, MAX_EXTENSION_SETTINGS_NESTING,
71    MAX_EXTENSION_SETTINGS_VALUE_BYTES, MAX_MCP_APPS_MIME_TYPE_BYTES, MAX_MCP_APPS_MIME_TYPES,
72    MCP_APPS_ACTIVATION_PREDICATE_ID, MCP_APPS_CLIENT_SETTINGS_SCHEMA_ID,
73    MCP_APPS_DOWNLOAD_FILE_METHOD, MCP_APPS_HOST_CONTEXT_CHANGED_NOTIFICATION,
74    MCP_APPS_HTML_MIME_TYPE, MCP_APPS_INITIALIZE_METHOD, MCP_APPS_INITIALIZED_NOTIFICATION,
75    MCP_APPS_MESSAGE_METHOD, MCP_APPS_NEGOTIATION_RESOLVER_ID, MCP_APPS_OPEN_LINK_METHOD,
76    MCP_APPS_REQUEST_DISPLAY_MODE_METHOD, MCP_APPS_REQUEST_TEARDOWN_NOTIFICATION,
77    MCP_APPS_RESOURCE_TEARDOWN_METHOD, MCP_APPS_SANDBOX_PROXY_READY_NOTIFICATION,
78    MCP_APPS_SANDBOX_RESOURCE_READY_NOTIFICATION, MCP_APPS_SERVER_SETTINGS_SCHEMA_ID,
79    MCP_APPS_SIZE_CHANGED_NOTIFICATION, MCP_APPS_TOOL_CANCELLED_NOTIFICATION,
80    MCP_APPS_TOOL_INPUT_NOTIFICATION, MCP_APPS_TOOL_INPUT_PARTIAL_NOTIFICATION,
81    MCP_APPS_TOOL_RESULT_NOTIFICATION, MCP_APPS_UPDATE_MODEL_CONTEXT_METHOD, McpAppsClientSettings,
82    McpAppsNegotiationResolver, OFFICIAL_MCP_APPS_EXTENSION_ID,
83    RejectingExtensionNegotiationResolver, ServerExtensionDiscovery, StdioCorrelationDescriptor,
84    official_mcp_apps_descriptor, official_mcp_apps_empty_server_settings,
85    official_mcp_apps_extension_id, official_mcp_apps_negotiation_resolver,
86    register_official_mcp_apps_extension, resolve_official_mcp_apps_settings,
87};
88#[cfg(feature = "tasks")]
89pub use extensions::{
90    OFFICIAL_TASKS_EMPTY_SETTINGS_CODEC_ID, OFFICIAL_TASKS_EMPTY_SETTINGS_SCHEMA_ID,
91    OFFICIAL_TASKS_EXTENSION_ID, OFFICIAL_TASKS_METHODS, OFFICIAL_TASKS_NOTIFICATION,
92    OFFICIAL_TASKS_RESULT_DISCRIMINATOR, OfficialTasksNegotiationResolver,
93    TasksNegotiationResolver, official_tasks_descriptor, official_tasks_empty_settings,
94    official_tasks_extension_id, register_official_tasks_extension,
95};
96pub use jsonrpc::{
97    ClientIngressFailureScope, CorrelationKey, JSONRPC_VERSION, JsonRpcAdmissionError,
98    JsonRpcEndpointRole, JsonRpcError, JsonRpcMessage, JsonRpcMessageDirection, JsonRpcRequest,
99    JsonRpcResponse, JsonRpcResponseAdmission, MAX_JSONRPC_STRING_ID_ENCODED_BYTES,
100    MAX_RAW_JSON_AGGREGATE_NUMBER_BYTES, MAX_RAW_JSON_CONTAINER_ENTRIES, MAX_RAW_JSON_EXPONENT,
101    MAX_RAW_JSON_NESTING_DEPTH, MAX_RAW_JSON_NUMBER_BYTES, RawJsonAdmissionError,
102    RawJsonRpcDisposition, RequestId, UncorrelatedJsonRpcErrorResponse, admit_raw_jsonrpc_document,
103    decode_strict_jsonrpc_message, decode_strict_jsonrpc_response, dispose_raw_jsonrpc_failure,
104};
105#[cfg(feature = "apps")]
106pub use mcp_apps_bridge::*;
107pub use messages::*;
108pub use methods::SERVER_DISCOVER;
109pub use protocol_version::{
110    FINAL_PROTOCOL_VERSION, FinalHttpRequestMetadata, FinalProtocolVersion, FinalRequestAdmission,
111    HEADER_MISMATCH_ERROR_CODE, HeaderMismatchError, HeaderMismatchReason,
112    MAX_REQUIRED_CAPABILITIES_ERROR_DATA_BYTES, MCP_METHOD_HEADER, MCP_NAME_HEADER,
113    MCP_PROTOCOL_VERSION_HEADER, MISSING_REQUIRED_CLIENT_CAPABILITY_ERROR_CODE,
114    MissingRequiredClientCapabilityError, ProtocolVersionError, RequestAdmissionError,
115    RequestVersionMetadata, RequiredCapabilitiesError, SUPPORTED_FINAL_PROTOCOL_VERSIONS,
116    UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE, UnsupportedProtocolVersionError,
117    admit_final_http_request, admit_final_request, validate_final_protocol_version,
118};
119pub use result::*;
120pub use schema::{
121    AdmittedFinalFormSchema, AdmittedSchema, FinalCoreResultType, SchemaAdmissionError,
122    ValidationError, ValidationResult, admit_final_form_schema, admit_final_schema, validate,
123    validate_final_core_result, validate_strict,
124};
125pub use server_discovery::{
126    DiscoveryCacheHints, MAX_SERVER_INSTRUCTIONS_BYTES, SERVER_DISCOVER_METHOD,
127    SERVER_DISCOVER_SERVER_INFO_META_KEY, SERVER_DISCOVER_SUPPORTED_VERSIONS, ServerBehavior,
128    ServerBehaviorRegistry, ServerDiscoverCapabilities, ServerDiscoverRequest,
129    ServerDiscoverResult, ServerDiscoveryError, ServerInstructionError, ServerInstructions,
130};
131#[cfg(feature = "tasks")]
132pub use tasks_extension::{
133    CancelTaskParams as FinalCancelTaskParams, CancelTaskResult as FinalCancelTaskResult,
134    CompleteTaskResult, CreateTaskResult, EmptyTaskResult, FinalTaskCallToolResult, FinalTaskError,
135    GetTaskParams as FinalGetTaskParams, GetTaskResult as FinalGetTaskResult, MAX_TASK_ID_BYTES,
136    MAX_TASK_INPUT_MAP_ENTRIES, MAX_TASK_SUBSCRIPTION_IDS, RELATED_TASK_META_KEY, TASK_CANCEL,
137    TASK_GET, TASK_STATUS_NOTIFICATION, TASK_SUBSCRIPTION_IDS_KEY, TASKS_EXTENSION, Task, TaskBase,
138    TaskDuration, TaskId as FinalTaskId, TaskInputLedger, TaskInputRequests, TaskInputResponses,
139    TaskMethodRequest, TaskRequestMeta, TaskStatus as FinalTaskStatus, TaskStatusNotification,
140    TaskStatusNotificationParams as FinalTaskStatusNotificationParams, TaskTimestamp,
141    TaskWireError, UpdateTaskParams, UpdateTaskResult, set_task_subscription_ids,
142    task_subscription_ids,
143};
144pub use types::*;
145pub use uri_template::{
146    MAX_URI_TEMPLATE_BYTES, MAX_URI_TEMPLATE_COMPOSITE_ITEMS,
147    MAX_URI_TEMPLATE_EXPANSION_OUTPUT_BYTES, MAX_URI_TEMPLATE_EXPRESSIONS, MAX_URI_TEMPLATE_PARTS,
148    MAX_URI_TEMPLATE_PREFIX_LENGTH, MAX_URI_TEMPLATE_VALUE_BYTES,
149    MAX_URI_TEMPLATE_VARIABLE_NAME_BYTES, MAX_URI_TEMPLATE_VARIABLES_PER_EXPRESSION,
150    ReversibleResourceTemplate, TemplateValue, TemplateValues, UriTemplate, UriTemplateError,
151    UriTemplateExpansionLimits, UriTemplateExpression, UriTemplateModifier, UriTemplateOperator,
152    UriTemplatePart,
153};
154
155// The FND-03 contract freezes unqualified `cargo test -- --exact` IDs. Keep
156// the executable entry points at the crate root while retaining their full
157// assertions beside the policy implementation.
158#[cfg(test)]
159#[test]
160fn fnd_03_policy_receipts_positive() {
161    protocol_policy::tests::fnd_03_policy_receipts_positive();
162}
163
164#[cfg(test)]
165#[test]
166fn fnd_03_policy_receipts_planted_negative() {
167    protocol_policy::tests::fnd_03_policy_receipts_planted_negative();
168}
169
170#[cfg(test)]
171#[test]
172fn fnd_03_era_classification_positive() {
173    protocol_policy::tests::fnd_03_era_classification_positive();
174}
175
176#[cfg(test)]
177#[test]
178fn fnd_03_era_classification_planted_negative() {
179    protocol_policy::tests::fnd_03_era_classification_planted_negative();
180}
181
182#[cfg(test)]
183#[test]
184fn prt_03_i_positive() {
185    let required_capabilities = ClientCapabilities {
186        roots: Some(RootsCapability { list_changed: true }),
187        ..ClientCapabilities::default()
188    };
189    let metadata = FinalRequestMeta::new(required_capabilities.clone());
190    let admission = admit_final_http_request(FinalHttpRequestMetadata {
191        version: metadata.version_metadata(Some(FINAL_PROTOCOL_VERSION)),
192        header_method: Some(SERVER_DISCOVER),
193        body_method: Some(SERVER_DISCOVER),
194        header_name: None,
195        body_name: None,
196    })
197    .expect("canonical final metadata and server discovery must be admitted");
198    let missing =
199        MissingRequiredClientCapabilityError::from_client_capabilities(&required_capabilities)
200            .expect("typed required capabilities must encode as final error data");
201
202    assert_eq!(FINAL_PROTOCOL_VERSION, "2026-07-28");
203    assert_eq!(SERVER_DISCOVER, SERVER_DISCOVER_METHOD);
204    assert_eq!(MCP_PROTOCOL_VERSION_HEADER, "MCP-Protocol-Version");
205    assert_eq!(MCP_METHOD_HEADER, "Mcp-Method");
206    assert_eq!(MCP_NAME_HEADER, "Mcp-Name");
207    assert_eq!(
208        admission.protocol_version().as_str(),
209        FINAL_PROTOCOL_VERSION
210    );
211    assert_eq!(missing.http_status(), 400);
212    assert_eq!(
213        missing.jsonrpc_error_code(),
214        MISSING_REQUIRED_CLIENT_CAPABILITY_ERROR_CODE
215    );
216    assert_eq!(
217        missing.canonical_error_data(),
218        serde_json::json!({"requiredCapabilities": {"roots": {"listChanged": true}}})
219    );
220}
221
222#[cfg(test)]
223#[test]
224fn prt_03_i_planted_negative() {
225    let metadata = FinalRequestMeta::new(ClientCapabilities {
226        roots: Some(RootsCapability { list_changed: true }),
227        ..ClientCapabilities::default()
228    });
229    let wire_before = serde_json::to_value(&metadata).expect("metadata serializes");
230    let error = admit_final_http_request(FinalHttpRequestMetadata {
231        version: metadata.version_metadata(Some("2025-11-25")),
232        header_method: Some(SERVER_DISCOVER),
233        body_method: Some(SERVER_DISCOVER),
234        header_name: None,
235        body_name: None,
236    })
237    .expect_err("changing only the protocol header must reject the request");
238
239    assert!(
240        matches!(&error, RequestAdmissionError::HeaderMismatch(_)),
241        "a mismatched version mirror must precede unsupported-version classification"
242    );
243    let RequestAdmissionError::HeaderMismatch(error) = error else {
244        return;
245    };
246    assert_eq!(
247        error.reason(),
248        HeaderMismatchReason::HeaderBodyVersionMismatch
249    );
250    assert_eq!(error.http_status(), 400);
251    assert_eq!(error.jsonrpc_error_code(), HEADER_MISMATCH_ERROR_CODE);
252    assert_eq!(error.canonical_error_data(), None);
253    assert_eq!(
254        serde_json::to_value(&metadata).expect("metadata remains serializable"),
255        wire_before
256    );
257}