Skip to main content
Glama
linux.ts5.14 kB
/** * Linux Secret Service Provider * * Stores credentials using the Secret Service API via `secret-tool`. * Requires libsecret-tools package (secret-tool command). * * Works with: * - GNOME Keyring * - KDE Wallet (via Secret Service bridge) * - Any Secret Service compatible backend * * Storage scheme: * - Attribute 'service': 'wpnav' * - Attribute 'account': site domain (e.g., 'example.com') * - Label: 'wpnav: example.com' * * @package WP_Navigator_MCP * @since 2.7.0 */ import { execFileSync, spawn } from 'child_process'; import { BaseKeychainProvider } from './keychain.js'; import type { StoredCredential } from './types.js'; import { KEYCHAIN_SERVICE } from './types.js'; /** * Linux Secret Service implementation * * Uses the `secret-tool` command from libsecret-tools. * Install: apt install libsecret-tools (Debian/Ubuntu) * dnf install libsecret (Fedora) */ export class LinuxSecretServiceProvider extends BaseKeychainProvider { readonly name = 'Linux Secret Service'; /** * Check if Secret Service is available */ get available(): boolean { // Only available on Linux if (process.platform !== 'linux') { return false; } // Check if secret-tool exists try { execFileSync('which', ['secret-tool'], { stdio: 'pipe', timeout: 5000, }); return true; } catch { return false; } } /** * Store a credential in Secret Service * * Uses: secret-tool store --label="wpnav: site" service wpnav account site * * Password is read from stdin for security. */ async store(credential: StoredCredential): Promise<void> { this.validateAccount(credential.account); this.validatePassword(credential.password); return new Promise((resolve, reject) => { const proc = spawn( 'secret-tool', [ 'store', '--label', `${KEYCHAIN_SERVICE}: ${credential.account}`, 'service', KEYCHAIN_SERVICE, 'account', credential.account, ], { stdio: ['pipe', 'ignore', 'pipe'], } ); let stderr = ''; proc.stderr.on('data', (data) => { stderr += data; }); proc.on('error', (err) => { reject(new Error(`Failed to spawn secret-tool: ${err.message}`)); }); proc.on('close', (code) => { if (code === 0) { resolve(); } else { reject( new Error( `Failed to store credential in Secret Service: ${stderr.trim() || `exit code ${code}`}` ) ); } }); // Write password to stdin proc.stdin.write(credential.password); proc.stdin.end(); }); } /** * Retrieve a password from Secret Service * * Uses: secret-tool lookup service wpnav account <site> * * @returns The password or null if not found */ async retrieve(account: string): Promise<string | null> { this.validateAccount(account); try { const result = execFileSync( 'secret-tool', ['lookup', 'service', KEYCHAIN_SERVICE, 'account', account], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000, } ); const password = result.trim(); // secret-tool returns empty string if not found return password || null; } catch { return null; } } /** * Delete a credential from Secret Service * * Uses: secret-tool clear service wpnav account <site> * * @returns true if deleted, false if not found */ async delete(account: string): Promise<boolean> { this.validateAccount(account); try { execFileSync('secret-tool', ['clear', 'service', KEYCHAIN_SERVICE, 'account', account], { stdio: 'pipe', timeout: 10000, }); // secret-tool clear exits 0 even if item doesn't exist // We can't easily distinguish, so return true return true; } catch { return false; } } /** * List all stored wpnav credentials * * Note: secret-tool doesn't have a native list command. * We use secret-tool search which lists matching items. * * @returns Array of account names (site domains) */ async list(): Promise<string[]> { try { // secret-tool search outputs matching items const result = execFileSync('secret-tool', ['search', '--all', 'service', KEYCHAIN_SERVICE], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30000, }); const accounts: string[] = []; // Parse output - each item has "attribute.account = value" const lines = result.split('\n'); for (const line of lines) { // Match: attribute.account = example.com const match = line.match(/attribute\.account\s*=\s*(.+)/); if (match) { accounts.push(match[1].trim()); } } // Dedupe and sort return [...new Set(accounts)].sort(); } catch { // If search fails, return empty list return []; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/littlebearapps/wp-navigator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server