Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Appium Auth

БесплатноНе проверен

Authentication and authorization plugin for appium-mcp, adding bearer API keys, OAuth JWT, session tokens, scope-based authorization, rate limiting, and per-ses

GitHubEmbed

Описание

Authentication and authorization plugin for appium-mcp, adding bearer API keys, OAuth JWT, session tokens, scope-based authorization, rate limiting, and per-session ownership when running over SSE/HTTP Stream.

README

Authentication & authorization for appium-mcp when hosted over SSE / HTTP Stream — built entirely on the appium-mcp Plugin API (no core changes).

When you expose appium-mcp over SSE, anyone who can reach the port gets a full Appium session and can drive real devices. appium-mcp-auth adds a security layer as a drop-in plugin:

  • 🔑 Bearer API keys (ak_<id>_<secret>) — hashed at rest, constant-time compared. For machines / CI.
  • 🪪 OAuth JWT access tokens — validated against the issuer JWKS (iss/aud/exp). For humans / IDE clients.
  • 🎫 Session tokens — exchange an API key for a short-lived token via auth_login.
  • 🛂 Scope-based authorization — per-tool required scopes, admin-role bypass.
  • 🧑‍🤝‍🧑 Per-caller session ownership — callers only see/drive the Appium sessions they created (multi-tenant isolation).
  • 🚦 Rate limiting & session quotas — per subject.

How it works (read this first)

A plugin runs inside appium-mcp's beforeCall / afterCall hooks and cannot read HTTP headers — so the credential is passed as a tool argument (default authToken). Authentication and authorization both happen in beforeCall; a denied call is short-circuited before the tool runs.

┌────────────┐   tool call { …args, authToken }   ┌───────────────────────────┐
│ MCP client │ ─────────────────────────────────► │ appium-mcp  + auth plugin │
└────────────┘                                    └─────────────┬─────────────┘
      ▲                                                         │ beforeCall
      │                                     ┌───────────────────▼──────────────────┐
      │                                     │ ① authenticate  authToken → Identity │
      │                                     │    (ak_… key · st_… token · JWT)     │
      │                                     │ ② rate-limit    per subject          │
      │                                     │ ③ authorize     scopes vs tool       │
      │                                     │    (admin role bypasses)             │
      │                                     │ ④ ownership     sessionId must       │
      │                                     │    belong to the caller              │
      │                                     │ ⑤ quota         per-subject session  │
      │                                     │    cap on session-creating tools     │
      │                                     └─────────┬─────────────────┬──────────┘
      │                                          deny │                 │ allow
      │◄──────── error result ────────────────────────┘                 ▼
      │          (tool never runs)                                  tool runs
      │                                                                 │
      │                                                     afterCall   ▼
      │◄──────── tool result ─────────────────  bind created session to caller ·
                                                release deleted sessions

