wrapper-mcp

wrapper-mcp

Stdio MCP server for QMS Wrapper that enables MCP-capable clients to manage tasks via HTTPS using a Personal Access Token.

Category
Visit Server

README

@uvaresearch/wrapper-mcp

Stdio MCP server for QMS Wrapper. Lets MCP-capable clients (Claude Code, Claude Desktop, Cursor, generic stdio hosts) drive a QMS Wrapper instance over HTTPS using a Personal Access Token (PAT). Communication is JSON-RPC over stdin/stdout; outbound traffic is HTTPS to a single configured origin.

The server is built domain-by-domain. v0.1 ships task management — 8 task tools, 4 lookup / identity resources, 3 task resource templates, 5 prompts. Subsequent versions will expand to additional QMS Wrapper domains (projects, processes, storage, risk, etc.) under the same package and PAT-auth model.

Status: v0.1.0. package.json is private: true so an accidental npm publish cannot ship it. Flip when ready.

Install

Via npx (no install)

Use this in your MCP host configuration (see Step 2 below). npx will download the package on first run and cache it; subsequent runs reuse the cache.

npx -y @uvaresearch/wrapper-mcp

(Once the package is flipped to public on the registry. While private: true is set, only an authenticated registry user with read access can install it.)

Via npm install -g

For users who want wrapper-mcp on PATH:

npm install -g @uvaresearch/wrapper-mcp
wrapper-mcp --help

The binary lands in your global npm bin dir. Find it with:

npm bin -g

On macOS/Linux this is typically /usr/local/bin or ~/.npm-global/bin; on Windows it is %AppData%\npm.

From source (contributors)

git clone https://github.com/uvaresearch/wrapper-mcp.git
cd wrapper-mcp
npm install
npm run build
node dist/index.js --help

Point your MCP host at the absolute path to dist/index.js.

Configure

Step 1: get a Personal Access Token

  1. Log in to your QMS Wrapper instance.
  2. Go to Profile -> Access Tokens -> Create token.
  3. Name it for the device it will live on (e.g. "Claude Desktop laptop").
  4. Pick scopes. Defaults cover read/write/create on tasks. Only tick tasks:assign and attachments:upload if you need reassignment and file uploads respectively.
  5. Submit and copy the wrapper_<32 chars> plaintext that is shown exactly once. Store it in your OS keychain or a password manager.

Step 2: configure your MCP host

WRAPPER_BASE_URL is the URL you use to sign in to QMS Wrapper in the browser (the user-facing app). For the hosted SaaS that is https://app.qmswrapper.com. Self-hosted instances use their own host.

Claude Code

Either project-scoped (.mcp.json in repo root) or user-scoped (~/.claude.json under the mcpServers key):

{
  "mcpServers": {
    "wrapper": {
      "command": "npx",
      "args": ["-y", "@uvaresearch/wrapper-mcp"],
      "env": {
        "WRAPPER_PAT": "wrapper_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "WRAPPER_BASE_URL": "https://app.qmswrapper.com"
      }
    }
  }
}

Claude Desktop

Edit claude_desktop_config.json (Settings -> Developer -> Edit Config):

{
  "mcpServers": {
    "wrapper": {
      "command": "npx",
      "args": ["-y", "@uvaresearch/wrapper-mcp"],
      "env": {
        "WRAPPER_PAT": "wrapper_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "WRAPPER_BASE_URL": "https://app.qmswrapper.com"
      }
    }
  }
}

Restart Claude Desktop after editing.

Cursor

Edit ~/.cursor/mcp.json:

{
  "mcpServers": {
    "wrapper": {
      "command": "npx",
      "args": ["-y", "@uvaresearch/wrapper-mcp"],
      "env": {
        "WRAPPER_PAT": "wrapper_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "WRAPPER_BASE_URL": "https://app.qmswrapper.com"
      }
    }
  }
}

Generic stdio MCP client

Spawn wrapper-mcp (or npx -y @uvaresearch/wrapper-mcp) as a subprocess with WRAPPER_PAT and WRAPPER_BASE_URL in the child environment, and speak JSON-RPC over its stdin/stdout. Other hosts (for example GitHub Copilot CLI) generally follow the same shape, but config file locations differ; see https://modelcontextprotocol.io/clients for the current list.

Step 3: verify

In Claude Code, run /mcp and confirm:

  • Server wrapper shows status connected.
  • Tools: 8. Resources: 4 (plus 3 templates). Prompts: 5.

