MCP Server
Connect AI coding tools like Claude Code and Cursor to QA Note with a single OAuth click — query and manage issues directly
Table of Contents
- What is MCP?
- One-click install
- Auth methods at a glance
- Method A. Remote HTTP + one-click OAuth (recommended)
- Claude Code
- Cursor
- Codex (OpenAI)
- OAuth flow under the hood
- Method B. Local binary (stdio · npx)
- Claude Code
- Cursor / Codex
- Authentication flow
- Method C. API Key (CI · headless · fallback)
- API Key with Remote HTTP
- API Key with stdio binary
- Environment variables (stdio binary)
- Available tools
- Query tools
- Write tools
- Project list lookup
- When to use it
- Input
- Response fields
- Permissions and multi-org behavior
- LLM usage scenarios
- Recommended workflows
- Verify setup
- Troubleshooting
- OAuth browser window does not open (Remote HTTP)
- "Unauthorized" error
- `invalid_grant — Refresh token reuse detected`
- MCP server won't connect (stdio binary)
- Reset token/session
- SSH · Docker — no browser available
What is MCP?
MCP (Model Context Protocol) is a standard protocol that lets AI coding tools access external data. By connecting the QA Note MCP server, you can query issue data, change statuses, and leave comments directly from Claude Code, Cursor, Codex, and other tools.
Since QA Note delivers rich technical metadata (console logs, network requests, JS errors, performance metrics, React component trees, user actions, element styles, DOM snapshots) to the AI in a structured form, issue-context-based debugging becomes possible.
One-click install
Pick the MCP client you use and the copy or deep-link action runs immediately. The first connection opens a single browser OAuth consent window.
Pick your MCP client
Each card triggers a copy or deep-link install. The first connection opens a one-time browser OAuth consent.
Claude Code
One CLI line · OAuth one-click
Paste into your terminal — the browser sign-in + consent window opens automatically.
Official install docsCodex
Shared CLI/IDE config.toml · OAuth login
Run it in your terminal to register the server, then complete OAuth in the browser. The IDE extension uses the same config.
Official install docsClaude Desktop
Copy the mcpServers config JSON
Config file: macOS ~/Library/Application Support/Claude/claude_desktop_config.json · Windows %APPDATA%\Claude\claude_desktop_config.json
Official install docsCursor
One-click deep link into Cursor
Not using Cursor? Paste the config JSON into ~/.cursor/mcp.json instead.
Official install docsWindsurf
Copy the mcpServers config JSON (serverUrl key)
Paste into Windsurf → Cascade → MCP Servers → Add Server.
Official install docsChatGPT
Developer Mode connector URL
ChatGPT → Settings → Connectors → enable Developer Mode → Add MCP server.
Official install docsAuth methods at a glance
| Method | When to use | Key required | Recommendation |
|---|---|---|---|
| Remote HTTP + OAuth | Everyday desktop use (Claude Code, Cursor, Codex) | No (one-time browser consent) | ★★★ Default |
| stdio local binary | Corporate proxy blocks HTTP MCP · offline credential cache | No (same OAuth) | ★★ |
| API Key | CI/CD · headless servers · MCP clients without OAuth | Yes, manual qn_... from dashboard | ★ (fallback) |
The default path is one-click OAuth — no need to paste an Authorization header. Claude Code discovers the QA Note authorization server through the standard MCP discovery endpoint (
/.well-known/oauth-protected-resource) and performs Dynamic Client Registration (RFC 7591) → Authorization Code + PKCE S256 → access/refresh token exchange automatically.
Method A. Remote HTTP + one-click OAuth (recommended)
Connect directly to the hosted MCP endpoint at https://qanote.app/api/mcp. No binary install, no API key copy-paste.
Claude Code
One line in your terminal:
claude mcp add --transport http qanote https://qanote.app/api/mcp
After registering, run /mcp in Claude Code — your browser opens automatically, and a single QA Note sign-in + consent completes authentication. The resulting access/refresh tokens are stored safely inside Claude Code and refreshed transparently from then on.
Cursor
Add to ~/.cursor/mcp.json:
{
"mcpServers": {
"qanote": {
"url": "https://qanote.app/api/mcp"
}
}
}
No headers — Cursor launches the OAuth browser window on first tool call.
Codex (OpenAI)
Same entry under mcpServers in your Codex CLI config:
{
"mcpServers": {
"qanote": {
"url": "https://qanote.app/api/mcp"
}
}
}
OAuth flow under the hood
- The client calls
https://qanote.app/api/mcp→401 UnauthorizedwithWWW-Authenticate: Bearer resource_metadata=... - The client reads
/.well-known/oauth-protected-resource(RFC 9728) →/.well-known/oauth-authorization-server(RFC 8414) to get the authorization server metadata - Dynamic Client Registration (RFC 7591) issues a
client_idautomatically (no user action needed) - Authorization Code + PKCE S256 flow — browser sign-in + MCP consent
/api/oauth/tokenreturns an access token (scopesmcp+offline_access, bound withaud=https://qanote.app/api/mcp— RFC 8707) and a refresh token- Subsequent requests keep the same refresh token and silently renew only the access token
The "MCP Prompt" button on each issue page copies a prompt that embeds the issue permalink so the LLM can pull the full context with a single resolve_by_url call.
Method B. Local binary (stdio · npx)
Use this when your corporate network blocks HTTP MCP, or when you want credentials cached locally for offline/SSH environments. The auth itself is the same OAuth browser flow.
Claude Code
claude mcp add qanote -- npx -y @qanote/mcp-server
Cursor / Codex
{
"mcpServers": {
"qanote": {
"command": "npx",
"args": ["-y", "@qanote/mcp-server"]
}
}
}
Authentication flow
- On first launch, if no stored credential exists, the browser opens automatically
- Log in with your QA Note account (if already signed in, only the consent step remains)
- The resulting token is saved to
~/.qanote/credentials.jsonwithchmod 600 - Subsequent runs reuse the stored token automatically and silently renew only the access token
To reset credentials:
rm ~/.qanote/credentials.json
Method C. API Key (CI · headless · fallback)
Use this only in environments where a browser cannot be opened (CI/CD, containers, remote servers) or with MCP clients that do not support OAuth.
- QA Note Dashboard → Organization Settings → API Keys
- "New API Key" → enter a name (e.g.,
QA Note production server), pick an expiry and permissions → "Create" - Copy the displayed key (
qn_...) immediately to a safe location (shown only once)
For server-side data import, keep the default projects:read + issues:read permissions. Add issues:write only when the automation needs to update issue status or create comments. To keep production and developer credentials separate, use an API Key for the server and a separate OAuth connection for interactive tools like Claude Code.
API Key with Remote HTTP
claude mcp add --transport http qanote https://qanote.app/api/mcp \
--header "Authorization: Bearer qn_YOUR_KEY"
Cursor / Codex JSON:
{
"mcpServers": {
"qanote": {
"url": "https://qanote.app/api/mcp",
"headers": { "Authorization": "Bearer qn_YOUR_KEY" }
}
}
}
API Key with stdio binary
{
"mcpServers": {
"qanote": {
"command": "npx",
"args": ["-y", "@qanote/mcp-server"],
"env": {
"QANOTE_API_KEY": "qn_YOUR_KEY"
}
}
}
}
Environment variables (stdio binary)
| Variable | Required | Description | Default |
|---|---|---|---|
QANOTE_API_KEY | Optional | API Key (qn_...). Uses OAuth browser login if not set | — |
QANOTE_URL | Optional | QA Note server URL | https://qanote.app |
Remote HTTP does not need environment variables since the client specifies the url directly.
Available tools
Query tools
| Tool | Description |
|---|---|
resolve_by_url | Load an issue + tech context in one call from a permalink (recommended entry point) |
list_projects | List accessible projects |
search_issues | Search and filter issues (status · priority · labels · query) |
list_issue_queue | Sequential work queue for terminal LLM workflows |
get_issue | Issue detail + metadata summary |
list_comments | List comments on an issue |
get_comments | List comments on an issue (list_comments alias) |
get_console_logs | Browser console logs (errors/warnings sorted first) |
get_network_logs | Network request logs (errors only by default, full list available) |
get_user_actions | Pre-issue user actions converted to natural language |
get_tech_context | Combined JS errors · performance · React tree · environment info |
get_element_styles | Computed style · box model · parent/sibling gap |
get_performance_metrics | Web Vitals · Navigation Timing |
get_environment_info | Browser · OS · network · GPU · fonts, etc. |
get_js_errors | Runtime errors + stack trace |
get_react_component_tree | React component tree snapshot |
get_storage | localStorage · sessionStorage · cookies (masked) |
get_dom_snapshot | DOM outerHTML (scripts and input values masked) |
get_screenshots | Screenshot URLs + embedded image content |
Write tools
| Tool | Description |
|---|---|
claim_issue | Mark work as started: move the issue to in_progress and add a start comment |
complete_issue | Record completion: add PR/deployment/validation details and move to resolved or closed |
update_issue | Change issue status/priority |
add_comment | Add a comment to an issue |
Project list lookup
list_projects is the first discovery tool an LLM should use when the user has not provided an issue URL.
When to use it
- "Show me the QA Note project list"
- "Show only projects in the studiobaton organization"
- "Find critical issues in the qa-note project"
- "Show the next issue queue from a project I can access"
If the user already pasted an issue permalink, call resolve_by_url first instead.
Input
| Parameter | Required | Description |
|---|---|---|
organization_slug | Optional | Return only projects in one organization. Example: studiobaton |
Response fields
list_projects returns a JSON array.
| Field | Description |
|---|---|
id | Project ID to pass to search_issues, get_issue, list_issue_queue, etc. |
name | Project display name |
slug | Project slug used in dashboard URLs |
createdAt | Project creation timestamp |
organizationId | Organization ID |
organizationSlug | Organization slug, used to disambiguate multi-org accounts |
organizationName | Organization display name |
Example:
[
{
"id": "018f2f4b-...",
"name": "QA Note",
"slug": "qa-note",
"description": null,
"createdAt": "2026-04-21T08:13:44.000Z",
"organizationId": "018f2d10-...",
"organizationSlug": "studiobaton",
"organizationName": "Studio Baton"
}
]
Permissions and multi-org behavior
- OAuth connections are user-scoped. If the user belongs to multiple organizations, projects from every accessible active organization are returned.
- API Key connections return projects only from the organization that issued the key.
- Different organizations can have projects with the same
slug. The LLM should resolve projects byorganizationSlug + slugor byid, not byslugalone. - If the organization is ambiguous, the LLM should ask the user which organization to use instead of picking the first result.
LLM usage scenarios
User: "Show me the QA Note project list"
LLM: Call list_projects({}) → summarize organization name/slug and project name/slug
User: "Show open issues in the QA Note project under studiobaton"
LLM:
1. list_projects({ "organization_slug": "studiobaton" })
2. Select the project whose slug or name matches QA Note
3. search_issues({ "project_id": "<id>", "status": "open" })
User: "Find critical issues in the qa-note project"
LLM:
1. list_projects({})
2. If multiple projects have slug qa-note, ask "Which organization: studiobaton/qa-note or client-a/qa-note?"
3. Call search_issues with the confirmed project_id
User: "Show my next work queue for qa-note"
LLM:
1. list_projects({})
2. Resolve the project by organizationSlug + slug
3. list_issue_queue({ "project_id": "<id>", "status": "open", "sort": "board_order" })
Recommended workflows
Just make natural-language requests to your AI coding tool:
# Search issues
"Find critical issues in QA Note"
# Debugging context
"Show me the console errors and network logs for issue #42"
# Reproduce user behavior
"Tell me what the user did in issue #15"
# Update an issue
"Move issue #42 to resolved and leave a comment about the fix"
Optimal debugging sequence:
- Copy a permalink-embedded prompt from the "MCP Prompt" button on any issue page → one
resolve_by_urlcall - Dig deeper with
get_tech_context·get_console_logs·get_network_logs - After resolution, call
update_issueto change status andadd_commentto record cause/fix
Verify setup
After setup, try asking your AI tool:
Show me the project list from QA Note
If the project list appears, setup is complete.
Troubleshooting
OAuth browser window does not open (Remote HTTP)
- In firewall/popup-blocker environments, copy the
authorizeURL printed by Claude Code into a browser manually to finish sign-in + consent. The token is then saved automatically. - If a corporate proxy blocks
.well-known/oauth-*discovery, switch to Method B (stdio binary) or use Method C (API Key).
"Unauthorized" error
- OAuth path: the stored token has expired or was revoked. Restart the MCP server to re-trigger browser login. For the stdio binary, delete
~/.qanote/credentials.json. - API Key path: verify the key starts with
qn_and has not been revoked. To access a different organization's resources, issue a new key in that organization.
invalid_grant — Refresh token reuse detected
This can appear during migration from the older OAuth server behavior that rotated MCP refresh tokens. The current server keeps MCP refresh tokens persistent, so repeated refreshes with the same token should work. If the error persists, delete the client's stored OAuth token once and reconnect.
MCP server won't connect (stdio binary)
- Run
npx -y @qanote/mcp-serverdirectly in your terminal to confirm it boots - Requires Node.js 20+
- Check that
QANOTE_URL/QANOTE_API_KEYare set correctly in the MCP config'senvblock
Reset token/session
# stdio binary
rm ~/.qanote/credentials.json
# Remote HTTP (Claude Code)
claude mcp remove qanote
claude mcp add --transport http qanote https://qanote.app/api/mcp
SSH · Docker — no browser available
→ Use Method C (API Key). Injecting the key via environment variable or --header is the safest option for CI/CD pipelines.