npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ai-ssh-toolkit

v0.3.0

Published

MCP server for AI-driven SSH sessions with pluggable credential backends

Readme

ai-ssh-toolkit

MCP server for AI-driven SSH session management and secure credential retrieval. Enables GitHub Copilot to execute commands on remote hosts via SSH with pluggable credential backends.

Features

  • SSH Command Execution — Connect, authenticate, run commands on remote hosts via PTY
  • Persistent SSH Sessions — Open an interactive shell once, run multiple commands, close when done
  • Pluggable Credentials — Bitwarden CLI, Azure Key Vault, environment variables (extensible)
  • Network Device Support — NX-OS, Dell OS10, SONiC, Linux with auto-prompt detection
  • Hardened Security — No temp files, Buffer-only passwords, PTY output scrubbing, ephemeral session IDs
  • Cross-Platform — Windows (ConPTY) + Linux/macOS (Unix PTY) from day one

Quick Start

GitHub Copilot CLI

Add to your MCP config:

{
  "mcpServers": {
    "ai-ssh-toolkit": {
      "command": "npx",
      "args": ["-y", "ai-ssh-toolkit"]
    }
  }
}

VS Code / GitHub Copilot Chat

Add to your .vscode/mcp.json (workspace) or user settings.json:

{
  "mcpServers": {
    "ai-ssh-toolkit": {
      "command": "npx",
      "args": ["-y", "ai-ssh-toolkit"]
    }
  }
}

MCP Tools

ssh_execute

Connect to a host via SSH, run a single command, return output, and close. Best for one-shot commands where you don't need to maintain state between invocations.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | host | string | ✅ | Hostname or IP | | username | string | ❌* | SSH username | | command | string | ✅ | Command to execute | | credential_backend | string | ❌ | Backend name: bitwarden, azure-keyvault, env, google-secret-manager (default: google-secret-manager) | | credential_ref | string | ❌ | Backend-specific reference (BW item name, AKV secret name) | | platform | string | ❌ | Target OS hint: nxos, os10, sonic, linux, auto (default: auto) | | timeout_ms | number | ❌ | Command timeout in ms (default: 30000) |

*Optional when credential_ref provides a username.

credential_get

Retrieve credential metadata (never returns actual passwords).

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | backend | string | ✅ | Backend name | | ref | string | ✅ | Backend-specific reference |

credential_list_backends

Discover available credential backends on the system.

No parameters required.

ssh_check_host

Check SSH host reachability via TCP connect, SSH banner probe, or full auth check.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | host | string | ✅ | Hostname or IP | | port | number | ❌ | Port to check (default: 22) | | username | string | ❌ | SSH username for the check | | timeout_ms | number | ❌ | Connection timeout in ms (default: 5000) | | mode | string | ❌ | 'tcp', 'banner' (default), or 'auth' | | use_ssh_config | boolean | ❌ | Honor ~/.ssh/config (default: true) |

Modes:

  • banner (default) — TCP connect + read SSH server banner. Works for password-auth hosts without false negatives.
  • tcp — TCP connect only (just checks the port is open). Returns tcp_open or tcp_unreachable.
  • auth — Full SSH auth attempt using BatchMode=yes. Returns auth_succeeded or auth_failed.

Response fields:

| Field | Type | Description | |-------|------|-------------| | reachable | boolean | Whether the host was reachable | | status | string | 'tcp_unreachable', 'tcp_open', 'ssh_banner_received', 'auth_succeeded', or 'auth_failed' | | latency_ms | number | null | Round-trip latency (null if unreachable) | | banner | string? | SSH server banner (e.g. 'SSH-2.0-OpenSSH_8.9'), only in banner mode | | error | string? | Error message when check fails |


Persistent SSH Sessions

For multi-step workflows — entering config mode, making changes, verifying state — use the persistent session tools instead of calling ssh_execute repeatedly. A persistent session keeps one SSH connection open so each command runs in the same shell context.

When to use persistent sessions vs ssh_execute

| Use case | Tool | |----------|------| | Run one command and you're done | ssh_execute | | Need shell state across commands (env vars, cd, configure) | ssh_session_open + ssh_session_execute | | Multi-step workflows on network devices (configure → commit) | Persistent sessions | | Parallel commands across many hosts | ssh_multi_execute |

Session lifecycle

ssh_session_open  →  ssh_session_execute (×N)  →  ssh_session_close
  1. ssh_session_open — Opens an interactive shell. Returns a session_id.
  2. ssh_session_execute — Sends a command and waits for the next shell prompt. Repeat as needed.
  3. ssh_session_close — Sends exit, kills the PTY, and removes the session.

