Skip to main content
Glama
README.md
# qualien-mcp

**One MCP server that gives your AI assistant a QA toolkit: Playwright, your test files, GitHub, Jira, Postgres and more, behind a single connection.**

[![npm](https://img.shields.io/npm/v/qualien-mcp)](https://www.npmjs.com/package/qualien-mcp) [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)

qualien-mcp is a **composite MCP gateway** for SDET / QE work. It aggregates [Model Context Protocol](https://modelcontextprotocol.io) servers behind **one** connection, with a curated catalog of 21 testing-relevant servers, per-tool allow/deny, and read-only-by-default guardrails. Add one server to Claude Code, Claude Desktop, Cursor or VS Code instead of five.

```bash
npx qualien-mcp
```

Out of the box it serves the **QE starter pair**: [Playwright MCP](https://github.com/microsoft/playwright-mcp) (browser automation, DOM, screenshots) and [Filesystem MCP](https://github.com/modelcontextprotocol/servers) (read/edit project files, page objects, test utils), with zero config. Tools are exposed to your assistant namespaced as `playwright__…` and `filesystem__…`.

## Why a gateway?

Your assistant can already connect to many MCP servers directly — so a gateway earns its place by doing what a pile of servers can't:

- **One endpoint** to add to any client (Claude Code, Claude Desktop, Cursor, …).
- **Curation** — enable/disable per server *and* per tool. Fewer, better-named tools = better tool-selection by the model (dumping 200 tools at it makes it *worse*).
- **One routing/logging surface** — every call is logged to stderr with the downstream, tool, and latency.
- **Clean lifecycle** — spawns each downstream as a child process and tears them **all** down on exit (no orphaned browser processes).
- **Curated for QE** — an opinionated SDET toolkit, not a generic proxy.

## Use it with your assistant

**Claude Code**:

```bash
claude mcp add qualien -- npx -y qualien-mcp
```

or in `.mcp.json` / user settings:

```json
{
  "mcpServers": {
    "qualien": { "command": "npx", "args": ["-y", "qualien-mcp"] }
  }
}
```

**Claude Desktop** (`claude_desktop_config.json`) and **Cursor** (`~/.cursor/mcp.json` or `.cursor/mcp.json`): the same shape under `mcpServers`.

**Claude Desktop extension**: download `qualien-mcp-<version>.mcpb` from the [latest release](https://github.com/qualienai/qualien-mcp/releases/latest) and open it. Claude Desktop asks for a **project folder**: the Filesystem tools are limited to it, and a `qualien-mcp.config.json` inside it turns on more servers. Also listed on [Smithery](https://smithery.ai/servers/qualienai/qualien-mcp).

**VS Code** (`.vscode/mcp.json`):

```json
{
  "servers": {
    "qualien": { "command": "npx", "args": ["-y", "qualien-mcp"] }
  }
}
```

That single entry gives your assistant every tool from every aggregated server.

## Add more servers

Drop a `qualien-mcp.config.json` in your working directory (or pass `--config <path>`). Entries **merge over** the built-in defaults — redefine a key to change it, or set `enabled: false` to turn a default off. No new release needed.

```json
{
  "servers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/abs/path/to/allow"]
    },
    "sequential-thinking": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]
    },
    "chrome-devtools": {
      "command": "npx",
      "args": ["-y", "chrome-devtools-mcp@latest"]
    },
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"],
      "tools": { "deny": ["browser_close"] }
    }
  }
}
```

See [`qualien-mcp.config.example.json`](./qualien-mcp.config.example.json) for the full shape (`command`, `args`, `env`, `enabled`, `tools.allow` / `tools.deny`).

### Built-in catalog

qualien-mcp ships a curated catalog of 21 known servers (each verified against npm), so you enable one by **key** with just your secrets — no need to remember packages or commands:

```json
{
  "servers": {
    "chrome-devtools": { "enabled": true },
    "postgres":  { "enabled": true, "env": { "DATABASE_URI": "postgres://…" } },
    "slack":     { "enabled": true, "env": { "SLACK_MCP_XOXP_TOKEN": "xoxp-…" } }
  }
}
```

Run `npx qualien-mcp catalog` to list them all with what each needs. Current catalog:

| Key | What | Needs |
|---|---|---|
| `playwright` *(default)* | Browser automation, DOM, screenshots | — |
| `filesystem` *(default)* | Read/edit project files | a dir to allow (default cwd) |
| `github` | PRs, issues, code review | `login github` (see below) |
| `sequential-thinking` | Structured reasoning / debugging | — |
| `memory` | Persistent knowledge graph | — |
| `chrome-devtools` | Network, console, perf, storage | — |
| `postgres` | SQL / validate backend data | `DATABASE_URI` |
| `mysql` | SQL / validate backend data | MySQL env |
| `slack` | Read/post Slack | `SLACK_MCP_XOXP_TOKEN` |
| `docker` | Manage containers | Docker daemon |
| `kubernetes` | Inspect/operate a cluster | kubeconfig |
| `openapi` | Drive any REST API from its spec | `API_BASE_URL`, `OPENAPI_SPEC_PATH` |
| `jira` | Read/update Jira issues | Atlassian API token env |
| `figma` | Read Figma designs | `FIGMA_API_KEY` |
| `confluence` | Test plans, release notes, living docs | Atlassian API token env |
| `azure-devops` | Work items, repos, pipelines, test plans | your ADO org as an arg + sign-in |
| `selenium` | Drive a browser via Selenium WebDriver | a browser + driver on PATH |
| `webdriverio` | Browser **and** native-mobile automation | — (cloud creds optional) |
| `browserstack` | Real browser/device cloud; read test runs | `BROWSERSTACK_USERNAME`, `BROWSERSTACK_ACCESS_KEY` |
| `ms-365` | Excel, Word, Outlook, Teams, SharePoint | `AZURE_CLIENT_ID`, `AZURE_TENANT_ID` |
| `sentry` | Real user-facing errors → new tests | a Sentry auth token |

`use` mounts a catalog server under a different key (e.g. two databases): `{ "db-prod": { "use": "postgres", "enabled": true, "env": {…} } }`. Anything not in the catalog you still define in full (`command`/`args` or `type`/`url`).

## Remote & OAuth servers (e.g. GitHub)

Downstreams can be **remote** (Streamable HTTP) as well as local. A remote server is `{ "type": "http", "url": "…" }`, and if it needs OAuth, **each user logs in with their own account** — tokens are stored per user at `~/.qualien-mcp/credentials.json` (0600), on that user's own machine, and are never bundled, shared, or sent anywhere by qualien-mcp.

GitHub's hosted MCP is the reference case, and it needs no setup beyond one command:

1. Turn it on in `qualien-mcp.config.json`:
   ```json
   { "servers": { "github": { "enabled": true } } }
   ```
2. Authorize once:
   ```bash
   npx qualien-mcp login github
   ```

`login` prints a short code and a URL; you approve it on github.com and you're done. There is no OAuth App to register and no client secret anywhere.

<details>
<summary>Why the device flow?</summary>

GitHub requires a `client_secret` to exchange an authorization code — [PKCE does not lift that](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/best-practices-for-creating-an-oauth-app), because GitHub does not distinguish public from confidential clients. qualien-mcp ships on npm, so a secret in the package would be a *published* secret. The [device flow](https://datatracker.ietf.org/doc/html/rfc8628) is the one grant GitHub allows without a secret, and it's the grant GitHub recommends for CLIs. So qualien-mcp ships only a **public client id** — an identifier, not a credential — and every user's tokens are minted on, and stay on, their own machine.

</details>

To use **your own** GitHub OAuth App instead of the shipped one, set `clientId` (and enable the device flow on it under *Settings → Developer settings → OAuth Apps*):

```json
{
  "servers": {
    "github": { "clientId": "<your client id>", "enabled": true }
  }
}
```

If a remote server isn't logged in yet, the gateway **skips it with a hint** (`run: npx qualien-mcp login github`) and still serves everything else — it never blocks startup. Servers that support dynamic client registration need no `clientId` at all.

## Safe by default

qualien-mcp enforces guardrails **centrally, before forwarding a call** — so they hold no matter what the downstream permits, and composite tools can't bypass them either:

- **Databases are read-only** — `postgres`/`mysql` calls containing write/DDL SQL (`INSERT/UPDATE/DELETE/DROP/…`) are refused. Opt in with `{ "postgres": { "readOnly": false } }`.
- **Filesystem roots** — `{ "filesystem": { "roots": ["./src", "./tests"] } }` refuses any call whose path argument escapes those directories.
- **Destructive infra tools blocked** — `docker`/`kubernetes` tools whose name looks destructive (`delete/remove/prune/kill/…`) are refused unless `{ "allowDestructive": true }`.
- Plus per-server **`tools.deny` / `tools.allow`** curation.

> Honest scope: these stop an LLM from *accidentally* doing damage. The SQL check is keyword-based (heuristic), not a parser — it is **not** adversarial sandboxing. Real isolation needs the downstream's own permissions or a container.

## Composite QE tools

Beyond passthrough, qualien-mcp ships tools it implements itself (namespace `qe__`) that orchestrate several downstreams in one call — the QE payoff of a gateway.

**`qe__verify_api_vs_db`** — end-to-end API↔DB consistency in one call. Give it two sub-calls (namespaced tools you can discover via `tools/list`); it runs both and diffs the payloads:

```jsonc
{
  "api": { "tool": "openapi__getUser", "arguments": { "id": 1 } },
  "db":  { "tool": "postgres__query", "arguments": { "sql": "select id, name from users where id = 1" } },
  "match": "subset"        // every field the DB returns must match the API (default)
}
// → { "match": false, "differences": [ { "path": "name", "api": "Ann", "db": "Bob" } ], … }
```

Composite tools appear only when their required downstreams are connected (`qe__verify_api_vs_db` needs a `database` server). More to come (repro-from-Jira, page-object-from-URL, flaky triage).

## How it works

In one process, qualien-mcp is **both** an MCP *server* to your assistant and an MCP *client* to each downstream:

```
assistant ⇄ qualien-mcp ⇄ playwright-mcp
                         ⇄ filesystem-mcp
                         ⇄ …
```

- `tools/list` fans out to every downstream, filters by your curation rules, and namespaces each as `<server>__<tool>`.
- `tools/call` routes by that prefix to the owning server and passes the response straight through.
- Resources and prompts are aggregated the same way when a downstream provides them.
- A downstream that fails to start is logged and **skipped** — the gateway still serves the rest.
- Logs go to **stderr** (stdout is the protocol); your host surfaces them in its MCP logs.

## Roadmap

- ✅ Playwright + Filesystem, namespacing, curation, logging, clean lifecycle
- ✅ Config-driven catalog of 21 servers, remote/OAuth servers (GitHub via device flow), safe-by-default guardrails, `qe__verify_api_vs_db`
- Next: more composite QE tools (repro-from-Jira, page-object-from-URL, flaky triage)

## Feedback

Found a bug, want a server added to the catalog, or have an idea? Email **[contact@qualien.ai](mailto:contact@qualien.ai)** or [open an issue](https://github.com/qualienai/qualien-mcp/issues). qualien-mcp is part of [qualien.ai](https://qualien.ai/?utm_source=github&utm_medium=readme&utm_campaign=qualien-mcp), which also builds AI agents that test web apps (Olympus) and native Android apps (Pulse) from plain English.

## License

MIT

TDQS

B3.2/5.0

Scored across 39 tools

Disambiguation4/5

Most tools target clearly distinct actions (click, hover, drag, navigate, etc.), but a few pairs overlap: read_file is a deprecated duplicate of read_text_file, list_directory essentially duplicates list_directory_with_sizes, and browser_network_requests vs browser_network_request differ only by singular/plural. Descriptions do enough to disambiguate in most cases.

Naming Consistency4/5

Names follow a consistent snake_case verb_noun pattern (read_text_file, write_file, create_directory, browser_navigate, browser_click) cleanly separated by filesystem__/playwright__ prefixes. Minor deviations like the legacy read_file vs read_text_file and the noun-first browser_console_messages slightly break the pattern.

Tool Count3/5

39 tools is heavy for a single server, though it reflects two bundled domains (filesystem ~14, playwright ~25). Each namespace is reasonable on its own, but combined the surface is on the large side and pushes toward the 'heavy' band.

Completeness3/5

Playwright coverage is thorough (navigation, interaction, forms, network, dialogs, screenshots, tabs), and filesystem covers read/write/edit/list/search/tree/move/info. However, the filesystem domain has a notable gap: there is no delete/remove tool for files or directories, forcing agents into dead ends for cleanup.

Maintenance

ActivityMaintained
ResponsivenessNo issues