Skip to content

Commit c78a33b

Browse files
authored
chore: upgrade rmcp to v0.17.0 and reqwest to v0.13 (#140)
Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent a4919db commit c78a33b

14 files changed

Lines changed: 161 additions & 250 deletions

File tree

Cargo.lock

Lines changed: 83 additions & 135 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ os_pipe = "1"
5858

5959
# MCP Protocol
6060
# NOTE: Never use local path dependency - E:\one-mcp\rust-sdk is for source lookup only
61-
rmcp = { version = "0.15.0", features = [
61+
rmcp = { version = "0.17.0", features = [
6262
"client",
6363
"server",
6464
"transport-io",
@@ -69,7 +69,7 @@ rmcp = { version = "0.15.0", features = [
6969
] }
7070

7171
# HTTP
72-
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
72+
reqwest = { version = "0.13", features = ["json", "form", "rustls"] }
7373
axum = { version = "0.8", features = ["macros"] }
7474
http-body-util = "0.1"
7575

@@ -84,9 +84,4 @@ lto = true
8484
codegen-units = 1
8585
strip = true
8686

87-
# Temporary patch: fixes SSE channel replacement bug (notifications lost on reconnect)
88-
# Remove once upstream merges https://github.com/modelcontextprotocol/rust-sdk/pull/660
89-
[patch.crates-io]
90-
rmcp = { git = "https://github.com/mcpmux/rust-sdk.git", branch = "fix/sse-channel-replacement-conflict" }
91-
9287

apps/desktop/src-tauri/src/commands/oauth.rs

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -668,28 +668,25 @@ pub async fn get_oauth_clients(
668668
// Map to response format
669669
let client_infos: Vec<OAuthClientInfo> = clients
670670
.into_iter()
671-
.map(|client| {
672-
OAuthClientInfo {
673-
client_id: client.client_id,
674-
registration_type: client.registration_type.as_str().to_string(),
675-
client_name: client.client_name,
676-
client_alias: client.client_alias,
677-
redirect_uris: client.redirect_uris,
678-
scope: client.scope,
679-
approved: client.approved,
680-
logo_uri: client.logo_uri,
681-
client_uri: client.client_uri,
682-
software_id: client.software_id,
683-
software_version: client.software_version,
684-
metadata_url: client.metadata_url,
685-
metadata_cached_at: client.metadata_cached_at,
686-
metadata_cache_ttl: client.metadata_cache_ttl,
687-
connection_mode: client.connection_mode,
688-
locked_space_id: client.locked_space_id,
689-
last_seen: client.last_seen,
690-
created_at: client.created_at,
691-
has_active_tokens: false, // TODO: Check if client has active tokens
692-
}
671+
.map(|client| OAuthClientInfo {
672+
client_id: client.client_id,
673+
registration_type: client.registration_type.as_str().to_string(),
674+
client_name: client.client_name,
675+
client_alias: client.client_alias,
676+
redirect_uris: client.redirect_uris,
677+
scope: client.scope,
678+
approved: client.approved,
679+
logo_uri: client.logo_uri,
680+
client_uri: client.client_uri,
681+
software_id: client.software_id,
682+
software_version: client.software_version,
683+
metadata_url: client.metadata_url,
684+
metadata_cached_at: client.metadata_cached_at,
685+
metadata_cache_ttl: client.metadata_cache_ttl,
686+
connection_mode: client.connection_mode,
687+
locked_space_id: client.locked_space_id,
688+
last_seen: client.last_seen,
689+
created_at: client.created_at,
693690
})
694691
.collect();
695692

@@ -763,7 +760,6 @@ pub struct OAuthClientInfo {
763760
pub locked_space_id: Option<String>,
764761
pub last_seen: Option<String>,
765762
pub created_at: String,
766-
pub has_active_tokens: bool,
767763
}
768764

769765
/// Request to update client settings
@@ -837,7 +833,6 @@ pub async fn update_oauth_client(
837833
locked_space_id: updated_client.locked_space_id,
838834
last_seen: updated_client.last_seen,
839835
created_at: updated_client.created_at,
840-
has_active_tokens: false,
841836
})
842837
}
843838

apps/desktop/src/features/clients/ClientsPage.tsx

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import {
1111
Lock,
1212
Unlock,
1313
HelpCircle,
14-
Wifi,
15-
WifiOff,
1614
RefreshCw,
1715
Settings,
1816
Trash2,
@@ -649,21 +647,6 @@ export default function ClientsPage() {
649647
</div>
650648
</div>
651649

652-
{/* Status Badge */}
653-
<div className="mb-4">
654-
<span className={`inline-flex items-center gap-2 text-sm px-3 py-1.5 rounded-full font-medium ${
655-
client.has_active_tokens
656-
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400'
657-
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'
658-
}`}>
659-
{client.has_active_tokens ? (
660-
<><Wifi className="h-4 w-4" /> Active</>
661-
) : (
662-
<><WifiOff className="h-4 w-4" /> Inactive</>
663-
)}
664-
</span>
665-
</div>
666-
667650
{/* Connection Mode */}
668651
<div className="flex items-center gap-2.5 text-sm text-[rgb(var(--foreground))] mb-2">
669652
<ModeIcon className={`h-4 w-4 ${modeInfo.color}`} />
@@ -722,25 +705,11 @@ export default function ClientsPage() {
722705
</button>
723706
</div>
724707

725-
{/* Quick Status */}
726-
<div className="flex items-center gap-2 flex-wrap">
727-
<span className={`inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-full font-medium ${
728-
selectedClient.has_active_tokens
729-
? 'bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400'
730-
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400'
731-
}`}>
732-
{selectedClient.has_active_tokens ? (
733-
<><Wifi className="h-3 w-3" /> Connected</>
734-
) : (
735-
<><WifiOff className="h-3 w-3" /> Inactive</>
736-
)}
708+
{selectedClient.software_version && (
709+
<span className="text-xs text-[rgb(var(--muted))] px-2.5 py-1 bg-[rgb(var(--background))] rounded-full inline-block mt-1">
710+
v{selectedClient.software_version}
737711
</span>
738-
{selectedClient.software_version && (
739-
<span className="text-xs text-[rgb(var(--muted))] px-2.5 py-1 bg-[rgb(var(--background))] rounded-full">
740-
v{selectedClient.software_version}
741-
</span>
742-
)}
743-
</div>
712+
)}
744713
</div>
745714

746715
{/* Scrollable Content */}

apps/desktop/src/lib/api/gateway.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ export interface OAuthClient {
131131
locked_space_id: string | null;
132132
last_seen: string | null;
133133
created_at: string;
134-
has_active_tokens: boolean;
135134
}
136135

137136
/**

crates/mcpmux-gateway/src/pool/credential_store.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! CredentialStore interface.
66
77
use std::sync::Arc;
8+
use std::time::{SystemTime, UNIX_EPOCH};
89

910
use async_trait::async_trait;
1011
use chrono::{Duration, Utc};
@@ -159,6 +160,19 @@ impl DatabaseCredentialStore {
159160
}
160161
}
161162

163+
/// Current time as seconds since UNIX epoch, matching rmcp's `AuthorizationManager::now_epoch_secs()`.
164+
///
165+
/// Used when loading credentials from the database: since `build_token_response` recalculates
166+
/// `expires_in` as remaining time from the stored `expires_at`, setting `token_received_at = now`
167+
/// makes rmcp's expiry arithmetic correct (`remaining = expires_in - (now - received_at)` = `expires_in`),
168+
/// enabling proactive token refresh before expiry instead of waiting for a 401.
169+
fn now_epoch_secs() -> u64 {
170+
SystemTime::now()
171+
.duration_since(UNIX_EPOCH)
172+
.unwrap_or_default()
173+
.as_secs()
174+
}
175+
162176
#[async_trait]
163177
impl CredentialStore for DatabaseCredentialStore {
164178
async fn load(&self) -> Result<Option<StoredCredentials>, AuthError> {
@@ -208,6 +222,7 @@ impl CredentialStore for DatabaseCredentialStore {
208222
client_id: reg.client_id,
209223
token_response: Some(token_response),
210224
granted_scopes: Vec::new(),
225+
token_received_at: Some(now_epoch_secs()),
211226
})
212227
}
213228
(Some(reg), None) => {
@@ -219,6 +234,7 @@ impl CredentialStore for DatabaseCredentialStore {
219234
client_id: reg.client_id,
220235
token_response: None,
221236
granted_scopes: Vec::new(),
237+
token_received_at: Some(now_epoch_secs()),
222238
})
223239
}
224240
(None, Some(access)) => {
@@ -231,6 +247,7 @@ impl CredentialStore for DatabaseCredentialStore {
231247
client_id: String::new(),
232248
token_response: Some(token_response),
233249
granted_scopes: Vec::new(),
250+
token_received_at: Some(now_epoch_secs()),
234251
})
235252
}
236253
(None, None) => {
@@ -588,6 +605,7 @@ mod tests {
588605
client_id: "new-client-id".to_string(),
589606
token_response: Some(token_response),
590607
granted_scopes: Vec::new(),
608+
token_received_at: None,
591609
};
592610

593611
store.save(credentials).await.unwrap();
@@ -646,6 +664,7 @@ mod tests {
646664
client_id: "client-id".to_string(),
647665
token_response: Some(token_response),
648666
granted_scopes: Vec::new(),
667+
token_received_at: None,
649668
};
650669

651670
store.save(credentials).await.unwrap();

crates/mcpmux-gateway/src/server/handlers.rs

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -602,12 +602,9 @@ pub async fn oauth_token(
602602
let client_id_for_tracking = pending.client_id.clone();
603603
drop(gateway_state);
604604

605-
// Track that this client has active tokens and emit event
605+
// Update last_seen and emit event
606606
{
607-
let mut gateway_state = state.write().await;
608-
gateway_state
609-
.clients_with_tokens
610-
.insert(client_id_for_tracking.clone());
607+
let gateway_state = state.read().await;
611608

612609
// Update last_seen in database
613610
if let Some(repo) = gateway_state.inbound_client_repository() {
@@ -949,7 +946,6 @@ pub struct OAuthClientInfoResponse {
949946
pub locked_space_id: Option<String>,
950947
pub last_seen: Option<String>,
951948
pub created_at: String,
952-
pub has_active_tokens: bool,
953949
}
954950

955951
/// List all registered OAuth clients
@@ -969,28 +965,24 @@ pub async fn oauth_list_clients(
969965
Ok(db_clients) => {
970966
let clients: Vec<OAuthClientInfoResponse> = db_clients
971967
.into_iter()
972-
.map(|c| {
973-
let has_active = gateway_state.clients_with_tokens.contains(&c.client_id);
974-
OAuthClientInfoResponse {
975-
client_id: c.client_id,
976-
registration_type: c.registration_type.as_str().to_string(),
977-
client_name: c.client_name,
978-
client_alias: c.client_alias,
979-
redirect_uris: c.redirect_uris,
980-
scope: c.scope,
981-
logo_uri: c.logo_uri,
982-
client_uri: c.client_uri,
983-
software_id: c.software_id,
984-
software_version: c.software_version,
985-
metadata_url: c.metadata_url,
986-
metadata_cached_at: c.metadata_cached_at,
987-
metadata_cache_ttl: c.metadata_cache_ttl,
988-
connection_mode: c.connection_mode,
989-
locked_space_id: c.locked_space_id,
990-
last_seen: c.last_seen,
991-
created_at: c.created_at,
992-
has_active_tokens: has_active,
993-
}
968+
.map(|c| OAuthClientInfoResponse {
969+
client_id: c.client_id,
970+
registration_type: c.registration_type.as_str().to_string(),
971+
client_name: c.client_name,
972+
client_alias: c.client_alias,
973+
redirect_uris: c.redirect_uris,
974+
scope: c.scope,
975+
logo_uri: c.logo_uri,
976+
client_uri: c.client_uri,
977+
software_id: c.software_id,
978+
software_version: c.software_version,
979+
metadata_url: c.metadata_url,
980+
metadata_cached_at: c.metadata_cached_at,
981+
metadata_cache_ttl: c.metadata_cache_ttl,
982+
connection_mode: c.connection_mode,
983+
locked_space_id: c.locked_space_id,
984+
last_seen: c.last_seen,
985+
created_at: c.created_at,
994986
})
995987
.collect();
996988
info!("[OAuth] Listed {} clients from database", clients.len());
@@ -1213,9 +1205,6 @@ pub async fn oauth_update_client(
12131205
.await
12141206
{
12151207
Ok(Some(client)) => {
1216-
let has_active = gateway_state
1217-
.clients_with_tokens
1218-
.contains(&client.client_id);
12191208
let response = OAuthClientInfoResponse {
12201209
client_id: client.client_id,
12211210
registration_type: client.registration_type.as_str().to_string(),
@@ -1234,7 +1223,6 @@ pub async fn oauth_update_client(
12341223
locked_space_id: client.locked_space_id,
12351224
last_seen: client.last_seen,
12361225
created_at: client.created_at,
1237-
has_active_tokens: has_active,
12381226
};
12391227
info!("[OAuth] Client updated: {}", response.client_id);
12401228
Json(response).into_response()
@@ -1264,7 +1252,7 @@ pub async fn oauth_delete_client(
12641252
) -> Response {
12651253
info!("[OAuth] Deleting client: {}", client_id);
12661254

1267-
let mut gateway_state = state.write().await;
1255+
let gateway_state = state.read().await;
12681256

12691257
let Some(repo) = gateway_state.inbound_client_repository() else {
12701258
warn!("[OAuth] Database not available for client deletion");
@@ -1273,8 +1261,6 @@ pub async fn oauth_delete_client(
12731261

12741262
match repo.delete_client(&client_id).await {
12751263
Ok(true) => {
1276-
// Remove from active tokens set
1277-
gateway_state.clients_with_tokens.remove(&client_id);
12781264
info!("[OAuth] Client deleted: {}", client_id);
12791265
StatusCode::NO_CONTENT.into_response()
12801266
}

crates/mcpmux-gateway/src/server/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ impl GatewayServer {
255255
LocalSessionManager::default().into(),
256256
StreamableHttpServerConfig {
257257
stateful_mode: true,
258+
json_response: false,
258259
sse_keep_alive: Some(std::time::Duration::from_secs(30)),
259260
sse_retry: Some(std::time::Duration::from_secs(3)),
260261
cancellation_token: CancellationToken::new(),

crates/mcpmux-gateway/src/server/state.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ pub struct GatewayState {
5151
pub oauth_tokens: HashMap<String, super::super::oauth::OAuthToken>,
5252
/// Pending authorization codes (code -> PendingAuthorization)
5353
pub pending_authorizations: HashMap<String, PendingAuthorization>,
54-
/// Set of client_ids that have been issued tokens (for "active" status)
55-
pub clients_with_tokens: std::collections::HashSet<String>,
5654
/// JWT signing secret (for issuing access tokens)
5755
pub jwt_signing_secret: Option<Zeroizing<[u8; JWT_SECRET_SIZE]>>,
5856
/// Database connection (for persistent OAuth storage)
@@ -74,7 +72,6 @@ impl GatewayState {
7472
access_keys: HashMap::new(),
7573
oauth_tokens: HashMap::new(),
7674
pending_authorizations: HashMap::new(),
77-
clients_with_tokens: std::collections::HashSet::new(),
7875
jwt_signing_secret: None,
7976
db: None,
8077
inbound_client_repository: None,

scripts/take-screenshots.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ const FEATURE_SETS = [
138138
];
139139

140140
const OAUTH_CLIENTS = [
141-
{ client_id: 'cursor-001', registration_type: 'dcr', client_name: 'Cursor', client_alias: null, redirect_uris: ['http://localhost:9315/callback'], scope: null, approved: true, logo_uri: null, client_uri: null, software_id: 'cursor', software_version: '0.45.0', metadata_url: null, metadata_cached_at: null, metadata_cache_ttl: null, connection_mode: 'follow_active', locked_space_id: null, last_seen: '2026-02-07T09:30:00Z', created_at: '2026-01-20T10:00:00Z', has_active_tokens: true },
142-
{ client_id: 'vscode-001', registration_type: 'dcr', client_name: 'VS Code', client_alias: null, redirect_uris: ['http://localhost:9315/callback'], scope: null, approved: true, logo_uri: null, client_uri: null, software_id: 'vscode', software_version: '1.96.0', metadata_url: null, metadata_cached_at: null, metadata_cache_ttl: null, connection_mode: 'follow_active', locked_space_id: null, last_seen: '2026-02-07T08:45:00Z', created_at: '2026-01-22T10:00:00Z', has_active_tokens: true },
141+
{ client_id: 'cursor-001', registration_type: 'dcr', client_name: 'Cursor', client_alias: null, redirect_uris: ['http://localhost:9315/callback'], scope: null, approved: true, logo_uri: null, client_uri: null, software_id: 'cursor', software_version: '0.45.0', metadata_url: null, metadata_cached_at: null, metadata_cache_ttl: null, connection_mode: 'follow_active', locked_space_id: null, last_seen: '2026-02-07T09:30:00Z', created_at: '2026-01-20T10:00:00Z' },
142+
{ client_id: 'vscode-001', registration_type: 'dcr', client_name: 'VS Code', client_alias: null, redirect_uris: ['http://localhost:9315/callback'], scope: null, approved: true, logo_uri: null, client_uri: null, software_id: 'vscode', software_version: '1.96.0', metadata_url: null, metadata_cached_at: null, metadata_cache_ttl: null, connection_mode: 'follow_active', locked_space_id: null, last_seen: '2026-02-07T08:45:00Z', created_at: '2026-01-22T10:00:00Z' },
143143
];
144144

145145
function mkRegistry(id, name, desc, alias, icon, categories, auth, transportType, publisher, caps, hostingType, badges) {

0 commit comments

Comments
 (0)