@baichen_yu/mcp-guard 0.3.0 → 0.3.2

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 CHANGED
@@ -1,58 +1,115 @@
1
1
  # mcp-guard
2
2
 
3
- [![CI](https://img.shields.io/github/actions/workflow/status/TomAs-1226/MCP-shariff/ci.yml?label=CI)](./.github/workflows/ci.yml)
4
- [![npm version](https://img.shields.io/npm/v/%40baichen_yu%2Fmcp-guard)](https://www.npmjs.com/package/@baichen_yu/mcp-guard)
5
- [![License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
3
+ <div align="center">
6
4
 
7
- Security auditing and policy gating for MCP servers (local + CI), with deterministic tests and Markdown/SARIF outputs.
5
+ ![mcp-guard logo](docs/public/brand-mark.svg)
8
6
 
9
- > Formerly **mcp-doctor**.
7
+ **Security auditing and policy gating for MCP servers (local + CI).**
8
+ Deterministic checks. Actionable findings. Reproducible reports.
9
+
10
+ [![CI](https://img.shields.io/github/actions/workflow/status/TomAs-1226/mcp-guard/ci.yml?label=CI&style=for-the-badge)](./.github/workflows/ci.yml)
11
+ [![npm version](https://img.shields.io/npm/v/%40baichen_yu%2Fmcp-guard?style=for-the-badge)](https://www.npmjs.com/package/@baichen_yu/mcp-guard)
12
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](./LICENSE)
13
+ [![Node >=20](https://img.shields.io/badge/node-%3E%3D20-3C873A?style=for-the-badge)](./package.json)
14
+ [![Docs](https://img.shields.io/badge/docs-github%20pages-7C3AED?style=for-the-badge)](https://tomas-1226.github.io/mcp-guard/)
15
+
16
+ </div>
17
+
18
+ ---
10
19
 
11
20
  > [!IMPORTANT]
12
- > Remote mode supports **HTTP JSON-RPC only** (`--http`). SSE is not implemented.
21
+ > Remote mode supports **HTTP JSON-RPC** (`--http`) and **SSE** (`--sse`, optional `--sse-post`).
22
+
23
+ ## Why people use mcp-guard
24
+
25
+ - ✅ **Deterministic contract tests** (no fuzzy behavior, no mystery calls)
26
+ - ✅ **Policy gate in CI** (`--fail-on off|low|medium|high`)
27
+ - ✅ **Security-focused rule packs** with profile controls (`default`, `strict`, `paranoid`)
28
+ - ✅ **Review-friendly output formats** (`report.md`, `report.json`, `report.sarif`)
29
+ - ✅ **Config discovery + redaction** for common MCP client config layouts
30
+
31
+ ### Quick stats
13
32
 
14
- ## Package name
33
+ | Signal | Value |
34
+ |---|---:|
35
+ | Transports | STDIO + HTTP JSON-RPC + SSE |
36
+ | Report formats | Markdown, JSON, SARIF |
37
+ | Rule profiles | 3 |
38
+ | Contract scenarios | list, call, error shape, cancellation behavior, large payload, timeout |
39
+ | Registry modes | lint, verify, score |
15
40
 
16
- - npm package: `@baichen_yu/mcp-guard` (scoped to avoid name collisions)
17
- - CLI command after install: `mcp-guard`
18
- - first scoped publish: `npm publish --access public`
41
+ ---
19
42
 
20
43
  ## 30-second quickstart
21
44
 
22
- ### A) No install (npx)
45
+ ### 1) No install (`npx`)
46
+
47
+ ```bash
48
+ npx @baichen_yu/mcp-guard audit \
49
+ --stdio "node /absolute/path/to/your-mcp-server.cjs" \
50
+ --out reports \
51
+ --fail-on off
52
+ ```
53
+
54
+ > [!TIP]
55
+ > `--stdio` runs in your **current working directory**. Use an absolute path or `cd` into the project that contains your server first.
56
+
57
+ ### Local demo (from this repo root)
23
58
 
24
59
  ```bash
25
- npx @baichen_yu/mcp-guard audit --stdio "node fixtures/servers/hello-mcp-server/server.cjs" --out reports --fail-on off
60
+ npm run fixtures:gen
61
+ npx @baichen_yu/mcp-guard audit \
62
+ --stdio "node fixtures/servers/hello-mcp-server/server.cjs" \
63
+ --out reports \
64
+ --fail-on off
26
65
  ```
27
66
 
28
- ### B) Global install
67
+ ### 2) Global install
29
68
 
30
69
  ```bash
31
70
  npm i -g @baichen_yu/mcp-guard
32
71
  mcp-guard --help
33
72
  ```
34
73
 
35
- ### C) GitHub Action (paste into workflow)
74
+ ### 3) Package + command naming
36
75
 
37
- ```yaml
38
- jobs:
39
- mcp-audit:
40
- runs-on: ubuntu-latest
41
- permissions:
42
- security-events: write
43
- actions: read
44
- contents: read
45
- steps:
46
- - uses: actions/checkout@v4
47
- - uses: actions/setup-node@v4
48
- with:
49
- node-version: 20
50
- - uses: ./.github/actions/mcp-guard
51
- with:
52
- stdio_command: node fixtures/servers/hello-mcp-server/server.cjs
53
- fail_on: high
76
+ - npm package: **`@baichen_yu/mcp-guard`**
77
+ - runtime CLI command: **`mcp-guard`**
78
+ - first scoped publish command: **`npm publish --access public`**
79
+
80
+ ---
81
+
82
+ ## Architecture
83
+
84
+ ```mermaid
85
+ flowchart LR
86
+ CLI[mcp-guard CLI] --> T[Transport layer\nSTDIO HTTP SSE]
87
+ T --> RPC[JSON-RPC client]
88
+ RPC --> TESTS[Contract tests]
89
+ RPC --> RULES[Rules and profiles]
90
+ TESTS --> REPORTS[Reports\nMD JSON SARIF]
91
+ RULES --> REPORTS
92
+ REPORTS --> GATE[Policy gate fail-on]
93
+ GATE --> CI[CI and code scanning]
54
94
  ```
55
95
 
96
+ ### Audit pipeline
97
+
98
+ ```mermaid
99
+ flowchart TD
100
+ A[Start audit] --> B[Initialize session]
101
+ B --> C[List tools]
102
+ C --> D[Run contract suite]
103
+ C --> E[Run schema and security rules]
104
+ D --> F[Apply profile tuning]
105
+ E --> F
106
+ F --> G[Compute score]
107
+ G --> H[Emit reports]
108
+ H --> I[Exit by policy threshold]
109
+ ```
110
+
111
+ ---
112
+
56
113
  ## Report preview
57
114
 
58
115
  ```text
@@ -63,67 +120,122 @@ jobs:
63
120
  - Target: node fixtures/servers/hello-mcp-server/server.cjs (stdio)
64
121
  ```
65
122
 
66
- ## Architecture
67
-
68
- ```mermaid
69
- graph LR
70
- CLI[mcp-guard CLI] --> T[Transports: stdio/http]
71
- T --> RPC[JSON-RPC]
72
- RPC --> RULES[Rules + Profiles]
73
- RULES --> REP[Reports: md/json/sarif]
74
- REP --> GATE[Policy Gate (--fail-on)]
75
- GATE --> CI[CI / Code Scanning]
76
- ```
123
+ ---
77
124
 
78
125
  ## Commands
79
126
 
80
127
  ```bash
128
+ # Validate / Test / Audit
81
129
  mcp-guard validate --stdio "node server.cjs" --profile default --out reports
82
130
  mcp-guard test --stdio "node server.cjs" --out reports
83
131
  mcp-guard audit --stdio "node server.cjs" --profile strict --fail-on medium --sarif reports/report.sarif
132
+
133
+ # Remote audit (HTTP JSON-RPC)
84
134
  mcp-guard audit --http "http://127.0.0.1:4010" --timeout-ms 30000 --fail-on off
135
+
136
+ # Remote audit (SSE stream + POST endpoint)
137
+ mcp-guard audit --sse "http://127.0.0.1:4013/sse" --sse-post "http://127.0.0.1:4013/message" --timeout-ms 30000 --fail-on off
138
+
139
+ # Config scan
85
140
  mcp-guard scan --repo . --format md --out reports
141
+
142
+ # Registry checks
86
143
  mcp-guard registry lint registry/servers.yaml
87
144
  mcp-guard registry verify registry/servers.yaml --sample 5
88
145
  mcp-guard registry score registry/servers.yaml
89
146
  ```
90
147
 
91
- ## Docs site (GitHub Pages)
148
+ ---
92
149
 
93
- - Docs URL pattern: `https://<owner>.github.io/MCP-shariff/`
94
- - One-time setup: GitHub repo **Settings → Pages → Source → GitHub Actions**
95
- - Deploy workflow: `.github/workflows/deploy-pages.yml`
150
+ ## CI integration (drop-in)
96
151
 
97
- ## Links
152
+ ```yaml
153
+ jobs:
154
+ mcp-audit:
155
+ runs-on: ubuntu-latest
156
+ permissions:
157
+ security-events: write
158
+ actions: read
159
+ contents: read
160
+ steps:
161
+ - uses: actions/checkout@v4
162
+ - uses: actions/setup-node@v4
163
+ with:
164
+ node-version: 20
165
+ - uses: ./.github/actions/mcp-guard
166
+ with:
167
+ stdio_command: node fixtures/servers/hello-mcp-server/server.cjs
168
+ fail_on: high
169
+ ```
98
170
 
99
- - Docs: https://tomas-1226.github.io/MCP-shariff/
100
- - GitHub: https://github.com/TomAs-1226/MCP-shariff
101
- - npm: https://www.npmjs.com/package/@baichen_yu/mcp-guard
171
+ ---
172
+
173
+ ## Automated releases
174
+
175
+ - On each push to `main`, `.github/workflows/release.yml` now:
176
+ - runs lint/test/build
177
+ - computes the next available version above local and npm latest
178
+ - builds release assets (package tarball + compiled `dist` archive)
179
+ - creates/updates a GitHub Release with GitHub generated (auto/AI-style) release notes + uploaded assets
180
+ - publishes the new package to npm (requires `NPM_TOKEN`)
181
+ - For npm publishing in CI, set `NPM_TOKEN` to an **npm Automation token** (no interactive password/OTP required).
182
+
183
+ ## Docs site (GitHub Pages)
184
+
185
+ - URL pattern: `https://<owner>.github.io/mcp-guard/`
186
+ - Current docs: https://tomas-1226.github.io/mcp-guard/
187
+ - One-time setup: **Settings → Pages → Source → GitHub Actions**
188
+
189
+ ---
102
190
 
103
191
  ## Troubleshooting
104
192
 
105
- - Node `>=20` required
106
- - On Windows, wrap `--stdio` command in double quotes
107
- - If startup is slow, increase `--timeout-ms`
108
- - HTTP target must accept JSON-RPC over POST
193
+ <details>
194
+ <summary><strong>Publishing ENOENT / package.json not found</strong></summary>
195
+
196
+ Run `npm publish` from the project root (the directory containing `package.json`).
197
+
198
+ </details>
199
+
200
+ <details>
201
+ <summary><strong>HTTP target fails</strong></summary>
202
+
203
+ Confirm endpoint supports JSON-RPC via HTTP POST and increase `--timeout-ms` if startup is slow.
109
204
 
110
- ## Releasing
205
+ </details>
111
206
 
112
- See [`docs/releasing.md`](docs/releasing.md) and [`RELEASE.md`](RELEASE.md).
207
+ <details>
208
+ <summary><strong>Windows command quoting</strong></summary>
113
209
 
114
- ### Local release bundle script
210
+ Wrap `--stdio` values in double quotes.
115
211
 
116
- Use this helper to generate a release-ready tarball locally:
212
+ </details>
213
+
214
+ ---
215
+
216
+ ## Release helper
217
+
218
+ Build release artifacts locally/offline (after one online `npm ci`):
117
219
 
118
220
  ```bash
119
- ./scripts/build-release-local.sh
221
+ npm run release:offline
120
222
  ```
121
223
 
122
- ### Publishing troubleshooting
224
+ ---
123
225
 
124
- Run `npm publish` from the project root that contains `package.json`.
125
- If you hit `ENOENT` for `package.json`, you are publishing from the wrong directory.
226
+ ## Links
227
+
228
+ - Docs: https://tomas-1226.github.io/mcp-guard/
229
+ - GitHub: https://github.com/TomAs-1226/mcp-guard
230
+ - npm: https://www.npmjs.com/package/@baichen_yu/mcp-guard
126
231
 
127
232
  ## License
128
233
 
129
- MIT. See [`LICENSE`](LICENSE).
234
+ MIT. See [LICENSE](LICENSE).
235
+
236
+
237
+ ### npm package run check
238
+
239
+ ```bash
240
+ npm run npm:test-run
241
+ ```
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { mkdir, readFile } from 'node:fs/promises';
3
3
  import { basename } from 'node:path';
4
4
  import { Command } from 'commander';
5
- import { HttpJsonRpcClient, StdioJsonRpcClient } from './mcp/jsonrpc.js';
5
+ import { HttpJsonRpcClient, SseJsonRpcClient, StdioJsonRpcClient } from './mcp/jsonrpc.js';
6
6
  import { StdioTransport } from './mcp/transport_stdio.js';
7
7
  import { writeJsonReport } from './report/json.js';
8
8
  import { writeMarkdownReport } from './report/markdown.js';
@@ -40,19 +40,27 @@ function shouldFailByPolicy(findings, failOn) {
40
40
  return findings.some((finding) => severityRank[finding.severity] >= rank[failOn]);
41
41
  }
42
42
  async function buildClient(options) {
43
- if (!options.stdio && !options.http)
44
- throw new Error('One of --stdio or --http is required.');
45
- if (options.stdio && options.http)
46
- throw new Error('Use only one transport: --stdio or --http.');
43
+ const selected = [options.stdio, options.http, options.sse].filter(Boolean).length;
44
+ if (selected === 0)
45
+ throw new Error('One of --stdio, --http, or --sse is required.');
46
+ if (selected > 1)
47
+ throw new Error('Use only one transport: --stdio, --http, or --sse.');
47
48
  if (options.stdio) {
48
49
  const transport = new StdioTransport();
49
50
  await transport.start({ stdioCommand: options.stdio, silent: options.silent });
50
51
  return { client: new StdioJsonRpcClient(transport, options.timeoutMs), target: options.stdio, transport: 'stdio' };
51
52
  }
52
- return { client: new HttpJsonRpcClient(options.http, options.timeoutMs), target: options.http, transport: 'http' };
53
+ if (options.http) {
54
+ return { client: new HttpJsonRpcClient(options.http, options.timeoutMs), target: options.http, transport: 'http' };
55
+ }
56
+ return {
57
+ client: new SseJsonRpcClient(options.sse, options.timeoutMs, options.ssePost ?? options.sse),
58
+ target: options.sse,
59
+ transport: 'sse'
60
+ };
53
61
  }
54
62
  async function executeSuite(params) {
55
- const { client, target, transport } = await buildClient({ stdio: params.stdio, http: params.http, timeoutMs: params.timeoutMs, silent: false });
63
+ const { client, target, transport } = await buildClient({ stdio: params.stdio, http: params.http, sse: params.sse, ssePost: params.ssePost, timeoutMs: params.timeoutMs, silent: false });
56
64
  const findings = [];
57
65
  let tools = [];
58
66
  const tests = [];
@@ -110,13 +118,15 @@ async function executeSuite(params) {
110
118
  }
111
119
  program
112
120
  .name('mcp-guard')
113
- .description('Validate, test, and security-audit MCP servers over STDIO or HTTP JSON-RPC')
121
+ .description('Validate, test, and security-audit MCP servers over STDIO, HTTP JSON-RPC, or SSE + JSON-RPC')
114
122
  .showHelpAfterError();
115
123
  for (const commandName of ['validate', 'test', 'audit']) {
116
124
  program.command(commandName)
117
125
  .description(`${commandName} an MCP server`)
118
126
  .option('--stdio <cmd>', 'Command used to launch MCP server over stdio')
119
127
  .option('--http <url>', 'HTTP JSON-RPC endpoint URL')
128
+ .option('--sse <url>', 'SSE endpoint URL for responses')
129
+ .option('--sse-post <url>', 'HTTP POST URL used to send JSON-RPC requests (defaults to --sse)')
120
130
  .option('--out <dir>', 'Output directory', 'reports')
121
131
  .option('--sarif <file>', 'SARIF output file (mainly for audit)', 'reports/report.sarif')
122
132
  .option('--profile <profile>', 'Rule profile: default|strict|paranoid', parseProfile, 'default')
@@ -128,6 +138,8 @@ for (const commandName of ['validate', 'test', 'audit']) {
128
138
  const code = await executeSuite({
129
139
  stdio: options.stdio,
130
140
  http: options.http,
141
+ sse: options.sse,
142
+ ssePost: options.ssePost,
131
143
  outDir: options.out,
132
144
  runTests: doTests,
133
145
  runAudit: doAudit,
@@ -20,4 +20,20 @@ export declare class HttpJsonRpcClient implements RpcClient {
20
20
  normalizeError(error: unknown): NormalizedError;
21
21
  close(): Promise<void>;
22
22
  }
23
+ export declare class SseJsonRpcClient implements RpcClient {
24
+ private readonly streamUrl;
25
+ private readonly timeoutMs;
26
+ private readonly postUrl;
27
+ private nextId;
28
+ private pending;
29
+ private streamController?;
30
+ private readerPromise?;
31
+ constructor(streamUrl: string, timeoutMs?: number, postUrl?: string);
32
+ request<T>(method: string, params?: unknown, timeoutOverrideMs?: number): Promise<T>;
33
+ normalizeError(error: unknown): NormalizedError;
34
+ close(): Promise<void>;
35
+ private ensureStream;
36
+ private openAndReadStream;
37
+ private resolvePending;
38
+ }
23
39
  export declare function normalizeError(error: unknown): NormalizedError;
@@ -101,6 +101,134 @@ export class HttpJsonRpcClient {
101
101
  await Promise.resolve();
102
102
  }
103
103
  }
104
+ export class SseJsonRpcClient {
105
+ streamUrl;
106
+ timeoutMs;
107
+ postUrl;
108
+ nextId = 1;
109
+ pending = new Map();
110
+ streamController;
111
+ readerPromise;
112
+ constructor(streamUrl, timeoutMs = 5000, postUrl = streamUrl) {
113
+ this.streamUrl = streamUrl;
114
+ this.timeoutMs = timeoutMs;
115
+ this.postUrl = postUrl;
116
+ }
117
+ async request(method, params, timeoutOverrideMs) {
118
+ await this.ensureStream();
119
+ const id = this.nextId++;
120
+ const payload = { jsonrpc: '2.0', id, method, params };
121
+ return new Promise(async (resolve, reject) => {
122
+ const timer = setTimeout(() => {
123
+ this.pending.delete(id);
124
+ reject(new Error(`Request timed out: ${method}`));
125
+ }, timeoutOverrideMs ?? this.timeoutMs);
126
+ this.pending.set(id, { resolve: resolve, reject, timer });
127
+ try {
128
+ const response = await fetch(this.postUrl, {
129
+ method: 'POST',
130
+ headers: { 'content-type': 'application/json' },
131
+ body: JSON.stringify(payload)
132
+ });
133
+ if (!response.ok) {
134
+ throw new Error(`SSE POST failed: ${response.status}`);
135
+ }
136
+ const contentType = response.headers.get('content-type') ?? '';
137
+ if (contentType.includes('application/json')) {
138
+ const parsed = (await response.json());
139
+ this.resolvePending(parsed);
140
+ }
141
+ }
142
+ catch (error) {
143
+ const pending = this.pending.get(id);
144
+ if (pending) {
145
+ clearTimeout(pending.timer);
146
+ this.pending.delete(id);
147
+ pending.reject(error);
148
+ }
149
+ }
150
+ });
151
+ }
152
+ normalizeError(error) {
153
+ return normalizeError(error);
154
+ }
155
+ async close() {
156
+ this.streamController?.abort();
157
+ await this.readerPromise?.catch(() => undefined);
158
+ for (const [id, pending] of this.pending) {
159
+ clearTimeout(pending.timer);
160
+ pending.reject(new Error('SSE client closed'));
161
+ this.pending.delete(id);
162
+ }
163
+ }
164
+ async ensureStream() {
165
+ if (this.readerPromise)
166
+ return;
167
+ this.streamController = new AbortController();
168
+ this.readerPromise = this.openAndReadStream(this.streamController.signal).catch((error) => {
169
+ for (const [id, pending] of this.pending) {
170
+ clearTimeout(pending.timer);
171
+ pending.reject(error);
172
+ this.pending.delete(id);
173
+ }
174
+ this.readerPromise = undefined;
175
+ });
176
+ }
177
+ async openAndReadStream(signal) {
178
+ const response = await fetch(this.streamUrl, {
179
+ method: 'GET',
180
+ headers: { Accept: 'text/event-stream' },
181
+ signal
182
+ });
183
+ if (!response.ok || !response.body) {
184
+ throw new Error(`Failed to open SSE stream: ${response.status}`);
185
+ }
186
+ const decoder = new TextDecoder();
187
+ const reader = response.body.getReader();
188
+ let buffer = '';
189
+ let dataLines = [];
190
+ while (true) {
191
+ const { value, done } = await reader.read();
192
+ if (done)
193
+ break;
194
+ buffer += decoder.decode(value, { stream: true });
195
+ let idx = buffer.indexOf('\n');
196
+ while (idx >= 0) {
197
+ const line = buffer.slice(0, idx).replace(/\r$/, '');
198
+ buffer = buffer.slice(idx + 1);
199
+ if (line.startsWith('data:')) {
200
+ dataLines.push(line.slice(5).trimStart());
201
+ }
202
+ else if (line === '') {
203
+ if (dataLines.length > 0) {
204
+ const payload = dataLines.join('\n');
205
+ dataLines = [];
206
+ try {
207
+ const parsed = JSON.parse(payload);
208
+ this.resolvePending(parsed);
209
+ }
210
+ catch {
211
+ // ignore malformed events
212
+ }
213
+ }
214
+ }
215
+ idx = buffer.indexOf('\n');
216
+ }
217
+ }
218
+ }
219
+ resolvePending(parsed) {
220
+ const pending = this.pending.get(parsed.id);
221
+ if (!pending)
222
+ return;
223
+ clearTimeout(pending.timer);
224
+ this.pending.delete(parsed.id);
225
+ if ('error' in parsed) {
226
+ pending.reject(parsed.error);
227
+ return;
228
+ }
229
+ pending.resolve(parsed.result);
230
+ }
231
+ }
104
232
  export function normalizeError(error) {
105
233
  if (typeof error === 'object' && error !== null && 'code' in error && 'message' in error) {
106
234
  const err = error;
@@ -29,6 +29,8 @@ export interface ToolDescriptor {
29
29
  export interface ServerConfig {
30
30
  stdioCommand?: string;
31
31
  httpUrl?: string;
32
+ sseUrl?: string;
33
+ ssePostUrl?: string;
32
34
  cwd?: string;
33
35
  env?: Record<string, string>;
34
36
  timeoutMs?: number;
@@ -52,7 +54,7 @@ export interface TestResult {
52
54
  export interface Report {
53
55
  server: {
54
56
  target: string;
55
- transport: 'stdio' | 'http';
57
+ transport: 'stdio' | 'http' | 'sse';
56
58
  protocolVersion?: string;
57
59
  };
58
60
  tools: ToolDescriptor[];
@@ -2,7 +2,7 @@ export interface DiscoveredServer {
2
2
  source: 'claude_desktop' | 'cursor';
3
3
  path: string;
4
4
  name: string;
5
- transport: 'stdio' | 'http' | 'unknown';
5
+ transport: 'stdio' | 'http' | 'sse' | 'unknown';
6
6
  command?: string;
7
7
  rawCommand?: string;
8
8
  permissions?: string[];
@@ -23,6 +23,8 @@ function redact(text) {
23
23
  function detectTransport(server) {
24
24
  if (typeof server.command === 'string')
25
25
  return 'stdio';
26
+ if (typeof server.sseUrl === 'string' || typeof server.sse === 'string')
27
+ return 'sse';
26
28
  if (typeof server.url === 'string' || typeof server.httpUrl === 'string')
27
29
  return 'http';
28
30
  return 'unknown';
package/docs/cli.md CHANGED
@@ -16,9 +16,9 @@ mcp-guard --help
16
16
 
17
17
  ## Core commands
18
18
 
19
- - `mcp-guard validate --stdio <cmd>|--http <url> [--profile default|strict|paranoid] [--out reports] [--timeout-ms 30000]`
20
- - `mcp-guard test --stdio <cmd>|--http <url> [--out reports] [--timeout-ms 30000]`
21
- - `mcp-guard audit --stdio <cmd>|--http <url> [--profile ...] [--fail-on off|low|medium|high] [--sarif reports/report.sarif]`
19
+ - `mcp-guard validate --stdio <cmd>|--http <url>|--sse <url> [--sse-post <url>] [--profile default|strict|paranoid] [--out reports] [--timeout-ms 30000]`
20
+ - `mcp-guard test --stdio <cmd>|--http <url>|--sse <url> [--sse-post <url>] [--out reports] [--timeout-ms 30000]`
21
+ - `mcp-guard audit --stdio <cmd>|--http <url>|--sse <url> [--sse-post <url>] [--profile ...] [--fail-on off|low|medium|high] [--sarif reports/report.sarif]`
22
22
 
23
23
  ## Scan and registry commands
24
24
 
@@ -35,4 +35,6 @@ mcp-guard --help
35
35
 
36
36
  ## Remote transport support
37
37
 
38
- `--http` supports HTTP JSON-RPC (POST) only. SSE is not supported.
38
+ `--http` supports HTTP JSON-RPC (POST).
39
+
40
+ `--sse` supports SSE response streams (GET) with request POSTs sent to `--sse-post` (or to `--sse` when omitted).
package/docs/index.md CHANGED
@@ -3,39 +3,39 @@ layout: home
3
3
 
4
4
  hero:
5
5
  name: "mcp-guard"
6
- text: "Practical security gating for MCP servers"
7
- tagline: "Deterministic MCP validation, contract tests, and audit reporting for local workflows and CI."
6
+ text: "Security gating for MCP servers, from local dev to CI"
7
+ tagline: "Validate protocol contracts, test runtime behavior, and enforce policy with reproducible reports."
8
8
  image:
9
9
  src: /brand-mark.svg
10
10
  alt: mcp-guard
11
11
  actions:
12
12
  - theme: brand
13
- text: 30-second quickstart
13
+ text: Start in 30 seconds
14
14
  link: /quickstart
15
+ - theme: alt
16
+ text: Run tests
17
+ link: /testing
15
18
  - theme: alt
16
19
  text: CLI reference
17
20
  link: /cli
18
- - theme: alt
19
- text: GitHub Action
20
- link: /github-action
21
21
 
22
22
  features:
23
23
  - icon: 🧪
24
- title: Deterministic contracts
25
- details: Fixed checks for tools/list, tool call behavior, invalid shapes, cancellation behavior, large payload handling, and timeouts.
24
+ title: Deterministic contract checks
25
+ details: Fixed checks for tools/list, tool-call behavior, malformed payloads, cancellation behavior, large responses, and timeout boundaries.
26
26
  - icon: 🛡️
27
27
  title: Policy gate built-in
28
- details: Use profiles and --fail-on thresholds to enforce guardrails in CI without custom glue code.
28
+ details: Use profile severity and --fail-on thresholds to enforce guardrails in CI without custom glue code.
29
29
  - icon: 📄
30
30
  title: Reproducible outputs
31
- details: Markdown + JSON + SARIF reports designed for review, snapshots, and code-scanning pipelines.
31
+ details: Generate Markdown + JSON + SARIF artifacts designed for pull-request review and code scanning.
32
32
  - icon: 🚦
33
- title: Two transport modes
34
- details: Local stdio and remote HTTP JSON-RPC support with bounded retries and timeouts.
33
+ title: Three transport modes
34
+ details: Local stdio plus remote HTTP JSON-RPC and SSE support with bounded retries and timeouts.
35
35
  ---
36
36
 
37
37
  > [!IMPORTANT]
38
- > Remote mode currently supports **HTTP JSON-RPC only** (`--http`). SSE is not implemented.
38
+ > Remote mode supports **HTTP JSON-RPC** (`--http`) and **SSE** (`--sse`).
39
39
 
40
40
  <div class="stats-grid">
41
41
  <div class="stat-card"><h4>Profiles</h4><p><code>default</code> · <code>strict</code> · <code>paranoid</code></p></div>
@@ -43,21 +43,31 @@ features:
43
43
  <div class="stat-card"><h4>Policy</h4><p><code>--fail-on off|low|medium|high</code></p></div>
44
44
  </div>
45
45
 
46
- ## Report preview
46
+ ## Typical workflow
47
47
 
48
- <div class="report-preview">
49
- <strong>MCP Guard Report</strong><br/>
50
- Risk score: <code>100/100</code><br/>
51
- Key findings: <code>0</code><br/>
52
- Contract tests: <code>6/6</code><br/>
53
- Target: <code>node fixtures/servers/hello-mcp-server/server.cjs (stdio)</code>
48
+ <div class="workflow-grid">
49
+ <div class="workflow-card">
50
+ <h4>1) Validate quickly</h4>
51
+ <p>Run checks locally against your MCP server before opening a PR.</p>
52
+ <code>mcp-guard validate --stdio "node server.cjs"</code>
53
+ </div>
54
+ <div class="workflow-card">
55
+ <h4>2) Test behavior</h4>
56
+ <p>Execute deterministic test probes and emit machine-readable reports.</p>
57
+ <code>mcp-guard test --stdio "node server.cjs"</code>
58
+ </div>
59
+ <div class="workflow-card">
60
+ <h4>3) Gate in CI</h4>
61
+ <p>Use <code>audit</code> with SARIF and severity thresholds to block risky changes.</p>
62
+ <code>mcp-guard audit --fail-on medium --sarif reports/report.sarif</code>
63
+ </div>
54
64
  </div>
55
65
 
56
66
  ## Architecture
57
67
 
58
68
  ```mermaid
59
69
  graph LR
60
- CLI[mcp-guard CLI] --> T[Transports: stdio/http]
70
+ CLI[mcp-guard CLI] --> T[Transports: stdio/http/sse]
61
71
  T --> RPC[JSON-RPC]
62
72
  RPC --> RULES[Rules + Profiles]
63
73
  RULES --> REP[Reports: md/json/sarif]
@@ -71,5 +81,5 @@ graph LR
71
81
  2. Upload SARIF so findings show in security dashboards.
72
82
  3. Gate merges on reproducible report output.
73
83
 
74
- - GitHub: https://github.com/TomAs-1226/MCP-shariff
84
+ - GitHub: https://github.com/TomAs-1226/mcp-guard
75
85
  - npm: https://www.npmjs.com/package/@baichen_yu/mcp-guard
@@ -27,8 +27,23 @@ npm publish --access public
27
27
 
28
28
  1. Enable **Settings → Pages → Source → GitHub Actions** once.
29
29
  2. Docs deploy from `.github/workflows/deploy-pages.yml`.
30
- 3. Expected URL: `https://<owner>.github.io/MCP-shariff/`.
30
+ 3. Expected URL after repo rename to `mcp-guard`: `https://<owner>.github.io/mcp-guard/`.
31
+ 4. If the site renders as plain text or a blank page, confirm docs base path is set to `/<repo-name>/`.
31
32
 
32
33
  ## Remote mode note
33
34
 
34
- Remote mode supports **HTTP JSON-RPC only** (`--http`). SSE is not supported yet.
35
+ Remote mode supports **HTTP JSON-RPC** (`--http`) and **SSE** (`--sse`, optional `--sse-post`).
36
+
37
+
38
+ ## SSE quick check
39
+
40
+ ```bash
41
+ node fixtures/servers/sse-mcp-server/server.cjs
42
+ # in another shell
43
+ npx @baichen_yu/mcp-guard audit --sse "http://127.0.0.1:4013/sse" --sse-post "http://127.0.0.1:4013/message" --out reports --fail-on off
44
+ ```
45
+
46
+
47
+ ## Release auth note
48
+
49
+ For automatic npm releases in GitHub Actions, create an **npm Automation token** and store it as repository secret `NPM_TOKEN`. This avoids interactive password/OTP prompts during CI publish.
package/docs/releasing.md CHANGED
@@ -36,4 +36,49 @@ git push origin v0.3.0
36
36
  Then create a GitHub Release and include:
37
37
  - key highlights
38
38
  - migration note (formerly mcp-doctor)
39
- - limitation note (HTTP JSON-RPC only, SSE not supported)
39
+ - transport note (HTTP JSON-RPC + SSE supported)
40
+
41
+
42
+ ## Automated release pipeline
43
+
44
+ On pushes to `main`, `.github/workflows/release.yml` will:
45
+
46
+ 1. Run `npm run lint`, `npm test`, and `npm run build`.
47
+ 2. Compute the next available version above both local `package.json` and npm latest, then bump to that version.
48
+ 3. Push commit + tag back to GitHub.
49
+ 4. Build release assets (`npm pack` tarball + compiled `dist` archive).
50
+ 5. Publish to npm with provenance.
51
+ 6. Create/update a GitHub Release with generated release notes and uploaded assets.
52
+
53
+ Required secret: `NPM_TOKEN` with publish permission for `@baichen_yu/mcp-guard`.
54
+
55
+ Use an **npm Automation token** (recommended) so the workflow can publish without interactive password/OTP prompts.
56
+
57
+ - npm: create token at <https://www.npmjs.com/settings/tokens>
58
+ - GitHub: add it as repository secret `NPM_TOKEN`
59
+ - workflow preflight runs `npm whoami` to confirm auth before version bump/publish
60
+
61
+
62
+ ## Offline release package build
63
+
64
+ After dependencies are installed once (`npm ci` while online), you can build the release tarball completely offline:
65
+
66
+ ```bash
67
+ npm run release:offline
68
+ ```
69
+
70
+ If `node_modules` is missing, the script exits with guidance.
71
+
72
+ ## npm package test-run command
73
+
74
+ Use this to verify the published package entrypoint works:
75
+
76
+ ```bash
77
+ npm run npm:test-run
78
+ ```
79
+
80
+
81
+ The workflow also supports manual trigger via `workflow_dispatch` in GitHub Actions.
82
+
83
+
84
+ If a release tag already exists (for example, from a re-run), the workflow updates that release and re-uploads assets with `--clobber` instead of failing.
@@ -0,0 +1,67 @@
1
+ # Testing options
2
+
3
+ Use these commands depending on what you want to verify.
4
+
5
+ ## 1) Unit/integration test suite (Vitest)
6
+
7
+ ```bash
8
+ npm test
9
+ ```
10
+
11
+ Runs the project test suite and regenerates fixtures first (`pretest`).
12
+
13
+ ## 2) TypeScript compile checks (no emit)
14
+
15
+ ```bash
16
+ npm run lint
17
+ ```
18
+
19
+ Confirms the codebase type-checks cleanly.
20
+
21
+ ## 3) Production build
22
+
23
+ ```bash
24
+ npm run build
25
+ ```
26
+
27
+ Compiles CLI sources to `dist/`.
28
+
29
+ ## 4) Docs build (GitHub Pages artifact parity)
30
+
31
+ ```bash
32
+ npm run docs:build
33
+ ```
34
+
35
+ Builds the VitePress static site exactly like the Pages workflow.
36
+
37
+ ## 5) Example end-to-end CLI run
38
+
39
+ ```bash
40
+ npx tsx src/cli.ts audit --stdio "node fixtures/servers/hello-mcp-server/server.cjs" --out reports --fail-on off
41
+ ```
42
+
43
+ This is a practical smoke test that exercises transport, rules, and report generation.
44
+
45
+ ## Minimal local validation flow
46
+
47
+ ```bash
48
+ npm run lint && npm test && npm run build && npm run docs:build
49
+ ```
50
+
51
+
52
+ ## SSE smoke test
53
+
54
+ ```bash
55
+ node fixtures/servers/sse-mcp-server/server.cjs
56
+ # new terminal
57
+ node dist/cli.js audit --sse "http://127.0.0.1:4013/sse" --sse-post "http://127.0.0.1:4013/message" --out reports --fail-on off
58
+ ```
59
+
60
+
61
+ ## Published npm package smoke test
62
+
63
+ ```bash
64
+ npm run npm:test-run
65
+ ```
66
+
67
+ This executes the package from npm and prints CLI help to verify install/entrypoint integrity.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baichen_yu/mcp-guard",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Security auditing and policy gating for MCP servers (STDIO/HTTP) with Markdown + SARIF reports",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,13 +11,18 @@
11
11
  },
12
12
  "scripts": {
13
13
  "build": "tsc -p tsconfig.json",
14
+ "prepack": "npm run build",
14
15
  "dev": "tsx src/cli.ts",
16
+ "pretest": "npm run fixtures:gen",
15
17
  "test": "vitest run",
16
18
  "lint": "tsc -p tsconfig.json --noEmit",
17
19
  "fixtures:gen": "tsx scripts/gen-fixtures.ts",
18
20
  "docs:dev": "vitepress dev docs",
19
21
  "docs:build": "vitepress build docs",
20
- "docs:preview": "vitepress preview docs"
22
+ "docs:preview": "vitepress preview docs",
23
+ "prepublishOnly": "npm run lint && npm test",
24
+ "release:offline": "bash scripts/build-release-local.sh",
25
+ "npm:test-run": "PKG=$(npm pack --silent | tail -n 1) && npx --yes --package \"./$PKG\" mcp-guard --help && rm -f \"$PKG\""
21
26
  },
22
27
  "dependencies": {
23
28
  "commander": "^12.1.0",
@@ -41,11 +46,11 @@
41
46
  ],
42
47
  "repository": {
43
48
  "type": "git",
44
- "url": "https://github.com/TomAs-1226/MCP-shariff.git"
49
+ "url": "https://github.com/TomAs-1226/mcp-guard.git"
45
50
  },
46
- "homepage": "https://tomas-1226.github.io/MCP-shariff/",
51
+ "homepage": "https://tomas-1226.github.io/mcp-guard/",
47
52
  "bugs": {
48
- "url": "https://github.com/TomAs-1226/MCP-shariff/issues"
53
+ "url": "https://github.com/TomAs-1226/mcp-guard/issues"
49
54
  },
50
55
  "publishConfig": {
51
56
  "access": "public"