Bursar FAQ
Bursar is the Blacksands zero-trust MCP server (@blacksandscyber/mcp-server-bursar, this repo:
mcp-server-blacksands-shield/). It exposes 55 tools across 9 categories to any MCP client
(Claude Code, Claude Desktop, or another MCP-compatible agent) for codebase security scanning,
app/org provisioning, mTLS certificate management, compliance reporting, security operations,
and Receiver (edge-proxy) lifecycle management.
This FAQ is the companion to the training guide, which walks through every tool with example calls/responses. Use this doc to answer "why does X behave this way" and "what do I do when Y goes wrong" questions; use the training guide to look up a specific tool's inputs/outputs.
This is developer-focused material (tool behavior, auth model, troubleshooting). For account, plan, or billing questions, see the live Bursar AI Agents knowledge base instead.
Terminology used throughout: Agent = the MCP client calling Bursar tools (e.g. Claude). Authorizer = the first mTLS hop, issues a session token. Receiver = the second mTLS hop, a public-IP edge proxy that forwards to the real backend. Backend = the actual protected service; never directly reachable.
Table of contents
- What is Bursar?
- How does the auth model work?
- Getting started
- Tool categories at a glance
- Gotchas and notable behaviors
- Troubleshooting
What is Bursar?
Q: What does Bursar actually do? Bursar is an MCP server that lets an AI agent drive Blacksands zero-trust security operations directly — no dashboard clicking required. It can scan a local codebase for security risk, provision an application into Blacksands (certs, DNS, policies), run compliance reports, rotate or revoke certificates, manage Receivers (the edge proxies that front protected services), and even provision a brand-new agent identity on a remote host.
Q: Is Bursar the same thing as "Shield"?
Yes, historically. The repo folder and some internal docs still say mcp-server-blacksands-shield
/ @blacksandscyber/mcp-server-shield, but the published package and all current tool names use
the Bursar prefix (bursar_*, receiver_*, broker_*). If you see "Shield" in an older doc
or path, it refers to the same system.
Q: Do I need a Blacksands account to use Bursar? No — 14 of the 55 tools are [FREE] and work with zero configuration: local codebase/environment scanning, framework/PII/endpoint/service/datastore detection, manifest generation, deployment guidance, and identity/protocol introspection. The other 41 tools require broker authentication (an mTLS cert + password, issued by a human admin through Overwatch/SysAdmin, or redeemed from a one-time bootstrap token). See How does the auth model work?.
Q: What are the 9 categories mentioned in the training guide?
| Category | Tool count | Theme |
|---|---|---|
| A — Codebase & Environment Scanning | 10 | Local, mostly [FREE]; scan code/infra, generate manifests |
| B — App & Org Provisioning | 7 | Create orgs/apps, submit manifests, provision the security stack |
| C — mTLS Certificate Management (MCP identities) | 2 | List/revoke the agent's own MCP client certs |
| D — Verification & Compliance | 4 | Posture scoring, SOC2/HIPAA/PCI-DSS/ISO27001 reports |
| E — Security Operations & Cert Management (apps) | 10 | Lockdown, per-app certs, policies, DNS rules, sessions |
| F — Receiver Lifecycle & Service Onboarding | 13 | Stand up a Receiver, onboard/pause/disable proxied services |
| G — Receiver Monitoring | 3 | Health, listing, fleet-wide statistics |
| H — Composite Remote Agent Installation | 1 | Provision + install a new agent identity on a remote host |
| I — Local Guidance & Learning | 4 | [FREE]; deployment guide, protocol walkthrough, identity |
Full per-tool detail (inputs, outputs, gotchas) lives in the training guide, organized by these same 9 categories.
How does the auth model work?
Q: What is the "bisected mTLS broker" pattern? Every authenticated call goes through two independent mTLS handshakes, not one:
- Agent → Authorizer. The agent presents its mTLS cert + password to the Authorizer
(
SHIELD_AUTHORIZER_URL, e.g.mauth.<your-org-domain>). The Authorizer validates the cert against the org's enrolled identities and returns a service list with a Receiver URL + short-lived session token. - Agent → Receiver. The agent presents the same cert again (a second, independently verified handshake) plus the session token to the Receiver — a public-IP edge proxy. The Receiver re-verifies the cert on its own, then forwards the request to the actual backend service over a private network.
The backend service is never directly reachable by the agent. This is unusual compared to
common mTLS patterns (Cloudflare Access, single-stage client-cert auth) — call
broker_get_protocol_walkthrough before writing any client code so you don't assume a
single-handshake model.
Q: Why two handshakes instead of one? Defense in depth: a compromised or spoofed Authorizer can only hand out a token — it cannot itself reach the backend. The Receiver independently re-verifies the cert, so a stolen token alone (without the matching cert) is useless, and a compromised Authorizer alone cannot forge Receiver access.
Q: What credentials does an agent need, and where do they come from? An mTLS client cert + key, an auth password, and an org id. These are either:
- issued by a human administrator through Overwatch or SysAdmin, and passed via
SHIELD_CLIENT_CERT/SHIELD_CLIENT_KEY/SHIELD_AUTH_PASSWORD/SHIELD_ORG_ID; or - redeemed automatically on first launch from a one-time
SHIELD_SETUP_TOKEN(abss_...token), after which the bundle is persisted to~/.blacksands/mcp-certs/; or - provisioned programmatically for a new agent identity via
bursar_install_agent_remotely(Category H), which issues a fresh bootstrap token for a target host to redeem.
There are no API keys and no bearer-token fallback — Bursar is cert-only. Fallback auth chains were deliberately stripped from every deployed service (see PRs #587–#595 in the Blacksands history); do not reintroduce one.
Q: What's the difference between "master" and "consumer" role?
master role sees all 55 tools (subject to which are gated). consumer role is a least-privilege
mode that sees only the 14 [FREE] tools plus the ability to go through the broker for whatever
services it's been granted — it cannot provision, revoke, or manage anything. Role is set at cert
issuance time (or via the role input on bursar_install_agent_remotely); an agent cannot
self-escalate.
Q: What happens if I call a gated tool without credentials (local-only mode)?
You get a friendly setup-token prompt, not a stack trace or a generic 401. The MCP server detects
it's running in local-only mode (no cert configured, or MCP_TRANSPORT=local-only) and short-
circuits gated tools before attempting any network call.
Q: Is there a session token I need to manage myself?
No — the session token returned by the Authorizer is ephemeral and consumed internally by the
tool implementation for the Receiver hop. If you're writing your own client code outside the
MCP tool layer (e.g. to call a Receiver-proxied service directly), see the code examples returned
by broker_get_protocol_walkthrough.
Getting started
Call these [FREE] tools first, in this order, before doing anything else:
broker_get_my_identity— confirms who you are (cert CN, role, org id, tier) and whether you're in local-only or broker mode. In local-only mode this returns a synthetic identity withrole: "consumer"rather than erroring, so it's always safe to call first.broker_get_protocol_walkthrough— the authoritative description of the bisected auth chain, with copy-paste Node/Python/curl examples. Call this before generating any client code that talks to a Blacksands-protected service directly (outside the MCP tool layer) — do not let an LLM improvise the auth flow from generic mTLS training data.bursar_guide_deployment(notargetargument) — the authoritative deployment architecture overview, plus a picklist of supported targets (digitalocean-droplet,local-docker-development). Call this before answering any deployment/hosting question; call it again with a specifictargetfor copy-paste steps.bursar_scan_codebase— if you're onboarding an actual application, this is usually the first substantive action: a full composite local scan (framework, endpoints, PII, external services, data stores) that also assembles a security manifest.bursar_get_protection_requirements— feed it the manifest (or a project path) to get a plain-English checklist of what infrastructure needs to exist (a deployed backend + Receiver) before Blacksands can protect the app, and whether you need a paid tier for any next step.
From there, the typical provisioning path (Category B) is:
bursar_create_org → bursar_create_app → bursar_generate_manifest → bursar_submit_manifest
→ bursar_provision_app (async — poll with bursar_poll_operation).
The typical Receiver setup path (Category F) is:
receiver_initialize → (installer runs the Docker bootstrap command on the target host) →
receiver_init_status (poll) → receiver_activate → receiver_onboard_service per service you
want proxied.
Tool categories at a glance
| Cat | Name | [FREE] tools | Gated tools |
|---|---|---|---|
| A | Codebase & Environment Scanning | 10 | bursar_publish_environment requires master + paid tier |
| B | App & Org Provisioning | 0 | all 7 |
| C | mTLS Certificate Management (MCP identities) | 0 | both |
| D | Verification & Compliance | 0 | all 4 |
| E | Security Operations & Cert Management | 0 | all 10 |
| F | Receiver Lifecycle & Service Onboarding | 0 | all 13 |
| G | Receiver Monitoring | 0 | all 3 |
| H | Composite Remote Agent Installation | 0 | the 1 tool |
| I | Local Guidance & Learning | 4 | 0 |
See the training guide for the per-tool breakdown within each category.
Gotchas and notable behaviors
This section is one FAQ entry per notable behavior surfaced during tool research. If a tool isn't mentioned here, its behavior is unsurprising — check the training guide for details.
Scanning (Category A)
Q: bursar_scan_codebase is slow on a large repo — is that expected?
Yes. It walks the full project tree and runs pattern matching for every sub-scan (endpoints, PII,
services, data stores) in one pass. 10–30 seconds on a large codebase is normal; there's no
incremental/cached mode yet.
Q: bursar_scan_environment's zt (zero-trust) plane came back empty — is that a bug?
No. The zt plane is only populated from live Shield broker state (receivers/sessions/
endpoints). If the broker is unreachable (local-only mode, or network issue), the plane is
legitimately empty and metadata.trust.authoritative is false — the tool is telling you the
data isn't authoritative, not silently failing.
Q: PII/endpoint/service detection found something that isn't actually there (false positive) — is that expected?
Yes, by design. bursar_flag_pii_candidates and bursar_scan_endpoints are heuristic scanners:
findings are candidates for human review, not confirmed facts. Confidence is reported per
finding (high/medium/low); comment-only or prose-only matches are filtered to cut noise, but
false positives still happen. Don't auto-remediate based solely on these scans.
Q: bursar_report_activity and bursar_show_topology didn't do anything I could see — is the tool broken?
Probably not. Both are purely presentational, targeting a local visualization launcher at
http://127.0.0.1:4178. If that launcher isn't running (and can't be auto-spawned, or you're in a
remote/sandboxed agent context), bursar_show_topology returns launched:false, opened:false with
a note to open the URL on your workstation, and bursar_report_activity falls back to a journal
file at ~/.blacksands/container-manager/activity.jsonl. Neither tool ever throws, and neither
can change topology, trust, certs, sessions, or any Blacksands state — they're cosmetic.
Q: Why did bursar_publish_environment reject my call with an upgrade-path error?
It requires master role and a paid tier (essentials/professional/enterprise) — community
and trial orgs get an upgrade-path error instead of a scan. Use bursar_scan_environment (fully
[FREE], local-only) if you just need the topology without cloud publishing/history retention.
Provisioning (Category B)
Q: bursar_provision_app returned right away with a status — do I need to poll?
The public contract is async: bursar_provision_app internally uses bursar_poll_operation to
wait for completion before returning, so a synchronous-looking "status": "completed" response is
normal for typical manifests. For larger/slower provisions, or if you're building your own client
around the underlying operation, be prepared to poll bursar_poll_operation yourself using the
returned operationId. Polling is blocking on the caller's side — implement backoff, don't hot-loop.
Q: bursar_submit_manifest failed with validation errors — what does that mean?
The Shield API schema-validates every manifest before accepting it. Validation errors block
submission entirely; validation warnings (e.g. "PII detected in endpoints; ensure encryption in
transit") don't block, but flag compliance follow-ups. Fix errors and resubmit; warnings are
informational.
Q: Org creation failed with a duplicate-name error.
Org names must be unique across Blacksands. Use bursar_list_orgs to check existing names before
calling bursar_create_org.
Q: My trial-plan org hit a hard limit (receivers/apps/time). Trial plans are capped: 1 Receiver, 3 apps max, 30-day limit. This is enforced server-side, not a client bug — upgrade the org's plan to lift the caps.
Certificates and sessions (Categories C, E)
Q: What's the difference between bursar_revoke_mcp_cert (Category C) and bursar_revoke_cert / bursar_rotate_cert (Category E)?
bursar_revoke_mcp_cert (Category C) revokes an MCP client identity — the cert an agent
uses to talk to Bursar at all. bursar_revoke_cert / bursar_rotate_cert (Category E) act on
app-level certificates — mTLS certs tied to a provisioned application's Receiver/backend
traffic. Don't confuse the two: revoking the wrong one either locks out your own agent or breaks
an unrelated app's connectivity.
Q: I revoked an MCP cert but the client using it still seems to work.
Revocation via bursar_revoke_mcp_cert is immediate on the server side, but existing sessions
are not forcibly terminated — they expire naturally on their own TTL. If you need an immediate
cutoff, use bursar_emergency_lockdown (Category E) on the affected app instead, which does
forcibly terminate active sessions.
Q: bursar_rotate_cert broke my running app/agent immediately — is that expected?
Yes. Rotation is synchronous: the old cert is revoked the instant the new one is issued, with
no grace period. Anything still using the old cert loses connectivity right away and must be
updated with the new cert/key. If you need a grace period, use bursar_revoke_cert on a schedule
of your choosing instead of rotating in place.
Q: Is revoking a cert or session reversible?
No — bursar_revoke_cert, bursar_revoke_session, and bursar_revoke_mcp_cert are all
immediate and irreversible. There's no "undo"; you'd need to provision/rotate a new
cert/session. bursar_revoke_mcp_cert is idempotent (calling it twice on an already-revoked cert
returns the same result, it doesn't error).
Q: What actually happens during bursar_emergency_lockdown, and how do I undo it?
Lockdown revokes all certs and forcibly terminates all sessions for the target app —
immediately, and it's idempotent (locking an already-locked app is a no-op). bursar_lift_lockdown
returns the app to active status, but it does not restore anything that was revoked — those
certs/sessions stay revoked permanently. You must re-provision (bursar_provision_app or
bursar_rotate_cert) to get a working cert bundle again after lifting a lockdown. Reserve this
tool for genuine incidents; the reason string is logged for the compliance audit trail.
Policies and DNS (Category E)
Q: My policy isn't behaving the way I expected — I have both an allow and a deny rule for the same target.
Policy rules are evaluated left-to-right / in array order — the first matching rule wins.
Order your rules array with the most specific matches first. The same first-match-wins semantics
apply to bursar_update_dns_rules.
Q: I called bursar_update_dns_rules but the app's inbound DNS didn't change.
bursar_update_dns_rules only manages application-level egress rules (outbound DNS lookups
the app is allowed to make). Receiver DNS (ingress) — the public hostname clients use to reach
the app — is auto-managed by Blacksands and cannot be edited through this tool; see
receiver_get_dns (read-only) instead.
Verification & compliance (Category D)
Q: bursar_verify_posture with scanType: "full" is taking minutes — is it hung?
Not necessarily. quick scans return in ~2–3 seconds; full (the default) can take 5–10 minutes
because it's an async operation that polls internally (bursar_poll_operation) and exercises
checks against the Receiver + backend. If a downstream service is unreachable, that check is
skipped rather than failing the whole scan.
Q: bursar_get_posture returned stale-looking data.
By design — bursar_get_posture returns the cached result of the most recent
bursar_verify_posture run; it never triggers a new scan itself. If no scan has ever run for that
app, expect a null/error response. Call bursar_verify_posture first if you need fresh data.
Q: My compliance report shows controls as "partial" — what does that mean?
bursar_compliance_report control statuses are pass, fail, partial, or not_applicable.
partial means the control is partly satisfied (e.g. data-in-transit is encrypted but at-rest
isn't) — check the evidence field per control for specifics. Reports are valid for ~90 days;
regenerate after remediation or near expiry.
Receiver lifecycle (Categories F, G)
Q: receiver_initialize succeeded but nothing is running yet.
That's expected — receiver_initialize only creates the Receiver record and emails a one-time
setup token (valid ~2 hours) to the installer. A human still has to run the Docker bootstrap
command on the actual target host (a Linux server with Docker — EC2, VPS, DigitalOcean droplet,
etc.). Poll receiver_init_status to watch progress once that's underway.
Q: My setup token/email expired before the installer ran the bootstrap command.
Setup tokens are short-lived (~2 hours for Receiver init, ~15 minutes for
bursar_install_agent_remotely's bootstrap tokens). Use receiver_resend_email to issue a fresh
token for a still-awaiting Receiver init; for an expired agent-install bootstrap token, re-run
bursar_install_agent_remotely.
Q: receiver_activate says it's a no-op — did something break?
No — activation is idempotent by design; calling it on an already-active Receiver just returns the
current state. Activation is only possible once initialization has reached status: "completed";
calling it earlier is the actual error case.
Q: What's the difference between pausing, disabling, and removing a service (Category F)? Three distinct strengths, all reversible except removal:
bursar_service_pause— stop accepting new connections; existing ones stay open. Good for maintenance windows. Reverse withbursar_service_resume(idempotent — resuming an already-active service is a no-op).bursar_service_disable— stronger: existing connections are severed immediately, not just blocked for new ones. Requires an explicit re-enable to recover.receiver_remove_service— deregisters the service entirely; its certificate is revoked asynchronously (10–30 seconds). There's no "undo" — you'd re-onboard withreceiver_onboard_service.
Q: receiver_onboard_service returned a public URL, but it's not reachable yet.
DNS propagation for the newly assigned subdomain can take 5–10 seconds after the call returns.
Also double-check protocol: "ssh" and "rdp" are forwarded directly (not HTTP-wrapped), so
path is meaningless for those and only applies to "http"/"https".
Q: bursar_service_status shows the upstream as unreachable, but the service still shows as active — is that a bug?
No — an unreachable upstream is logged, not treated as an error state. The Receiver keeps accepting
connections and retries health checks (every 10–30 seconds); it doesn't automatically flip the
service to paused/disabled. If you want it taken offline, do that explicitly with
bursar_service_pause or bursar_service_disable.
Q: Can I change a Receiver's DNS name?
No — receiver_get_dns is read-only from the agent's perspective. DNS is auto-provisioned at
Receiver registration; changing it requires re-registering the Receiver (a new receiver_initialize
call), not an edit.
Remote agent install (Category H)
Q: I tried bursar_install_agent_remotely with transport.type: "ssh" or "aws-ssm" and got a "not-implemented" error.
Expected in the current phase (Phase A.1) — only transport.type: "bootstrap-token" is fully
implemented. SSH and AWS SSM transports are defined in the schema but stub out with a
"not-implemented" throw; don't route users toward them yet.
Q: If something fails partway through bursar_install_agent_remotely, do I end up with an orphaned cert?
Not by default — rollbackOnFailure defaults to true, so if delivery, config write, or restart
fails after the credential was already issued, the tool revokes that cert automatically. Set
rollbackOnFailure: false only if you specifically want to keep a partially-provisioned identity
around for manual cleanup.
Q: waitForHandshake is true by default — what does it actually wait for?
In the current phase it's a no-op — there's no real handshake check implemented yet
(handshakeStatus in the response is always "pending"). Don't build logic that blocks on this
field flipping to a "confirmed" state; it doesn't yet.
Guidance tools (Category I)
Q: bursar_guide_deployment insists Blacksands is "NOT like Cloudflare Tunnel / Tailscale / ngrok" — why does it matter?
Because the Receiver model is inbound, not outbound: it requires an inbound public IP with TCP
443 open (mTLS termination), and the control plane (policy/DNS/cert-rotation pushes from Manager)
is Kafka, not HTTP polling by the Receiver. If you generate deployment instructions or firewall
rules from generic "reverse tunnel" assumptions, you'll get the security model wrong — that's why
this tool front-loads the "false models to reject" list.
Q: Why can't I run a Receiver on my laptop for local dev?
Laptops typically don't have a public IP, and the Receiver needs one for the inbound mTLS edge.
bursar_guide_deployment's local-docker-development target is dev-only — you run your app
locally and test through other means (e.g. the Blacksands CLI); production paths (like
digitalocean-droplet) are what actually stand up a reachable Receiver.
Troubleshooting
Auth failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Gated tool call returns a setup-token prompt instead of running | You're in local-only mode (no cert configured, or MCP_TRANSPORT=local-only) | Configure SHIELD_CLIENT_CERT/SHIELD_CLIENT_KEY/SHIELD_AUTH_PASSWORD/SHIELD_ORG_ID, or set SHIELD_SETUP_TOKEN for one-time bootstrap |
| "Certificate verification failed" when the tool talks to the Receiver | Blacksands CA cert missing/expired locally | Confirm ~/.blacksands/mcp-certs/blacksands-ca.crt exists and is current; override with SHIELD_CA_CERT if needed |
| "Unauthorized" / "Invalid token" from the Receiver | Session token expired, or the cert presented to the Receiver doesn't match the one the Authorizer issued it for | Re-authenticate with the Authorizer to get a fresh token; make sure the same cert is reused for both the Authorizer and Receiver hops |
broker_get_my_identity returns cn: null | Broker handshake failed | Not itself an error — it's included in the identity response rather than thrown. Check warning field in the response and the auth-failure causes above |
| Everything worked yesterday, fails today with no config change | MCP cert may have been revoked (bursar_revoke_mcp_cert) or naturally expired | Check bursar_list_mcp_certs for status; re-issue via Overwatch/SysAdmin or a fresh bootstrap token |
Timeouts / hangs
| Symptom | Likely cause | Fix |
|---|---|---|
bursar_verify_posture (full scan) seems to hang for minutes | Expected — full scans take 5–10 minutes and poll internally | Use scanType: "quick" (~2–3s) if you need a fast signal instead |
bursar_provision_app / any async op seems slow | Provisioning is async under the hood | If you're driving the underlying operation yourself, poll bursar_poll_operation with the returned operationId and implement backoff — don't hot-loop |
bursar_scan_codebase takes 10–30s on a big repo | Expected — full tree walk + multi-scan pattern matching | No incremental mode currently; scope projectPath to a subdirectory if you only need part of a monorepo scanned |
A new service isn't reachable right after receiver_onboard_service | DNS propagation lag | Wait 5–10 seconds and retry; check receiver_list_services for health status |
Permission errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Consumer-role agent can't see/call a tool you expected | consumer role is restricted to the 14 [FREE] tools + broker access to granted services | Use a master-role cert for provisioning/admin tools, or confirm this agent is intentionally least-privilege |
bursar_publish_environment rejected with an upgrade-path error | Trial/community org, or non-master cert | Upgrade the org's plan (essentials/professional/enterprise) and/or use a master-role cert; or just use the [FREE] bursar_scan_environment if you don't need cloud publishing |
bursar_create_app/bursar_create_org rejected | Trial-plan limits (1 receiver / 3 apps / 30 days) or duplicate org name | Check bursar_list_orgs/bursar_list_apps first; upgrade plan if limits are the blocker |
SSH/AWS-SSM transport on bursar_install_agent_remotely errors "not-implemented" | Those transports are stubs in the current phase | Use transport.type: "bootstrap-token" instead |
Where to look next
- Full protocol reference: call
broker_get_protocol_walkthrough(or see the training guide's Category I section) for the authoritative auth-chain description and code samples. - Deployment architecture questions: call
bursar_guide_deploymentbefore answering — don't improvise from generic tunnel/VPN mental models. - Per-tool inputs/outputs/gotchas: see the training guide.
- Server logs: stderr only (stdout is reserved for the MCP wire protocol). macOS Claude Desktop:
~/Library/Logs/Claude/mcp-server-Bursar.log. Claude Code: run with--mcp-debug.
