@blacksandscyber/mcp-server-bursar 0.5.0
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.
- package/README.md +230 -0
- package/build/config.d.ts +45 -0
- package/build/config.js +177 -0
- package/build/http-transport.d.ts +16 -0
- package/build/http-transport.js +191 -0
- package/build/index.d.ts +16 -0
- package/build/index.js +31 -0
- package/build/server.d.ts +41 -0
- package/build/server.js +902 -0
- package/build/shared/errors.d.ts +50 -0
- package/build/shared/errors.js +69 -0
- package/build/shared/linkBuilder.d.ts +93 -0
- package/build/shared/linkBuilder.js +148 -0
- package/build/shared/logger.d.ts +10 -0
- package/build/shared/logger.js +28 -0
- package/build/shield/bootRole.d.ts +60 -0
- package/build/shield/bootRole.js +145 -0
- package/build/shield/client.d.ts +265 -0
- package/build/shield/client.js +656 -0
- package/build/shield/deploy/index.d.ts +69 -0
- package/build/shield/deploy/index.js +569 -0
- package/build/shield/discovery/dataStoreDetector.d.ts +3 -0
- package/build/shield/discovery/dataStoreDetector.js +125 -0
- package/build/shield/discovery/dockerScanner.d.ts +34 -0
- package/build/shield/discovery/dockerScanner.js +543 -0
- package/build/shield/discovery/endpointScanner.d.ts +3 -0
- package/build/shield/discovery/endpointScanner.js +306 -0
- package/build/shield/discovery/environmentScanner.d.ts +86 -0
- package/build/shield/discovery/environmentScanner.js +545 -0
- package/build/shield/discovery/externalServiceDetector.d.ts +3 -0
- package/build/shield/discovery/externalServiceDetector.js +98 -0
- package/build/shield/discovery/frameworkDetector.d.ts +3 -0
- package/build/shield/discovery/frameworkDetector.js +114 -0
- package/build/shield/discovery/manifestGenerator.d.ts +12 -0
- package/build/shield/discovery/manifestGenerator.js +124 -0
- package/build/shield/discovery/piiDetector.d.ts +5 -0
- package/build/shield/discovery/piiDetector.js +203 -0
- package/build/shield/discovery/severity.d.ts +47 -0
- package/build/shield/discovery/severity.js +138 -0
- package/build/shield/discovery/topologyNormalizer.d.ts +109 -0
- package/build/shield/discovery/topologyNormalizer.js +416 -0
- package/build/shield/identity.d.ts +53 -0
- package/build/shield/identity.js +70 -0
- package/build/shield/install/configMerge.d.ts +91 -0
- package/build/shield/install/configMerge.js +324 -0
- package/build/shield/install/keystore.d.ts +25 -0
- package/build/shield/install/keystore.js +156 -0
- package/build/shield/install/orchestrator.d.ts +33 -0
- package/build/shield/install/orchestrator.js +404 -0
- package/build/shield/install/transports/awsSsm.d.ts +43 -0
- package/build/shield/install/transports/awsSsm.js +378 -0
- package/build/shield/install/transports/bootstrapToken.d.ts +39 -0
- package/build/shield/install/transports/bootstrapToken.js +117 -0
- package/build/shield/install/transports/ssh.d.ts +50 -0
- package/build/shield/install/transports/ssh.js +569 -0
- package/build/shield/install/types.d.ts +139 -0
- package/build/shield/install/types.js +10 -0
- package/build/shield/protocol-walkthrough.d.ts +65 -0
- package/build/shield/protocol-walkthrough.js +392 -0
- package/build/shield/provision/appProvisioner.d.ts +15 -0
- package/build/shield/provision/appProvisioner.js +25 -0
- package/build/shield/types.d.ts +261 -0
- package/build/shield/types.js +4 -0
- package/build/shield/verify/postureReporter.d.ts +4 -0
- package/build/shield/verify/postureReporter.js +31 -0
- package/dxt/blacksands-ca.crt +67 -0
- package/dxt/scripts/setup.js +520 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# @blacksandscyber/mcp-server-bursar
|
|
2
|
+
|
|
3
|
+
Zero-trust security operations for AI agents. **45 tools** for codebase analysis, application provisioning, mTLS certificate management, compliance reporting, and Receiver proxy lifecycle — exposed through the Model Context Protocol so Claude (or any MCP client) can drive Blacksands Bursar directly.
|
|
4
|
+
|
|
5
|
+
Two install paths, depending on which Claude surface you use:
|
|
6
|
+
|
|
7
|
+
| Surface | Install | Effort |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **Claude Code** | `claude mcp add bursar -- npx -y @blacksandscyber/mcp-server-bursar` | One command |
|
|
10
|
+
| **Claude Desktop** | Drag-and-drop the signed `.dxt` bundle | One drag |
|
|
11
|
+
|
|
12
|
+
**Nine tools work without any Blacksands account.** Codebase scanning, framework detection, PII discovery, deployment guidance — installed and ready immediately. Sign up at [bursar.blacksandscyber.online](https://bursar.blacksandscyber.online) to unlock the remaining 36 tools (provisioning, compliance, Receiver management).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Claude Code — one-line install
|
|
17
|
+
|
|
18
|
+
The fastest path. Works in any directory — Claude Code launches the MCP server when needed.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Local-only mode — 9 [FREE] tools work immediately, no account needed
|
|
22
|
+
claude mcp add bursar -- npx -y @blacksandscyber/mcp-server-bursar
|
|
23
|
+
|
|
24
|
+
# Verify
|
|
25
|
+
claude mcp list
|
|
26
|
+
# bursar: npx -y @blacksandscyber/mcp-server-bursar - ✓ Connected
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then, in any Claude Code session:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
> Scan my project at ~/some-app for security risks
|
|
33
|
+
[Claude calls bursar_scan_codebase, returns the security manifest]
|
|
34
|
+
|
|
35
|
+
> What's the Blacksands deployment architecture?
|
|
36
|
+
[Claude calls bursar_guide_deployment, returns the authoritative overview]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Bring your own credentials (full Bursar API access)
|
|
40
|
+
|
|
41
|
+
To unlock all 45 tools, pass your existing cert bundle (issued through Overwatch / SysAdmin / a setup token):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
claude mcp add bursar \
|
|
45
|
+
--env BURSAR_AUTHORIZER_URL=https://mauth-beta.blacksandscyber.online \
|
|
46
|
+
--env BURSAR_CLIENT_CERT=$HOME/.blacksands/mcp-certs/test-mcp9.crt \
|
|
47
|
+
--env BURSAR_CLIENT_KEY=$HOME/.blacksands/mcp-certs/test-mcp9.key \
|
|
48
|
+
--env BURSAR_AUTH_PASSWORD=$(cat $HOME/.blacksands/mcp-certs/.auth-password) \
|
|
49
|
+
--env BURSAR_ORG_ID=blacksands \
|
|
50
|
+
-- npx -y @blacksandscyber/mcp-server-bursar
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or, for a one-time bootstrap with a setup token from your Blacksands admin:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
claude mcp add bursar \
|
|
57
|
+
--env BURSAR_SETUP_TOKEN=bss_xxxxxxxxxxxx \
|
|
58
|
+
-- npx -y @blacksandscyber/mcp-server-bursar
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The MCP server redeems the token on first launch, persists the cert bundle to `~/.blacksands/mcp-certs/`, and uses it for every subsequent run.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Claude Desktop — drag-and-drop the .dxt
|
|
66
|
+
|
|
67
|
+
1. Build (or download) `blacksands-bursar-x.y.z.dxt`:
|
|
68
|
+
```bash
|
|
69
|
+
cd mcp-server-blacksands-bursar
|
|
70
|
+
MANIFEST=manifest-v2.json npm run build:dxt
|
|
71
|
+
# outputs build/blacksands-bursar-x.y.z.dxt
|
|
72
|
+
```
|
|
73
|
+
2. Drag the `.dxt` onto the Claude Desktop app icon.
|
|
74
|
+
3. Confirm install. Send a chat message — the MCP server starts on first tool call.
|
|
75
|
+
|
|
76
|
+
For the non-coder onboarding flow (admin issues a setup token, user clicks the install email), see [bursar.blacksandscyber.online/docs/install](https://bursar.blacksandscyber.online/docs/install).
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## What you get
|
|
81
|
+
|
|
82
|
+
### 9 [FREE] tools — no account needed
|
|
83
|
+
|
|
84
|
+
Run on any local project. No network calls except for `bursar_guide_deployment`'s overview text (which is bundled).
|
|
85
|
+
|
|
86
|
+
| Tool | What it does |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `bursar_scan_codebase` | Composite scan — framework, endpoints, PII, services, datastores, manifest |
|
|
89
|
+
| `bursar_detect_framework` | Express, Flask, Rails, Next.js, Django, etc. |
|
|
90
|
+
| `bursar_scan_endpoints` | Extract HTTP/API routes with method + auth detection |
|
|
91
|
+
| `bursar_detect_pii` | SSN, credit card, health data, DOB; produces compliance hints |
|
|
92
|
+
| `bursar_detect_external_services` | Stripe, OpenAI, AWS, Firebase, Twilio, etc. |
|
|
93
|
+
| `bursar_detect_data_stores` | PostgreSQL, MongoDB, Redis, ORMs |
|
|
94
|
+
| `bursar_generate_manifest` | Assemble detection results into a Security Manifest JSON |
|
|
95
|
+
| `bursar_get_protection_requirements` | Plain-English checklist of what's needed to protect this app |
|
|
96
|
+
| `bursar_guide_deployment` | Authoritative deployment walkthrough — DigitalOcean Droplet, local-docker dev, etc. |
|
|
97
|
+
|
|
98
|
+
### 36 tools requiring a Blacksands account
|
|
99
|
+
|
|
100
|
+
Provisioning (`bursar_create_org`, `bursar_create_app`, `bursar_provision_app`, `bursar_install_agent_remotely`), compliance (`bursar_compliance_report`, `bursar_compliance_controls`), certs (`bursar_list_certs`, `bursar_revoke_cert`, `bursar_rotate_cert`), Receiver lifecycle (`receiver_initialize`, `receiver_activate`, `receiver_onboard_service`, `receiver_health`), sessions (`bursar_list_sessions`, `bursar_revoke_session`), and emergency controls (`bursar_emergency_lockdown`, `bursar_lift_lockdown`).
|
|
101
|
+
|
|
102
|
+
Calling any of these in local-only mode returns a friendly setup-token prompt instead of a stack trace.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
All env vars are optional except where noted. Sensible defaults are baked in so the MCP server starts in local-only mode with zero configuration.
|
|
109
|
+
|
|
110
|
+
| Var | Default | Required for | Description |
|
|
111
|
+
|---|---|---|---|
|
|
112
|
+
| `MCP_TRANSPORT` | `stdio` | — | Set to `local-only` to skip all auth and run only the 9 [FREE] tools |
|
|
113
|
+
| `BURSAR_AUTHORIZER_URL` | `https://mauth-beta.blacksandscyber.online` | broker mode | Where the MCP client authenticates to Bursar |
|
|
114
|
+
| `BURSAR_SETUP_URL` | `https://onboard.beta.blacksandscyber.online` | token bootstrap | Where setup tokens are redeemed |
|
|
115
|
+
| `BURSAR_SETUP_TOKEN` | (none) | first-time bootstrap | One-time `bss_…` token from a Blacksands admin |
|
|
116
|
+
| `BURSAR_CLIENT_CERT` | (none) | broker mode | Path to mTLS client cert PEM file |
|
|
117
|
+
| `BURSAR_CLIENT_KEY` | (none) | broker mode | Path to mTLS client key PEM file |
|
|
118
|
+
| `BURSAR_AUTH_PASSWORD` | (none) | broker mode | Cert bundle's auth password |
|
|
119
|
+
| `BURSAR_ORG_ID` | (none) | broker mode | Your organization id |
|
|
120
|
+
| `BURSAR_CA_CERT` | bundled | optional | Path to Blacksands CA cert (override the bundled one) |
|
|
121
|
+
| `LOG_LEVEL` | `info` | — | `debug`, `info`, `warn`, `error` |
|
|
122
|
+
|
|
123
|
+
Persisted state lives in `~/.blacksands/mcp-certs/`:
|
|
124
|
+
- `<client-name>.crt`, `<client-name>.key` — your mTLS bundle
|
|
125
|
+
- `.auth-password`, `.client-name`, `.org-id`, `.last-token-prefix` — bookkeeping
|
|
126
|
+
- `blacksands-ca.crt` — the Blacksands CA chain
|
|
127
|
+
|
|
128
|
+
To rotate credentials: `rm -rf ~/.blacksands/mcp-certs/`, then start the MCP server with a fresh `BURSAR_SETUP_TOKEN`.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Accessing protected services
|
|
133
|
+
|
|
134
|
+
Once your agent has a valid mTLS cert (issued by Blacksands), reaching a protected service is **NOT** a single mTLS call. The auth chain is **bisected**: agent does mTLS to the **Authorizer** (which returns a list of services it's allowed to reach), then a SECOND independently-verified mTLS handshake to the **Receiver** (a public-IP edge proxy that forwards to the actual backend service).
|
|
135
|
+
|
|
136
|
+
**For LLM-generated client code:** ask Claude to call **`broker_get_protocol_walkthrough`** before generating anything. That tool returns the authoritative protocol description plus copy-paste examples in Node.js, Python, and curl. Don't let Claude improvise the auth chain from training-data assumptions about mTLS — Blacksands is unusual: cert at TWO independent stages, backend service URL never reachable directly.
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
INTERNET
|
|
140
|
+
│
|
|
141
|
+
│ 443 (mTLS, mandatory)
|
|
142
|
+
▼
|
|
143
|
+
┌────────────────────┐
|
|
144
|
+
│ Authorizer │ ← step 1: agent mTLS handshake
|
|
145
|
+
│ (mauth-…) │ ← step 2: returns service-list with Receiver URLs
|
|
146
|
+
└────────────────────┘
|
|
147
|
+
│
|
|
148
|
+
│ agent picks service, builds URL with sessionToken
|
|
149
|
+
▼
|
|
150
|
+
┌────────────────────┐
|
|
151
|
+
│ Receiver │ ← step 4: SECOND mTLS handshake (same cert,
|
|
152
|
+
│ (public IP edge) │ re-verified independently)
|
|
153
|
+
└─────────┬──────────┘
|
|
154
|
+
│ private network
|
|
155
|
+
▼
|
|
156
|
+
backend service
|
|
157
|
+
(never reachable directly)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The MCP server's `broker_get_protocol_walkthrough` tool is the canonical reference — fetched lazily when an LLM needs it. Same content is also surfaced as the `blacksands://broker-protocol` MCP resource for clients (like Claude Desktop) that prime context at session start.
|
|
161
|
+
|
|
162
|
+
A minimal Node.js sketch:
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
const agent = new https.Agent({ cert, key, ca });
|
|
166
|
+
|
|
167
|
+
// STEP 1+2: mTLS to Authorizer, get service URL + token
|
|
168
|
+
const auth = await axios.post(
|
|
169
|
+
'https://mauth-beta.blacksandscyber.online/api/agent/auth',
|
|
170
|
+
{ password, serviceId: 'bursar-api' },
|
|
171
|
+
{ httpsAgent: agent }
|
|
172
|
+
);
|
|
173
|
+
const receiverUrl = auth.data.service.serviceUrl; // points at the Receiver, not the service
|
|
174
|
+
const [base, qs] = receiverUrl.split('?');
|
|
175
|
+
const token = new URLSearchParams(qs).get('token');
|
|
176
|
+
|
|
177
|
+
// STEP 3+4+5+6: SAME agent reused for the second mTLS handshake
|
|
178
|
+
const api = axios.create({
|
|
179
|
+
baseURL: `${base}/v1`,
|
|
180
|
+
httpsAgent: agent, // same cert presented again, Receiver re-verifies independently
|
|
181
|
+
params: { token },
|
|
182
|
+
});
|
|
183
|
+
const orgs = await api.get('/orgs');
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Full Node.js, Python, and curl examples — plus the anti-patterns to avoid — are in the tool's output.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Verifying the install
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# Should show "bursar: ... ✓ Connected"
|
|
194
|
+
claude mcp list
|
|
195
|
+
|
|
196
|
+
# Quickest check — call a [FREE] tool that doesn't need Bursar API
|
|
197
|
+
claude --print "What is the Blacksands deployment architecture? Use bursar_guide_deployment."
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The MCP server logs to stderr (stdout is reserved for the MCP wire protocol). On macOS look in `~/Library/Logs/Claude/mcp-server-Bursar.log` (Claude Desktop) or run with `claude --mcp-debug` (Claude Code).
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
git clone https://github.com/b0bmarl3y/Blacksands_2_0.git
|
|
208
|
+
cd Blacksands_2_0/mcp-server-blacksands-bursar
|
|
209
|
+
npm install
|
|
210
|
+
npm run lint && npm test # 137 tests, ~4s
|
|
211
|
+
npm run build # tsc → build/
|
|
212
|
+
node dxt/scripts/setup.js # boot the server directly
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Test harness layout:
|
|
216
|
+
|
|
217
|
+
| Tier | Location | What it covers | Cadence |
|
|
218
|
+
|---|---|---|---|
|
|
219
|
+
| 1 | `tests/manifest.test.ts`, `tests/dxt-package.test.ts`, `tests/bursar/deploy/*`, `tests/config.test.ts`, `tests/server-tags.test.ts` | DXT manifest spec, content invariants on tool output, mode dispatch, [FREE] tag discipline | Every commit |
|
|
220
|
+
| 2 | `tests/harness/*` | MCP wire-protocol harness — spawns the compiled server and exercises tools/list + tools/call against a fixture project | Every PR |
|
|
221
|
+
| 3 | `tests/integration/live-broker.test.ts` (gated) | Live broker chain canary against the real Bursar API | Release / on-demand (`RUN_LIVE_INTEGRATION=1`) |
|
|
222
|
+
| 4 | (manual checklist) | Drag-install validation + LLM behavior + destructive-tool exercise | Release prep |
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT — see [LICENSE](./LICENSE).
|
|
229
|
+
|
|
230
|
+
For commercial support, custom integrations, or enterprise deployments (cryptographic remote attestation, HSM-backed cert storage, on-prem control plane), contact [support@blacksands.io](mailto:support@blacksands.io).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type DeploymentMode = "stdio" | "http-service" | "local-only";
|
|
2
|
+
export interface MtlsConfig {
|
|
3
|
+
/** PEM-encoded client certificate (required in stdio / broker mode) */
|
|
4
|
+
clientCert: string;
|
|
5
|
+
/** PEM-encoded client private key (required in stdio / broker mode) */
|
|
6
|
+
clientKey: string;
|
|
7
|
+
/** Optional PEM-encoded CA bundle for server certificate pinning */
|
|
8
|
+
caCert: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface BrokerConfig {
|
|
11
|
+
/** URL of the Blacksands Authorizer (e.g. https://auth.blacksands.io) */
|
|
12
|
+
authorizerUrl: string;
|
|
13
|
+
/** Service ID to request from the Authorizer (default: "shield-api") */
|
|
14
|
+
serviceId: string;
|
|
15
|
+
/** Unique password issued alongside the mTLS cert for Authorizer session auth */
|
|
16
|
+
authPassword: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ServiceMtlsConfig {
|
|
19
|
+
/** Shield API base URL (service discovery URL inside the VPC) */
|
|
20
|
+
apiUrl: string;
|
|
21
|
+
/** PEM-encoded service client cert */
|
|
22
|
+
clientCert: string;
|
|
23
|
+
/** PEM-encoded service client key */
|
|
24
|
+
clientKey: string;
|
|
25
|
+
/** Optional CA bundle */
|
|
26
|
+
caCert: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface Config {
|
|
29
|
+
mode: DeploymentMode;
|
|
30
|
+
shield: {
|
|
31
|
+
orgId: string;
|
|
32
|
+
/** Present in stdio mode; null when running as an http-service without broker */
|
|
33
|
+
broker: BrokerConfig | null;
|
|
34
|
+
/** Present in stdio mode; null when running as an http-service */
|
|
35
|
+
mtls: MtlsConfig | null;
|
|
36
|
+
/** Present in http-service mode when a service cert is configured */
|
|
37
|
+
service: ServiceMtlsConfig | null;
|
|
38
|
+
};
|
|
39
|
+
aws: {
|
|
40
|
+
region: string;
|
|
41
|
+
profile?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export declare function loadConfig(): Config;
|
|
45
|
+
//# sourceMappingURL=config.d.ts.map
|
package/build/config.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadConfig = loadConfig;
|
|
37
|
+
/**
|
|
38
|
+
* Configuration loaded from environment variables.
|
|
39
|
+
*
|
|
40
|
+
* The Shield MCP Server runs in one of two deployment modes:
|
|
41
|
+
*
|
|
42
|
+
* - stdio (default): the server runs on the agent's machine and connects
|
|
43
|
+
* to Shield through the full Broker handshake. The agent's mTLS cert is
|
|
44
|
+
* issued by a human administrator through Overwatch or SysAdmin.
|
|
45
|
+
*
|
|
46
|
+
* - http-service: the server runs as an HTTP service behind a Blacksands
|
|
47
|
+
* Receiver. The Receiver authenticates the incoming agent and forwards
|
|
48
|
+
* identity headers. The MCP server itself authenticates to Shield API
|
|
49
|
+
* using a service-level mTLS certificate (not broker).
|
|
50
|
+
*
|
|
51
|
+
* - local-only: the server starts without any Shield credentials. Only
|
|
52
|
+
* local codebase-scan tools (bursar_scan_codebase, bursar_flag_pii_candidates,
|
|
53
|
+
* etc.) are available. Shield-dependent tools fail gracefully at call
|
|
54
|
+
* time with a user-friendly message pointing to the signup flow.
|
|
55
|
+
* setup.js sets MCP_TRANSPORT=local-only when neither a cert bundle
|
|
56
|
+
* nor a setup token is present, enabling the "free analyze" experience
|
|
57
|
+
* without any account required.
|
|
58
|
+
*
|
|
59
|
+
* Set MCP_TRANSPORT=http to select http-service mode. The stdio entrypoint
|
|
60
|
+
* (src/index.ts) never sets that var, so stdio deployments always require
|
|
61
|
+
* the full broker configuration.
|
|
62
|
+
*/
|
|
63
|
+
const fs = __importStar(require("fs"));
|
|
64
|
+
const os = __importStar(require("os"));
|
|
65
|
+
/** Resolve ~ in file paths to the actual home directory. */
|
|
66
|
+
function resolvePath(p) {
|
|
67
|
+
if (!p)
|
|
68
|
+
return null;
|
|
69
|
+
return p.startsWith("~") ? p.replace("~", os.homedir()) : p;
|
|
70
|
+
}
|
|
71
|
+
function readPem(inlineEnv, pathEnv) {
|
|
72
|
+
if (inlineEnv && inlineEnv.includes("-----BEGIN"))
|
|
73
|
+
return inlineEnv;
|
|
74
|
+
const path = resolvePath(pathEnv);
|
|
75
|
+
if (!path)
|
|
76
|
+
return null;
|
|
77
|
+
try {
|
|
78
|
+
return fs.readFileSync(path, "utf8");
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function missingStdioConfigError(missing) {
|
|
85
|
+
return new Error([
|
|
86
|
+
"Shield MCP Server (stdio mode) cannot start: required broker configuration is missing.",
|
|
87
|
+
"",
|
|
88
|
+
"Missing: " + missing.join(", "),
|
|
89
|
+
"",
|
|
90
|
+
"The stdio MCP server operates in Broker mode. An mTLS client certificate",
|
|
91
|
+
"and auth password must be issued by a human administrator through Overwatch",
|
|
92
|
+
"or SysAdmin, then supplied to this Agent as config.",
|
|
93
|
+
"",
|
|
94
|
+
"Required environment variables:",
|
|
95
|
+
" SHIELD_AUTHORIZER_URL, SHIELD_AUTH_PASSWORD, SHIELD_CLIENT_CERT,",
|
|
96
|
+
" SHIELD_CLIENT_KEY, SHIELD_ORG_ID",
|
|
97
|
+
"",
|
|
98
|
+
"If you intended to run the server in http-service mode (behind a Receiver),",
|
|
99
|
+
"set MCP_TRANSPORT=http.",
|
|
100
|
+
"",
|
|
101
|
+
"If you only need local codebase scanning tools (no Shield account),",
|
|
102
|
+
"set MCP_TRANSPORT=local-only.",
|
|
103
|
+
].join("\n"));
|
|
104
|
+
}
|
|
105
|
+
function loadConfig() {
|
|
106
|
+
const mode = process.env.MCP_TRANSPORT === "http" ? "http-service" :
|
|
107
|
+
process.env.MCP_TRANSPORT === "local-only" ? "local-only" :
|
|
108
|
+
"stdio";
|
|
109
|
+
const orgId = process.env.SHIELD_ORG_ID || "";
|
|
110
|
+
// ── local-only mode ─────────────────────────────────────────────────
|
|
111
|
+
// No credentials required. Local codebase-scan tools work; Shield API
|
|
112
|
+
// tools fail gracefully at call time with a signup prompt.
|
|
113
|
+
if (mode === "local-only") {
|
|
114
|
+
return {
|
|
115
|
+
mode,
|
|
116
|
+
shield: { orgId, broker: null, mtls: null, service: null },
|
|
117
|
+
aws: { region: process.env.AWS_REGION || "us-east-1", profile: process.env.AWS_PROFILE },
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (mode === "stdio") {
|
|
121
|
+
const clientCertPem = readPem(process.env.SHIELD_CLIENT_CERT_PEM, process.env.SHIELD_CLIENT_CERT);
|
|
122
|
+
const clientKeyPem = readPem(process.env.SHIELD_CLIENT_KEY_PEM, process.env.SHIELD_CLIENT_KEY);
|
|
123
|
+
const caCertPem = readPem(process.env.SHIELD_CA_CERT_PEM, process.env.SHIELD_CA_CERT);
|
|
124
|
+
const authorizerUrl = process.env.SHIELD_AUTHORIZER_URL || "";
|
|
125
|
+
const serviceId = process.env.SHIELD_SERVICE_ID || "shield-api";
|
|
126
|
+
const authPassword = process.env.SHIELD_AUTH_PASSWORD || "";
|
|
127
|
+
const missing = [];
|
|
128
|
+
if (!authorizerUrl)
|
|
129
|
+
missing.push("SHIELD_AUTHORIZER_URL");
|
|
130
|
+
if (!authPassword)
|
|
131
|
+
missing.push("SHIELD_AUTH_PASSWORD");
|
|
132
|
+
if (!clientCertPem)
|
|
133
|
+
missing.push("SHIELD_CLIENT_CERT");
|
|
134
|
+
if (!clientKeyPem)
|
|
135
|
+
missing.push("SHIELD_CLIENT_KEY");
|
|
136
|
+
if (!orgId)
|
|
137
|
+
missing.push("SHIELD_ORG_ID");
|
|
138
|
+
if (missing.length > 0)
|
|
139
|
+
throw missingStdioConfigError(missing);
|
|
140
|
+
return {
|
|
141
|
+
mode,
|
|
142
|
+
shield: {
|
|
143
|
+
orgId,
|
|
144
|
+
mtls: { clientCert: clientCertPem, clientKey: clientKeyPem, caCert: caCertPem },
|
|
145
|
+
broker: { authorizerUrl, serviceId, authPassword },
|
|
146
|
+
service: null,
|
|
147
|
+
},
|
|
148
|
+
aws: { region: process.env.AWS_REGION || "us-east-1", profile: process.env.AWS_PROFILE },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// ── http-service mode ───────────────────────────────────────────────
|
|
152
|
+
//
|
|
153
|
+
// The MCP server is reached through a Blacksands Receiver, which has
|
|
154
|
+
// already authenticated the calling agent and forwards identity headers.
|
|
155
|
+
// The MCP server itself may optionally talk to Shield API using a
|
|
156
|
+
// service-level mTLS certificate. If that cert is absent, Shield-dependent
|
|
157
|
+
// tools will fail at call time, but local tools (codebase discovery) and
|
|
158
|
+
// the MCP protocol surface stay fully functional.
|
|
159
|
+
const apiUrl = process.env.SHIELD_API_URL || "";
|
|
160
|
+
const serviceCert = readPem(process.env.SHIELD_SERVICE_CERT_PEM, process.env.SHIELD_SERVICE_CERT);
|
|
161
|
+
const serviceKey = readPem(process.env.SHIELD_SERVICE_KEY_PEM, process.env.SHIELD_SERVICE_KEY);
|
|
162
|
+
const serviceCa = readPem(process.env.SHIELD_SERVICE_CA_PEM, process.env.SHIELD_SERVICE_CA);
|
|
163
|
+
const service = (apiUrl && serviceCert && serviceKey)
|
|
164
|
+
? { apiUrl, clientCert: serviceCert, clientKey: serviceKey, caCert: serviceCa }
|
|
165
|
+
: null;
|
|
166
|
+
return {
|
|
167
|
+
mode,
|
|
168
|
+
shield: {
|
|
169
|
+
orgId,
|
|
170
|
+
broker: null,
|
|
171
|
+
mtls: null,
|
|
172
|
+
service,
|
|
173
|
+
},
|
|
174
|
+
aws: { region: process.env.AWS_REGION || "us-east-1", profile: process.env.AWS_PROFILE },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP + SSE Transport for Blacksands Bursar MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Runs the MCP server as a network service (instead of stdio) so it can be
|
|
5
|
+
* deployed behind a Blacksands Receiver and accessed by authenticated AI agents.
|
|
6
|
+
*
|
|
7
|
+
* Supports the MCP Streamable HTTP transport:
|
|
8
|
+
* POST /mcp — JSON-RPC request/response (and server→client notifications via SSE)
|
|
9
|
+
* GET /mcp — SSE stream for server-initiated messages
|
|
10
|
+
* DELETE /mcp — Session termination
|
|
11
|
+
*
|
|
12
|
+
* Identity headers injected by the Receiver are used for authorization:
|
|
13
|
+
* X-User-DN, X-User-CN, X-Authenticated-User, X-Blacksands-Session
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=http-transport.d.ts.map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP + SSE Transport for Blacksands Bursar MCP Server.
|
|
4
|
+
*
|
|
5
|
+
* Runs the MCP server as a network service (instead of stdio) so it can be
|
|
6
|
+
* deployed behind a Blacksands Receiver and accessed by authenticated AI agents.
|
|
7
|
+
*
|
|
8
|
+
* Supports the MCP Streamable HTTP transport:
|
|
9
|
+
* POST /mcp — JSON-RPC request/response (and server→client notifications via SSE)
|
|
10
|
+
* GET /mcp — SSE stream for server-initiated messages
|
|
11
|
+
* DELETE /mcp — Session termination
|
|
12
|
+
*
|
|
13
|
+
* Identity headers injected by the Receiver are used for authorization:
|
|
14
|
+
* X-User-DN, X-User-CN, X-Authenticated-User, X-Blacksands-Session
|
|
15
|
+
*/
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
// Signal to the rest of the server that we're running as an HTTP service.
|
|
21
|
+
// Must be set BEFORE importing config/server, which read it at module load.
|
|
22
|
+
process.env.MCP_TRANSPORT = "http";
|
|
23
|
+
const http_1 = __importDefault(require("http"));
|
|
24
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
25
|
+
const server_1 = require("./server");
|
|
26
|
+
const logger_1 = require("./shared/logger");
|
|
27
|
+
const PORT = parseInt(process.env.MCP_HTTP_PORT || "3100", 10);
|
|
28
|
+
const HOST = process.env.MCP_HTTP_HOST || "0.0.0.0";
|
|
29
|
+
/**
|
|
30
|
+
* Session store: maps Mcp-Session-Id → transport instance.
|
|
31
|
+
* Each AI agent connection gets its own transport + server pair.
|
|
32
|
+
*/
|
|
33
|
+
const sessions = new Map();
|
|
34
|
+
/** Extract Blacksands identity from Receiver-injected headers. */
|
|
35
|
+
function extractIdentity(req) {
|
|
36
|
+
return {
|
|
37
|
+
userDN: req.headers["x-user-dn"],
|
|
38
|
+
userCN: req.headers["x-user-cn"],
|
|
39
|
+
authenticatedUser: req.headers["x-authenticated-user"],
|
|
40
|
+
sessionId: req.headers["x-blacksands-session"],
|
|
41
|
+
certFingerprint: req.headers["x-client-cert-fingerprint"],
|
|
42
|
+
receiverBackend: req.headers["x-receiver-backend"],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Validate that the request came through an authenticated Receiver. */
|
|
46
|
+
function isAuthenticated(req) {
|
|
47
|
+
const identity = extractIdentity(req);
|
|
48
|
+
// In production (behind Receiver), identity headers are always present.
|
|
49
|
+
// In local dev mode, allow unauthenticated access.
|
|
50
|
+
if (process.env.MCP_REQUIRE_AUTH === "false")
|
|
51
|
+
return true;
|
|
52
|
+
return !!(identity.authenticatedUser && identity.sessionId);
|
|
53
|
+
}
|
|
54
|
+
async function handleMcpRequest(req, res) {
|
|
55
|
+
// CORS for browser-based MCP clients
|
|
56
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
57
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
58
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
|
|
59
|
+
res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
|
|
60
|
+
if (req.method === "OPTIONS") {
|
|
61
|
+
res.writeHead(204);
|
|
62
|
+
res.end();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Auth check
|
|
66
|
+
if (!isAuthenticated(req)) {
|
|
67
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
68
|
+
res.end(JSON.stringify({ error: "Unauthorized", message: "Missing Blacksands identity headers" }));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
72
|
+
if (req.method === "POST") {
|
|
73
|
+
// If no session exists for this ID, create a new one
|
|
74
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
75
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
76
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
77
|
+
onsessioninitialized: (newSessionId) => {
|
|
78
|
+
const entry = sessions.get("_pending");
|
|
79
|
+
if (entry) {
|
|
80
|
+
sessions.delete("_pending");
|
|
81
|
+
sessions.set(newSessionId, entry);
|
|
82
|
+
}
|
|
83
|
+
const identity = extractIdentity(req);
|
|
84
|
+
logger_1.logger.info("MCP session initialized", {
|
|
85
|
+
mcpSessionId: newSessionId,
|
|
86
|
+
user: identity.authenticatedUser,
|
|
87
|
+
blacksandsSession: identity.sessionId,
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
const server = (0, server_1.createServer)();
|
|
92
|
+
await server.connect(transport);
|
|
93
|
+
sessions.set("_pending", { transport, server });
|
|
94
|
+
await transport.handleRequest(req, res);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Existing session
|
|
98
|
+
const entry = sessions.get(sessionId);
|
|
99
|
+
if (entry) {
|
|
100
|
+
await entry.transport.handleRequest(req, res);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
104
|
+
res.end(JSON.stringify({ error: "Session not found" }));
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (req.method === "GET") {
|
|
109
|
+
// SSE stream for server-initiated messages
|
|
110
|
+
if (!sessionId || !sessions.has(sessionId)) {
|
|
111
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
112
|
+
res.end(JSON.stringify({ error: "Missing or invalid Mcp-Session-Id" }));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const entry = sessions.get(sessionId);
|
|
116
|
+
if (entry) {
|
|
117
|
+
await entry.transport.handleRequest(req, res);
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (req.method === "DELETE") {
|
|
122
|
+
// Session termination
|
|
123
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
124
|
+
const entry = sessions.get(sessionId);
|
|
125
|
+
if (entry) {
|
|
126
|
+
await entry.transport.handleRequest(req, res);
|
|
127
|
+
await entry.server.close();
|
|
128
|
+
sessions.delete(sessionId);
|
|
129
|
+
logger_1.logger.info("MCP session terminated", { mcpSessionId: sessionId });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
res.writeHead(204);
|
|
134
|
+
res.end();
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
139
|
+
res.end(JSON.stringify({ error: "Method not allowed" }));
|
|
140
|
+
}
|
|
141
|
+
function handleHealthCheck(_req, res) {
|
|
142
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
143
|
+
res.end(JSON.stringify({
|
|
144
|
+
status: "healthy",
|
|
145
|
+
service: "blacksands-shield-mcp",
|
|
146
|
+
version: "0.1.0",
|
|
147
|
+
activeSessions: sessions.size,
|
|
148
|
+
uptime: process.uptime(),
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
function startHttpServer() {
|
|
152
|
+
const server = http_1.default.createServer((req, res) => {
|
|
153
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
154
|
+
if (url.pathname === "/health") {
|
|
155
|
+
handleHealthCheck(req, res);
|
|
156
|
+
}
|
|
157
|
+
else if (url.pathname === "/mcp") {
|
|
158
|
+
handleMcpRequest(req, res).catch((err) => {
|
|
159
|
+
logger_1.logger.error("MCP request handler error", { error: err.message });
|
|
160
|
+
if (!res.headersSent) {
|
|
161
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
162
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
168
|
+
res.end(JSON.stringify({ error: "Not found", endpoints: ["/mcp", "/health"] }));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
server.listen(PORT, HOST, () => {
|
|
172
|
+
logger_1.logger.info(`MCP HTTP server listening on ${HOST}:${PORT}`);
|
|
173
|
+
logger_1.logger.info("Endpoints: POST/GET/DELETE /mcp, GET /health");
|
|
174
|
+
});
|
|
175
|
+
// Graceful shutdown
|
|
176
|
+
const shutdown = async () => {
|
|
177
|
+
logger_1.logger.info("Shutting down MCP HTTP server...");
|
|
178
|
+
for (const [id, entry] of sessions) {
|
|
179
|
+
await entry.server.close();
|
|
180
|
+
sessions.delete(id);
|
|
181
|
+
}
|
|
182
|
+
server.close(() => {
|
|
183
|
+
logger_1.logger.info("MCP HTTP server stopped");
|
|
184
|
+
process.exit(0);
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
process.on("SIGINT", shutdown);
|
|
188
|
+
process.on("SIGTERM", shutdown);
|
|
189
|
+
}
|
|
190
|
+
startHttpServer();
|
|
191
|
+
//# sourceMappingURL=http-transport.js.map
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @blacksandscyber/mcp-server-bursar
|
|
4
|
+
*
|
|
5
|
+
* Blacksands Bursar MCP Server.
|
|
6
|
+
* 39 tools exposing the human-admin surface via the Blacksands Broker.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node build/index.js # Start via stdio transport
|
|
10
|
+
* npx -y @blacksandscyber/mcp-server-bursar # Run via npx
|
|
11
|
+
*
|
|
12
|
+
* Credentials are issued by a human administrator through Overwatch or
|
|
13
|
+
* SysAdmin and provided here as configuration. See README.md for details.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|