{"name":"dev.useaxis/axis","title":"Axis","description":"One coordination board for a team's coding agents. Claude Code, Cursor, Codex and Windsurf share a job board and take a lock on a file before editing it, so agents run by different people do not overwrite each other.","version":"2.0.0","protocolVersion":"2025-06-18","websiteUrl":"https://useaxis.dev","documentationUrl":"https://useaxis.dev/docs","llmsTxtUrl":"https://useaxis.dev/llms.txt","openApiUrl":"https://useaxis.dev/openapi.json","repository":{"url":"https://github.com/VirSanghavi/axis","source":"github","subfolder":"packages/axis-server"},"license":"AGPL-3.0-only","whenToUse":["More than one person on a repository is running coding agents at the same time.","Agents from different vendors need to queue behind the same file lock.","Work has to outlive a session, so tomorrow's agent sees what last night's finished.","A human needs to watch or steer agent work from a browser.","You want to know whether a file changed under a held lock before overwriting it."],"remotes":[{"type":"streamable-http","url":"https://useaxis.dev/api/mcp","authentication":{"type":"oauth2","description":"OAuth is primary: add the server with no header and authenticate from the client. An Axis API key sent as `Authorization: Bearer sk_sc_...` is the headless fallback.","authorizationServerMetadata":"https://useaxis.dev/.well-known/oauth-authorization-server","protectedResourceMetadata":"https://useaxis.dev/.well-known/oauth-protected-resource","dynamicClientRegistration":"https://useaxis.dev/api/oauth/register","alternative":{"type":"http-bearer","header":"Authorization","format":"Bearer sk_sc_*","issuedAt":"https://useaxis.dev/dashboard"}}}],"packages":[{"registryType":"npm","registryBaseUrl":"https://registry.npmjs.org","identifier":"@virsanghavi/axis-server","runtimeHint":"npx","transport":{"type":"stdio"},"environmentVariables":[{"name":"AXIS_API_KEY","description":"Board credential from useaxis.dev. Omit to run local-only, with no cross-machine coordination."},{"name":"AXIS_API_SECRET","description":"Secret paired with AXIS_API_KEY.","isSecret":true},{"name":"AXIS_ORG_ID","description":"Pin the board to one organization, when you belong to several."},{"name":"AXIS_ENFORCE_LOCKS","description":"Set to 1 to chmod held files read-only, turning advisory locks into enforced ones on this machine."}]}],"capabilities":{"tools":{"listChanged":false}},"rateLimits":{"documentation":"https://useaxis.dev/docs#rate-limits","headers":["RateLimit-Limit","RateLimit-Remaining","RateLimit-Reset","RateLimit","RateLimit-Policy","Retry-After"]},"tools":[{"name":"post_job","description":"Post a job to the shared job board so agents (including this one) can claim and work it. Use before non-trivial multi-step work.","inputSchema":{"type":"object","properties":{"title":{"type":"string","description":"Short job title (max 500 chars)."},"description":{"type":"string","description":"What the job entails (max 5000 chars)."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"Optional priority."},"dependencies":{"type":"array","items":{"type":"string"},"description":"Job ids that must be done before this one can be claimed."},"completion_key":{"type":"string","description":"Optional key you mint; whoever completes the job must present it (or be the assigned agent)."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["title"]}},{"name":"claim_next_job","description":"Claim the next available job from the board (server picks by priority, skipping blocked jobs). Use for autonomous load-balanced work.","inputSchema":{"type":"object","properties":{"agentId":{"type":"string","description":"Your agent id, e.g. 'claude-code'."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["agentId"]}},{"name":"claim_job","description":"Claim a SPECIFIC job by id (atomically), instead of whatever is next. Prefer this in multi-agent runs so you only pull the job you intend to work and keep your context clean. Returns CLAIMED, ALREADY_CLAIMED, BLOCKED_BY_DEPENDENCIES (a prerequisite job isn't done), or NOT_FOUND. Pair with list_jobs to pick the id.","inputSchema":{"type":"object","properties":{"jobId":{"type":"string","description":"The id of the job to claim (from list_jobs)."},"agentId":{"type":"string","description":"Your agent id."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["jobId","agentId"]}},{"name":"complete_job","description":"Mark a job completed (releases its locks). If the job is claimed, pass the SAME agentId used to claim it (or the job's completionKey) — completion is credential-checked.","inputSchema":{"type":"object","properties":{"jobId":{"type":"string"},"agentId":{"type":"string","description":"The agent id that claimed the job. Required to complete a job you claimed."},"completionKey":{"type":"string","description":"Alternative credential: the completion key minted when the job was posted."},"outcome":{"type":"string","description":"Brief result summary."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["jobId"]}},{"name":"cancel_job","description":"Cancel a job that is no longer needed.","inputSchema":{"type":"object","properties":{"jobId":{"type":"string"},"reason":{"type":"string","description":"Why the job is being cancelled."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["jobId"]}},{"name":"release_job","description":"Put an abandoned in_progress job back on the board (status → todo, assignment cleared). The server only allows this when the assigned agent looks dead (no recent activity, no live lock) unless force is true. Use when list_jobs shows a job flagged stale.","inputSchema":{"type":"object","properties":{"jobId":{"type":"string"},"force":{"type":"boolean","description":"Release even if the agent still looks active. Use sparingly."},"agentId":{"type":"string","description":"Your agent id, recorded as who released it."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["jobId"]}},{"name":"list_jobs","description":"List jobs on the board for a project.","inputSchema":{"type":"object","properties":{"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}}}},{"name":"propose_file_access","description":"Request a lock on a file before editing it. Returns GRANTED or DENIED (another agent holds it). Lock individual files, not directories. Editing several files? Pass `filePaths` to lock them in ONE call — all-or-nothing, so a partial batch never blocks others. Pass `contentHash` (a hash of the file's current contents) to enable tamper detection via verify_file_lock (single-file calls only).","inputSchema":{"type":"object","properties":{"filePath":{"type":"string","description":"One file to lock. Use `filePaths` instead for a multi-file batch."},"filePaths":{"type":"array","items":{"type":"string"},"description":"Lock several files in one call (all-or-nothing: on any denial, locks granted earlier in the batch are released)."},"agentId":{"type":"string","description":"Your agent id."},"intent":{"type":"string","description":"Descriptive intent, e.g. 'Refactor auth to JWT'."},"contentHash":{"type":"string","description":"Optional fingerprint of the file's current contents, for later tamper detection. Ignored for `filePaths` batches."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["agentId"]}},{"name":"verify_file_lock","description":"Tamper check before overwriting a file you locked: pass the file's CURRENT content hash and the server compares it to the fingerprint recorded at lock time. Returns OK (unchanged), CONFLICT (changed under your lock — re-read first), NO_LOCK, or UNKNOWN (no fingerprint recorded). Hosted servers can't read your local files, so you must supply currentHash.","inputSchema":{"type":"object","properties":{"filePath":{"type":"string"},"agentId":{"type":"string","description":"Your agent id — so the answer can tell you whether the lock is yours or another agent's."},"currentHash":{"type":"string","description":"Hash of the file's current on-disk contents."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["filePath","currentHash"]}},{"name":"list_agents","description":"See which agents are currently active on the project, derived from who holds locks and who has claimed jobs. Use to understand your team before posting/claiming work.","inputSchema":{"type":"object","properties":{"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}}}},{"name":"release_file_access","description":"Release a file lock you hold. `agentId` is required and must match the lock's owner — you can only release your own locks. To break another agent's stale lock, use force_unlock (audited).","inputSchema":{"type":"object","properties":{"filePath":{"type":"string"},"agentId":{"type":"string","description":"Your agent id. The release is scoped to locks you own; without it the server cannot tell your lock from someone else's."},"reason":{"type":"string"},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["filePath","agentId"]}},{"name":"list_locks","description":"List currently held file locks for a project.","inputSchema":{"type":"object","properties":{"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}}}},{"name":"force_unlock","description":"ADMIN OVERRIDE: break a file lock held by another agent. Only for locks that are clearly stale (crashed agent). A reason is required — every force-unlock is recorded in the org audit log (lock_events).","inputSchema":{"type":"object","properties":{"filePath":{"type":"string"},"reason":{"type":"string","description":"Why the lock is being broken (required; audited)."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["filePath","reason"]}},{"name":"search_codebase","description":"Hybrid code search (semantic + full-text + trigram, reranked) over the project's indexed code. Returns ranked symbols with file:line locations, plus `related` files that historically change together. Use to find where something is implemented.","inputSchema":{"type":"object","properties":{"query":{"type":"string"},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["query"]}},{"name":"deep_search","description":"Agentic answer engine over the codebase: retrieves, reads, follows up across files, and returns a CITED natural-language answer (path:line) to a question — instead of raw chunks. Use for 'how does X work?' / 'where is Y handled and why?' questions.","inputSchema":{"type":"object","properties":{"query":{"type":"string","description":"A question about the codebase."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["query"]}},{"name":"index_codebase","description":"Index files into the searchable codebase so search_codebase/deep_search can find them. Incremental and content-hashed: unchanged files are skipped server-side (no re-embedding), so it's cheap to call repeatedly. Pass the files you created or edited as `files: [{path, content}]` — do this after writing files so search stays fresh. For a full initial index of a large repo, the `npx @virsanghavi/axis-server index` CLI is faster. To remove deleted files, pass `prune: true` and `allPaths` (every path that should remain indexed).","inputSchema":{"type":"object","properties":{"files":{"type":"array","description":"Files to index.","items":{"type":"object","properties":{"path":{"type":"string","description":"Repo-relative path, e.g. src/auth.ts"},"content":{"type":"string","description":"Full current file content."}},"required":["path","content"]}},"prune":{"type":"boolean","description":"If true, delete indexed files not present in allPaths."},"allPaths":{"type":"array","description":"When pruning, the complete list of paths that should remain indexed.","items":{"type":"string"}},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["files"]}},{"name":"update_shared_context","description":"Append a short note to the project's LIVE NOTEPAD — the shared scratchpad every agent on the project reads. Use it to broadcast status as you work ('fixed auth bug in login.ts', 'indexed repo', 'taking the billing task') so other agents stay in sync. Returns the updated notepad. This is the team's shared short-term memory.","inputSchema":{"type":"object","properties":{"text":{"type":"string","description":"The note to append (short — what you just did or are about to do)."},"agentId":{"type":"string","description":"Your agent id, e.g. 'cursor' or 'claude-code'."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}},"required":["text"]}},{"name":"get_shared_context","description":"Read the project's LIVE NOTEPAD — the shared scratchpad of recent notes from all agents on the project. Call this when you start work or need to see what teammates are doing.","inputSchema":{"type":"object","properties":{"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}}}},{"name":"finalize_session","description":"Archive the current session's shared context and clear remaining locks. Call when the user's request is fully complete.","inputSchema":{"type":"object","properties":{"content":{"type":"string","description":"Final session summary."},"projectName":{"type":"string","description":"Project name to scope to. Defaults to 'default'."}}}},{"name":"get_subscription_status","description":"Get the current user's Axis subscription status (free/pro) and renewal info.","inputSchema":{"type":"object","properties":{}}},{"name":"get_usage_stats","description":"Get the current user's recent API usage stats.","inputSchema":{"type":"object","properties":{}}}]}