Skip to main content

Qvasa MCP Server — Security and Integration Reference

Written by Amer Shalan

Qvasa MCP Server — Security and Integration Reference

This article describes the Qvasa Model Context Protocol (MCP) server: how AI agents connect to it, how access is authenticated and scoped, exactly which tools are exposed, and how every tool call reaches your Qvasa data. It is written for security reviewers evaluating the integration before connecting it.

At a glance. Toolkit version 2026-06-15.user_access_api_attribute_aliases_filter_validator_v1 · Connector v0.6.1 · Transport: Streamable HTTP / JSON-RPC 2.0 · Auth: OAuth 2.1 + PKCE.

Contents

  1. Overview

  2. Architecture

  3. Authentication

  4. Token storage & validation

  5. Authorization & scoping

  6. Scope reference

  7. Multi-tenancy & isolation

  8. The MCP tools

  9. How tools reach your data

  10. The client connector

  11. Token lifecycle

  12. Auditing & observability

  13. Data handling & limits

  14. Independent review & download


1. Overview

The Qvasa MCP server lets an AI agent (e.g. Claude) work with your Qvasa customer-support analytics on a user's behalf — running metrics, inspecting tickets, and managing dashboards and AI classifiers — strictly within the permissions that user already holds in Qvasa.

It is implemented natively inside the Qvasa Rails application using the official Model Context Protocol Ruby SDK. The agent talks to a single HTTPS endpoint; there is no separate hosted service, queue, or data store introduced by the MCP layer. It reuses the same application, authorization, and database paths as the rest of Qvasa.

Security posture in one line — every MCP request is authenticated, scoped to a single account, intersected against the calling user's live permissions, restricted to a hard endpoint allowlist, and audited. There is no privileged bypass path introduced by the MCP.

2. Architecture

The integration has two halves:

  • Hosted MCP server: a single endpoint, POST /mcp, served by the Qvasa application. It is stateless (each request is fully self-contained), speaks JSON-RPC 2.0 over the MCP Streamable HTTP transport, and is the sole authority for authentication, scope enforcement, validation, and tenant isolation.

  • Client connector: a small, open, reviewable package that runs inside the user's AI client (Claude Desktop, Claude Code, Cursor). It is a thin proxy: it holds the user's credential locally and forwards calls to the hosted server. It enforces no business logic of its own (see section 10).

Discovery follows the standard OAuth protected-resource pattern. An unauthenticated call to /mcp returns 401 with a WWW-Authenticate header that points clients to the metadata endpoints:

GET /.well-known/oauth-protected-resource     # RFC 9728: resource metadata + supported scopes
GET /.well-known/oauth-authorization-server   # RFC 8414: authorize / token / registration endpoints, PKCE

3. Authentication

The MCP accepts two credential forms, both of which resolve to the same backing token record:

  • OAuth 2.1 (hosted connector path). Authorization-code flow with PKCE required, implemented with Doorkeeper. The agent obtains a bearer access token and presents it as Authorization: Bearer <token> on every /mcp request. Refresh tokens are supported and rotated.

  • Personal access token (CLI path). A user-generated token (prefix qva_uat_) for clients that pass a static credential. Same authorization and scoping rules apply.

Authentication and the foundational permission gate run as before_actions on the MCP controller, so no tool code executes until the caller is identified and authorized.

4. Token storage & validation

  • Raw tokens are never persisted. The plaintext token is shown to the user exactly once at creation.

  • What is stored is an HMAC-SHA256 digest of the token (keyed by a secret held only in the server environment), plus a short non-sensitive prefix for display (e.g. qva_uat_1a2b…). The digest column is uniquely indexed.

  • Validation hashes the presented token and looks it up by digest among active tokens (not revoked, not expired). A miss is an authentication failure.

  • OAuth access tokens are managed and validated by Doorkeeper independently of the digest path.

