chore: declare and check MSRV (#1034)
* chore: declare and check MSRV * fix: clippy fixes for collapsing if-statements * ci: add explicit contents read permission to MSRV job
This commit is contained in:
parent
e660b805ee
commit
76988023b2
22 changed files with 322 additions and 315 deletions
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
|
|
@ -205,6 +205,30 @@ jobs:
|
|||
- name: Spell Check Repo
|
||||
uses: crate-ci/typos@master
|
||||
|
||||
msrv:
|
||||
name: Check MSRV
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Read MSRV from Cargo.toml
|
||||
run: |
|
||||
MSRV=$(cargo metadata --no-deps --format-version 1 \
|
||||
| jq -r '.packages[] | select(.name == "rmcp") | .rust_version')
|
||||
echo "MSRV=$MSRV" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install MSRV Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.MSRV }}
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Check default workspace members with MSRV
|
||||
run: cargo +${{ env.MSRV }} check --all-targets --all-features
|
||||
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ rmcp-macros = { version = "3.0.0-beta.1", path = "./crates/rmcp-macros" }
|
|||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
rust-version = "1.88"
|
||||
version = "3.0.0-beta.1"
|
||||
authors = ["4t145 <u4t145@163.com>"]
|
||||
license = "Apache-2.0"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
msrv = "1.85"
|
||||
too-many-arguments-threshold = 10
|
||||
check-private-items = false
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ name = "rmcp-macros"
|
|||
license = { workspace = true }
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
readme = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -55,9 +55,9 @@ pub fn extract_doc_line(
|
|||
/// Returns the full Parameters<T> type if found
|
||||
pub fn find_parameters_type_in_sig(sig: &Signature) -> Option<Box<Type>> {
|
||||
sig.inputs.iter().find_map(|input| {
|
||||
if let FnArg::Typed(pat_type) = input {
|
||||
if let Type::Path(type_path) = &*pat_type.ty {
|
||||
if type_path
|
||||
if let FnArg::Typed(pat_type) = input
|
||||
&& let Type::Path(type_path) = &*pat_type.ty
|
||||
&& type_path
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
|
|
@ -65,8 +65,6 @@ pub fn find_parameters_type_in_sig(sig: &Signature) -> Option<Box<Type>> {
|
|||
{
|
||||
return Some(pat_type.ty.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,15 +132,15 @@ pub fn prompt(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream>
|
|||
// 3. make body: { Box::pin(async move { #body }) }
|
||||
let new_output = syn::parse2::<ReturnType>({
|
||||
let mut lt = quote! { 'static };
|
||||
if let Some(receiver) = fn_item.sig.receiver() {
|
||||
if let Some((_, receiver_lt)) = receiver.reference.as_ref() {
|
||||
if let Some(receiver) = fn_item.sig.receiver()
|
||||
&& let Some((_, receiver_lt)) = receiver.reference.as_ref()
|
||||
{
|
||||
if let Some(receiver_lt) = receiver_lt {
|
||||
lt = quote! { #receiver_lt };
|
||||
} else {
|
||||
lt = quote! { '_ };
|
||||
}
|
||||
}
|
||||
}
|
||||
match &fn_item.sig.output {
|
||||
syn::ReturnType::Default => {
|
||||
if omit_send {
|
||||
|
|
|
|||
|
|
@ -7,17 +7,14 @@ use crate::common::extract_doc_line;
|
|||
|
||||
/// Check if a type is Json<T> and extract the inner type T
|
||||
fn extract_json_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
|
||||
if let syn::Type::Path(type_path) = ty {
|
||||
if let Some(last_segment) = type_path.path.segments.last() {
|
||||
if last_segment.ident == "Json" {
|
||||
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments {
|
||||
if let Some(syn::GenericArgument::Type(inner_type)) = args.args.first() {
|
||||
if let syn::Type::Path(type_path) = ty
|
||||
&& let Some(last_segment) = type_path.path.segments.last()
|
||||
&& last_segment.ident == "Json"
|
||||
&& let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
|
||||
&& let Some(syn::GenericArgument::Type(inner_type)) = args.args.first()
|
||||
{
|
||||
return Some(inner_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
|
@ -286,15 +283,15 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
|
|||
let omit_send = cfg!(feature = "local") || attribute.local;
|
||||
let new_output = syn::parse2::<ReturnType>({
|
||||
let mut lt = quote! { 'static };
|
||||
if let Some(receiver) = fn_item.sig.receiver() {
|
||||
if let Some((_, receiver_lt)) = receiver.reference.as_ref() {
|
||||
if let Some(receiver) = fn_item.sig.receiver()
|
||||
&& let Some((_, receiver_lt)) = receiver.reference.as_ref()
|
||||
{
|
||||
if let Some(receiver_lt) = receiver_lt {
|
||||
lt = quote! { #receiver_lt };
|
||||
} else {
|
||||
lt = quote! { '_ };
|
||||
}
|
||||
}
|
||||
}
|
||||
match &fn_item.sig.output {
|
||||
syn::ReturnType::Default => {
|
||||
if omit_send {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ name = "rmcp"
|
|||
license = { workspace = true }
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
readme = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -550,12 +550,12 @@ where
|
|||
}
|
||||
|
||||
fn notify_if_visible(&self, name: &str) {
|
||||
if self.map.contains_key(name) {
|
||||
if let Some(notifier) = &self.notifier {
|
||||
if self.map.contains_key(name)
|
||||
&& let Some(notifier) = &self.notifier
|
||||
{
|
||||
notifier();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call(
|
||||
&self,
|
||||
|
|
|
|||
|
|
@ -983,17 +983,15 @@ impl EnumSchemaBuilder<MultiSelect> {
|
|||
return Err("One of the provided default values is not in enum values".to_string());
|
||||
}
|
||||
}
|
||||
if let Some(min) = self.min_items {
|
||||
if (default_values.len() as u64) < min {
|
||||
if let Some(min) = self.min_items
|
||||
&& (default_values.len() as u64) < min
|
||||
{
|
||||
return Err("Number of provided default values is less than min_items".to_string());
|
||||
}
|
||||
}
|
||||
if let Some(max) = self.max_items {
|
||||
if (default_values.len() as u64) > max {
|
||||
return Err(
|
||||
"Number of provided default values is greater than max_items".to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(max) = self.max_items
|
||||
&& (default_values.len() as u64) > max
|
||||
{
|
||||
return Err("Number of provided default values is greater than max_items".to_string());
|
||||
}
|
||||
self.default = default_values;
|
||||
Ok(self)
|
||||
|
|
|
|||
|
|
@ -554,15 +554,14 @@ impl<R: ServiceRole> RequestHandle<R> {
|
|||
None => None,
|
||||
}
|
||||
}, if reset_timeout_on_progress && idle_sleep.is_some() && self.progress_reset_rx.is_some() => {
|
||||
if progress.is_some() {
|
||||
if let Some((timeout, sleep)) = idle_sleep.as_mut() {
|
||||
if progress.is_some()
|
||||
&& let Some((timeout, sleep)) = idle_sleep.as_mut() {
|
||||
sleep.as_mut().reset(tokio::time::Instant::now() + *timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel this request
|
||||
pub async fn cancel(self, reason: Option<String>) -> Result<(), ServiceError> {
|
||||
|
|
@ -1351,12 +1350,11 @@ where
|
|||
tracing::trace!(?evt, "new event");
|
||||
match evt {
|
||||
Event::SendTaskResult(SendTaskResult::Request { id, result }) => {
|
||||
if let Err(e) = result {
|
||||
if let Some(responder) = local_responder_pool.remove(&id) {
|
||||
if let Err(e) = result
|
||||
&& let Some(responder) = local_responder_pool.remove(&id) {
|
||||
let _ = responder.send(Err(ServiceError::TransportSend(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::SendTaskResult(SendTaskResult::Notification {
|
||||
responder,
|
||||
result,
|
||||
|
|
@ -1368,17 +1366,15 @@ where
|
|||
Ok(())
|
||||
};
|
||||
let _ = responder.send(response);
|
||||
if let Some(param) = cancellation_param {
|
||||
if let Some(request_id) = ¶m.request_id {
|
||||
if let Some(responder) = local_responder_pool.remove(request_id) {
|
||||
if let Some(param) = cancellation_param
|
||||
&& let Some(request_id) = ¶m.request_id
|
||||
&& let Some(responder) = local_responder_pool.remove(request_id) {
|
||||
tracing::info!(id = %request_id, reason = param.reason, "cancelled");
|
||||
let _response_result = responder.send(Err(ServiceError::Cancelled {
|
||||
reason: param.reason.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::ResponseSendTaskResult(result) => {
|
||||
if let Err(error) = result {
|
||||
tracing::error!(%error, "response send task failed");
|
||||
|
|
|
|||
|
|
@ -320,15 +320,13 @@ fn try_parse_with_compatibility<T: serde::de::DeserializeOwned>(
|
|||
Ok(item) => Ok(Some(item)),
|
||||
Err(e) => {
|
||||
// Check if this is a notification that should be ignored for compatibility
|
||||
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(line_str) {
|
||||
if let Some(method) =
|
||||
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(line_str)
|
||||
&& let Some(method) =
|
||||
json_value.get("method").and_then(serde_json::Value::as_str)
|
||||
&& should_ignore_notification(&json_value, method)
|
||||
{
|
||||
if should_ignore_notification(&json_value, method) {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"Failed to parse message {}: {} | Error: {}",
|
||||
|
|
|
|||
|
|
@ -1248,8 +1248,9 @@ impl AuthorizationManager {
|
|||
/// the client if credentials are found. Returns `false` when credentials
|
||||
/// are absent or discarded after an authorization-server change.
|
||||
pub async fn initialize_from_store(&mut self) -> Result<bool, AuthError> {
|
||||
if let Some(stored) = self.credential_store.load().await? {
|
||||
if stored.token_response.is_some() {
|
||||
if let Some(stored) = self.credential_store.load().await?
|
||||
&& stored.token_response.is_some()
|
||||
{
|
||||
if self.metadata.is_none() {
|
||||
let metadata = self.discover_metadata().await?;
|
||||
self.metadata = Some(metadata);
|
||||
|
|
@ -1298,7 +1299,6 @@ impl AuthorizationManager {
|
|||
self.configure_client_id(&stored.client_id)?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
|
|
@ -1425,11 +1425,11 @@ impl AuthorizationManager {
|
|||
|
||||
// RFC 8414 RECOMMENDS response_types_supported in the metadata. This field is optional,
|
||||
// but if present and does not include the flow we use ("code"), bail out early with a clear error.
|
||||
if let Some(response_types_supported) = metadata.response_types_supported.as_ref() {
|
||||
if !response_types_supported.contains(&response_type.to_string()) {
|
||||
if let Some(response_types_supported) = metadata.response_types_supported.as_ref()
|
||||
&& !response_types_supported.contains(&response_type.to_string())
|
||||
{
|
||||
return Err(AuthError::InvalidScope(response_type.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// The client always sends an S256 challenge. A server that advertises
|
||||
// methods without S256 can't do the flow we require, so refuse it. A
|
||||
|
|
@ -1713,13 +1713,12 @@ impl AuthorizationManager {
|
|||
}
|
||||
|
||||
// nothing requested or challenged yet: seed from AS metadata, then caller defaults
|
||||
if let Some(metadata) = &self.metadata {
|
||||
if let Some(scopes_supported) = &metadata.scopes_supported {
|
||||
if !scopes_supported.is_empty() {
|
||||
if let Some(metadata) = &self.metadata
|
||||
&& let Some(scopes_supported) = &metadata.scopes_supported
|
||||
&& !scopes_supported.is_empty()
|
||||
{
|
||||
return scopes_supported.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default_scopes.iter().map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
|
@ -1730,14 +1729,13 @@ impl AuthorizationManager {
|
|||
if scopes.is_empty() || scopes.iter().any(|s| s == "offline_access") {
|
||||
return;
|
||||
}
|
||||
if let Some(metadata) = &self.metadata {
|
||||
if let Some(supported) = &metadata.scopes_supported {
|
||||
if supported.iter().any(|s| s == "offline_access") {
|
||||
if let Some(metadata) = &self.metadata
|
||||
&& let Some(supported) = &metadata.scopes_supported
|
||||
&& supported.iter().any(|s| s == "offline_access")
|
||||
{
|
||||
scopes.push("offline_access".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// attempt to upgrade scopes after receiving a 403 insufficient_scope error.
|
||||
/// returns the authorization URL for re-authorization with upgraded scopes.
|
||||
|
|
@ -2255,11 +2253,11 @@ impl AuthorizationManager {
|
|||
self.validate_resource_metadata_resource(&resource_metadata)?;
|
||||
|
||||
// store scopes_supported from protected resource metadata for select_scopes()
|
||||
if let Some(scopes) = resource_metadata.scopes_supported {
|
||||
if !scopes.is_empty() {
|
||||
if let Some(scopes) = resource_metadata.scopes_supported
|
||||
&& !scopes.is_empty()
|
||||
{
|
||||
*self.resource_scopes.write().await = scopes;
|
||||
}
|
||||
}
|
||||
|
||||
let mut candidates = Vec::new();
|
||||
|
||||
|
|
@ -2701,8 +2699,7 @@ impl AuthorizationManager {
|
|||
if let ClientCredentialsConfig::PrivateKeyJwt {
|
||||
signing_algorithm, ..
|
||||
} = config
|
||||
{
|
||||
if let Some(algs) = metadata
|
||||
&& let Some(algs) = metadata
|
||||
.additional_fields
|
||||
.get("token_endpoint_auth_signing_alg_values_supported")
|
||||
.and_then(|v| v.as_array())
|
||||
|
|
@ -2716,7 +2713,6 @@ impl AuthorizationManager {
|
|||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,11 +178,11 @@ pub struct FixedInterval {
|
|||
|
||||
impl SseRetryPolicy for FixedInterval {
|
||||
fn retry(&self, current_times: usize) -> Option<Duration> {
|
||||
if let Some(max_times) = self.max_times {
|
||||
if current_times >= max_times {
|
||||
if let Some(max_times) = self.max_times
|
||||
&& current_times >= max_times
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(self.duration)
|
||||
}
|
||||
}
|
||||
|
|
@ -222,11 +222,11 @@ impl Default for ExponentialBackoff {
|
|||
|
||||
impl SseRetryPolicy for ExponentialBackoff {
|
||||
fn retry(&self, current_times: usize) -> Option<Duration> {
|
||||
if let Some(max_times) = self.max_times {
|
||||
if current_times >= max_times {
|
||||
if let Some(max_times) = self.max_times
|
||||
&& current_times >= max_times
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(self.base_duration * (2u32.pow(current_times as u32)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,13 +104,13 @@ fn param_header_annotations(input_schema: &JsonObject) -> Vec<(String, String)>
|
|||
let mut out = Vec::new();
|
||||
if let Some(Value::Object(props)) = input_schema.get("properties") {
|
||||
for (prop, schema) in props {
|
||||
if let Some(Value::String(header)) = schema.get("x-mcp-header") {
|
||||
if !header.is_empty() {
|
||||
if let Some(Value::String(header)) = schema.get("x-mcp-header")
|
||||
&& !header.is_empty()
|
||||
{
|
||||
out.push((prop.clone(), header.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
|
|
@ -236,22 +236,20 @@ pub(crate) fn standard_request_headers(
|
|||
push(HEADER_MCP_NAME, &encode_header_value(&name));
|
||||
}
|
||||
|
||||
if method == "tools/call" {
|
||||
if let (Some(schema), Some(arguments)) =
|
||||
if method == "tools/call"
|
||||
&& let (Some(schema), Some(arguments)) =
|
||||
(tool_schema, params.and_then(|p| p.get("arguments")))
|
||||
{
|
||||
for (prop, header) in param_header_annotations(schema) {
|
||||
let Some(arg) = arguments.get(&prop) else {
|
||||
continue;
|
||||
};
|
||||
let Some(encoded) = primitive_to_string(arg).map(|s| encode_header_value(&s))
|
||||
else {
|
||||
let Some(encoded) = primitive_to_string(arg).map(|s| encode_header_value(&s)) else {
|
||||
continue;
|
||||
};
|
||||
push(&format!("{HEADER_MCP_PARAM_PREFIX}{header}"), &encoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
|
|
@ -296,8 +294,9 @@ pub(crate) fn validate_request_headers(
|
|||
}
|
||||
}
|
||||
|
||||
if method == "tools/call" {
|
||||
if let Some(schema) = tool_schema {
|
||||
if method == "tools/call"
|
||||
&& let Some(schema) = tool_schema
|
||||
{
|
||||
let arguments = params.and_then(|p| p.get("arguments"));
|
||||
for (prop, header) in param_header_annotations(schema) {
|
||||
let full = format!("{HEADER_MCP_PARAM_PREFIX}{header}");
|
||||
|
|
@ -327,7 +326,6 @@ pub(crate) fn validate_request_headers(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,8 +178,9 @@ impl StreamableHttpClient for reqwest::Client {
|
|||
request = request.header(HEADER_SESSION_ID, session_id.as_ref());
|
||||
}
|
||||
let response = request.json(&message).send().await?;
|
||||
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
|
||||
if let Some(header) = response.headers().get(WWW_AUTHENTICATE) {
|
||||
if response.status() == reqwest::StatusCode::UNAUTHORIZED
|
||||
&& let Some(header) = response.headers().get(WWW_AUTHENTICATE)
|
||||
{
|
||||
let header = header
|
||||
.to_str()
|
||||
.map_err(|_| {
|
||||
|
|
@ -192,9 +193,9 @@ impl StreamableHttpClient for reqwest::Client {
|
|||
www_authenticate_header: header,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if response.status() == reqwest::StatusCode::FORBIDDEN {
|
||||
if let Some(header) = response.headers().get(WWW_AUTHENTICATE) {
|
||||
if response.status() == reqwest::StatusCode::FORBIDDEN
|
||||
&& let Some(header) = response.headers().get(WWW_AUTHENTICATE)
|
||||
{
|
||||
let header_str = header.to_str().map_err(|_| {
|
||||
StreamableHttpError::UnexpectedServerResponse(Cow::from(
|
||||
"invalid www-authenticate header value",
|
||||
|
|
@ -208,7 +209,6 @@ impl StreamableHttpClient for reqwest::Client {
|
|||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
let status = response.status();
|
||||
if matches!(
|
||||
status,
|
||||
|
|
|
|||
|
|
@ -226,8 +226,9 @@ impl StreamableHttpClient for UnixSocketHttpClient {
|
|||
|
||||
let status = response.status();
|
||||
|
||||
if status == StatusCode::UNAUTHORIZED {
|
||||
if let Some(header) = response.headers().get(WWW_AUTHENTICATE) {
|
||||
if status == StatusCode::UNAUTHORIZED
|
||||
&& let Some(header) = response.headers().get(WWW_AUTHENTICATE)
|
||||
{
|
||||
let www_authenticate_header = header
|
||||
.to_str()
|
||||
.map_err(|_| {
|
||||
|
|
@ -240,10 +241,10 @@ impl StreamableHttpClient for UnixSocketHttpClient {
|
|||
www_authenticate_header,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if status == StatusCode::FORBIDDEN {
|
||||
if let Some(header) = response.headers().get(WWW_AUTHENTICATE) {
|
||||
if status == StatusCode::FORBIDDEN
|
||||
&& let Some(header) = response.headers().get(WWW_AUTHENTICATE)
|
||||
{
|
||||
let header_str = header.to_str().map_err(|_| {
|
||||
StreamableHttpError::UnexpectedServerResponse(Cow::from(
|
||||
"invalid www-authenticate header value",
|
||||
|
|
@ -257,7 +258,6 @@ impl StreamableHttpClient for UnixSocketHttpClient {
|
|||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(status, StatusCode::ACCEPTED | StatusCode::NO_CONTENT) {
|
||||
return Ok(StreamableHttpPostResponse::Accepted);
|
||||
|
|
@ -438,8 +438,9 @@ impl StreamableHttpClient for UnixSocketHttpClient {
|
|||
return Err(StreamableHttpError::ServerDoesNotSupportSse);
|
||||
}
|
||||
|
||||
if response.status() == StatusCode::UNAUTHORIZED {
|
||||
if let Some(header) = response.headers().get(WWW_AUTHENTICATE) {
|
||||
if response.status() == StatusCode::UNAUTHORIZED
|
||||
&& let Some(header) = response.headers().get(WWW_AUTHENTICATE)
|
||||
{
|
||||
let www_authenticate_header = header
|
||||
.to_str()
|
||||
.map_err(|_| {
|
||||
|
|
@ -452,10 +453,10 @@ impl StreamableHttpClient for UnixSocketHttpClient {
|
|||
www_authenticate_header,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if response.status() == StatusCode::FORBIDDEN {
|
||||
if let Some(header) = response.headers().get(WWW_AUTHENTICATE) {
|
||||
if response.status() == StatusCode::FORBIDDEN
|
||||
&& let Some(header) = response.headers().get(WWW_AUTHENTICATE)
|
||||
{
|
||||
let header_str = header.to_str().map_err(|_| {
|
||||
StreamableHttpError::UnexpectedServerResponse(Cow::from(
|
||||
"invalid www-authenticate header value",
|
||||
|
|
@ -469,7 +470,6 @@ impl StreamableHttpClient for UnixSocketHttpClient {
|
|||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(StreamableHttpError::UnexpectedServerResponse(Cow::Owned(
|
||||
|
|
|
|||
|
|
@ -42,8 +42,9 @@ fn build_request_headers(
|
|||
use serde_json::Value;
|
||||
|
||||
let mut headers = base.clone();
|
||||
if *version >= ProtocolVersion::STANDARD_HEADERS {
|
||||
if let Ok(value) = serde_json::to_value(message) {
|
||||
if *version >= ProtocolVersion::STANDARD_HEADERS
|
||||
&& let Ok(value) = serde_json::to_value(message)
|
||||
{
|
||||
let schema = value
|
||||
.get("method")
|
||||
.and_then(Value::as_str)
|
||||
|
|
@ -57,7 +58,6 @@ fn build_request_headers(
|
|||
headers.insert(name, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
headers
|
||||
}
|
||||
|
||||
|
|
@ -90,8 +90,9 @@ fn cache_tools_from_response(
|
|||
if protocol_version < &ProtocolVersion::STANDARD_HEADERS {
|
||||
return;
|
||||
}
|
||||
if let ServerJsonRpcMessage::Response(response) = message {
|
||||
if let ServerResult::ListToolsResult(list) = &mut response.result {
|
||||
if let ServerJsonRpcMessage::Response(response) = message
|
||||
&& let ServerResult::ListToolsResult(list) = &mut response.result
|
||||
{
|
||||
list.tools.retain(|tool| {
|
||||
let Err(reason) =
|
||||
mcp_headers::validate_param_header_annotations(&tool.input_schema)
|
||||
|
|
@ -103,7 +104,6 @@ fn cache_tools_from_response(
|
|||
false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn negotiate_version_headers(
|
||||
|
|
@ -112,15 +112,15 @@ fn negotiate_version_headers(
|
|||
) -> (ProtocolVersion, HashMap<HeaderName, HeaderValue>) {
|
||||
let mut version = ProtocolVersion::default();
|
||||
let mut headers = base;
|
||||
if let ServerJsonRpcMessage::Response(response) = init_response {
|
||||
if let ServerResult::InitializeResult(init_result) = &response.result {
|
||||
if let ServerJsonRpcMessage::Response(response) = init_response
|
||||
&& let ServerResult::InitializeResult(init_result) = &response.result
|
||||
{
|
||||
version = init_result.protocol_version.clone();
|
||||
// HeaderName::from_static requires lowercase
|
||||
if let Ok(hv) = HeaderValue::from_str(init_result.protocol_version.as_str()) {
|
||||
headers.insert(HeaderName::from_static("mcp-protocol-version"), hv);
|
||||
}
|
||||
}
|
||||
}
|
||||
(version, headers)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -479,13 +479,13 @@ impl LocalSessionWorker {
|
|||
&mut self,
|
||||
notification: &JsonRpcNotification<ClientNotification>,
|
||||
) {
|
||||
if let ClientNotification::CancelledNotification(n) = ¬ification.notification {
|
||||
if let Some(request_id) = n.params.request_id.clone() {
|
||||
if let ClientNotification::CancelledNotification(n) = ¬ification.notification
|
||||
&& let Some(request_id) = n.params.request_id.clone()
|
||||
{
|
||||
let resource = ResourceKey::McpRequestId(request_id);
|
||||
self.unregister_resource(&resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn evict_expired_channels(&mut self) {
|
||||
let ttl = self.session_config.completed_cache_ttl;
|
||||
self.tx_router
|
||||
|
|
@ -638,13 +638,11 @@ impl LocalSessionWorker {
|
|||
OutboundChannel::RequestWise { id, close } => {
|
||||
if let Some(request_wise) = self.tx_router.get_mut(&id) {
|
||||
request_wise.tx.send(message).await?;
|
||||
if close {
|
||||
if let Some(channel) = self.tx_router.remove(&id) {
|
||||
if close && let Some(channel) = self.tx_router.remove(&id) {
|
||||
for resource in channel.resources {
|
||||
self.resource_router.remove(&resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(SessionError::ChannelClosed(Some(id)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,14 +238,14 @@ impl ClientHandler for MrtrClient {
|
|||
request: ElicitRequestParams,
|
||||
_context: RequestContext<RoleClient>,
|
||||
) -> Result<ElicitResult, ErrorData> {
|
||||
if let ElicitRequestParams::FormElicitationParams { message, .. } = &request {
|
||||
if message == "FAIL" {
|
||||
if let ElicitRequestParams::FormElicitationParams { message, .. } = &request
|
||||
&& message == "FAIL"
|
||||
{
|
||||
return Err(ErrorData::internal_error(
|
||||
"elicitation handler failed",
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(ElicitResult::new(ElicitationAction::Accept).with_content(json!({ "name": "Ferris" })))
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue