Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ Show the current API base URL and its source.
codeant get-base-url
```

#### `hotlist`

Query the same organization-wide prioritized findings shown in the CodeAnt Hotlist, or fetch one finding by its stable ID.

```bash
codeant hotlist list --org CodeAnt-AI --service github --severity critical,high
codeant hotlist get 0123456789abcdef0123456789abcdef --org CodeAnt-AI --service github
```

#### `api request`

Call any CodeAnt application API using the saved bearer token. Only relative paths on the configured CodeAnt API host are accepted.

```bash
codeant api request GET /some/read/endpoint --org CodeAnt-AI --service github --query '{"page":1}'
codeant api request POST /some/app/endpoint --org CodeAnt-AI --service github --body '{"repo":"CodeAnt-AI/example"}'
```

See [cli-api.md](cli-api.md) for the complete Hotlist, raw API, authentication, self-hosted provider, and agent/MCP manual.

### Global Options

```bash
Expand Down Expand Up @@ -240,11 +260,11 @@ node src/index.js secrets --all

This package also ships an MCP (Model Context Protocol) server that exposes CodeAnt's scan, review, and PR data as tools to Claude and other MCP clients. The same source tree is packaged as a Desktop Extension (`.mcpb`) for one-click install in Claude Desktop.

See [mcp.md](mcp.md) for the tools listing, install paths (Claude Code CLI, Claude Desktop manual config, MCPB double-click), and bundling/submission instructions.
See [mcp.md](mcp.md) for the tools listing, install paths (Claude Code CLI, Claude Desktop manual config, MCPB double-click), and bundling/submission instructions. See [cli-api.md](cli-api.md) for Hotlist and generic authenticated API usage.

## Privacy Policy

Full policy: **https://codeant.ai/privacy**
Full policy: **https://www.codeant.ai/privacy-policy**

Summary of what this CLI / MCP server sends and stores:

Expand Down
123 changes: 123 additions & 0 deletions cli-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# CodeAnt application APIs from the CLI

The CLI can call the authenticated application endpoints used by the CodeAnt web app without adding a second backend adapter for each endpoint. Sign in once, select one exact organization/provider connection, then use a first-class command or the generic API request command.

```bash
codeant login
codeant scans orgs
```

`CODEANT_API_TOKEN` and `CODEANT_API_URL` can be used instead of the saved login for agents, CI, and self-hosted installations.

The browser login binds the CLI key to the signed-in user and the exact connections visible to that user. For application API calls, the backend resolves the selected connection and injects the same verified user identity used by the app. The existing organization-membership, RBAC, repository-access, audit, and request guards still run. CLI keys expire after 90 days by default (`CLI_API_KEY_TTL_DAYS` controls the backend deployment value), and `codeant logout` revokes the key server-side before deleting it locally.

Keys created before this authenticated application-API bridge do not contain the verified user identity. Run `codeant logout` followed by `codeant login` once after upgrading.

## Hotlist findings

Hotlist commands use the same organization-wide snapshot, ranking, filters, stable finding IDs, and cursor pagination as the app.

```bash
# First page; org/service are auto-selected when unambiguous
codeant hotlist list

# Highest-priority production findings for one authenticated connection
codeant hotlist list \
--org CodeAnt-AI \
--service github \
--severity critical,high \
--validation exploit_confirmed \
--limit 50

# Fetch every SCA finding across the organization
codeant hotlist list --org CodeAnt-AI --service github --type SCA --all

# Continue a page using next_cursor from the previous response
codeant hotlist list --org CodeAnt-AI --service github --cursor '<cursor>'

# Fetch exactly one finding using the stable ID shown in the app
codeant hotlist get 0123456789abcdef0123456789abcdef \
--org CodeAnt-AI \
--service github
```

Supported `hotlist list` filters:

| Option | Values |
|---|---|
| `--search` | title, repository/account, path, package, CVE, or check ID |
| `--type` | `AI Exploitation`, `SCA`, `SAST`, `Secrets`, `IaC`, `Infrastructure` |
| `--location` | repository full names or cloud accounts |
| `--severity` | `critical`, `high`, `medium`, `low`, `unknown` |
| `--ticket-status` | `created`, `not_created` |
| `--compliance` | framework keys such as `soc2` |
| `--validation` | `exploit_confirmed` |

Comma-separated values are accepted. The default page size is 30 and the maximum is 100. `--all` follows every cursor. If the first organization snapshot is still being built, the command waits up to 60 seconds; change that with `--max-wait <seconds>`.