Implication — a database disclosure does not expose usable credentials: stored digests are not reversible to tokens, and the HMAC secret is not in the database.

5. Authorization & scoping

Access is least-privilege by construction. Every request is authorized through three independent layers, and is permitted only if all of them allow it:

  1. Token scopes. Each token carries an explicit set of capability scopes (a bitfield). A token only ever has the scopes it was minted with.

  2. The user's live permissions. At request time the token's scopes are intersected with the calling user's current account permissions. If a user's permission is later revoked in Qvasa, their tokens immediately lose that capability, even if the scope bit is still set on the token.

  3. Account-level enablement (foundational gate). Before any scope is even consulted, three settings must hold: the feature is enabled for the account at the platform level (can_enable_user_access_tokens), the account has opted in (scoped_user_access_tokens_enabled), and the user is permitted to use access tokens (can_manage_user_access_tokens). Any failure denies the request before it reaches a controller.

Baseline vs. above-baseline

Reading individual ticket detail (can_inspect_objects) and conversational content (can_access_conversational_data) are opt-in capabilities. The broad read umbrella (can_read_all_endpoints) deliberately does not auto-grant ticket-detail inspection, which is a separate, explicit scope. Writes always require an explicit write scope; nothing is writable by default.

6. Scope reference

The tables below reflect the scopes that can be granted to a token today. Two umbrella scopes (can_read_all_endpoints, can_write_all_endpoints) act as convenience grants over the granular read/write scopes; the remaining scopes can be granted individually for finer-grained tokens.

Umbrella scopes

Scope

Grants

can_read_all_endpoints

Grants every Read scope listed below

can_write_all_endpoints

Grants every Write scope listed below

Additional Read Access (Above Baseline)

Scope

Capability

What it grants

can_inspect_objects

Inspect ticket details

Drill into individual ticket details (metadata, tags, assignments, history)

can_access_conversational_data

Access conversations

Read message bodies and transcript content

Ability to Manage Qualitative Questions (AI Classifiers)

Scope

Capability

What it grants

can_create_qualitative_questions

Create AI classifiers

Create new custom basic_score or two_choice questions

can_edit_inactive_qualitative_questions

Edit inactive AI classifiers

Edit response options while the classifier is inactive

can_enable_qualitative_question_backfill

Activate & backfill AI classifiers

Submit for AI review, activate, run backfill

can_cancel_qualitative_question_backfill

Cancel AI classifier backfills

Cancel an in-progress backfill job

can_edit_active_qualitative_questions

Edit active AI classifiers

Edit response options on an active classifier

Ability to Leverage On-Demand AI Insights

Scope

Capability

What it grants

can_leverage_on_demand_ai_insights

Run AI insights jobs

Run extract_themes or knowledge_base_article on a ticket set

Ability to Manage Generative Definitions (Generative AI Workflows)

Scope

Capability

What it grants

can_create_generative_definitions

Create generative AI workflows

Create new drafts and clone existing definitions

can_publish_generative_definitions

Publish generative AI workflows

Publish a draft (one-way; locks core fields)

can_edit_published_generative_definitions

Edit published generative AI

Edit mutable fields of a published definition

can_connect_generative_definitions

Wire generative AI to classifiers

Wire a published definition to a classifier response option

can_connect_nested_qualitative_questions

Wire nested AI classifiers

Wire a nested classifier to a parent response option

Ability to Manage Scheduled Report Exports

Scope

Capability

What it grants

can_manage_scheduled_report_exports

Scheduled report exports

Create and schedule recurring dashboard exports

Ability to Manage Workflow Condition Alerts

Scope

Capability

What it grants

can_manage_workflow_condition_alerts

Workflow condition alerts

Build threshold conditions on widgets and fire alerts

Ability to Manage Standard Threshold Alerts

Scope

Capability

What it grants

can_manage_alert_conditions

Standard threshold alerts

Manage the legacy AlertCondition queue-based alerts