Auto-expiry

Sessions auto-expire after 5 minutes of inactivity (default). Each ssh_session_execute call resets the idle timer. You can override the timeout per session:

{ "idle_timeout_ms": 600000 }  // 10-minute idle timeout

Expired sessions are cleaned up automatically — you don't need to explicitly close them, though it's good practice.

Security

  • Session IDs are ephemeral crypto.randomUUID() values — never logged, never included in error messages.
  • Credentials are resolved once at open time and the password Buffer is zero-filled immediately after the PTY write.
  • The env allowlist prevents full process.env leakage to SSH child processes.

Tool reference

ssh_session_open

Open a persistent interactive SSH shell. Returns a session_id for subsequent calls.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | host | string | ✅ | Hostname or IP address | | username | string | ❌* | SSH username (overrides credential ref username) | | credential_ref | string | ❌ | Credential reference (BW item name, AKV secret, etc.) | | credential_backend | string | ❌ | Backend name: bitwarden, azure-keyvault, env, google-secret-manager (default: google-secret-manager) | | platform | string | ❌ | Prompt detection hint: nxos, os10, sonic, linux, auto (default: auto) | | timeout_ms | number | ❌ | Connect + initial prompt timeout in ms (default: 30000) | | idle_timeout_ms | number | ❌ | Inactivity auto-close timeout in ms (default: 300000) |

*Optional when credential_ref provides a username.

Returns:

{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "host": "myswitch.local",
  "username": "admin",
  "message": "Session opened successfully"
}

ssh_session_execute

Run a command inside an open session. Waits for the shell prompt to return before resolving.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | session_id | string | ✅ | Session ID from ssh_session_open | | command | string | ✅ | Command to run | | timeout_ms | number | ❌ | Command timeout in ms (default: 30000) |

Returns:

{
  "output": "Linux myhost 5.15.0-91-generic ...",
  "exit_code": null,
  "session_id": "550e8400-e29b-41d4-a716-446655440000"
}

exit_code is null for interactive sessions — the shell doesn't produce an exit code until it closes.

ssh_session_close

Gracefully close a session. Sends exit, kills the PTY, and removes the session from the store.

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | session_id | string | ✅ | Session ID from ssh_session_open |

Returns:

{ "message": "Session closed" }

Example: multi-step workflow on a Linux server

This example opens a session, runs a few commands in sequence (each inheriting the previous shell state), then closes.

1. ssh_session_open
   host: "myserver.local"
   username: "admin"
   credential_ref: "myserver-login"
   credential_backend: "bitwarden"
   platform: "linux"

   → session_id: "abc123..."

2. ssh_session_execute  (session_id: "abc123...")
   command: "cd /etc && pwd"
   → output: "/etc"

3. ssh_session_execute  (session_id: "abc123...")
   command: "ls *.conf | head -5"
   → output: "hosts  hostname  nsswitch.conf  ..."

4. ssh_session_execute  (session_id: "abc123...")
   command: "cat hostname"
   → output: "myserver"

5. ssh_session_close   (session_id: "abc123...")
   → message: "Session closed"

Example: network device config workflow (NX-OS)

1. ssh_session_open
   host: "core-switch-01"
   username: "netadmin"
   credential_ref: "core-switch-01"
   credential_backend: "bitwarden"
   platform: "nxos"

2. ssh_session_execute  command: "configure terminal"
   → enters config mode, prompt changes to (config)#

3. ssh_session_execute  command: "interface Ethernet1/1"
   → prompt: (config-if)#

4. ssh_session_execute  command: "description Uplink to spine"

5. ssh_session_execute  command: "end"

6. ssh_session_execute  command: "copy running-config startup-config"

7. ssh_session_close

Credential Backends

Bitwarden CLI

Requires bw CLI installed and unlocked. Reference items by name.

credential_backend: "bitwarden"
credential_ref: "my-switch-password"

Azure Key Vault

Requires az CLI installed and authenticated. Reference secrets by vault/name.

credential_backend: "azure-keyvault"
credential_ref: "my-vault/my-secret"

Environment Variables

Read credentials from environment variables. Three ref formats are supported:

credential_backend: "env"

# Password-only (username from the tool's username arg):
credential_ref: "SWITCH_PASS"

# Named keys (recommended):
credential_ref: "user=SWITCH_USER,pass=SWITCH_PASS"

# Legacy colon format (backwards compatible):
credential_ref: "SWITCH_USER:SWITCH_PASS"

SSH Agent

Delegates authentication to a running ssh-agent. No passwords or key material are handled by the toolkit — the agent performs signing internally. Requires SSH_AUTH_SOCK to be set (or the OpenSSH named pipe on Windows) and at least one identity loaded.

