#ipc #daemon #mcp #secret

spn-client

Client library for communicating with the spn daemon - secure secret management for AI applications

12 releases

new 0.3.4 Mar 10, 2026
0.3.3 Mar 9, 2026
0.2.4 Mar 6, 2026
0.1.2 Mar 4, 2026

#175 in Authentication

Download history 645/week @ 2026-03-03

659 downloads per month
Used in 3 crates

AGPL-3.0-or-later

190KB
3.5K SLoC

spn-client

Client library for communicating with the spn daemon.

Overview

spn-client provides a simple, secure interface for Rust applications to retrieve API keys and secrets from the spn daemon. This eliminates the need for each application to directly access the OS keychain, solving the macOS Keychain popup problem where multiple binaries cannot share "Always Allow" permissions.

Features

  • Unix Socket IPC: Secure communication with the spn daemon
  • Fallback Mode: Gracefully falls back to environment variables if daemon is unavailable
  • Zero Dependencies on Keychain: No direct keyring/keychain access required
  • Async-First: Built on Tokio for modern async Rust applications

Installation

Add to your Cargo.toml:

[dependencies]
spn-client = "0.1"

Usage

Basic Usage

use spn_client::SpnClient;

#[tokio::main]
async fn main() -> Result<(), spn_client::Error> {
    // Connect to the daemon
    let mut client = SpnClient::connect().await?;

    // Get an API key
    let api_key = client.get_secret("anthropic").await?;

    // Use with your LLM client
    let llm_client = anthropic::Client::with_api_key(api_key.expose_secret());

    Ok(())
}

Fallback Mode

For applications that should work even without the daemon:

use spn_client::SpnClient;

#[tokio::main]
async fn main() -> Result<(), spn_client::Error> {
    // Falls back to env vars if daemon unavailable
    let mut client = SpnClient::connect_with_fallback().await?;

    if client.is_fallback_mode() {
        println!("Warning: Running without daemon, using env vars");
    }

    let api_key = client.get_secret("anthropic").await?;
    Ok(())
}

Check Secret Availability

use spn_client::SpnClient;

async fn check_providers(client: &mut SpnClient) -> Result<(), spn_client::Error> {
    // Check if a specific provider is configured
    if client.has_secret("openai").await? {
        println!("OpenAI available");
    }

    // List all available providers
    let providers = client.list_providers().await?;
    println!("Available: {:?}", providers);

    Ok(())
}

Supported Providers

Provider Environment Variable Fallback
anthropic ANTHROPIC_API_KEY
openai OPENAI_API_KEY
mistral MISTRAL_API_KEY
groq GROQ_API_KEY
deepseek DEEPSEEK_API_KEY
gemini GEMINI_API_KEY
ollama OLLAMA_HOST
neo4j NEO4J_PASSWORD
github GITHUB_TOKEN
perplexity PERPLEXITY_API_KEY
firecrawl FIRECRAWL_API_KEY

Protocol

Communication uses length-prefixed JSON over Unix sockets:

Socket: ~/.spn/daemon.sock
Format: [4-byte big-endian length][JSON payload]

Protocol: Length-prefixed JSON messages (4-byte big-endian length + JSON payload).

Security

  • Socket permissions: 0600 (owner only)
  • Peer credential verification via SO_PEERCRED
  • Secrets never written to disk
  • Memory zeroed on drop (via secrecy crate)

License

MIT

Dependencies

~7–13MB
~154K SLoC