Ability to Acknowledge Alerting Incidents

Scope

Capability

What it grants

can_acknowledge_alerting_incidents

Acknowledge alert incidents

Mark threshold-alert incidents as recognized

Ability to Access Open Dashboards

Scope

Capability

What it grants

can_read_open_dashboards

Read open dashboards

List and fetch open (team-shared) dashboards

can_manage_open_dashboards

Build & edit open dashboards

Create and edit widget/row layout on open dashboards

Ability to Access Private Dashboards

Scope

Capability

What it grants

can_read_private_dashboards

Read private dashboards

List and fetch private dashboards

can_manage_private_dashboards

Build & edit private dashboards

Create and edit widget/row layout on private dashboards

Ability to Access Agent Dashboards

Scope

Capability

What it grants

can_read_agent_dashboards

Read agent dashboards

List and fetch agent dashboards

can_manage_agent_dashboards

Build & edit agent dashboards

Create and edit widget/row layout on agent dashboards

Note — running metric queries (single-number stats, charts, donuts, heat-maps) and maintaining a personal-dashboard "memory" are baseline capabilities of any token and do not require an additional scope. Everything above the baseline is opt-in per the scopes listed here.

7. Multi-tenancy & isolation

Qvasa is multi-tenant; account isolation is enforced at every layer the MCP touches:

  • Every token and every session record belongs_to exactly one account.

  • A token's user is validated to belong to the token's account; a mismatch is rejected.

  • Sessions are validated to share the user and account of their token.

  • All data access flows through the standard Qvasa application queries, which are scoped by account_id. There is no MCP code path that reads across accounts.

8. The MCP tools

The server exposes a deliberately small "portal" of eight tools. Most Qvasa capabilities are reached not by a dedicated tool but through a search → guide → call pattern over the catalog, which keeps the tool surface minimal and auditable.

Tool

What it does

Reaches

qvasa_overview

Returns a small orientation packet describing how to use the API.

Read-only guidance content.

qvasa_search_tools

Searches the tool / metric / filter catalog by phrase (primary discovery verb).

Read-only catalog.

qvasa_get_tool_guide

Fetches the full guide (prose, paths, required scopes) for one named tool.

Read-only guidance content.

qvasa_get_workflow

Fetches one workflow recipe (an ordered tool sequence) by intent id.

Read-only guidance content.

qvasa_query_metric

Runs a metric (quick_stat, basic_chart, donut_chart, multi_data_chart, heat_map) and returns the result. Encapsulates the async kickoff plus a bounded server-side poll (max 12s) so threads are never held open indefinitely.

Analytics over your ticket data, scoped to the account.

qvasa_call_api

Generic dispatcher to a Qvasa API endpoint by method + path (the workhorse). Used after discovery to execute a specific capability.