For self-hosted GitHub, GitLab, Bitbucket, or Azure DevOps, the CLI normally discovers the provider base URL from the authenticated connection. Use `--provider-base-url` only to override it.

## Any app API

Use the generic request command when a first-class command does not exist yet:

```bash
codeant api request GET /some/read/endpoint \
--org CodeAnt-AI --service github \
--query '{"page":1}'

codeant api request POST /some/app/endpoint \
--org CodeAnt-AI --service github \
--body '{"repo":"CodeAnt-AI/example"}'

codeant api request PATCH /some/app/endpoint \
--org CodeAnt-AI --service github \
--body-file ./request.json \
--header 'If-Match: revision-123'
```

The output is JSON:

```json
{
"ok": true,
"status": 200,
"tenant": {
"org": "CodeAnt-AI",
"service": "github"
},
"data": {}
}
```

Security properties:

- The path must start with `/` and is always resolved against the configured CodeAnt API host. Absolute and protocol-relative URLs are rejected, so the bearer token cannot be forwarded to another host.
- Authentication is supplied from `CODEANT_API_TOKEN` or the key saved by `codeant login`.
- `--org`, `--service`, and the discovered provider base URL must match one saved login connection exactly. They are auto-selected only when unambiguous. Use `--provider-base-url` for a self-hosted override.
- POST/PUT/PATCH/DELETE bodies must be JSON objects. The CLI adds the selected tenant fields before sending the request; conflicting tenant values are rejected by the backend.
- `Authorization`, `Cookie`, `Host`, `Content-Length`, and the `X-CodeAnt-CLI-*` tenant headers cannot be overridden.
- The backend remains authoritative for account access, organization membership, RBAC, and endpoint authorization.

The generic command can call write endpoints. Review the method, path, and body before running it.

## Agent and MCP access

Run `codeant mcp` or install the CodeAnt MCP bundle. Agents receive dedicated read-only tools:

- `codeant_hotlist_list` — filter and page through organization-wide findings.
- `codeant_hotlist_get` — fetch one finding by stable ID.
- `codeant_api_get` — authenticated GET access for newly-added read APIs.

Set `CODEANT_READ_ONLY=0` to opt in to write tools, including `codeant_api_request` for POST/PUT/PATCH/DELETE. Read-only mode is the default. The MCP server never opens a browser during startup; the agent must explicitly call `codeant_login` when no token is configured.

## Troubleshooting

| Error | Resolution |
|---|---|
| No matching organization | Run `codeant scans orgs`, then pass its exact `organizationName` and `service`. |
| Multiple organizations match | Pass both `--org` and `--service`. |
| Access denied (403) | Run `codeant logout`, then `codeant login`, or replace `CODEANT_API_TOKEN`. |
| Invalid token after upgrading | Older keys lack verified CLI identity metadata. Run `codeant logout`, then `codeant login`. |
| Hotlist is still building | Retry, or increase `--max-wait`. |
| Finding not found | Refresh the app/Hotlist and copy the current stable finding ID and tenant context. |
10 changes: 8 additions & 2 deletions mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@ The CodeAnt CLI ships an MCP (Model Context Protocol) server that exposes CodeAn
| `codeant_scans_get` | read | Severity/category summary for one scan. |
| `codeant_scans_results` | read | Full findings (SAST, SCA, secrets, IaC, …) for one scan. |
| `codeant_scans_dismissed` | read | Dismissed alerts for a repo. |
| `codeant_hotlist_list` | read | Prioritized organization-wide Hotlist findings with stable IDs. |
| `codeant_hotlist_get` | read | One complete Hotlist finding by stable ID. |
| `codeant_api_get` | read | Authenticated GET request to any relative CodeAnt app API path, with exact org/provider context. |
| `codeant_pr_list` | read | List PRs/MRs across GitHub, GitLab, Bitbucket, Azure DevOps. |
| `codeant_pr_get` | read | Detail for a PR/MR. |
| `codeant_pr_comments` | read | Comments on a PR, filtered. |
| `codeant_comments_search` | read | Free-text search across CodeAnt review comments. |
| `codeant_review_local` | read | Run a CodeAnt review on local working-copy changes. |
| `codeant_scans_start` | **write** | Trigger a new scan. Gated. |
| `codeant_pr_resolve` | **write** | Resolve a PR conversation thread. Gated. |
| `codeant_api_request` | **write** | Authenticated POST/PUT/PATCH/DELETE request to a relative CodeAnt app API path, with exact org/provider context. Gated. |

Write tools are only registered when `CODEANT_READ_ONLY=0`. Default = read-only.

For Hotlist examples, raw API syntax, tenant/provider selection, and response details, see [cli-api.md](cli-api.md).

