Authdog

MCP SDK: Rust

Last updated Oct 13, 2026
View as Markdown

The Rust MCP SDK (authdog-mcp) is a bearer-token verifier for Model Context Protocol servers. AuthdogVerifier takes the access token an MCP client presents, calls Authdog GET /v1/userinfo with it, and returns an AuthInfo identity you use to authorize the request. It is async and built on tokio and reqwest.

Availability

authdog-mcp is source-available in github.com/authdog/mcp-sdk (rust/). Publication on crates.io is unverified, so cargo add authdog-mcp may not resolve yet. Until it does, depend on a local checkout.

Install

Clone the repository and point a path dependency at the rust/ directory:

git clone https://github.com/authdog/mcp-sdk.git
[dependencies]
authdog-mcp = { path = "../mcp-sdk/rust" }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

The crate is version 0.1.0, edition 2021, MIT licensed. Its dependencies are reqwest (with json), serde, serde_json, tokio, and chrono.

Configure

VerifierConfig has two fields:

Field Type Default Description
base_url String https://api.authdog.com Base URL of the Authdog API. A trailing / is trimmed.
timeout Duration 10 seconds Request timeout.
use authdog_mcp::{AuthdogVerifier, VerifierConfig};
use std::time::Duration;

let verifier = AuthdogVerifier::new(VerifierConfig {
    base_url: "https://api.authdog.com".to_string(),
    timeout: Duration::from_secs(10),
});

// or use the defaults
let verifier = AuthdogVerifier::new(VerifierConfig::default());

Construct the verifier once and share it, for example behind Arc. It holds a pooled HTTP client and sends User-Agent: authdog-mcp-rust-sdk/0.1.0 on every request.

Verify a token

verify_token sends GET {base_url}/v1/userinfo with Authorization: Bearer <access_token>:

match verifier.verify_token("incoming-access-token").await {
    Ok(auth) => {
        println!("subject: {}", auth.subject);
        println!("username: {}", auth.username);
        if let Some(email) = &auth.email {
            println!("email: {}", email);
        }
        if let Some(expires_at) = auth.expires_at {
            println!("expires at: {}", expires_at);
        }
    }
    Err(e) => eprintln!("verification failed: {}", e),
}

AuthInfo is mapped from the userinfo response:

Field Type Source
token String The access token that was verified.
subject String user.id
username String user.userName
email Option<String> First non-empty user.emails[].value, or None.
scopes Vec<String> Always empty. Userinfo does not expose scopes yet.
expires_at Option<DateTime<Utc>> Now plus session.remainingSeconds.
user User The full user object from userinfo.

Wire into an MCP server

An HTTP MCP server receives the caller's bearer token in the Authorization header. Strip the prefix, verify on each request, and use the returned AuthInfo to authorize the call:

use authdog_mcp::{AuthdogError, AuthInfo, AuthdogVerifier};

async fn authenticate(
    verifier: &AuthdogVerifier,
    authorization_header: Option<&str>,
) -> Result<AuthInfo, AuthdogError> {
    let token = authorization_header
        .and_then(|h| h.strip_prefix("Bearer "))
        .ok_or_else(|| AuthdogError::new("missing bearer token".to_string()))?;

    verifier.verify_token(token).await
}

The crate does not ship framework middleware. Call this from your own handler or middleware and map failures to your server's unauthorized response.

Errors

verify_token returns Result<AuthInfo, AuthdogError>. AuthenticationError and APIError each convert into AuthdogError via From, and the conversion keeps only the message. The returned AuthdogError therefore carries no variant: tell a rejected token from an upstream failure by message, not by type.

Condition Message
HTTP 401 Unauthorized - invalid or expired token
Other non-2xx response HTTP error <status>: <body>
Transport failure, including timeout Request failed: <cause>
Success response that is not valid JSON for the userinfo shape Invalid JSON response: <cause>

The three types (AuthdogError, AuthenticationError, APIError) implement std::error::Error and Display. AuthdogError::new(String) is public.

If the HTTP client cannot be built with your timeout, the verifier falls back to a default reqwest client. In that case the configured timeout does not apply.