Implications (by design):

  • Terminate TLS in front of the server — the credential travels in the request body.
  • No OAuth .well-known discovery endpoints (a plugin can't serve them); front with a gateway if your MCP client needs auto-negotiation. This package still validates JWTs.
  • Calls that omit sessionId hit Appium's global active session — in multi-tenant mode, require an owned sessionId on every call.

Install

npm install @appclaw/appium-mcp-auth

That's it — appium-mcp (which brings fastmcp and zod) and jose for JWT validation come along automatically as regular dependencies. The plugin resolves the exact fastmcp/zod copies that appium-mcp uses at runtime, so there is no peer-dependency juggling.

Requires Node.js ≥ 20.


Implement it in your project

There are two ways to use it. Pick one.

Option 1 — Turnkey CLI (fastest)

Run this package's binary instead of appium-mcp's. It is the full appium-mcp SSE server with the auth plugin composed in — one process, same /sse endpoint.

# 1. Generate a credential — prints the client key (ak_…) AND the server record
npx @appclaw/appium-mcp-auth keygen --id=ci --subject=ci-bot --scopes=appium:use

# 2. Register the printed record with the server (it stores the hash, never the secret)
export APPIUM_MCP_AUTH_API_KEYS='[{"id":"ci","hash":"c65c…","subject":"ci-bot","kind":"service","scopes":["appium:use"]}]'

# 3. Start the auth-protected SSE server
npx @appclaw/appium-mcp-auth --httpStream --port=8080 --endpoint=/sse
# → SSE listening on http://localhost:8080/sse

Clients authenticate with the ak_… key from step 1 — or exchange it for a short-lived st_… token via the auth_login tool. Full keygen flags and key anatomy: Create an API key.

Option 2 — Compose the plugin into your own server (most control)

If you already build a custom appium-mcp server, just add the plugin. No core changes requiredcreateAppiumMcpServer already accepts plugins.

import { createAppiumMcpServer } from 'appium-mcp/core';
import { createAuthPluginFromEnv } from '@appclaw/appium-mcp-auth';

const server = await createAppiumMcpServer({
  plugins: [createAuthPluginFromEnv()], // reads APPIUM_MCP_AUTH_* env vars
});

await server.start({
  transportType: 'httpStream',
  httpStream: { endpoint: '/sse', port: 8080 },
});

Prefer explicit config over environment variables? Build the config yourself:

import { createAppiumMcpServer } from 'appium-mcp/core';
import { createAuthPlugin, sha256Hex, type AuthConfig } from '@appclaw/appium-mcp-auth';

const config: AuthConfig = {
  credentialArg: 'authToken',
  publicTools: ['auth_login'],
  apiKeys: [
    {
      id: 'ci',
      hash: sha256Hex('CHANGE-ME'),   // store the hash, not the secret
      subject: 'ci-bot',
      kind: 'service',
      scopes: ['appium:use'],
    },
  ],
  toolScopes: { mobile_clear_app: ['appium:admin'] },
  defaultScopes: ['appium:use'],
  adminRole: 'admin',
  sessionTokenTtlMs: 3_600_000,
  rateLimit: { limit: 120, windowMs: 60_000 },
  maxSessionsPerSubject: 3,
  enforceOwnership: true,
  sessionIdArgs: ['sessionId'],
  sessionCreatingTools: ['appium_session_management'],
  audit: true,
};

const server = await createAppiumMcpServer({
  plugins: [createAuthPlugin(config)],
});

How clients authenticate

Whatever the MCP client, the credential is supplied as the authToken argument on tool calls.

  1. Exchange an API key for a session token (the auth_login tool is public):

    { "tool": "auth_login", "arguments": { "apiKey": "ak_ci_CHANGE-ME" } }
    → { "sessionToken": "st_…", "expiresAt": "…" }
    
  2. Pass the token (or the API key, or an OAuth JWT) as authToken on every subsequent call:

    { "tool": "appium_session_management", "arguments": { "action": "create", "authToken": "st_…" } }
    { "tool": "appium_gesture", "arguments": { "sessionId": "…", "authToken": "st_…" } }
    
  3. auth_whoami echoes the caller identity; auth_logout revokes a session token.

Will my MCP client work?

Client behavior Works? How
Sends an Authorization: Bearer header (Cursor, Claude Desktop, most SSE clients) Run gateway mode (below) — it reads the header and injects the credential. No per-call argument needed.
Forwards agent-chosen tool arguments verbatim (e.g. AppClaw) Pass the token as the authToken argument (shown above).
Injects a fixed argument on every tool call Set authToken deterministically instead of relying on the LLM.

Tip: for LLM-driven clients, prefer a session token (auth_login) over the raw API key so the long-lived secret isn't repeated in every prompt/trace.


Header auth for Cursor / Claude Desktop (gateway mode)

A plugin can't read HTTP headers, so header-based clients are served by a built-in credential-injecting reverse proxy. It reads the Authorization header, rewrites each tools/call to add the authToken argument, and forwards to the appium-mcp server on loopback. Nothing in appium-mcp core changes.

Cursor ──(Authorization: Bearer ak_…)──►  gateway (public :8080)  ──►  appium-mcp + plugin (127.0.0.1:8790)
                                            reads header,               beforeCall sees authToken,
                                            injects authToken arg        authorizes exactly as normal

Start it:

export APPIUM_MCP_AUTH_API_KEYS='[{"id":"dev","secret":"CHANGE-ME","subject":"dev","kind":"user","roles":["admin"],"scopes":["appium:use","appium:admin"]}]'
npx @appclaw/appium-mcp-auth --gateway --port=8080 --endpoint=/sse
# gateway (header auth) on http://localhost:8080/sse
# upstream on http://127.0.0.1:8790/sse (loopback — firewall this port)

Point Cursor at it — .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{
  "mcpServers": {
    "appium-auth": {
      "url": "http://localhost:8080/sse",
      "headers": {
        "Authorization": "Bearer ak_dev_CHANGE-ME"
      }
    }
  }
}

Claude Desktop connects via the mcp-remote bridge:

{
  "mcpServers": {
    "appium-auth": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote", "http://localhost:8080/sse",
        "--header", "Authorization: Bearer ak_dev_CHANGE-ME"
      ]
    }
  }
}

The Bearer value is any accepted credential: an API key (ak_…), a session token from auth_login (st_…), or an OAuth JWT. Requests with no/invalid credential get 401. GET /health is always allowed for probes.

Gateway options: --port (public), --upstream-port (loopback inner server), --endpoint; APPIUM_MCP_AUTH_GATEWAY_HEADER changes which header is read (default authorization).

Security: put TLS in front (the token is in a header) and firewall the upstream port — the inner server trusts the injected authToken, so it must only be reachable through the gateway.


Configuration

All settings are environment variables (used by createAuthPluginFromEnv and the CLI). Full examples in .env.example.