CLI smoke test (no PAT required):

npx -y @uvaresearch/wrapper-mcp --help
npx -y @uvaresearch/wrapper-mcp --version

If the server fails to start it writes a single-line reason to stderr and exits with code 64.

Environment variables

Variable Required Default Notes
WRAPPER_PAT yes Personal access token, wrapper_<32 alnum>. Must come from env, not CLI flag.
WRAPPER_BASE_URL yes Browser sign-in URL of your QMS Wrapper instance. HTTPS only; plain HTTP allowed only for loopback or *.local.
WRAPPER_UPLOAD_DIR for task.attach Absolute path. task.attach will only read files inside this directory. Default-deny when unset.
WRAPPER_UPLOAD_MAX_BYTES no 52428800 (50 MB) Per-file upload size cap.
WRAPPER_CACHE_DIR no ~/.cache/wrapper-mcp Scratch dir, created with mode 0700.
DEBUG no unset When set (any value), emits verbose HTTP and session logs to stderr.

Tools, resources, prompts at a glance

All v0.1 surfaces live in the task.* namespace. Future domains will use sibling namespaces (project.*, process.*, etc.) without breaking existing tool names.

Task tools (8)

Name Effect Annotations
task.list List my tasks, newest first. read-only, idempotent
task.get Full task detail (journal, attachments, etc.). read-only, idempotent
task.update Apply one batched change (R2 enforced). write, non-destructive
task.create Create bug/task; requires previewed hash (R9). write, non-destructive
task.assign Reassign; requires userExplicitlyRequested. write, destructive
task.relate Link this task to another. write, non-destructive
task.tag Merge claude-* tags (idempotent). write, idempotent
task.attach Upload one or more files in one multipart POST. write, non-destructive

Resources (4 concrete + 3 templates)

URI Returns
wrapper://lookups/statuses All issue statuses.
wrapper://lookups/priorities All issue priorities.
wrapper://lookups/trackers All trackers (bug, task, etc.).
wrapper://me Identity of the PAT owner.
wrapper://task/{id} Full task detail.
wrapper://task/{id}/journal Journal entries for the task.
wrapper://task/{id}/attachments/{fileId} Signed download URL + metadata for one file.

Task prompts (5)

Slash command Purpose
/qms-plate Summarize my open tasks grouped by priority.
/qms-summary Read a task plus attachments; produce a user-language recap.
/qms-start Flip a task to in-progress in one atomic update.
/qms-finish Close out a task with status=resolved + comment in one call.
/qms-file Gather, preview, approve, hash, and create a new task (R9).

Security and threat model

This section reflects what the code in src/ actually enforces. If a claim is not backed by code, it is not made here.

What the client does

  • Opens one outbound TCP connection per request to WRAPPER_BASE_URL using Node's built-in fetch.
  • Sends Authorization: Bearer <PAT> plus a small set of X-Mcp-* headers (X-Mcp-Session-Id, X-Mcp-Tool-Name, optional X-Mcp-Request-Id) so the backend can audit each call.
  • Reads/writes only inside WRAPPER_CACHE_DIR (default ~/.cache/wrapper-mcp, created with mode 0700). The HTTP layer is stateless; the cache dir is reserved for ancillary scratch state.
  • For task.attach, reads file paths supplied by the user in the tool arguments and includes their full contents as multipart fields. Reads are restricted to WRAPPER_UPLOAD_DIR.

What the client does NOT do

  • No telemetry. No analytics. No usage pings.
  • No auto-update. The binary you installed is the code that runs.
  • No remote config. All configuration is local env vars and tool args.
  • No outbound network calls beyond WRAPPER_BASE_URL. There is no fallback host and no DNS prefetch.
  • No filesystem access outside WRAPPER_CACHE_DIR and explicit file paths inside WRAPPER_UPLOAD_DIR passed to task.attach.

What the PAT grants and does not grant

A PAT carries the scopes the user ticked at creation time. With default scopes the bearer can read, write, and create tasks they would normally have access to in the web UI. It is bearer-only: anyone who holds the plaintext value can act as the user until the token is revoked. It does not grant filesystem, OS, or shell access on the QMS Wrapper host. The server scopes the bearer to the issuing user; it does not elevate.