Allowlisted only: paths under /user_access/api/* and verbs GET/POST/PUT/PATCH/DELETE. Each call is independently scope-gated.

qvasa_submit_feedback

Sends a bug report / friction note back to the Qvasa team (the escape hatch).

Writes a feedback record only.

qvasa_create_session

Compatibility no-op on the hosted connector (the OAuth connection is the session). Present for parity with the local package.

None.

On qvasa_call_api the generic dispatcher cannot reach arbitrary application routes. It is hard-restricted to the /user_access/api/* namespace and the five standard verbs; anything else is refused before dispatch. Every endpoint it reaches enforces its own scope and validation, and the dispatcher grants no additional authority.

9. How tools reach your data

A tool call does not open a new network connection or use a privileged internal channel. The server translates the tool call into an in-process request that re-enters the same Qvasa controller stack used by the rest of the application, carrying the caller's credentials. This means:

  • The identical authentication, scope checks, validation, and account scoping apply, so there is no parallel, less-guarded code path.

  • The dispatcher enforces the /user_access/api/* path allowlist and verb allowlist before any controller runs.

  • Reads are the default; any write requires an explicit write scope (see section 6).

  • HTTP status codes from the underlying endpoint (e.g. 403 scope denial) are surfaced back to the agent unchanged.

10. The client connector

The downloadable connector (the package distributed in the .mcpb bundle, see section 14) is a thin Python proxy (Python 3.11+, run via uv). Its security-relevant properties:

  • Single destination. It makes outbound HTTPS calls only to the configured Qvasa host (default https://www.qvasa.com, overridable for staging or self-hosted). There is no telemetry, analytics, or third-party / model-provider call of any kind.

  • Mirrored allowlist. It enforces the same /user_access/api/* path prefix and the same verb allowlist as the server, for defense in depth on both ends.

  • Credential handling. The user's access token is supplied via an environment variable. In Claude Desktop it is declared as a sensitive config field and stored in the OS keychain. The short-lived session is held in process memory only and is never written to disk.

  • No secret logging. The connector does not log credentials or request / response payloads; errors are returned as structured objects with recovery hints.

  • No independent authority. The connector interprets no scopes and makes no authorization decisions; Qvasa is the sole authority.

11. Token lifecycle

Credential

Lifetime

Notes

OAuth access token

8 hours

Short-lived bearer; refreshed via rotating refresh token.

OAuth refresh token

Rotated on use

Prior refresh token is revoked when a new one is issued.

Personal access token (UAT)

90 days default, 365 max

User-chosen expiry within bounds; revocable at any time.

Session token

24 hours

One active session per token; pins the toolkit version for the session.

Revocation is comprehensive. Disconnecting an OAuth connection (or revoking a token) runs in a single transaction that revokes the OAuth access and refresh tokens and their grants, revokes the backing token record, and invalidates all active sessions, so in-flight agent sessions stop working immediately.

12. Auditing & observability

  • Token lifecycle audit trail. Creation, permission changes, revocation, expiry, and deletion are recorded as events (with the acting user and token metadata). The audit write is fail-loud: if it cannot be recorded, the surrounding operation is rolled back.

  • Per-request records. Metric and export requests are persisted with the account, user, token, session, status, and timestamps, providing an attributable record of what was run.

  • Last-used tracking. Tokens and sessions track last-used time (throttled to limit write churn).

  • Error reporting. Unexpected tool errors raise an internal alert with a support code that is also surfaced to the agent, so issues are traceable end-to-end.

13. Data handling & limits

Through the MCP a sufficiently-scoped token can reach the same data the user can already see in Qvasa: ticket metadata and analytics, and, only with the explicit conversational scope, message and transcript content. Nothing beyond the user's own visibility is exposed.

Stated plainly, for completeness — abuse controls today are concurrency caps (e.g. one active session per token; one active bulk export per token) rather than a global request-rate limit, and the MCP endpoint does not currently enforce IP allowlisting. Access is, however, fully bounded by per-request scope checks, the live-permission intersection, account isolation, and short credential lifetimes described above. We are happy to discuss additional network controls for your deployment.

14. Independent review & download

The client connector is provided for your team to review directly. The download is an MCP bundle (.mcpb), a standard ZIP archive, containing the connector's full source:

Qvasa MCP connector bundleqvasa-mcp.mcpb · connector v0.6.1 · Python 3.11+ (run via uv).

To inspect it:

unzip qvasa-mcp.mcpb -d qvasa-mcp
# Contents:
#   manifest.json     : MCP/DXT manifest (declares the sensitive token field, default host)
#   pyproject.toml    : package metadata and dependencies
#   uv.lock           : fully pinned dependency lockfile
#   src/qvasa_mcp/**  : connector source (tools, client, allowlist)
#   README.md         : usage and configuration

Reviewing the source confirms the properties in section 10: a single outbound destination, the mirrored endpoint allowlist, in-memory-only session handling, and no telemetry. Dependencies are pinned in uv.lock for reproducible installs.

Did this answer your question?