Skip to main content

mcp_trace_validator/checks/
inventory.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2026 Tom F. (https://github.com/tomtom215)
3
4//! The check inventory itself: the registered [`Check`] list, lookup by id, and
5//! the ledger of check ids the registry names ahead of their implementation.
6//!
7//! Split from [`super`] so the type definitions stay readable beside their
8//! contract while the list — which grows with every extracted area — lives on
9//! its own.
10
11use super::{Check, base, lifecycle, negotiation, prompts, resources, tools, transport, utilities};
12
13/// One registration row. The list is long and perfectly uniform, so the literal
14/// `Check { id: …, run: … }` form cost four lines each and pushed the file past
15/// the 500-line cap for no gain in reviewability — the pair *is* the row.
16macro_rules! check {
17    ($id:literal, $run:path) => {
18        Check { id: $id, run: $run }
19    };
20}
21
22/// The `2025-11-25` half of the inventory.
23const SHIPPED: &[Check] = &[
24    check!("base.request-id-type", base::request_id_type),
25    check!("base.request-id-not-null", base::request_id_not_null),
26    check!("base.request-id-unique", base::request_id_unique),
27    check!("base.result-id-matches", base::result_id_matches),
28    check!("base.notification-no-id", base::notification_no_id),
29    check!("base.error-shape", base::error_shape),
30    check!("base.error-code-integer", base::error_code_integer),
31    check!("base.jsonrpc-version", base::jsonrpc_version),
32    check!("base.error-id-matches", base::error_id_matches),
33    check!(
34        "lifecycle.first-interaction-initialize",
35        lifecycle::first_interaction_initialize
36    ),
37    check!("lifecycle.initialize-params", lifecycle::initialize_params),
38    check!(
39        "lifecycle.initialized-notification",
40        lifecycle::initialized_notification
41    ),
42    check!(
43        "lifecycle.client-requests-before-init-response",
44        lifecycle::client_requests_before_init_response
45    ),
46    check!(
47        "lifecycle.server-requests-before-initialized",
48        lifecycle::server_requests_before_initialized
49    ),
50    check!(
51        "lifecycle.initialize-result-version",
52        lifecycle::initialize_result_version
53    ),
54    check!(
55        "lifecycle.initialize-result-shape",
56        lifecycle::initialize_result_shape
57    ),
58    check!("base.meta-key-format", base::meta_key_format),
59    check!("base.result-field", base::result_field),
60    check!(
61        "lifecycle.initialize-protocol-version",
62        lifecycle::initialize_protocol_version
63    ),
64    check!(
65        "lifecycle.negotiated-capabilities-only",
66        negotiation::negotiated_capabilities_only
67    ),
68    check!(
69        "transport.stdio-server-output-valid",
70        transport::stdio_server_output_valid
71    ),
72    check!(
73        "transport.stdio-client-input-valid",
74        transport::stdio_client_input_valid
75    ),
76    check!(
77        "transport.session-id-visible-ascii",
78        transport::session_id_visible_ascii
79    ),
80    check!("transport.session-id-echoed", transport::session_id_echoed),
81    check!(
82        "transport.protocol-version-header",
83        transport::protocol_version_header
84    ),
85    check!(
86        "transport.protocol-version-negotiated",
87        transport::protocol_version_negotiated
88    ),
89    check!(
90        "transport.http-post-single-message",
91        transport::http_post_single_message
92    ),
93    check!(
94        "transport.client-post-accept-header",
95        transport::client_post_accept_header
96    ),
97    check!(
98        "transport.client-get-accept-header",
99        transport::client_get_accept_header
100    ),
101    check!(
102        "transport.client-messages-use-post",
103        transport::client_messages_use_post
104    ),
105    check!(
106        "transport.success-content-type",
107        transport::success_content_type
108    ),
109    check!("tools.capability-declared", tools::capability_declared),
110    check!("tools.input-schema-object", tools::input_schema_object),
111    check!("tools.name-length", tools::name_length),
112    check!("tools.name-charset", tools::name_charset),
113    check!("tools.name-unique", tools::name_unique),
114    check!(
115        "tools.embedded-resource-capability",
116        tools::embedded_resource_capability
117    ),
118    check!(
119        "tools.structured-content-text",
120        tools::structured_content_text
121    ),
122    check!(
123        "tools.output-schema-structured-result",
124        tools::output_schema_structured_result
125    ),
126    check!(
127        "resources.capability-declared",
128        resources::capability_declared
129    ),
130    check!(
131        "resources.uri-scheme-rfc3986",
132        resources::uri_scheme_rfc3986
133    ),
134    check!("resources.blob-base64", resources::blob_base64),
135    check!("prompts.capability-declared", prompts::capability_declared),
136    check!(
137        "prompts.image-content-encoding",
138        prompts::image_content_encoding
139    ),
140    check!(
141        "prompts.audio-content-encoding",
142        prompts::audio_content_encoding
143    ),
144    check!(
145        "prompts.embedded-resource-shape",
146        prompts::embedded_resource_shape
147    ),
148    check!("prompts.arguments-validated", prompts::arguments_validated),
149    check!(
150        "logging.capability-declared",
151        utilities::logging_capability_declared
152    ),
153    check!(
154        "completion.capability-declared",
155        utilities::completion_capability_declared
156    ),
157    check!("pagination.cursor-opacity", utilities::cursor_opacity),
158    check!(
159        "pagination.invalid-cursor-rejected",
160        utilities::invalid_cursor_rejected
161    ),
162];
163
164/// Concatenates the two halves at compile time, so each may live in its own file
165/// while `ALL` stays one `&[Check]` — the shape every caller and the coverage
166/// invariants read. `Check` is `Copy`, which is what makes the const loop legal.
167const fn concatenated<const N: usize>(first: &[Check], second: &[Check]) -> [Check; N] {
168    let mut out = [first[0]; N];
169    let mut index = 0;
170    while index < first.len() {
171        out[index] = first[index];
172        index += 1;
173    }
174    let mut offset = 0;
175    while offset < second.len() {
176        out[first.len() + offset] = second[offset];
177        offset += 1;
178    }
179    out
180}
181
182/// The backing storage for [`ALL`].
183static EVERY: [Check; SHIPPED.len() + draft_rows::DRAFT.len()] =
184    concatenated(SHIPPED, draft_rows::DRAFT);
185
186/// Every check implemented by this build, in stable order.
187pub static ALL: &[Check] = &EVERY;
188/// Looks up a check by its stable ID.
189#[must_use]
190pub fn find(id: &str) -> Option<&'static Check> {
191    ALL.iter().find(|check| check.id == id)
192}
193
194mod draft_rows;
195
196#[cfg(test)]
197mod planned;
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use std::collections::HashSet;
203
204    #[test]
205    fn check_ids_are_unique() {
206        let mut seen = HashSet::new();
207        for check in ALL {
208            assert!(seen.insert(check.id), "duplicate check id {}", check.id);
209        }
210    }
211}