Variable Purpose Default
APPIUM_MCP_AUTH_API_KEYS JSON array of API-key records (id, hash or secret, subject, kind, scopes, roles?, expiresAt?)
APPIUM_MCP_AUTH_OAUTH JSON OAuth JWT validation config (issuer, audience, jwksUri?, scopeClaim?, rolesClaim?)
APPIUM_MCP_AUTH_ARG Tool-argument name carrying the credential authToken
APPIUM_MCP_AUTH_PUBLIC_TOOLS Comma list of tools that skip auth auth_login
APPIUM_MCP_AUTH_TOOL_SCOPES JSON map `tool → scope scope[]`
APPIUM_MCP_AUTH_DEFAULT_SCOPES Scopes required for unlisted tools appium:use
APPIUM_MCP_AUTH_ADMIN_ROLE Role that bypasses scope checks admin
APPIUM_MCP_AUTH_SESSION_TTL_MS Session-token lifetime 3600000
APPIUM_MCP_AUTH_RATE_LIMIT "<limit>/<windowMs>" per subject disabled
APPIUM_MCP_AUTH_MAX_SESSIONS Per-subject Appium session cap (0 = off) 0
APPIUM_MCP_AUTH_ENFORCE_OWNERSHIP Enforce session ownership true
APPIUM_MCP_AUTH_SESSION_ID_ARGS Arg names carrying a session id sessionId
APPIUM_MCP_AUTH_SESSION_TOOLS Tools that create sessions appium_session_management
APPIUM_MCP_AUTH_AUDIT Emit JSON audit lines to stderr true

If neither API keys nor OAuth are configured, every protected call is denied and the server logs a warning at startup.

Create an API key (built-in command)

Use the keygen command — it prints the client bearer token and the server config record (which stores the hash, never the secret):

npx @appclaw/appium-mcp-auth keygen --id=ci --subject=ci-bot --scopes=appium:use
Give this to the CLIENT (Authorization header) — shown once, store it securely:
  Authorization: Bearer ak_ci_Tgaz5NSbOTqgz3A4s_CdPeV7FePHLExS

Add this record to APPIUM_MCP_AUTH_API_KEYS on the SERVER (stores the hash, not the secret):
  {"id":"ci","hash":"c65c…","subject":"ci-bot","kind":"service","scopes":["appium:use"]}

Flags: --id --subject [--scopes=a,b] [--kind=service|user] [--roles=admin] [--name="…"] [--expires-in=30d] [--secret=…] [--json].

The three strings are linked: the client presents ak_<id>_<secret>; the server stores hash = SHA256(secret); on each call it checks SHA256(presented secret) === stored hash (constant-time). The plaintext secret never leaves the client, and the --json form is handy for scripting/rotation.


Authorization model

  • Scopes — each tool requires a scope set (APPIUM_MCP_AUTH_TOOL_SCOPES), falling back to APPIUM_MCP_AUTH_DEFAULT_SCOPES. A caller needs all of them.
  • Admin role — a caller with the admin role bypasses scope checks.
  • Ownership — sessions a caller creates are bound to its subject; a call referencing someone else's tracked sessionId is denied (not_session_owner). Untracked ids (pre-existing / attach flows) pass through.
  • Quota / rate limit — per subject, via MAX_SESSIONS and RATE_LIMIT.

Public API

import {
  AppiumAuthPlugin,          // the plugin class
  createAuthPlugin,          // build from an AuthConfig
  createAuthPluginFromEnv,   // build from environment
  buildAuthenticatedServer,  // full server (used by the CLI)
  loadConfig, sha256Hex,     // config helpers
  // building blocks: Authenticator, Authorizer, KeyStore, OAuthValidator,
  // OwnershipRegistry, RateLimiter, AuditLog
} from '@appclaw/appium-mcp-auth';

Security notes

  • Store API-key hashes, never plaintext secrets, in committed config.
  • Give services and humans distinct scopes so a leaked CI key can't act as a human.
  • Pin OAuth issuer and audience so tokens minted for other apps are rejected.
  • Rate limiter and ownership map are process-local — front with a shared store (e.g. Redis) if you run multiple SSE replicas.
  • Credentials are never written to audit logs.

Development

npm install
npm run typecheck   # tsc --noEmit
npm test            # node:test via tsx (35 tests)
npm run build       # emit dist/

License

Apache-2.0

from github.com/appclawhq/appium-mcp-auth

Установить Appium Auth в Claude Desktop, Claude Code, Cursor

Рекомендуется · одна команда, все IDE
unyly install appium-mcp-auth

Ставит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.

Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh

Или настроить вручную

Выполни в терминале:

claude mcp add appium-mcp-auth -- npx -y @appclaw/appium-mcp-auth

FAQ

Appium Auth MCP бесплатный?

Да, Appium Auth MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Appium Auth?

Нет, Appium Auth работает без API-ключей и переменных окружения.

Appium Auth — hosted или self-hosted?

Доступен hosted-вариант: Unyly запускает сервер в облаке, локальная установка не обязательна.

Как установить Appium Auth в Claude Desktop, Claude Code или Cursor?

Открой Appium Auth на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Appium Auth with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории development