@qeekai/ide-client
v0.5.3
Published
Qeek IDE integration client library + Claude Desktop MCP server (QEEK-87 Phase 2).
Readme
@qeekai/ide-client
Client library + MCP server for the qeek IDE Integration.
Two consumers:
- MCP-aware IDEs — Claude Code, Claude Desktop, Cursor, VS Code
(1.95+). Run the registry-pinned
npxcommand below as an MCP server; the IDE picks up eleven tools (list-briefs,get-brief,list-specs,search-specs,get-spec,list-spec-questions,check-spec-implementation,update-spec,ask-spec-question,check-spec-answer,get-repo-context). - Custom IDE extensions — import the lib and wire
IdeApiClientIdeNotificationListenerinto your extension surface (~30 lines).
If you just want to use the integration from your IDE, the two steps below are all you need. The rest of this README is for library / extension authors and people deploying the MCP server.
Why the
--@qeekai:registry=…flag? This package is published to the public npm registry. Several QEEK repos ship an.npmrcthat routes@qeekai/*to GitHub Packages for private workspace packages, so a barenpx -y @qeekai/ide-client …404s when run from those folders. Pinning the scope makes login and MCP work from any cwd.
Step 1 — sign in (once)
npx -y --@qeekai:registry=https://registry.npmjs.org @qeekai/ide-client loginOpens your browser to my.qeek.ai to sign in, then saves credentials
to ~/.qeek/credentials.json. Every IDE config below picks these up
automatically — no tokens or env vars needed. Re-run this if you ever
need to switch accounts; append logout instead of login to clear it.
If you'd rather use a static long-lived token instead (e.g. for CI or a shared machine), skip this step and see "Alternative: static token" below.
Step 2 — configure your IDE
Claude Code
claude mcp add qeek -- npx -y --@qeekai:registry=https://registry.npmjs.org @qeekai/ide-clientThe eleven MCP tools become available in every Claude Code session after the next reload. Claude Code's CLI, desktop app, and VS Code extension are expected to share this same config (same underlying product) — if the tools don't show up in one of them, check that surface's own MCP settings before assuming a separate config is needed.
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json
(on macOS — Windows/Linux paths in Anthropic's docs):
{
"mcpServers": {
"qeek": {
"command": "npx",
"args": [
"-y",
"--@qeekai:registry=https://registry.npmjs.org",
"@qeekai/ide-client"
]
}
}
}Restart Claude Desktop. Ask it "What qeek specs do I have?" — it
should call list-specs and reply with your specs.
Cursor
npx -y --@qeekai:registry=https://registry.npmjs.org @qeekai/ide-client setup-cursorWrites (or merges) the qeek server into ~/.cursor/mcp.json with the
registry pin already in the generated args. Pass --local to write
.cursor/mcp.json in the current project instead (per-workspace, takes
precedence over the global file). Re-run after upgrading if your
existing entry still lacks the registry flag.
Reload Cursor (Cmd+Shift+P → "Reload Window"). The qeek server
should show up under MCP tools in the agent panel.
VS Code (native MCP, 1.95+)
This is VS Code's own built-in MCP client (used by Copilot Chat and other MCP-aware extensions) — a different integration path from the Claude Code extension above, which shares the CLI's config instead.
Per-workspace config (preferred — keeps env out of global settings):
.vscode/mcp.json in your project root:
{
"servers": {
"qeek": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"--@qeekai:registry=https://registry.npmjs.org",
"@qeekai/ide-client"
]
}
}
}Or global: Cmd+Shift+P → "MCP: Add server" → fill in the same
command / args. Stored in User Settings.
Alternative: static token (no login, e.g. CI or a shared machine)
Generate a token from qeek-ui at /settings/api-tokens, then set it
instead of running login. Config style depends on which quick start
you followed above:
Claude Desktop, VS Code (JSON config) — add an env block:
"env": {
"QEEK_IDE_TOKEN": "<qek_live_...>",
"QEEK_IDE_ACCOUNT": "<your-account-id>",
"QEEK_IDE_CHAT_SESSION": "<chat-session-id-for-clarifications>"
}Cursor — run setup-cursor first (see above), then add the same
env block to the generated qeek entry in ~/.cursor/mcp.json
(or .cursor/mcp.json if you used --local):
"qeek": {
"command": "npx",
"args": [
"-y",
"--@qeekai:registry=https://registry.npmjs.org",
"@qeekai/ide-client"
],
"env": {
"QEEK_IDE_TOKEN": "<qek_live_...>",
"QEEK_IDE_ACCOUNT": "<your-account-id>"
}
}Claude Code (CLI) — pass --env flags instead:
claude mcp add qeek \
--env QEEK_IDE_TOKEN=<qek_live_...> \
--env QEEK_IDE_ACCOUNT=<your-account-id> \
--env QEEK_IDE_CHAT_SESSION=<chat-session-id-for-clarifications> \
-- npx -y --@qeekai:registry=https://registry.npmjs.org @qeekai/ide-clientQEEK_IDE_TOKEN— long-lived qeek API token. Treat as a password; revoke from/settings/api-tokensif leaked.QEEK_IDE_ACCOUNT— your account ID, visible in qeek-ui URLs (https://my.qeek.ai/<accountId>/…).QEEK_IDE_CHAT_SESSION— the chat session id where clarification answers should land (…/chat/<sessionId>). Optional — if unset, theask-spec-questiontool requireschatSessionIdin the call args.
~/.qeek/credentials.json (from login) takes priority over
QEEK_IDE_TOKEN if both are present.
Custom IDE extensions (advanced)
import {
IdeApiClient,
IdeNotificationListener,
} from "@qeekai/ide-client";
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth, signInWithCustomToken } from "firebase/auth";
const app = initializeApp({ /* your Firebase config */ });
const auth = getAuth(app);
const db = getFirestore(app);
// 1. Sign in (your extension handles this however — paste, OAuth,
// PAT exchange via api_tokens).
await signInWithCustomToken(auth, "<custom-token-from-your-flow>");
// 2. REST client
const api = new IdeApiClient({
baseUrl: "https://qeek-ide-service-bg2lz2topa-uc.a.run.app",
token: () => auth.currentUser!.getIdToken(),
accountId: "<account-id>",
toolType: "vscode",
toolVersion: "0.1.0",
});
const { specs } = await api.listSpecs();
// 3. Notification listener (Firestore push)
const listener = new IdeNotificationListener({
db,
userId: auth.currentUser!.uid,
accountId: "<account-id>",
handlers: {
onSpecUpdated: (notif, payload) => {
vscode.window.showInformationMessage(
`Spec "${payload.specTitle}" updated to v${payload.newVersion}`,
"Refresh",
);
},
onClarificationAnswered: (notif, payload) => {
vscode.window.showInformationMessage(
`qeek answered: ${payload.answer.slice(0, 80)}…`,
);
},
},
});
listener.start();
// Cleanup on extension deactivation
context.subscriptions.push({ dispose: () => listener.stop() });MCP tools
| Tool | What |
|---|---|
| list-briefs | The "what should we build / what's left?" entrypoint. List the briefs (work-items) you can access in a project — id, title, specCount, updatedAt, access (owned|shared), and a url to open it in QEEK. Requires projectId. Then get-brief to drill in. |
| list-specs | List specs in the user's account. Filters: projectId, status, limit. |
| search-specs | Free-text search across the account's specs — terms are AND-matched over title + body (title hits first), returning id/title/type/scope + a match snippet. Use it to discover specs when you don't know their ids, then get-spec. Args: query (required), projectId, status, limit. |
| get-brief | Pull a whole brief (a qeek chat session) and summaries of ALL its specs in one call — per spec: id, title, type, a short preview, word count — plus the brief's title, projectId, repositoryNames, tickets (linked Jira/Linear issue(s), or empty = none), and a url to open it in QEEK. Then get-spec a spec id for the full body. Arg: briefId. |
| get-spec | Fetch one spec's full content by id. Also returns version (use as baseVersion for update-spec), tickets (the Jira/Linear issue(s) the spec's brief is linked to; empty = no ticket attached), and a url to open it in QEEK. Optional saveTo: true writes to .qeek/specs/<id>.md, or a string path (cwd-relative, path-traversal guarded). When set, the assistant can re-read the file across later turns without another API call. |
| list-spec-questions | A spec's clarification questions labeled open / answered·authoritative / answered·inferred / failed, with counts — the "what's still undecided" view. Arg: specId. |
| check-spec-implementation | "What's left to build." Fetches a spec + its repo context in one call and asks the assistant to label each acceptance criterion implemented / partial / missing with repo evidence. Args: specId, optional depth/repoName/repoNames. |
| update-spec | Write a new version of a spec after you and the user agree on a change — the spec body is updated in QEEK and the change is recorded in version history with your rationale (a trail of why it changed; reversible). Read the spec first (get-spec) for its version, pass it as baseVersion; a concurrent edit returns 409 VERSION_CONFLICT (re-read and retry). Args: specId, chatSessionId (the brief id), baseVersion, content, changeDescription. |
| ask-spec-question | Submit a clarification question. The qeek agent answers; the answer threads into the user's qeek chat session. The tool long-polls up to 45s for the answer and returns it inline; on timeout returns a questionId for check-spec-answer. Answers are labeled authoritative (grounded in the spec) or inferred (reasoned beyond it). |
| check-spec-answer | Poll an in-flight question for its answer. Used when ask-spec-question timed out. |
| get-repo-context | Fetch spec-scoped repo context — per-file summaries, dependency edges, and (when present) project architecture overview + directory structure. Optional saveTo writes a directory of markdown + json files (.qeek/context/<specId>/) the assistant can re-read across turns. Args: depth (minimal | related), includeWiki, repoName, forceRefresh. |
A common flow: get-brief <briefId> (or search-specs) to load the
specs, review them, then ask-spec-question for anything unclear.
ask-spec-question routes through the existing qeek chat agent (same
model, same cost line). The conversation persists in the user's qeek
chat session — the Q and the answer show up in qeek-ui alongside normal
chat turns for anyone looking at that chat, tagged with the answer's
authoritative / inferred source.
Auth model
The MCP server (registry-pinned npx … @qeekai/ide-client mcp, the
default subcommand) supports two auth paths, checked in this order:
~/.qeek/credentials.json, written bynpx -y --@qeekai:registry=https://registry.npmjs.org @qeekai/ide-client login— a long-lived qeek API token (qek_live_…), minted by the browser at login time and revoked automatically the next time you log in (or onlogout). No hourly expiry. The MCP server re-reads this file on each request (token and account id) and retries once on 401 when the token changed, so re-login does not require restarting the IDE (QEEK-355). This is the path every quick-start above uses.QEEK_IDE_TOKEN— the same kind of long-lived API token, set manually via the qeek-ui/settings/api-tokenspage instead oflogin. Used when there's no login session (e.g. CI, a shared machine). Env-only setups do not reload from disk — update the env var and restart the MCP server after rotating.
IdeApiClient.config.token (the library's REST client, for custom IDE
extensions — see below) accepts either a string or a getter function —
the getter is called before every request, so an extension can plumb
its own refresh loop in without touching this package.
Configuration
| Env var | Purpose | Default |
|---|---|---|
| QEEK_IDE_URL | ide-service base URL | prod URL |
| QEEK_IDE_CHAT_SESSION | Default chat session id for ask-spec-question | optional — if unset, tool call must pass chatSessionId |
QEEK_IDE_TOKEN / QEEK_IDE_ACCOUNT aren't listed here — normal use
is login (Step 1 above). See "Alternative: static token" if you
need them for CI or a shared machine.
Development
# Build (from repo root or this package)
pnpm -C ide-client build
# Test
pnpm -C ide-client test
# Run the MCP server locally with stdio (for poking via @modelcontextprotocol/inspector)
QEEK_IDE_TOKEN=... QEEK_IDE_ACCOUNT=... pnpm -C ide-client start:mcpBehaviour notes
get-spectool results are ephemeral. The MCP tool returns the spec as a JSON tool-result block; the assistant reads it for the current turn, but the IDE's context window may compact it away on later turns. UsesaveTo(see the table above) when you want the spec available to re-read across turns / sessions.- No streaming responses. MCP tool calls are request-response; long answers come back as a single text block when the agent finishes generating.
- Notification listener is for IDE extensions, not the MCP server.
MCP prefers synchronous tool responses, so the MCP server uses the
REST polling endpoint (
/v1/ide/questions/:id) instead of opening a Firestore listener. TheIdeNotificationListenerexport is for the VS Code / Cursor extension surface.
Architecture
MCP-aware IDE (Claude Code / Desktop / Cursor / VS Code 1.95+)
│ (stdio MCP protocol)
▼
┌─────────────────────────────────────────┐
│ npx … @qeekai/ide-client (this package) │
│ ┌───────────────────────────────────┐ │
│ │ createMcpServer (11 tools) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ IdeApiClient (REST wrapper) │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
│ HTTPS + Bearer token
▼
┌─────────────────────────────────────────┐
│ ide-service (Cloud Run) │
└─────────────────────────────────────────┘IDE extensions skip the MCP server and use IdeApiClient +
IdeNotificationListener directly.