Hardening baked into v0.1.0

  1. URL guard (src/security/urlGuard.ts): refuses WRAPPER_BASE_URL that is not https://, except for loopback hosts (localhost, 127.0.0.1, ::1) and *.local. Also rejects userinfo (user:pass@host) and any RFC1918 / link-local / cloud-metadata IP literal. Fails closed at process start before any HTTP call.
  2. Response scrubber (src/security/responseScrubber.ts): runs over every tool / resource / error payload before it leaves the process and replaces any wrapper_<32 alnum> substring with [REDACTED]. The check happens on the serialized JSON string, so exotic types cannot bypass. If a match ever fires it also emits a stderr warning so the regression is visible.
  3. Safe logger (src/security/safeLog.ts): writes only to stderr (stdout is reserved for JSON-RPC framing). Every line is scrubbed twice: once by the PAT regex and once by literal-replacing the current WRAPPER_PAT env value even if it does not match the regex shape.
  4. CLI flag refused: --pat, --token, --bearer, --auth, -p, -t, their =value forms, glued shorts (-pSECRET), case variants, and positional wrapper_<32 alnum> arguments all exit with code 64 before doing anything else. Rationale: command-line tokens land in shell history and ps.
  5. Minimal dependency tree: runtime deps are @modelcontextprotocol/sdk and zod. No HTTP client library, no logger framework, no arg parser. Smaller supply-chain attack surface.
  6. No telemetry, no auto-update, no remote config (see above).
  7. Provenance prep: publishConfig.provenance is true and publishConfig.access is restricted; package.json is private: true for v0.1 so accidental npm publish is blocked. When ready, flip private to false and publish with npm publish --provenance --access public to ship a sigstore attestation.
  8. task.attach default-denies. Requires WRAPPER_UPLOAD_DIR env to be set; resolves and bounds every path inside it; rejects symlinks; caps file size at 50 MB by default.

Known limitations

  • Cookie jar deferred: the client is Bearer-only. If the backend ever starts requiring a session cookie alongside the PAT, this client will need a cookie store; today it sends no cookies.
  • task.attach reads each uploaded file fully into memory before posting. Very large attachments will be bounded by Node's heap.

What users should do

  • Issue PATs with the narrowest scope set that gets your work done.
  • Rotate PATs periodically; revoke on device loss or job change.
  • Prefer HTTPS endpoints. The URL guard will refuse anything else outside of loopback / *.local dev hosts.
  • Keep the PAT in the MCP host's env block (which most hosts read from a permissioned config file), not in shell rc files.
  • Set WRAPPER_UPLOAD_DIR to a tightly-scoped directory if you need task.attach.

Troubleshooting

Symptom Likely cause / fix
WRAPPER_PAT is not set Env var missing or empty in the MCP host config. Restart the host after editing the config file.
HTTP 401 from any tool PAT expired or revoked. Mint a new one in Profile -> Access Tokens.
HTTP 403 with insufficient_scope PAT lacks a scope. For task.assign tick tasks:assign; for task.attach tick attachments:upload.
HTTP 404 on every request WRAPPER_BASE_URL points at the wrong host. Use the URL you would type into the browser to sign in (e.g. https://app.qmswrapper.com).
WRAPPER_BASE_URL must use https:// URL guard rejected a plaintext HTTP URL on a non-loopback host. Use https:// or move to a localhost / *.local dev URL.
PAT must be supplied via the WRAPPER_PAT environment variable You passed --pat=... on the command line. Move the token into the env block of your MCP host config.
task.attach refused: WRAPPER_UPLOAD_DIR is not set Upload safety is default-deny. Set WRAPPER_UPLOAD_DIR in the host's env block to a directory you trust.
Empty tools list in Claude Code after /mcp The server crashed at startup. Run wrapper-mcp --version in a terminal to confirm the binary works, then check stderr.
npm install fails Node 20+ is required (see .nvmrc). Check node --version.
previewedHash mismatch You called task.create directly. Use the /qms-file prompt; it gathers fields, previews, hashes, and posts in one flow.
task.assign refused: userExplicitlyRequested must be true The agent tried to reassign without explicit user instruction (R1). Confirm with the user, then re-call with the flag.

Contributing

  • Node 20+ (.nvmrc is the source of truth).
  • npm install.
  • npm run build (strict TypeScript; warnings are errors).
  • npm test (vitest).
  • Source: https://github.com/uvaresearch/wrapper-mcp.
  • Do not land changes that introduce a new runtime dependency without discussion; the minimal-deps property is a security feature, not laziness.

License

MIT. See LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured