@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.
Files changed (68) hide show
  1. package/README.md +230 -0
  2. package/build/config.d.ts +45 -0
  3. package/build/config.js +177 -0
  4. package/build/http-transport.d.ts +16 -0
  5. package/build/http-transport.js +191 -0
  6. package/build/index.d.ts +16 -0
  7. package/build/index.js +31 -0
  8. package/build/server.d.ts +41 -0
  9. package/build/server.js +902 -0
  10. package/build/shared/errors.d.ts +50 -0
  11. package/build/shared/errors.js +69 -0
  12. package/build/shared/linkBuilder.d.ts +93 -0
  13. package/build/shared/linkBuilder.js +148 -0
  14. package/build/shared/logger.d.ts +10 -0
  15. package/build/shared/logger.js +28 -0
  16. package/build/shield/bootRole.d.ts +60 -0
  17. package/build/shield/bootRole.js +145 -0
  18. package/build/shield/client.d.ts +265 -0
  19. package/build/shield/client.js +656 -0
  20. package/build/shield/deploy/index.d.ts +69 -0
  21. package/build/shield/deploy/index.js +569 -0
  22. package/build/shield/discovery/dataStoreDetector.d.ts +3 -0
  23. package/build/shield/discovery/dataStoreDetector.js +125 -0
  24. package/build/shield/discovery/dockerScanner.d.ts +34 -0
  25. package/build/shield/discovery/dockerScanner.js +543 -0
  26. package/build/shield/discovery/endpointScanner.d.ts +3 -0
  27. package/build/shield/discovery/endpointScanner.js +306 -0
  28. package/build/shield/discovery/environmentScanner.d.ts +86 -0
  29. package/build/shield/discovery/environmentScanner.js +545 -0
  30. package/build/shield/discovery/externalServiceDetector.d.ts +3 -0
  31. package/build/shield/discovery/externalServiceDetector.js +98 -0
  32. package/build/shield/discovery/frameworkDetector.d.ts +3 -0
  33. package/build/shield/discovery/frameworkDetector.js +114 -0
  34. package/build/shield/discovery/manifestGenerator.d.ts +12 -0
  35. package/build/shield/discovery/manifestGenerator.js +124 -0
  36. package/build/shield/discovery/piiDetector.d.ts +5 -0
  37. package/build/shield/discovery/piiDetector.js +203 -0
  38. package/build/shield/discovery/severity.d.ts +47 -0
  39. package/build/shield/discovery/severity.js +138 -0
  40. package/build/shield/discovery/topologyNormalizer.d.ts +109 -0
  41. package/build/shield/discovery/topologyNormalizer.js +416 -0
  42. package/build/shield/identity.d.ts +53 -0
  43. package/build/shield/identity.js +70 -0
  44. package/build/shield/install/configMerge.d.ts +91 -0
  45. package/build/shield/install/configMerge.js +324 -0
  46. package/build/shield/install/keystore.d.ts +25 -0
  47. package/build/shield/install/keystore.js +156 -0
  48. package/build/shield/install/orchestrator.d.ts +33 -0
  49. package/build/shield/install/orchestrator.js +404 -0
  50. package/build/shield/install/transports/awsSsm.d.ts +43 -0
  51. package/build/shield/install/transports/awsSsm.js +378 -0
  52. package/build/shield/install/transports/bootstrapToken.d.ts +39 -0
  53. package/build/shield/install/transports/bootstrapToken.js +117 -0
  54. package/build/shield/install/transports/ssh.d.ts +50 -0
  55. package/build/shield/install/transports/ssh.js +569 -0
  56. package/build/shield/install/types.d.ts +139 -0
  57. package/build/shield/install/types.js +10 -0
  58. package/build/shield/protocol-walkthrough.d.ts +65 -0
  59. package/build/shield/protocol-walkthrough.js +392 -0
  60. package/build/shield/provision/appProvisioner.d.ts +15 -0
  61. package/build/shield/provision/appProvisioner.js +25 -0
  62. package/build/shield/types.d.ts +261 -0
  63. package/build/shield/types.js +4 -0
  64. package/build/shield/verify/postureReporter.d.ts +4 -0
  65. package/build/shield/verify/postureReporter.js +31 -0
  66. package/dxt/blacksands-ca.crt +67 -0
  67. package/dxt/scripts/setup.js +520 -0
  68. 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
@@ -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
@@ -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