Every tool carries MCP annotations (`title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) so the client can decide whether to auto-approve calls.

## Configuration (env vars)
Expand Down Expand Up @@ -148,7 +154,7 @@ cd dist/mcpb-stage
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; sleep 1) | node server/index.js
```

Expect 11 tools in the `tools/list` response (or 13 if `CODEANT_READ_ONLY=0`).
Expect 16 tools in the `tools/list` response (or 19 if `CODEANT_READ_ONLY=0`).

### Bumping the version

Expand All @@ -173,7 +179,7 @@ CodeAnt's MCP server uses stdio + a packaged bundle, so the submission route is
- **Submission URL:** https://claude.com/docs/connectors/building/submission
- **Bundle:** upload `dist/codeant.mcpb`
- **Required metadata:** already in [mcpb/manifest.json](mcpb/manifest.json) — display name, description, author, homepage, documentation, repository, license, keywords, `privacy_policies`, `tools` static listing, `user_config` schema.
- **Privacy policy.** Linked from both [README.md](README.md#privacy-policy) and the manifest's `privacy_policies` field (`https://codeant.ai/privacy`).
- **Privacy policy.** Linked from both [README.md](README.md#privacy-policy) and the manifest's `privacy_policies` field (`https://www.codeant.ai/privacy-policy`).

Reviewer notes worth preparing:

Expand Down
14 changes: 9 additions & 5 deletions mcpb/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
"manifest_version": "0.3",
"name": "codeant",
"display_name": "CodeAnt AI",
"version": "0.5.1",
"version": "0.5.3",
"description": "Drive CodeAnt AI security scans and code review from Claude — org-wide secret triage, cross-repo SAST/SCA findings, on-demand scans, and local PR review.",
"long_description": "CodeAnt AI inside Claude. Ask things like \"how many critical SAST findings do I have across my org?\", \"show every exposed secret in payments-service\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 11 read-only tools (orgs, repos, scan history, scan metadata, findings, dismissed alerts, PRs, comments, comment search, local review) and 2 opt-in write tools (trigger a scan, resolve a PR conversation) gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai. To authenticate, call the `codeant_login` tool — it opens the CodeAnt sign-in page in your browser and saves the token automatically.\n\nCollects anonymous usage telemetry via PostHog by default; set CODEANT_TELEMETRY_DISABLED=1 to opt out.",
"long_description": "CodeAnt AI inside Claude. Ask things like \"show my highest-priority Hotlist findings\", \"how many critical SAST findings do I have across my org?\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 16 read-only tools, including organization Hotlist list/get and an authenticated GET escape hatch for new APIs, plus 3 opt-in write tools gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai. To authenticate, call the `codeant_login` tool — it opens the CodeAnt sign-in page in your browser and saves the token automatically.\n\nCollects anonymous usage telemetry via PostHog by default; set CODEANT_TELEMETRY_DISABLED=1 to opt out.",
"author": {
"name": "CodeAnt AI",
"email": "support@codeant.ai",
"url": "https://codeant.ai"
},
"homepage": "https://codeant.ai",
"documentation": "https://docs.codeant.ai/cli/claude-code-plugin",
"support": "https://docs.codeant.ai/support",
"documentation": "https://github.com/CodeAnt-AI/codeant-cli#readme",
"support": "https://github.com/CodeAnt-AI/codeant-cli/issues",
"repository": {
"type": "git",
"url": "https://github.com/CodeAnt-AI/codeant-cli"
Expand Down Expand Up @@ -65,6 +65,9 @@
{ "name": "codeant_scans_get", "description": "Get summary metadata for a single scan (no findings)." },
{ "name": "codeant_scans_results", "description": "Fetch full findings (SAST, SCA, secrets, IaC, etc.) for a scan." },
{ "name": "codeant_scans_dismissed", "description": "List dismissed alerts for a repository." },
{ "name": "codeant_hotlist_list", "description": "List prioritized organization-wide Hotlist findings with stable IDs." },
{ "name": "codeant_hotlist_get", "description": "Fetch one complete Hotlist finding by its stable ID." },
{ "name": "codeant_api_get", "description": "Call any authenticated GET endpoint on the configured CodeAnt API host." },
{ "name": "codeant_pr_list", "description": "List pull requests / merge requests across GitHub, GitLab, Bitbucket, Azure DevOps." },
{ "name": "codeant_pr_get", "description": "Fetch detailed information for a single PR/MR." },
{ "name": "codeant_pr_comments", "description": "List comments on a PR/MR with optional filters." },
Expand All @@ -73,7 +76,8 @@
{ "name": "codeant_login", "description": "Open app.codeant.ai in the browser and poll until the user completes sign-in; saves the resulting API token." },
{ "name": "codeant_logout", "description": "Clear the saved API token and sign out of CodeAnt AI." },
{ "name": "codeant_scans_start", "description": "Trigger a new scan run (write — gated behind read_only=false)." },
{ "name": "codeant_pr_resolve", "description": "Resolve a PR conversation thread (write — gated behind read_only=false)." }
{ "name": "codeant_pr_resolve", "description": "Resolve a PR conversation thread (write — gated behind read_only=false)." },
{ "name": "codeant_api_request", "description": "Call an authenticated POST, PUT, PATCH, or DELETE API (write — gated behind read_only=false)." }
],
"user_config": {
"api_token": {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
"./scans/scan-history": "./src/scans/getScanHistory.js",
"./scans/fetch-results": "./src/scans/fetchScanResults.js",
"./scans/fetch-advanced-results": "./src/scans/fetchAdvancedScanResults.js",
"./scans/dismissed-alerts": "./src/scans/fetchDismissedAlerts.js"
"./scans/dismissed-alerts": "./src/scans/fetchDismissedAlerts.js",
"./hotlist": "./src/hotlist/client.js",
"./api": "./src/commands/api/request.js"
},
"files": [
"src"
Expand Down
54 changes: 54 additions & 0 deletions src/api/tenant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { validateConnection } from '../scans/connectionHandler.js';

const SERVICE_ALIASES = { azure_devops: 'azuredevops', ado: 'azuredevops' };
const PROVIDER_BASE_FIELDS = {
github: 'github_base_url',
gitlab: 'gitlab_base_url',
bitbucket: 'bitbucket_base_url',
azuredevops: 'azure_devops_base_url',
};

export function normalizeService(value) {
const service = String(value || '').trim().toLowerCase();
return SERVICE_ALIASES[service] || service;
}

export async function resolveCliTenant({ org, service, providerBaseUrl } = {}) {
const validation = await validateConnection();
if (!validation.success) {
throw new Error(validation.error || 'Unable to load authenticated CodeAnt organizations.');
}
const requestedService = normalizeService(service);
const candidates = (validation.connections || []).filter((connection) => {
const orgMatches = !org || connection.organizationName === org;
const serviceMatches = !requestedService || normalizeService(connection.service) === requestedService;
return orgMatches && serviceMatches;
});
if (candidates.length === 0) {
throw new Error('No authenticated organization matches --org/--service. Run `codeant scans orgs` to list available connections.');
}
if (candidates.length > 1) {
throw new Error('More than one organization matches. Pass both --org and --service; run `codeant scans orgs` to list values.');
}

const connection = candidates[0];
const normalizedService = normalizeService(connection.service);
const baseField = PROVIDER_BASE_FIELDS[normalizedService];
if (!baseField) throw new Error(`Application APIs are not supported for service ${connection.service}.`);
const organization = org || connection.organizationName;
const baseUrl = providerBaseUrl || connection.baseUrl;
if (!baseUrl) {
throw new Error('The provider base URL is unavailable. Pass --provider-base-url explicitly.');
}
return {
organization,
service: normalizedService,
providerBaseUrl: baseUrl,
requestBody: {
org: organization,
organization,
service: normalizedService,
[baseField]: baseUrl,
},
};
}
33 changes: 33 additions & 0 deletions src/commands/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { runApiRequest } from './request.js';

function collect(value, previous) {
return [...previous, value];
}

export default function registerApiCommands(program, { runCmd }) {
const api = program
.command('api')
.description('Call any authenticated CodeAnt application API');

api
.command('request <method> <path>')
.description('Send an authenticated request to a relative CodeAnt API path')
.option('--query <json>', 'Query parameters as a JSON object')
.option('--body <json>', 'JSON request body')
.option('--body-file <path>', 'Read the JSON request body from a file')
.option('--org <organization>', 'Authenticated CodeAnt organization')
.option('--service <provider>', 'SCM provider: github, gitlab, bitbucket, or azuredevops')
.option('--provider-base-url <url>', 'Provider base URL override for self-hosted SCMs')
.option('-H, --header <header>', 'Additional header (repeatable, "Name: value")', collect, [])
.action((method, path, options) => runCmd(() => runApiRequest({
method,
path,
query: options.query,
body: options.body,
bodyFile: options.bodyFile,
headers: options.header,
org: options.org,
service: options.service,
providerBaseUrl: options.providerBaseUrl,
})));
}
Loading