credential_backend: "ssh-agent"
credential_ref: "admin"                    # username only
credential_ref: "admin:SHA256:abc123..."   # username:fingerprint (select specific key)

The SSH child process inherits SSH_AUTH_SOCK automatically, so agent-based auth works without any additional configuration.

Credential Map

The credential map provides automatic host→credential resolution so you don't need to specify credential_backend and credential_ref on every tool call.

Configuration

Create a JSON file at:

  • Linux/macOS: ~/.config/ai-ssh-toolkit/credential-map.json
  • Windows: %APPDATA%\ai-ssh-toolkit\credential-map.json

Format

{
  "rules": [
    { "match": "*.prod.example.com", "backend": "bitwarden", "ref": "af83b31a", "username": "admin" },
    { "match": "build-*", "backend": "ssh-agent" },
    { "match": "10.218.191.*", "backend": "env", "ref": "SWITCH_USER:SWITCH_PASS" },
    { "match_regex": "^db-\\d+\\.internal$", "match": "*", "backend": "bitwarden", "ref": "db-cred" },
    { "match": "*", "backend": "ssh-agent" }
  ]
}

Rule fields

| Field | Required | Description | |-------|----------|-------------| | match | Yes | Glob pattern (* matches anything, ? matches one char) | | match_regex | No | If present, used as a regex instead of the glob pattern | | backend | Yes | Credential backend name (bitwarden, azure-keyvault, env, google-secret-manager, ssh-agent) | | ref | No | Credential reference string passed to the backend | | username | No | SSH username override |

Behavior

  • First-match-wins: rules are evaluated top-to-bottom; the first match is used.
  • Tool arguments override: explicit credential_backend/credential_ref in tool calls always take precedence over the map.
  • Missing file = no-op: if the config file doesn't exist, tools continue with existing behavior (no credentials).
  • Diagnostics: use the credential_diagnose tool to test which rule matches a given host.

Platform Support

| Platform | SSH Client | PTY Type | |----------|-----------|----------| | Windows | OpenSSH (System32) | ConPTY via node-pty | | Linux | /usr/bin/ssh | Unix PTY via node-pty | | macOS | /usr/bin/ssh | Unix PTY via node-pty |

Security Model

  • Passwords stored as Buffer, zero-filled after use
  • No temporary files for credential staging
  • CLI secrets passed via stdin (never command-line arguments)
  • PTY output scrubbed for password echoes
  • StrictHostKeyChecking=no is never used
  • External CLI paths resolved to absolute at startup

Command Validation: Out of Scope (By Design)

ssh_execute and related tools accept any non-empty command string and do not enforce an allowlist, blocklist, or length restriction. This is intentional — ai-ssh-toolkit is a general-purpose tool and restricting commands would break legitimate use cases. Command validation and command-level authorization are out of scope; those controls remain the operator's responsibility (via sshd_config, TACACS+, sudoers, etc.). The tool's security guarantees cover credential handling and transport hardening — see SECURITY.md for details.

See DESIGN-DECISIONS.md for the full rationale.

See SECURITY.md for full details and vulnerability reporting.

Development

git clone https://github.com/ebmarquez/ai-ssh-toolkit.git
cd ai-ssh-toolkit
npm install
npm run build
npm test

Integration Tests

Integration tests run against live Azure resources and require the az CLI to be authenticated.

Run locally

# Authenticate first (SP login or az login)
bash ~/.config/azure/sp-login.sh   # service principal
# or
az login

# Run Azure Key Vault integration tests
AZURE_KV_ENABLED=true AZURE_KV_NAME=rg-ut-bw npx vitest run test/integration/

The SSH end-to-end test also requires SSH_E2E_ENABLED=true and surface-aac-1.local to be reachable:

AZURE_KV_ENABLED=true SSH_E2E_ENABLED=true AZURE_KV_NAME=rg-ut-bw npx vitest run test/integration/mcp-azure-keyvault.integration.test.ts

GitHub Actions

The .github/workflows/integration.yml workflow runs the Azure KV integration test automatically on push and pull request using OIDC authentication — no long-lived secrets or credentials to rotate.

Required repository secrets:

| Secret | Description | |---|---| | AZURE_CLIENT_ID | OIDC app registration client ID | | AZURE_TENANT_ID | Azure AD tenant ID | | AZURE_SUBSCRIPTION_ID | Azure subscription ID | | AZURE_KV_NAME | Key vault name (e.g. rg-ut-bw) |

Note: The SSH E2E test is excluded from CI — surface-aac-1.local is not reachable from GitHub runners. Run it locally.

License

MIT