appknox-mcp 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- appknox_mcp-1.0.0/.claude-plugin/marketplace.json +14 -0
- appknox_mcp-1.0.0/.claude-plugin/plugin.json +8 -0
- appknox_mcp-1.0.0/.github/workflows/ci.yml +50 -0
- appknox_mcp-1.0.0/.github/workflows/publish.yml +113 -0
- appknox_mcp-1.0.0/.gitignore +13 -0
- appknox_mcp-1.0.0/.mcp.json +12 -0
- appknox_mcp-1.0.0/.pre-commit-config.yaml +34 -0
- appknox_mcp-1.0.0/CONTRIBUTING.md +53 -0
- appknox_mcp-1.0.0/INSTALL.md +329 -0
- appknox_mcp-1.0.0/LICENSE +14 -0
- appknox_mcp-1.0.0/PKG-INFO +269 -0
- appknox_mcp-1.0.0/README.md +243 -0
- appknox_mcp-1.0.0/agents/appknox-fixer.md +53 -0
- appknox_mcp-1.0.0/commands/fix.md +48 -0
- appknox_mcp-1.0.0/commands/triage.md +61 -0
- appknox_mcp-1.0.0/commands/upload.md +48 -0
- appknox_mcp-1.0.0/commands/verify.md +45 -0
- appknox_mcp-1.0.0/pyproject.toml +78 -0
- appknox_mcp-1.0.0/scripts/configure_mcp.py +21 -0
- appknox_mcp-1.0.0/scripts/install.ps1 +241 -0
- appknox_mcp-1.0.0/scripts/install.sh +250 -0
- appknox_mcp-1.0.0/scripts/release.sh +72 -0
- appknox_mcp-1.0.0/scripts/uninstall.ps1 +61 -0
- appknox_mcp-1.0.0/scripts/uninstall.sh +49 -0
- appknox_mcp-1.0.0/src/appknox_mcp/__init__.py +0 -0
- appknox_mcp-1.0.0/src/appknox_mcp/app.py +20 -0
- appknox_mcp-1.0.0/src/appknox_mcp/client.py +205 -0
- appknox_mcp-1.0.0/src/appknox_mcp/configure.py +377 -0
- appknox_mcp-1.0.0/src/appknox_mcp/findings.py +166 -0
- appknox_mcp-1.0.0/src/appknox_mcp/instructions.py +117 -0
- appknox_mcp-1.0.0/src/appknox_mcp/models.py +146 -0
- appknox_mcp-1.0.0/src/appknox_mcp/resolve.py +52 -0
- appknox_mcp-1.0.0/src/appknox_mcp/server.py +179 -0
- appknox_mcp-1.0.0/src/appknox_mcp/status.py +115 -0
- appknox_mcp-1.0.0/src/appknox_mcp/upload.py +93 -0
- appknox_mcp-1.0.0/src/appknox_mcp/verify.py +159 -0
- appknox_mcp-1.0.0/tests/conftest.py +13 -0
- appknox_mcp-1.0.0/tests/test_client.py +190 -0
- appknox_mcp-1.0.0/tests/test_configure.py +422 -0
- appknox_mcp-1.0.0/tests/test_findings.py +281 -0
- appknox_mcp-1.0.0/tests/test_resolve.py +149 -0
- appknox_mcp-1.0.0/tests/test_server.py +159 -0
- appknox_mcp-1.0.0/tests/test_status.py +92 -0
- appknox_mcp-1.0.0/tests/test_upload.py +157 -0
- appknox_mcp-1.0.0/tests/test_verify.py +136 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "appknox",
|
|
3
|
+
"description": "Appknox MCP server and Claude Code plugin (slash commands + fixer agent).",
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "Appknox"
|
|
6
|
+
},
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "appknox",
|
|
10
|
+
"source": "./",
|
|
11
|
+
"description": "Appknox KnoxIQ fix-and-verify: fetch the vulnerabilities Appknox found in your app, fix them in your repo, verify with the finding's PoC, and re-scan."
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [develop]
|
|
6
|
+
push:
|
|
7
|
+
branches: [develop]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v5
|
|
16
|
+
- name: Run tests
|
|
17
|
+
run: uv run pytest -q
|
|
18
|
+
|
|
19
|
+
lint:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v5
|
|
25
|
+
# Runs the exact hooks in .pre-commit-config.yaml (black, ruff, mypy,
|
|
26
|
+
# plus the hygiene checks) — a pre-commit hook is opt-in locally and
|
|
27
|
+
# skippable with --no-verify, so this is what actually enforces it.
|
|
28
|
+
- uses: actions/cache@v4
|
|
29
|
+
with:
|
|
30
|
+
path: ~/.cache/pre-commit
|
|
31
|
+
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
|
|
32
|
+
- name: Run pre-commit hooks
|
|
33
|
+
run: uv run --with pre-commit pre-commit run --all-files --show-diff-on-failure
|
|
34
|
+
|
|
35
|
+
# Single required status check for branch protection, instead of naming
|
|
36
|
+
# `test`/`lint` individually there — this succeeds only if every job in the
|
|
37
|
+
# workflow did, so adding a new job here later doesn't also require a
|
|
38
|
+
# branch-protection settings change to actually gate merges on it.
|
|
39
|
+
ci:
|
|
40
|
+
name: CI
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
needs: [test, lint]
|
|
43
|
+
if: always()
|
|
44
|
+
steps:
|
|
45
|
+
- name: Check all jobs succeeded
|
|
46
|
+
run: |
|
|
47
|
+
if [ "${{ needs.test.result }}" != "success" ] || [ "${{ needs.lint.result }}" != "success" ]; then
|
|
48
|
+
echo "::error::One or more required CI jobs failed (test=${{ needs.test.result }}, lint=${{ needs.lint.result }})."
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
name: Build, Attach & Publish Release
|
|
2
|
+
|
|
3
|
+
# Fires when a GitHub Release is published — created by YOU (GitHub UI's
|
|
4
|
+
# "Draft a new release", or `gh release create <tag>` from your own login),
|
|
5
|
+
# never by this workflow itself. That distinction matters: GitHub's
|
|
6
|
+
# anti-recursion guard blocks an event created by a workflow's own
|
|
7
|
+
# GITHUB_TOKEN from triggering other workflows, so a release this workflow
|
|
8
|
+
# created would never actually fire `release: published` — which is exactly
|
|
9
|
+
# why release creation now has to be a human action, not automated. In
|
|
10
|
+
# exchange, you get full control: pick any tag (new or existing) and any
|
|
11
|
+
# branch/commit as the release's target, from GitHub's own release UI.
|
|
12
|
+
#
|
|
13
|
+
# Verifies pyproject.toml's version at that exact commit matches the tag you
|
|
14
|
+
# chose (a mismatch almost always means you forgot to bump the version before
|
|
15
|
+
# tagging), then tests, builds the wheel + sdist, smoke-tests the BUILT wheel,
|
|
16
|
+
# attaches both to the release you created, and publishes:
|
|
17
|
+
# - "Set as a pre-release" checked -> TestPyPI (safe, repeatable dry run)
|
|
18
|
+
# - unchecked (a real release) -> PyPI
|
|
19
|
+
#
|
|
20
|
+
# Requires one-time setup on both index's side before this can succeed —
|
|
21
|
+
# nothing here works until you do this by hand (PyPI Trusted Publishing has no
|
|
22
|
+
# API for it):
|
|
23
|
+
# 1. On test.pypi.org and pypi.org, under your account -> Publishing, add a
|
|
24
|
+
# "pending" trusted publisher for this project:
|
|
25
|
+
# - PyPI project name: appknox-mcp
|
|
26
|
+
# - Owner: appknox Repository: appknox-mcp
|
|
27
|
+
# - Workflow filename: publish.yml
|
|
28
|
+
# - Environment name: testpypi (on test.pypi.org) / pypi (on pypi.org)
|
|
29
|
+
# 2. In this repo's Settings -> Environments, create two environments named
|
|
30
|
+
# "testpypi" and "pypi" (the names must match what you registered above).
|
|
31
|
+
# Optionally add required reviewers to the "pypi" environment for a manual
|
|
32
|
+
# gate on real publishes.
|
|
33
|
+
# No secrets to create or store — Trusted Publishing uses this job's own OIDC
|
|
34
|
+
# token (the `id-token: write` permission below), which is why the workflow
|
|
35
|
+
# filename and environment name both have to match exactly what you registered.
|
|
36
|
+
on:
|
|
37
|
+
release:
|
|
38
|
+
types: [published]
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
publish:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
# Has to be resolved here, not inside a step — environment protection
|
|
44
|
+
# rules are evaluated before the job's steps run. github.event.release is
|
|
45
|
+
# available directly since this triggers on the release event itself —
|
|
46
|
+
# no separate lookup job needed (unlike when we reacted to a workflow_run).
|
|
47
|
+
environment: ${{ github.event.release.prerelease && 'testpypi' || 'pypi' }}
|
|
48
|
+
permissions:
|
|
49
|
+
contents: write # to attach the wheel/sdist to the release you created
|
|
50
|
+
id-token: write # PyPI/TestPyPI Trusted Publishing (OIDC)
|
|
51
|
+
env:
|
|
52
|
+
# Dummy token so importing the server / running tests never blocks on
|
|
53
|
+
# auth. Not a credential — the server accepts an empty/placeholder
|
|
54
|
+
# token and only fails on an actual API call, which this never makes.
|
|
55
|
+
APPKNOX_ACCESS_TOKEN: "PLACEHOLDER:PLACEHOLDER"
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
with:
|
|
59
|
+
# Build exactly what you tagged, not whatever the default branch's
|
|
60
|
+
# HEAD happens to be.
|
|
61
|
+
ref: ${{ github.event.release.tag_name }}
|
|
62
|
+
|
|
63
|
+
- name: Install uv
|
|
64
|
+
uses: astral-sh/setup-uv@v6
|
|
65
|
+
|
|
66
|
+
- name: Verify pyproject.toml's version matches the release tag
|
|
67
|
+
run: |
|
|
68
|
+
EXPECTED="v$(uv version --short)"
|
|
69
|
+
TAG="${{ github.event.release.tag_name }}"
|
|
70
|
+
if [ "$TAG" != "$EXPECTED" ]; then
|
|
71
|
+
echo "::error::Release tag '$TAG' doesn't match pyproject.toml's version"\
|
|
72
|
+
"('$EXPECTED' at this commit). Bump the version to match before"\
|
|
73
|
+
"tagging, or delete this release and re-tag."
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
- name: Run tests
|
|
78
|
+
run: uv run pytest -q
|
|
79
|
+
|
|
80
|
+
- name: Build wheel + sdist
|
|
81
|
+
run: uv build
|
|
82
|
+
|
|
83
|
+
- name: Smoke-test the built wheel (import + tool count) in a clean venv
|
|
84
|
+
run: |
|
|
85
|
+
uv venv /tmp/verify
|
|
86
|
+
uv pip install --python /tmp/verify/bin/python dist/*.whl
|
|
87
|
+
# Run from /tmp so a source-relative file (e.g. the repo's docs/) can't
|
|
88
|
+
# mask a packaging bug — this is exactly what caught the spec-load crash.
|
|
89
|
+
cd /tmp
|
|
90
|
+
/tmp/verify/bin/python -c "import asyncio, appknox_mcp.server as s; n=len(asyncio.run(s.mcp.list_tools())); print(f'built wheel OK — {n} tools'); assert n >= 7, n"
|
|
91
|
+
|
|
92
|
+
- name: Attach wheel + sdist to the release
|
|
93
|
+
env:
|
|
94
|
+
GH_TOKEN: ${{ github.token }}
|
|
95
|
+
run: gh release upload "${{ github.event.release.tag_name }}" dist/*.whl dist/*.tar.gz --clobber
|
|
96
|
+
|
|
97
|
+
- name: Publish to TestPyPI (prerelease dry run)
|
|
98
|
+
if: github.event.release.prerelease
|
|
99
|
+
run: uv publish --index testpypi
|
|
100
|
+
|
|
101
|
+
- name: Publish to PyPI
|
|
102
|
+
if: ${{ !github.event.release.prerelease }}
|
|
103
|
+
run: uv publish
|
|
104
|
+
|
|
105
|
+
- name: Summary
|
|
106
|
+
run: |
|
|
107
|
+
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
|
108
|
+
echo "### Published to TestPyPI :test_tube:" >> "$GITHUB_STEP_SUMMARY"
|
|
109
|
+
echo "Verify with: \`uv tool install --reinstall --index https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ --index-strategy unsafe-best-match appknox-mcp\`" >> "$GITHUB_STEP_SUMMARY"
|
|
110
|
+
else
|
|
111
|
+
echo "### Published to PyPI :rocket:" >> "$GITHUB_STEP_SUMMARY"
|
|
112
|
+
echo "Verify with: \`uv tool install --reinstall appknox-mcp\`" >> "$GITHUB_STEP_SUMMARY"
|
|
113
|
+
fi
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Installed with `uv run pre-commit install` (one-time, per clone) so these
|
|
2
|
+
# run automatically on `git commit`. CI (.github/workflows/ci.yml) runs the
|
|
3
|
+
# same checks via `uv run pre-commit run --all-files` — a pre-commit hook is
|
|
4
|
+
# opt-in locally and skippable with `--no-verify`, so CI is what actually
|
|
5
|
+
# enforces this on every PR, not just a courtesy for whoever remembers to
|
|
6
|
+
# install it.
|
|
7
|
+
repos:
|
|
8
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
9
|
+
rev: v5.0.0
|
|
10
|
+
hooks:
|
|
11
|
+
- id: trailing-whitespace
|
|
12
|
+
- id: end-of-file-fixer
|
|
13
|
+
- id: check-yaml
|
|
14
|
+
- id: check-toml
|
|
15
|
+
- id: check-merge-conflict
|
|
16
|
+
- id: check-added-large-files
|
|
17
|
+
|
|
18
|
+
- repo: https://github.com/psf/black
|
|
19
|
+
rev: 25.9.0
|
|
20
|
+
hooks:
|
|
21
|
+
- id: black
|
|
22
|
+
|
|
23
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
24
|
+
rev: v0.16.7
|
|
25
|
+
hooks:
|
|
26
|
+
- id: ruff-check
|
|
27
|
+
args: [--fix]
|
|
28
|
+
|
|
29
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
30
|
+
rev: v1.18.2
|
|
31
|
+
hooks:
|
|
32
|
+
- id: mypy
|
|
33
|
+
additional_dependencies: ["fastmcp>=2.0.0,<4.0.0", "httpx>=0.27.0", "pydantic>=2.0.0"]
|
|
34
|
+
files: ^src/
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv sync
|
|
7
|
+
uv run pre-commit install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The second command wires up git's `pre-commit` hook so black, ruff, and mypy
|
|
11
|
+
(plus a few hygiene checks) run automatically on every `git commit` — see
|
|
12
|
+
`.pre-commit-config.yaml`. It only touches files you've staged, so it's fast.
|
|
13
|
+
|
|
14
|
+
To run everything by hand (e.g. before opening a PR):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv run pre-commit run --all-files
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
CI runs this same command on every PR — a local git hook is opt-in and
|
|
21
|
+
skippable with `git commit --no-verify`, so CI is what actually enforces it.
|
|
22
|
+
|
|
23
|
+
## Branch rules
|
|
24
|
+
|
|
25
|
+
`develop` is the default branch and requires at least one approving review
|
|
26
|
+
plus a green CI run before a PR can merge — direct pushes aren't allowed for
|
|
27
|
+
regular contributors. Target `develop` with your PR unless you're stacking on
|
|
28
|
+
another open PR. (Repo admins can still push directly — see Releasing below,
|
|
29
|
+
which relies on exactly that to bump the version with no PR round-trip.)
|
|
30
|
+
|
|
31
|
+
## Tests
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv run pytest -q
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Releasing
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
./scripts/release.sh patch # or minor / major
|
|
41
|
+
./scripts/release.sh patch --real # skip TestPyPI, publish straight to PyPI
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Bumps `pyproject.toml`'s version, pushes it straight to `develop`, tags it,
|
|
45
|
+
and creates a GitHub Release using your own `gh` login — which is what
|
|
46
|
+
triggers `.github/workflows/publish.yml` to actually build, test, and publish
|
|
47
|
+
(TestPyPI by default, or real PyPI with `--real`). No PAT or stored secret
|
|
48
|
+
involved: since it's your own account doing the push and creating the
|
|
49
|
+
release, branch protection's admin exemption and GitHub's normal "a human did
|
|
50
|
+
this" behavior both apply, exactly as if you'd done each step by hand.
|
|
51
|
+
|
|
52
|
+
Must be run from a clean `develop` checkout, by an account with admin access
|
|
53
|
+
to this repo (needed to push directly, bypassing the PR requirement above).
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Installing the Appknox MCP server
|
|
2
|
+
|
|
3
|
+
This document is written so an **AI coding agent** (Claude Code, Cursor, Codex, …)
|
|
4
|
+
can install and wire up the Appknox MCP server end-to-end. A human can follow it
|
|
5
|
+
too. It needs no clone of this repo — the server installs as a global command,
|
|
6
|
+
and this exact file ships inside the package: once step 1 below is done, run
|
|
7
|
+
`appknox-mcp --install-guide` any time to print it again.
|
|
8
|
+
|
|
9
|
+
> **Agent:** if a user handed you this repo's URL (or this file) and said "install
|
|
10
|
+
> it," you're in the right place — do the steps below in order. **First identify
|
|
11
|
+
> which client you are actually running as** (Claude Code, Cursor, Codex, GitHub
|
|
12
|
+
> Copilot CLI, VS Code, Windsurf, Claude Desktop, or something else) — you need
|
|
13
|
+
> this for step 3 regardless, and it matters more once you reach the section
|
|
14
|
+
> titled "Claude Code only": if you are not Claude Code, skip that entire
|
|
15
|
+
> section (do not run its commands "just in case" or because it looked like the
|
|
16
|
+
> next step in order) and go straight to the step after it. The only things to
|
|
17
|
+
> ask the user for are their **Access Key ID + Secret Access Key** (step 2) and,
|
|
18
|
+
> if it's unclear, **which client** to configure. Never print the secret back.
|
|
19
|
+
|
|
20
|
+
## 0. Prerequisite: `uv`
|
|
21
|
+
|
|
22
|
+
If `uv` is not on PATH, install it:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh # macOS/Linux
|
|
26
|
+
# Windows PowerShell: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then ensure `~/.local/bin` is on PATH (the installer usually handles this).
|
|
30
|
+
|
|
31
|
+
## 1. Install the server as a global tool
|
|
32
|
+
|
|
33
|
+
**You (the agent) run these commands** — the user doesn't. Try them **in order,
|
|
34
|
+
stopping at the first that works**; all produce a location-independent
|
|
35
|
+
`appknox-mcp` command on PATH (usually `~/.local/bin/appknox-mcp`).
|
|
36
|
+
|
|
37
|
+
> **Always include `--reinstall`** on every `uv tool install` below, even the
|
|
38
|
+
> very first attempt. Without it, `uv` silently does **nothing** if any
|
|
39
|
+
> version of `appknox-mcp` is already installed — including a stale one that
|
|
40
|
+
> predates a flag like `--install-guide` — so re-running "install" can look
|
|
41
|
+
> like it worked while actually leaving old, broken code in place. This is a
|
|
42
|
+
> real failure mode that has happened, not a hypothetical edge case.
|
|
43
|
+
|
|
44
|
+
**a) From PyPI (preferred — try this first).**
|
|
45
|
+
```bash
|
|
46
|
+
uv tool install --reinstall appknox-mcp
|
|
47
|
+
```
|
|
48
|
+
Not live yet — while `appknox-mcp` is only on TestPyPI, install from there
|
|
49
|
+
instead (needs `--extra-index-url` since deps like `fastmcp` aren't on
|
|
50
|
+
TestPyPI, and `--index-strategy unsafe-best-match` to let `uv` resolve across
|
|
51
|
+
both indexes):
|
|
52
|
+
```bash
|
|
53
|
+
uv tool install --reinstall --index https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ --index-strategy unsafe-best-match appknox-mcp
|
|
54
|
+
```
|
|
55
|
+
Once it's on real PyPI, switch to the plain command above and skip straight to
|
|
56
|
+
verifying — only fall through to (b) if both of these fail.
|
|
57
|
+
|
|
58
|
+
**b) From a released wheel.**
|
|
59
|
+
The repo is **private for now**, so download the wheel with the GitHub CLI (it
|
|
60
|
+
uses the user's existing `gh`/GitHub auth), then install the local file:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
gh release download --repo appknox/appknox-mcp --pattern '*.whl' --dir /tmp/appknox-mcp
|
|
64
|
+
uv tool install --reinstall /tmp/appknox-mcp/*.whl
|
|
65
|
+
```
|
|
66
|
+
Add `<tag>` (e.g. `v0.1.0`) as the first arg to `gh release download` to pin a
|
|
67
|
+
version; omit it for the latest. **When the repo is public**, skip `gh` entirely
|
|
68
|
+
and install straight from the asset URL:
|
|
69
|
+
`uv tool install --reinstall "https://github.com/appknox/appknox-mcp/releases/latest/download/appknox_mcp-<version>-py3-none-any.whl"`.
|
|
70
|
+
|
|
71
|
+
**c) From git (no release needed).**
|
|
72
|
+
```bash
|
|
73
|
+
uv tool install --reinstall "git+ssh://git@github.com/appknox/appknox-mcp@develop" # private: uses the user's SSH key
|
|
74
|
+
# public: uv tool install --reinstall "git+https://github.com/appknox/appknox-mcp@develop"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**d) From a local checkout** (if the user already cloned it):
|
|
78
|
+
`uv tool install --reinstall /path/to/appknox-mcp`.
|
|
79
|
+
|
|
80
|
+
Then verify — `command -v appknox-mcp` only proves *a* binary is on PATH, not
|
|
81
|
+
that it's actually the one you just installed (a stale prior install would
|
|
82
|
+
also pass that check silently, as `--reinstall` above exists specifically to
|
|
83
|
+
prevent). Confirm the real thing instead:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
appknox-mcp --install-guide | head -1
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
If that doesn't print `# Installing the Appknox MCP server`, the install
|
|
90
|
+
didn't actually take — re-run step 1's command with `--reinstall` (if you
|
|
91
|
+
skipped it) rather than assuming this step is broken.
|
|
92
|
+
|
|
93
|
+
To update later: `uv tool upgrade appknox-mcp` (works regardless of which
|
|
94
|
+
source it was originally installed from) or re-run the `gh release download` +
|
|
95
|
+
`uv tool install --reinstall` step for a newer wheel.
|
|
96
|
+
|
|
97
|
+
## 2. Ask the user for credentials
|
|
98
|
+
|
|
99
|
+
Prompt the user for three things (from Appknox dashboard → **Service Accounts**):
|
|
100
|
+
|
|
101
|
+
1. **Access Key ID**
|
|
102
|
+
2. **Secret Access Key**
|
|
103
|
+
3. **Base URL** — the API host for their Appknox instance. Don't assume a
|
|
104
|
+
default: white-labeled deployments use a different host, so ask rather than
|
|
105
|
+
guess (their dashboard has it if they're unsure; Appknox's own KnoxIQ beta
|
|
106
|
+
host is `https://sherlock-mcp.staging.appknox.io`, for reference only).
|
|
107
|
+
|
|
108
|
+
Combine the first two into the token the server expects:
|
|
109
|
+
`APPKNOX_ACCESS_TOKEN = "<Access Key ID>:<Secret Access Key>"` (a single colon
|
|
110
|
+
between them). Hold these in memory for step 3 — never echo the secret back to
|
|
111
|
+
the user or write it anywhere except the client config's `env`.
|
|
112
|
+
|
|
113
|
+
## 3. Write the MCP config for the client
|
|
114
|
+
|
|
115
|
+
Figure out which client you're configuring (if you're an agent, that's usually
|
|
116
|
+
the client you're running in; otherwise ask). **Merge** the `appknox` entry into
|
|
117
|
+
the existing config — never overwrite other servers. Use `command: "appknox-mcp"`
|
|
118
|
+
with **no args** (the tool install put it on PATH).
|
|
119
|
+
|
|
120
|
+
### JSON clients — Cursor, Claude Desktop, Windsurf, Claude Code
|
|
121
|
+
|
|
122
|
+
Key is `mcpServers`. Files:
|
|
123
|
+
|
|
124
|
+
| Client | Config file |
|
|
125
|
+
|---|---|
|
|
126
|
+
| Cursor | `~/.cursor/mcp.json` |
|
|
127
|
+
| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
128
|
+
| Claude Desktop (Linux) | `~/.config/Claude/claude_desktop_config.json` |
|
|
129
|
+
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
130
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
131
|
+
| Claude Code (this project) | `.mcp.json` in the repo |
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"mcpServers": {
|
|
136
|
+
"appknox": {
|
|
137
|
+
"command": "appknox-mcp",
|
|
138
|
+
"env": {
|
|
139
|
+
"APPKNOX_ACCESS_TOKEN": "<Access Key ID>:<Secret Access Key>",
|
|
140
|
+
"APPKNOX_BASE_URL": "<your Appknox base URL>"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### GitHub Copilot CLI — `~/.copilot/mcp-config.json`
|
|
148
|
+
|
|
149
|
+
Key is `mcpServers` too, but each entry additionally needs `"type": "local"`
|
|
150
|
+
(Copilot CLI's name for a stdio server) and a `"tools"` allowlist — omit
|
|
151
|
+
`"tools"` and the server starts but exposes none of its tools:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"mcpServers": {
|
|
156
|
+
"appknox": {
|
|
157
|
+
"type": "local",
|
|
158
|
+
"command": "appknox-mcp",
|
|
159
|
+
"args": [],
|
|
160
|
+
"env": {
|
|
161
|
+
"APPKNOX_ACCESS_TOKEN": "<Access Key ID>:<Secret Access Key>",
|
|
162
|
+
"APPKNOX_BASE_URL": "<your Appknox base URL>"
|
|
163
|
+
},
|
|
164
|
+
"tools": ["*"]
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The path honors `$COPILOT_HOME` if set (defaults to `~/.copilot`). Verify with
|
|
171
|
+
`/mcp` inside a `copilot` session.
|
|
172
|
+
|
|
173
|
+
> **Note:** this is the standalone **GitHub Copilot CLI**, not Copilot Chat
|
|
174
|
+
> inside VS Code — that one is configured via the **VS Code** section below
|
|
175
|
+
> (VS Code's `.vscode/mcp.json` is shared by both plain VS Code MCP support and
|
|
176
|
+
> Copilot Chat).
|
|
177
|
+
|
|
178
|
+
> **Important — Copilot CLI ignores this server's workflow guidance by
|
|
179
|
+
> default.** Every MCP server can send high-level instructions alongside its
|
|
180
|
+
> tools (ours describes the resolve → triage → fix → verify flow, how to
|
|
181
|
+
> select findings, etc.) — Copilot CLI deliberately does **not** feed these
|
|
182
|
+
> into the model unless you start it with `copilot --allow-all-mcp-server-instructions`
|
|
183
|
+
> (v1.0.66+). Without that flag, Copilot only sees each tool's own
|
|
184
|
+
> name/parameters/docstring, not the overall workflow — it can still call the
|
|
185
|
+
> tools correctly, but won't follow the intended multi-step flow or
|
|
186
|
+
> presentation guidance (e.g. showing exploitability) unless asked explicitly
|
|
187
|
+
> each time. Recommend this flag to anyone using Copilot CLI who wants the
|
|
188
|
+
> guided experience the other clients get by default.
|
|
189
|
+
|
|
190
|
+
### VS Code — `.vscode/mcp.json`
|
|
191
|
+
|
|
192
|
+
Key is `servers` and the entry needs `"type": "stdio"`:
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"servers": {
|
|
197
|
+
"appknox": {
|
|
198
|
+
"type": "stdio",
|
|
199
|
+
"command": "appknox-mcp",
|
|
200
|
+
"env": { "APPKNOX_ACCESS_TOKEN": "…", "APPKNOX_BASE_URL": "…" }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
This is project-scoped (only active in this one repo), unlike every other
|
|
207
|
+
client here — `appknox-mcp --configure vscode` writes the same project-scoped
|
|
208
|
+
file, we don't automate the alternative below. VS Code does have a genuine
|
|
209
|
+
**user-scope** option (works in every workspace): Command Palette → **MCP:
|
|
210
|
+
Open User Configuration**, or `code --add-mcp '{"name":"appknox",...}'` and
|
|
211
|
+
choose **Global** over **Workspace**. VS Code's own docs recommend *against* a
|
|
212
|
+
literal secret even there — use an `${input:...}` variable (`password: true`)
|
|
213
|
+
instead, which prompts once and caches the value in VS Code's own secret
|
|
214
|
+
storage from then on. Set that up by hand if you want VS Code to behave like
|
|
215
|
+
the other clients here; see [VS Code's MCP docs](https://code.visualstudio.com/docs/copilot/customization/mcp-servers)
|
|
216
|
+
for the exact `inputs` array syntax.
|
|
217
|
+
|
|
218
|
+
### Codex — `~/.codex/config.toml`
|
|
219
|
+
|
|
220
|
+
The env vars **must** go under a nested `[mcp_servers.appknox.env]` table, or
|
|
221
|
+
Codex silently ignores them:
|
|
222
|
+
|
|
223
|
+
```toml
|
|
224
|
+
[mcp_servers.appknox]
|
|
225
|
+
command = "appknox-mcp"
|
|
226
|
+
args = []
|
|
227
|
+
|
|
228
|
+
[mcp_servers.appknox.env]
|
|
229
|
+
APPKNOX_ACCESS_TOKEN = "<Access Key ID>:<Secret Access Key>"
|
|
230
|
+
APPKNOX_BASE_URL = "<your Appknox base URL>"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
> You can skip writing any of the JSON/TOML above by hand and instead let the
|
|
234
|
+
> already-installed `appknox-mcp` command do the merge for you (handles the
|
|
235
|
+
> Codex nesting, Copilot's `type`/`tools` fields, etc. correctly) — works with
|
|
236
|
+
> **no repo clone needed**, since the writer ships inside the package itself:
|
|
237
|
+
> `APPKNOX_ACCESS_TOKEN=<id>:<secret> appknox-mcp --configure <name> --base-url <url>`.
|
|
238
|
+
> To remove an entry later: `appknox-mcp --remove-client <name>`.
|
|
239
|
+
|
|
240
|
+
### Keep project-scoped tokens out of git
|
|
241
|
+
|
|
242
|
+
`vscode` (`.vscode/mcp.json`) lives in the app repo and now contains the
|
|
243
|
+
token — add it to that repo's `.gitignore`. `claude` no longer needs this: it
|
|
244
|
+
registers at user scope (`~/.claude.json`, outside any repo), not a
|
|
245
|
+
project-scoped file.
|
|
246
|
+
|
|
247
|
+
### Claude Code only: the plugin (slash commands + fixer agent)
|
|
248
|
+
|
|
249
|
+
> **STOP — agent, check this first:** this section applies ONLY if you are
|
|
250
|
+
> Claude Code. If you are Copilot CLI, Cursor, Codex, VS Code, Windsurf, Claude
|
|
251
|
+
> Desktop, or anything else, **skip this entire section** — do not run
|
|
252
|
+
> `claude plugin ...` commands (the `claude` CLI likely isn't even installed in
|
|
253
|
+
> your environment, and this step is not part of your setup at all) — and
|
|
254
|
+
> continue at "4. Restart and verify" below.
|
|
255
|
+
|
|
256
|
+
The MCP config above gets Claude Code the **tools**, but not `/appknox:triage`,
|
|
257
|
+
`/appknox:fix`, `/appknox:upload`, `/appknox:verify`, or the `appknox-fixer`
|
|
258
|
+
sub-agent — those only load if this repo is installed as a Claude Code
|
|
259
|
+
**plugin**, which is a separate registration.
|
|
260
|
+
|
|
261
|
+
**a) No-clone (preferred — works from a bare `uv tool install`, needs no
|
|
262
|
+
GitHub access at all).** The wheel bundles the plugin's files; this extracts
|
|
263
|
+
them to `~/.appknox-mcp/claude-plugin` and registers that local path:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
appknox-mcp --install-claude-plugin
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**b) From a repo you already have cloned locally**, or the GitHub-hosted form
|
|
270
|
+
(needs the repo on its default branch and — until it's public — doesn't
|
|
271
|
+
reliably work; see below):
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
claude plugin marketplace add /path/to/appknox-mcp # local clone's path
|
|
275
|
+
# or: claude plugin marketplace add appknox/appknox-mcp
|
|
276
|
+
claude plugin install appknox@appknox -y
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
> **Two separate ways the GitHub-hosted form (b, second line) fails, easy to
|
|
280
|
+
> confuse — (a) and (b)'s local-path form both sidestep both of these:**
|
|
281
|
+
> 1. `.claude-plugin/marketplace.json` must exist **on the repo's default
|
|
282
|
+
> branch** (`develop`, not `main`) — `claude plugin marketplace add
|
|
283
|
+
> owner/repo` always clones that branch, never a feature branch.
|
|
284
|
+
> 2. Even on the default branch, `claude plugin marketplace add owner/repo`
|
|
285
|
+
> does **not** reliably work against a private repo today — it clones via
|
|
286
|
+
> its own internal git (SSH needs a key already loaded in `ssh-agent`;
|
|
287
|
+
> HTTPS fails outright, ignoring `gh`/keychain credentials). See
|
|
288
|
+
> [anthropics/claude-code#17201](https://github.com/anthropics/claude-code/issues/17201).
|
|
289
|
+
|
|
290
|
+
This installs at **user scope** (the default), so the commands/agent are
|
|
291
|
+
available from any repo afterward, not just the one you ran this in. Commands
|
|
292
|
+
are namespaced `/appknox:<name>` (Claude Code's plugin system always prefixes
|
|
293
|
+
`<plugin>:<command>` — there's no way to opt out of that and still use the
|
|
294
|
+
formal plugin mechanism). Its bundled MCP entry reads `APPKNOX_ACCESS_TOKEN`/
|
|
295
|
+
`APPKNOX_BASE_URL` from the environment at launch — export them in the user's
|
|
296
|
+
shell profile, or rely on the project-scoped `.mcp.json` above (token baked in,
|
|
297
|
+
works without exporting anything, but only in that one repo).
|
|
298
|
+
`scripts/install.sh`/`install.ps1` do this step automatically when Claude Code
|
|
299
|
+
is selected.
|
|
300
|
+
|
|
301
|
+
## 4. Restart and verify
|
|
302
|
+
|
|
303
|
+
MCP servers are spawned when the client starts, so **restart the client** (fully
|
|
304
|
+
quit Claude Desktop with Cmd+Q; restart the Codex or Copilot CLI session; reopen
|
|
305
|
+
Cursor). Then:
|
|
306
|
+
|
|
307
|
+
- Claude Code / Codex / Copilot CLI: run `/mcp` — you should see `appknox` with
|
|
308
|
+
8 tools.
|
|
309
|
+
- Claude Code only: run `claude plugin list` — you should see `appknox@appknox`
|
|
310
|
+
enabled; try `/appknox:triage` to confirm the slash commands loaded.
|
|
311
|
+
- Ask: *"list the Appknox tools"* — the agent should see `resolve_latest_file`,
|
|
312
|
+
`list_analyses`, `knoxiq_get_fix_plan`, `knoxiq_prepare_fix`,
|
|
313
|
+
`knoxiq_verify_fixes`, etc.
|
|
314
|
+
|
|
315
|
+
## 5. Update / uninstall
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
uv tool upgrade appknox-mcp # pull the latest server
|
|
319
|
+
uv tool uninstall appknox-mcp # remove the server
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Before (or instead of) removing the server itself, remove its entry from each
|
|
323
|
+
client's config: `appknox-mcp --remove-client <client>` — this needs no repo
|
|
324
|
+
clone, since it ships inside the package (see the note in step 3). If the repo
|
|
325
|
+
happens to be cloned, `scripts/uninstall.sh <client>`/`scripts/uninstall.ps1
|
|
326
|
+
<client>` do the same thing and additionally remove the Claude Code plugin
|
|
327
|
+
registration for `claude`. To remove just the plugin by hand:
|
|
328
|
+
`claude plugin uninstall appknox@appknox` then
|
|
329
|
+
`claude plugin marketplace remove appknox`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Copyright (c) 2026 Appknox. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its associated documentation (the "Software") are the
|
|
4
|
+
proprietary and confidential property of Appknox. The Software is licensed, not
|
|
5
|
+
sold, and may be used only in accordance with the terms of a separate written
|
|
6
|
+
agreement with Appknox. Unauthorized copying, distribution, modification, public
|
|
7
|
+
display, or use of the Software, in whole or in part, via any medium, is strictly
|
|
8
|
+
prohibited.
|
|
9
|
+
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
11
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
12
|
+
FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL APPKNOX BE
|
|
13
|
+
LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN
|
|
14
|
+
CONNECTION WITH THE SOFTWARE OR ITS USE.
|