From 1f84970fb3882a805914364fbf973df6ee216bc8 Mon Sep 17 00:00:00 2001 From: GangGreenTemperTatum <104169244+GangGreenTemperTatum@users.noreply.github.com> Date: Thu, 9 Jul 2026 10:56:49 -0400 Subject: [PATCH 1/2] fix(web-security): prefer caido-sdk-client over MCP when importable The Caido MCP skill (caido-proxy) was the only documented Caido surface, so agents always routed through MCP even when the caido-sdk-client library was directly importable. Add a basic caido-sdk skill that steers agents to the direct SDK when available, with a runtime availability probe and an explicit fallback chain (direct import -> uv run --with -> caido-proxy MCP). - New skill skills/caido-sdk/SKILL.md - Cross-reference note in caido-proxy/SKILL.md pointing to the SDK path No SDK or MCP server code changed. --- .../web-security/skills/caido-proxy/SKILL.md | 5 + .../web-security/skills/caido-sdk/SKILL.md | 120 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 capabilities/web-security/skills/caido-sdk/SKILL.md diff --git a/capabilities/web-security/skills/caido-proxy/SKILL.md b/capabilities/web-security/skills/caido-proxy/SKILL.md index 95901b8..9d83d45 100644 --- a/capabilities/web-security/skills/caido-proxy/SKILL.md +++ b/capabilities/web-security/skills/caido-proxy/SKILL.md @@ -7,6 +7,11 @@ description: "Caido proxy integration for HTTP history search, request replay, f MCP integration with Caido proxy. Results load into context -- keep queries focused. +> If `python3 -c "import caido_sdk_client"` succeeds in the current runtime, +> prefer the **`caido-sdk`** skill instead — direct SDK calls avoid per-tool MCP +> round-trips and are more efficient. Use this MCP path when the SDK is not +> importable (its usual state outside the MCP's own env) or Caido is unreachable. + ## HTTPQL Quick Reference ``` diff --git a/capabilities/web-security/skills/caido-sdk/SKILL.md b/capabilities/web-security/skills/caido-sdk/SKILL.md new file mode 100644 index 0000000..67f5bbc --- /dev/null +++ b/capabilities/web-security/skills/caido-sdk/SKILL.md @@ -0,0 +1,120 @@ +--- +name: caido-sdk +description: "Direct Caido interaction via the caido-sdk-client Python library, bypassing the Caido MCP server. Prefer this over the caido-proxy MCP skill for efficiency WHEN the SDK is importable in the current runtime. If the import fails, or Caido/the MCP is not loaded, fall back to the caido-proxy skill." +--- + +# Caido SDK (direct) + +Talk to a running Caido instance directly through the `caido-sdk-client` Python +library instead of the Caido MCP server. When the library is importable, this is +more efficient than MCP: one process, no per-call tool round-trips, and full +access to the SDK's typed objects. + +This does not replace the `caido-proxy` skill — it is the preferred path only +when the SDK is available. Decide with the availability check below. + +## Step 0 — Availability check (do this first) + +The SDK is often only installed inside the Caido MCP's isolated env, not the +agent runtime. Probe before committing: + +```bash +python3 -c "import caido_sdk_client" 2>/dev/null \ + && echo "USE SDK DIRECTLY" || echo "SDK NOT IN RUNTIME" +``` + +Fallback order: + +1. `import caido_sdk_client` succeeds → use it directly (this skill). +2. Import fails but `uv` is on PATH → run one-off scripts with + `uv run --with caido-sdk-client script.py` (ephemeral install, matches how + the MCP provisions itself). Needs network on first run. +3. Neither works, or Caido itself is not reachable → **load the `caido-proxy` + skill and use the `caido_*` MCP tools instead.** + +Do not assume the SDK is present just because "caido" is in the task. If Step 0 +prints `SDK NOT IN RUNTIME` and `uv` is unavailable, switch to `caido-proxy`. + +## Authentication + +Resolution order (same as the MCP server uses): + +1. `CAIDO_PAT` env var → `PATAuthOptions(pat=...)`, no `connect()` needed. +2. `~/.caido-mcp/token.json` (`accessToken` / `refreshToken`) → + `TokenAuthOptions` + `await client.connect()`. +3. No auth → guest mode, only `health()` works. + +`CAIDO_URL` overrides the default `http://localhost:8080`. + +## Minimal usage + +```python +import asyncio, os, json +from pathlib import Path +from caido_sdk_client import Client + +async def main(): + url = os.environ.get("CAIDO_URL", "http://localhost:8080") + pat = os.environ.get("CAIDO_PAT") + if pat: + from caido_sdk_client.auth import PATAuthOptions + client = Client(url, auth=PATAuthOptions(pat=pat)) + else: + from caido_sdk_client.auth import TokenAuthOptions, TokenPair + data = json.loads((Path.home() / ".caido-mcp" / "token.json").read_text()) + client = Client(url, auth=TokenAuthOptions( + token=TokenPair(access_token=data["accessToken"], + refresh_token=data.get("refreshToken")))) + await client.connect() + + # health + h = await client.health() + print(h.name, h.version, h.ready) + + # search proxy history (HTTPQL — same filter syntax as the MCP/caido-proxy skill) + conn = await client.request.list().first(20).filter('req.host.eq:"example.com"').execute() + for edge in conn.edges: + r = edge.node.request + resp = edge.node.response + print(r.id, r.method, resp.status_code if resp else "-", r.host + r.path) + + # get one request/response + entry = await client.request.get("") + if entry and entry.response and entry.response.raw: + print(entry.response.raw.decode(errors="replace")[:2000]) + + # replay a modified raw request + from caido_sdk_client.types.replay_session import ReplaySendOptions + session = await client.replay.sessions.create() + result = await client.replay.send(session.id, ReplaySendOptions( + raw=b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", + host="example.com", port=443, tls=True)) + print(result.task_status) + + # document a finding + from caido_sdk_client.types.finding import CreateFindingOptions + await client.findings.create("", CreateFindingOptions( + title="IDOR in /api/users/{id}", reporter="dreadnode-agent")) + + await client.aclose() + +asyncio.run(main()) +``` + +Run inline with `python3 - <<'PY' ... PY`, or via `uv run --with caido-sdk-client` +when the library is not in the runtime env. + +## HTTPQL + +Filter syntax is identical to the `caido-proxy` skill (e.g. +`req.host.eq:"example.com" AND req.method.eq:"POST"`, `resp.code.gte:500`). See +that skill's quick reference — do not duplicate it here. + +## Notes + +- `caido-server-auth` is a separate auth-only helper package (device-flow / PAT + approval) that `caido-sdk-client` pulls in. You normally interact only with + `caido_sdk_client`; reach for `caido_server_auth` only when scripting an + initial device-flow login. +- Do not modify the `caido-sdk-client` package or the capability's MCP wrappers. + This skill only *uses* the SDK. From 0d07bce5b3331998b2c19ad03a4d507e47cbb475 Mon Sep 17 00:00:00 2001 From: GangGreenTemperTatum <104169244+GangGreenTemperTatum@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:08:03 -0400 Subject: [PATCH 2/2] docs(web-security): trim caido-sdk skill bloat Remove repetition (fallback order stated twice, 'does not replace caido-proxy' stated three times, verbose Notes). Condense code example. 120 -> 85 lines, no loss of substance. --- .../web-security/skills/caido-sdk/SKILL.md | 99 ++++++------------- 1 file changed, 32 insertions(+), 67 deletions(-) diff --git a/capabilities/web-security/skills/caido-sdk/SKILL.md b/capabilities/web-security/skills/caido-sdk/SKILL.md index 67f5bbc..b00399e 100644 --- a/capabilities/web-security/skills/caido-sdk/SKILL.md +++ b/capabilities/web-security/skills/caido-sdk/SKILL.md @@ -5,48 +5,31 @@ description: "Direct Caido interaction via the caido-sdk-client Python library, # Caido SDK (direct) -Talk to a running Caido instance directly through the `caido-sdk-client` Python -library instead of the Caido MCP server. When the library is importable, this is -more efficient than MCP: one process, no per-call tool round-trips, and full -access to the SDK's typed objects. +Talk to a running Caido instance directly through `caido-sdk-client` — one +process, no per-call MCP round-trips. Preferred over the `caido-proxy` MCP skill +**only when the library is importable**. -This does not replace the `caido-proxy` skill — it is the preferred path only -when the SDK is available. Decide with the availability check below. +## Step 0 — availability (probe first, never assume) -## Step 0 — Availability check (do this first) - -The SDK is often only installed inside the Caido MCP's isolated env, not the -agent runtime. Probe before committing: +The SDK usually lives only inside the MCP's isolated env, not the agent runtime. ```bash -python3 -c "import caido_sdk_client" 2>/dev/null \ - && echo "USE SDK DIRECTLY" || echo "SDK NOT IN RUNTIME" +python3 -c "import caido_sdk_client" 2>/dev/null && echo "USE SDK" || echo "NO SDK" ``` -Fallback order: - -1. `import caido_sdk_client` succeeds → use it directly (this skill). -2. Import fails but `uv` is on PATH → run one-off scripts with - `uv run --with caido-sdk-client script.py` (ephemeral install, matches how - the MCP provisions itself). Needs network on first run. -3. Neither works, or Caido itself is not reachable → **load the `caido-proxy` - skill and use the `caido_*` MCP tools instead.** - -Do not assume the SDK is present just because "caido" is in the task. If Step 0 -prints `SDK NOT IN RUNTIME` and `uv` is unavailable, switch to `caido-proxy`. - -## Authentication +1. Import works → use the SDK (below). +2. Import fails but `uv` is on PATH → `uv run --with caido-sdk-client script.py`. +3. Neither, or Caido unreachable → load the **`caido-proxy`** skill, use `caido_*` MCP tools. -Resolution order (same as the MCP server uses): +## Auth (resolution order) -1. `CAIDO_PAT` env var → `PATAuthOptions(pat=...)`, no `connect()` needed. -2. `~/.caido-mcp/token.json` (`accessToken` / `refreshToken`) → - `TokenAuthOptions` + `await client.connect()`. -3. No auth → guest mode, only `health()` works. +1. `CAIDO_PAT` env → `PATAuthOptions(pat=...)`, no `connect()`. +2. `~/.caido-mcp/token.json` → `TokenAuthOptions` + `await client.connect()`. +3. None → guest mode, only `health()` works. -`CAIDO_URL` overrides the default `http://localhost:8080`. +`CAIDO_URL` overrides the `http://localhost:8080` default. -## Minimal usage +## Usage ```python import asyncio, os, json @@ -62,59 +45,41 @@ async def main(): else: from caido_sdk_client.auth import TokenAuthOptions, TokenPair data = json.loads((Path.home() / ".caido-mcp" / "token.json").read_text()) - client = Client(url, auth=TokenAuthOptions( - token=TokenPair(access_token=data["accessToken"], - refresh_token=data.get("refreshToken")))) + client = Client(url, auth=TokenAuthOptions(token=TokenPair( + access_token=data["accessToken"], refresh_token=data.get("refreshToken")))) await client.connect() - # health - h = await client.health() - print(h.name, h.version, h.ready) + h = await client.health(); print(h.name, h.version, h.ready) - # search proxy history (HTTPQL — same filter syntax as the MCP/caido-proxy skill) + # search history (HTTPQL — same syntax as the caido-proxy skill) conn = await client.request.list().first(20).filter('req.host.eq:"example.com"').execute() for edge in conn.edges: - r = edge.node.request - resp = edge.node.response + r, resp = edge.node.request, edge.node.response print(r.id, r.method, resp.status_code if resp else "-", r.host + r.path) - # get one request/response - entry = await client.request.get("") + # inspect one request/response + entry = await client.request.get("") if entry and entry.response and entry.response.raw: print(entry.response.raw.decode(errors="replace")[:2000]) - # replay a modified raw request + # replay from caido_sdk_client.types.replay_session import ReplaySendOptions - session = await client.replay.sessions.create() - result = await client.replay.send(session.id, ReplaySendOptions( - raw=b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", - host="example.com", port=443, tls=True)) - print(result.task_status) + s = await client.replay.sessions.create() + res = await client.replay.send(s.id, ReplaySendOptions( + raw=b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", host="example.com", port=443, tls=True)) + print(res.task_status) - # document a finding + # finding from caido_sdk_client.types.finding import CreateFindingOptions - await client.findings.create("", CreateFindingOptions( - title="IDOR in /api/users/{id}", reporter="dreadnode-agent")) + await client.findings.create("", CreateFindingOptions(title="IDOR", reporter="dreadnode-agent")) await client.aclose() asyncio.run(main()) ``` -Run inline with `python3 - <<'PY' ... PY`, or via `uv run --with caido-sdk-client` -when the library is not in the runtime env. - -## HTTPQL - -Filter syntax is identical to the `caido-proxy` skill (e.g. -`req.host.eq:"example.com" AND req.method.eq:"POST"`, `resp.code.gte:500`). See -that skill's quick reference — do not duplicate it here. - ## Notes -- `caido-server-auth` is a separate auth-only helper package (device-flow / PAT - approval) that `caido-sdk-client` pulls in. You normally interact only with - `caido_sdk_client`; reach for `caido_server_auth` only when scripting an - initial device-flow login. -- Do not modify the `caido-sdk-client` package or the capability's MCP wrappers. - This skill only *uses* the SDK. +- HTTPQL filter syntax is identical to the `caido-proxy` skill; see its reference. +- `caido-server-auth` is a separate auth-only helper the SDK pulls in — only needed to script an initial device-flow login. +- Only *uses* the SDK; do not modify `caido-sdk-client` or the MCP wrappers.