op-mcp 0.1.0

MCP server providing LLM access to 1Password CLI
Documentation
//! Secret reading and injection tools for 1Password

use rust_mcp_schema::schema_utils::CallToolError;
use rust_mcp_schema::CallToolResult;
use rust_mcp_sdk::macros::{mcp_tool, JsonSchema};
use serde::{Deserialize, Serialize};

use crate::op::OpClient;
use crate::tools::{op_error_to_tool_error, text_result};

// ============================================================================
// secret_read Tool
// ============================================================================

/// Read a secret by its op:// reference.
#[mcp_tool(
    name = "secret_read",
    description = "Read a secret value using a 1Password secret reference (op:// URL). Format: op://vault/item/field or op://vault/item/section/field. Returns the raw secret value."
)]
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SecretReadTool {
    /// The secret reference in op:// format. Examples: "op://Personal/GitHub/password", "op://Work/AWS/Access Keys/access_key_id".
    pub reference: String,
}

impl SecretReadTool {
    pub async fn call(&self, client: &OpClient) -> Result<CallToolResult, CallToolError> {
        let result = client
            .read(&self.reference)
            .await
            .map_err(op_error_to_tool_error)?;
        text_result(result)
    }
}

// ============================================================================
// secret_inject Tool
// ============================================================================

/// Inject secrets into a template string.
#[mcp_tool(
    name = "secret_inject",
    description = "Inject 1Password secrets into a template string. Secret references in op:// format within the template will be replaced with their actual values. Useful for generating configuration files."
)]
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SecretInjectTool {
    /// The template string containing op:// references to replace. Example: 'DATABASE_URL=postgres://user:op://vault/db/password@localhost/mydb'
    pub template: String,
}

impl SecretInjectTool {
    pub async fn call(&self, client: &OpClient) -> Result<CallToolResult, CallToolError> {
        let result = client
            .inject(&self.template)
            .await
            .map_err(op_error_to_tool_error)?;
        text_result(result)
    }
}

// ============================================================================
// secret_run Tool
// ============================================================================

/// Run a command with secrets injected as environment variables.
#[mcp_tool(
    name = "secret_run",
    description = "Run a command with 1Password secrets injected as environment variables. Secret references (op://) in the environment or .env file will be replaced with actual values. Note: This executes a command on the system."
)]
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SecretRunTool {
    /// The command to run (will be executed in a shell).
    pub command: String,

    /// Environment variables to set, with op:// references. Format: ['VAR=op://vault/item/field', ...]
    #[serde(default)]
    pub env: Option<Vec<String>>,

    /// Path to a .env file containing secret references.
    #[serde(default)]
    pub env_file: Option<String>,

    /// If true, don't mask secrets in output (dangerous - use only when necessary).
    #[serde(default)]
    pub no_masking: bool,
}

impl SecretRunTool {
    pub async fn call(&self, client: &OpClient) -> Result<CallToolResult, CallToolError> {
        let env_refs: Option<Vec<&str>> = self
            .env
            .as_ref()
            .map(|e| e.iter().map(|s| s.as_str()).collect());

        let result = client
            .run(
                &self.command,
                env_refs.as_deref(),
                self.env_file.as_deref(),
                self.no_masking,
            )
            .await
            .map_err(op_error_to_tool_error)?;
        text_result(result)
    }
}