curatedmcp 2.0.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -0
  3. package/dist/audit/catalog.d.ts +5 -0
  4. package/dist/audit/catalog.d.ts.map +1 -0
  5. package/dist/audit/catalog.js +69 -0
  6. package/dist/audit/index.d.ts +10 -0
  7. package/dist/audit/index.d.ts.map +1 -0
  8. package/dist/audit/index.js +32 -0
  9. package/dist/audit/report.d.ts +6 -0
  10. package/dist/audit/report.d.ts.map +1 -0
  11. package/dist/audit/report.js +79 -0
  12. package/dist/audit/risk.d.ts +3 -0
  13. package/dist/audit/risk.d.ts.map +1 -0
  14. package/dist/audit/risk.js +68 -0
  15. package/dist/audit/scanner.d.ts +8 -0
  16. package/dist/audit/scanner.d.ts.map +1 -0
  17. package/dist/audit/scanner.js +69 -0
  18. package/dist/audit/types.d.ts +29 -0
  19. package/dist/audit/types.d.ts.map +1 -0
  20. package/dist/audit/types.js +2 -0
  21. package/dist/auth.d.ts +23 -0
  22. package/dist/auth.d.ts.map +1 -0
  23. package/dist/auth.js +52 -0
  24. package/dist/cli/add.d.ts +18 -0
  25. package/dist/cli/add.d.ts.map +1 -0
  26. package/dist/cli/add.js +114 -0
  27. package/dist/cli/audit.d.ts +2 -0
  28. package/dist/cli/audit.d.ts.map +1 -0
  29. package/dist/cli/audit.js +58 -0
  30. package/dist/cli/guard.d.ts +2 -0
  31. package/dist/cli/guard.d.ts.map +1 -0
  32. package/dist/cli/guard.js +58 -0
  33. package/dist/cli/init.d.ts +2 -0
  34. package/dist/cli/init.d.ts.map +1 -0
  35. package/dist/cli/init.js +44 -0
  36. package/dist/cli/list.d.ts +5 -0
  37. package/dist/cli/list.d.ts.map +1 -0
  38. package/dist/cli/list.js +33 -0
  39. package/dist/cli/login.d.ts +6 -0
  40. package/dist/cli/login.d.ts.map +1 -0
  41. package/dist/cli/login.js +43 -0
  42. package/dist/cli/remove.d.ts +6 -0
  43. package/dist/cli/remove.d.ts.map +1 -0
  44. package/dist/cli/remove.js +15 -0
  45. package/dist/cli/sync.d.ts +2 -0
  46. package/dist/cli/sync.d.ts.map +1 -0
  47. package/dist/cli/sync.js +104 -0
  48. package/dist/cli.d.ts +10 -0
  49. package/dist/cli.d.ts.map +1 -0
  50. package/dist/cli.js +132 -0
  51. package/dist/guard/broker.d.ts +62 -0
  52. package/dist/guard/broker.d.ts.map +1 -0
  53. package/dist/guard/broker.js +147 -0
  54. package/dist/guard/dashboard.d.ts +14 -0
  55. package/dist/guard/dashboard.d.ts.map +1 -0
  56. package/dist/guard/dashboard.js +428 -0
  57. package/dist/guard/default-policy.json +33 -0
  58. package/dist/guard/index.d.ts +20 -0
  59. package/dist/guard/index.d.ts.map +1 -0
  60. package/dist/guard/index.js +61 -0
  61. package/dist/guard/logger.d.ts +30 -0
  62. package/dist/guard/logger.d.ts.map +1 -0
  63. package/dist/guard/logger.js +118 -0
  64. package/dist/guard/policy.d.ts +19 -0
  65. package/dist/guard/policy.d.ts.map +1 -0
  66. package/dist/guard/policy.js +108 -0
  67. package/dist/guard/proxy.d.ts +29 -0
  68. package/dist/guard/proxy.d.ts.map +1 -0
  69. package/dist/guard/proxy.js +109 -0
  70. package/dist/guard/types.d.ts +70 -0
  71. package/dist/guard/types.d.ts.map +1 -0
  72. package/dist/guard/types.js +2 -0
  73. package/dist/index.d.ts +3 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +259 -0
  76. package/dist/proxy.d.ts +122 -0
  77. package/dist/proxy.d.ts.map +1 -0
  78. package/dist/proxy.js +165 -0
  79. package/dist/stack.d.ts +45 -0
  80. package/dist/stack.d.ts.map +1 -0
  81. package/dist/stack.js +93 -0
  82. package/dist/telemetry.d.ts +15 -0
  83. package/dist/telemetry.d.ts.map +1 -0
  84. package/dist/telemetry.js +71 -0
  85. package/dist/tools/get-details.d.ts +14 -0
  86. package/dist/tools/get-details.d.ts.map +1 -0
  87. package/dist/tools/get-details.js +27 -0
  88. package/dist/tools/install.d.ts +2 -0
  89. package/dist/tools/install.d.ts.map +1 -0
  90. package/dist/tools/install.js +74 -0
  91. package/dist/tools/list-categories.d.ts +2 -0
  92. package/dist/tools/list-categories.d.ts.map +1 -0
  93. package/dist/tools/list-categories.js +13 -0
  94. package/dist/tools/search.d.ts +16 -0
  95. package/dist/tools/search.d.ts.map +1 -0
  96. package/dist/tools/search.js +17 -0
  97. package/package.json +78 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CuratedMCP
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # @curatedmcp/launcher
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@curatedmcp/launcher?color=brightgreen)](https://www.npmjs.com/package/@curatedmcp/launcher)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@curatedmcp/launcher)](https://www.npmjs.com/package/@curatedmcp/launcher)
5
+ [![CI](https://github.com/oneprofile-dev/mcp-launcher/actions/workflows/test.yml/badge.svg)](https://github.com/oneprofile-dev/mcp-launcher/actions/workflows/test.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![Node.js ≥18](https://img.shields.io/node/v/@curatedmcp/launcher)](https://nodejs.org)
8
+
9
+ > **The MCP Hub.** One config that bridges every AI agent (Claude, Cursor, Windsurf, Copilot, Gemini) to every MCP server you register.
10
+
11
+ ```bash
12
+ npx @curatedmcp/launcher init
13
+ ```
14
+
15
+ **Plug it in once. Add servers anytime. Use them in any AI agent.**
16
+
17
+ ---
18
+
19
+ ## Why
20
+
21
+ If you use MCP servers across multiple AI clients, you've felt this pain:
22
+
23
+ - You configure GitHub MCP in Claude Desktop. Then you switch to Cursor and have to do it again.
24
+ - You add five servers to Claude. Want them in Windsurf too? Edit a different config file.
25
+ - A new AI agent ships? Re-paste every server config from scratch.
26
+
27
+ **Launcher fixes that.** It's one MCP entry that fans out to every server you've added, in every AI client.
28
+
29
+ ```
30
+ Claude Cursor Windsurf Copilot Gemini
31
+ \ \ | / /
32
+ ┌──────────────────────────┐
33
+ │ @curatedmcp/launcher │ ← one config in each agent
34
+ │ (the MCP hub) │
35
+ └────┬──────┬──────┬───────┘
36
+ │ │ │
37
+ GitHub Postgres Stripe ← `launcher add`'d once, available everywhere
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Install (60 seconds)
43
+
44
+ ### 1. Add Launcher to your AI client
45
+
46
+ Drop this entry into your MCP config:
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "curatedmcp": {
52
+ "command": "npx",
53
+ "args": ["-y", "@curatedmcp/launcher"]
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ Config file location:
60
+
61
+ | Client | Path |
62
+ | --------------- | --------------------------------------------------------------------- |
63
+ | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (mac) / `%APPDATA%\Claude\claude_desktop_config.json` (win) |
64
+ | Cursor | `~/.cursor/mcp.json` |
65
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
66
+ | Claude Code | `~/.claude/mcp.json` (or `.claude/mcp.json` per-project) |
67
+
68
+ ### 2. Add servers to your stack
69
+
70
+ ```bash
71
+ npx @curatedmcp/launcher add github
72
+ # Prompts for GITHUB_TOKEN
73
+
74
+ npx @curatedmcp/launcher add postgres --env DATABASE_URL=postgres://...
75
+ npx @curatedmcp/launcher list
76
+ ```
77
+
78
+ ### 3. Restart your AI client
79
+
80
+ Tools appear with a `<slug>__` prefix:
81
+
82
+ - `github__create_issue`
83
+ - `postgres__query`
84
+ - `filesystem__read_file`
85
+
86
+ That's it. Add more servers any time — just `add` and restart.
87
+
88
+ ---
89
+
90
+ ## CLI Reference
91
+
92
+ ```
93
+ launcher # Run as MCP server (used by AI clients)
94
+ launcher init # Print the config snippet for your AI client
95
+ launcher add <slug> # Add a server from the CuratedMCP catalog
96
+ --env KEY=value # Pre-supply env vars (otherwise prompted)
97
+ launcher remove <slug> # Remove a server from your stack
98
+ launcher list # Show your stack
99
+ launcher --version # Print version
100
+ launcher --help # Print help
101
+ ```
102
+
103
+ ---
104
+
105
+ ## How it works
106
+
107
+ 1. Your AI client launches `npx @curatedmcp/launcher` over stdio (one MCP entry, like any other).
108
+ 2. Launcher reads `~/.curatedmcp/stack.json` and **spawns each registered server as a child process** over stdio.
109
+ 3. On `tools/list`, Launcher **aggregates** every child's tools and returns them prefixed with the server's slug.
110
+ 4. On `tools/call`, Launcher **routes** the request to the matching child by name prefix and forwards the response unchanged.
111
+
112
+ This makes Launcher invisible to the agent — it sees one MCP server with all the tools — while behind the scenes you've got N independent processes, isolated, each with its own credentials.
113
+
114
+ ---
115
+
116
+ ## Config file
117
+
118
+ `~/.curatedmcp/stack.json` — plain JSON, hand-editable, version-controllable:
119
+
120
+ ```json
121
+ {
122
+ "version": 1,
123
+ "entries": [
124
+ {
125
+ "slug": "github",
126
+ "name": "GitHub",
127
+ "command": "npx",
128
+ "args": ["-y", "@modelcontextprotocol/server-github"],
129
+ "env": { "GITHUB_TOKEN": "ghp_xxxxxxxxxxxx" },
130
+ "addedAt": "2026-05-01T10:14:00.000Z"
131
+ }
132
+ ]
133
+ }
134
+ ```
135
+
136
+ Set `"disabled": true` on an entry to skip it without removing it.
137
+
138
+ ---
139
+
140
+ ## In-agent discovery
141
+
142
+ Launcher itself exposes 5 discovery tools to your AI client, so you can ask the agent:
143
+
144
+ > "Find me an MCP server for Postgres."
145
+ > "What's the best Stripe MCP?"
146
+ > "Add the Postgres MCP server to my stack."
147
+
148
+ The agent uses `search_servers`, `get_server_details`, and `add_to_stack` to do all of that without you leaving the chat.
149
+
150
+ ---
151
+
152
+ ## Privacy
153
+
154
+ - **All config is local** at `~/.curatedmcp/stack.json`. No cloud sync, no account.
155
+ - **Anonymous telemetry only** (event names like "search", "add"). Disable with `--no-telemetry` or `CURATOR_TELEMETRY=false`.
156
+ - A persistent UUID is stored at `~/.curatedmcp/launcher.json` for de-duplication.
157
+
158
+ ---
159
+
160
+ ## Compatibility
161
+
162
+ - Works with Claude Desktop, Claude Code, Cursor, Windsurf, Copilot, Gemini, OpenAI Agents — anything that supports MCP over stdio.
163
+ - Node.js ≥ 18.
164
+ - Single dependency: `@modelcontextprotocol/sdk`.
165
+
166
+ ---
167
+
168
+ ## Links
169
+
170
+ - 🌐 [curatedmcp.com/launcher](https://curatedmcp.com/launcher)
171
+ - 📚 [Marketplace](https://curatedmcp.com/marketplace)
172
+ - 🐙 [GitHub](https://github.com/curatedmcp/launcher)
173
+ - 💬 [Issues](https://github.com/curatedmcp/launcher/issues)
174
+
175
+ MIT licensed.
@@ -0,0 +1,5 @@
1
+ export declare function getCatalog(): Promise<{
2
+ slugs: Set<string>;
3
+ npm: Set<string>;
4
+ }>;
5
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/audit/catalog.ts"],"names":[],"mappings":"AA0DA,wBAAsB,UAAU,IAAI,OAAO,CAAC;IAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAkBpF"}
@@ -0,0 +1,69 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import * as https from "https";
5
+ const CATALOG_URL = "https://www.curatedmcp.com/api/catalog";
6
+ const CACHE_DIR = path.join(os.homedir(), ".curatedmcp");
7
+ const CACHE_FILE = path.join(CACHE_DIR, "catalog.json");
8
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
9
+ function readCache() {
10
+ try {
11
+ const raw = fs.readFileSync(CACHE_FILE, "utf-8");
12
+ return JSON.parse(raw);
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ function writeCache(data) {
19
+ try {
20
+ if (!fs.existsSync(CACHE_DIR))
21
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
22
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(data), "utf-8");
23
+ }
24
+ catch {
25
+ // Cache write failure is non-fatal
26
+ }
27
+ }
28
+ function fetchCatalog() {
29
+ return new Promise((resolve) => {
30
+ const req = https.get(CATALOG_URL, { timeout: 5000 }, (res) => {
31
+ let body = "";
32
+ res.on("data", (chunk) => { body += chunk.toString(); });
33
+ res.on("end", () => {
34
+ try {
35
+ const json = JSON.parse(body);
36
+ resolve(Array.isArray(json.servers) ? json.servers : []);
37
+ }
38
+ catch {
39
+ resolve([]);
40
+ }
41
+ });
42
+ });
43
+ req.on("error", () => resolve([]));
44
+ req.on("timeout", () => { req.destroy(); resolve([]); });
45
+ });
46
+ }
47
+ export async function getCatalog() {
48
+ // Check cache first
49
+ const cached = readCache();
50
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
51
+ return buildSets(cached.servers);
52
+ }
53
+ // Fetch fresh
54
+ const servers = await fetchCatalog();
55
+ if (servers.length > 0) {
56
+ writeCache({ fetchedAt: Date.now(), servers });
57
+ return buildSets(servers);
58
+ }
59
+ // Fall back to stale cache if fetch failed
60
+ if (cached)
61
+ return buildSets(cached.servers);
62
+ return { slugs: new Set(), npm: new Set() };
63
+ }
64
+ function buildSets(servers) {
65
+ const slugs = new Set(servers.map((s) => s.slug.toLowerCase()));
66
+ const npm = new Set(servers.filter((s) => s.npm).map((s) => s.npm));
67
+ return { slugs, npm };
68
+ }
69
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1,10 @@
1
+ import type { AuditReport } from "./types.js";
2
+ export type { AuditReport, AnalyzedServer } from "./types.js";
3
+ /**
4
+ * Run a full MCP security scan of the local machine.
5
+ * Pure data — no printing — so callers (CLI display, sync forwarding) decide output.
6
+ */
7
+ export declare function runAudit(opts?: {
8
+ offline?: boolean;
9
+ }): Promise<AuditReport>;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,YAAY,CAAC;AAE9D,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE9D;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,OAAO,CAAC,WAAW,CAAC,CAyBtB"}
@@ -0,0 +1,32 @@
1
+ import { scanConfigs } from "./scanner.js";
2
+ import { analyzeServer } from "./risk.js";
3
+ import { getCatalog } from "./catalog.js";
4
+ /**
5
+ * Run a full MCP security scan of the local machine.
6
+ * Pure data — no printing — so callers (CLI display, sync forwarding) decide output.
7
+ */
8
+ export async function runAudit(opts = {}) {
9
+ const catalog = opts.offline
10
+ ? { slugs: new Set(), npm: new Set() }
11
+ : await getCatalog();
12
+ const configFiles = scanConfigs();
13
+ const allServers = [];
14
+ const foundFiles = [];
15
+ for (const { filePath, servers } of configFiles) {
16
+ if (servers.length > 0)
17
+ foundFiles.push(filePath);
18
+ for (const server of servers) {
19
+ allServers.push(analyzeServer(server, catalog.slugs, catalog.npm));
20
+ }
21
+ }
22
+ return {
23
+ scannedAt: new Date().toLocaleString(),
24
+ configFiles: foundFiles,
25
+ totalServers: allServers.length,
26
+ high: allServers.filter((s) => s.level === "HIGH"),
27
+ medium: allServers.filter((s) => s.level === "MEDIUM"),
28
+ verified: allServers.filter((s) => s.level === "VERIFIED"),
29
+ unverified: allServers.filter((s) => s.level === "LOW"),
30
+ };
31
+ }
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ import type { AuditReport } from "./types.js";
2
+ export declare function printReport(report: AuditReport, opts?: {
3
+ synced?: boolean;
4
+ }): Promise<void>;
5
+ export declare function printJson(report: AuditReport): void;
6
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/audit/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,YAAY,CAAC;AAqB9D,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9B,OAAO,CAAC,IAAI,CAAC,CA8Df;AAaD,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAEnD"}
@@ -0,0 +1,79 @@
1
+ // Chalk is ESM-only in v5, imported dynamically.
2
+ let chalk;
3
+ async function getChalk() {
4
+ if (!chalk) {
5
+ const m = await import("chalk");
6
+ chalk = m.default;
7
+ }
8
+ return chalk;
9
+ }
10
+ const FLAG_LABELS = {
11
+ FILE_SYSTEM_ACCESS: "FILE_SYSTEM_ACCESS",
12
+ KEYCHAIN_ACCESS: "KEYCHAIN_ACCESS",
13
+ CREDENTIAL_IN_ENV: "CREDENTIAL_IN_ENV",
14
+ NETWORK_ACCESS: "NETWORK_ACCESS",
15
+ UNVERIFIED: "UNVERIFIED",
16
+ };
17
+ export async function printReport(report, opts = {}) {
18
+ const c = await getChalk();
19
+ console.log("\n" + c.bold("MCP Security Audit") + c.dim(` — ${report.scannedAt}`));
20
+ console.log(c.dim("━".repeat(50)));
21
+ console.log();
22
+ if (report.configFiles.length === 0) {
23
+ console.log(c.yellow("No MCP configuration files found on this machine."));
24
+ console.log(c.dim("Install Claude Desktop, Cursor, or Claude Code to get started."));
25
+ console.log();
26
+ return;
27
+ }
28
+ console.log(c.dim(`Found ${report.configFiles.length} config file${report.configFiles.length !== 1 ? "s" : ""}. ` +
29
+ `${report.totalServers} server${report.totalServers !== 1 ? "s" : ""} detected.`));
30
+ for (const f of report.configFiles) {
31
+ console.log(c.dim(` ✓ ${f}`));
32
+ }
33
+ console.log();
34
+ if (report.high.length > 0) {
35
+ console.log(c.red.bold(`HIGH RISK (${report.high.length})`));
36
+ for (const s of report.high)
37
+ printServer(c, s, "red");
38
+ console.log();
39
+ }
40
+ if (report.medium.length > 0) {
41
+ console.log(c.yellow.bold(`MEDIUM RISK (${report.medium.length})`));
42
+ for (const s of report.medium)
43
+ printServer(c, s, "yellow");
44
+ console.log();
45
+ }
46
+ if (report.unverified.length > 0) {
47
+ console.log(c.dim(`UNVERIFIED (${report.unverified.length}) — not in the CuratedMCP catalog`));
48
+ for (const s of report.unverified) {
49
+ console.log(c.dim(` ? ${s.name}`));
50
+ console.log(c.dim(` ${s.sourceFile}`));
51
+ }
52
+ console.log();
53
+ }
54
+ if (report.verified.length > 0) {
55
+ console.log(c.green(`VERIFIED (${report.verified.length})`));
56
+ const names = report.verified.map((s) => s.name).join(", ");
57
+ console.log(c.dim(` ✓ ${names}`));
58
+ console.log();
59
+ }
60
+ console.log(c.dim("─".repeat(50)));
61
+ if (opts.synced) {
62
+ console.log(c.green(" ✓ Scan synced to your CuratedMCP account."));
63
+ }
64
+ else {
65
+ console.log(c.dim(" Sign in to sync scans & get alerts: ") + c.cyan("curatedmcp login"));
66
+ }
67
+ console.log();
68
+ }
69
+ function printServer(c, s, color) {
70
+ const flagStr = s.flags.map((f) => FLAG_LABELS[f]).join(", ");
71
+ console.log(c[color](` ⚠ ${s.name}`) + c.dim(` — ${flagStr}`));
72
+ console.log(c.dim(` ${s.sourceFile}`));
73
+ if (s.command)
74
+ console.log(c.dim(` ${s.command}`));
75
+ }
76
+ export function printJson(report) {
77
+ console.log(JSON.stringify(report, null, 2));
78
+ }
79
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1,3 @@
1
+ import type { MCPServerEntry, AnalyzedServer } from "./types.js";
2
+ export declare function analyzeServer(server: MCPServerEntry, verifiedSlugs: Set<string>, verifiedNpm: Set<string>): AnalyzedServer;
3
+ //# sourceMappingURL=risk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk.d.ts","sourceRoot":"","sources":["../../src/audit/risk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAuB,cAAc,EAAE,MAAM,YAAY,CAAC;AAkBtF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,EAC1B,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,GACvB,cAAc,CAyDhB"}
@@ -0,0 +1,68 @@
1
+ const CREDENTIAL_PATTERN = /SECRET|TOKEN|PASSWORD|KEY|APIKEY|API_KEY|PRIVATE|AUTH/i;
2
+ const FILESYSTEM_ARGS = /--allow-write|--allow-read|--allow-all|\/Users|\/home|\/var|C:\\/i;
3
+ const KEYCHAIN_CMDS = /keychain|security\s+find|secret-tool|kwallet/i;
4
+ const NETWORK_CMDS = /curl|wget|fetch|http/i;
5
+ function extractNpmPackage(command, args) {
6
+ if (!command)
7
+ return undefined;
8
+ // "npx -y @scope/pkg" or "npx pkg"
9
+ if (command === "npx" && args?.length) {
10
+ const pkg = args.find((a) => !a.startsWith("-"));
11
+ return pkg;
12
+ }
13
+ // "node /path/to/script" — not an npm package
14
+ return undefined;
15
+ }
16
+ export function analyzeServer(server, verifiedSlugs, verifiedNpm) {
17
+ const flags = [];
18
+ const cmdLine = [server.command, ...(server.args ?? [])].join(" ");
19
+ const npmPackage = extractNpmPackage(server.command, server.args);
20
+ // File system access
21
+ if (FILESYSTEM_ARGS.test(cmdLine)) {
22
+ flags.push("FILE_SYSTEM_ACCESS");
23
+ }
24
+ // Keychain access
25
+ if (KEYCHAIN_CMDS.test(cmdLine)) {
26
+ flags.push("KEYCHAIN_ACCESS");
27
+ }
28
+ // Credentials in env block
29
+ if (server.env) {
30
+ const envKeys = Object.keys(server.env).join(" ");
31
+ if (CREDENTIAL_PATTERN.test(envKeys)) {
32
+ flags.push("CREDENTIAL_IN_ENV");
33
+ }
34
+ }
35
+ // Network access (non-npx commands that fetch data)
36
+ if (server.command !== "npx" && server.command !== "node" && NETWORK_CMDS.test(cmdLine)) {
37
+ flags.push("NETWORK_ACCESS");
38
+ }
39
+ // Unverified — not in catalog
40
+ const isVerified = verifiedSlugs.has(server.name.toLowerCase()) ||
41
+ (npmPackage != null && verifiedNpm.has(npmPackage));
42
+ if (!isVerified) {
43
+ flags.push("UNVERIFIED");
44
+ }
45
+ // Determine overall level
46
+ let level;
47
+ if (flags.some((f) => f !== "UNVERIFIED") && flags.includes("UNVERIFIED")) {
48
+ level = "HIGH";
49
+ }
50
+ else if (flags.some((f) => f !== "UNVERIFIED")) {
51
+ level = "MEDIUM"; // has risk flags but is a verified server
52
+ }
53
+ else if (flags.includes("UNVERIFIED")) {
54
+ level = "LOW"; // unknown server but no other flags
55
+ }
56
+ else {
57
+ level = "VERIFIED";
58
+ }
59
+ return {
60
+ name: server.name,
61
+ sourceFile: server.sourceFile,
62
+ command: [server.command, ...(server.args ?? [])].filter(Boolean).join(" ") || undefined,
63
+ flags,
64
+ level,
65
+ ...(npmPackage ? { npmPackage } : {}),
66
+ };
67
+ }
68
+ //# sourceMappingURL=risk.js.map
@@ -0,0 +1,8 @@
1
+ import type { MCPServerEntry } from "./types.js";
2
+ interface ConfigFile {
3
+ filePath: string;
4
+ servers: MCPServerEntry[];
5
+ }
6
+ export declare function scanConfigs(): ConfigFile[];
7
+ export {};
8
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/audit/scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAgED,wBAAgB,WAAW,IAAI,UAAU,EAAE,CAS1C"}
@@ -0,0 +1,69 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ // All known MCP config locations, keyed by platform
5
+ function getConfigPaths() {
6
+ const home = os.homedir();
7
+ const platform = process.platform;
8
+ const appdata = process.env.APPDATA ?? "";
9
+ const paths = [
10
+ // Claude Code — user-level
11
+ path.join(home, ".claude", "mcp.json"),
12
+ // Claude Code — project-level (current working directory)
13
+ path.join(process.cwd(), ".claude", "mcp.json"),
14
+ ];
15
+ if (platform === "darwin" || platform === "linux") {
16
+ paths.push(
17
+ // Claude Desktop (macOS)
18
+ path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json"),
19
+ // Cursor (macOS/Linux)
20
+ path.join(home, ".cursor", "mcp.json"),
21
+ // Windsurf (macOS/Linux)
22
+ path.join(home, ".codeium", "windsurf", "mcp_config.json"));
23
+ }
24
+ if (platform === "win32") {
25
+ paths.push(
26
+ // Claude Desktop (Windows)
27
+ path.join(appdata, "Claude", "claude_desktop_config.json"),
28
+ // Cursor (Windows)
29
+ path.join(appdata, ".cursor", "mcp.json"),
30
+ // Windsurf (Windows)
31
+ path.join(appdata, "Codeium", "Windsurf", "mcp_config.json"));
32
+ }
33
+ return paths;
34
+ }
35
+ function parseConfig(filePath) {
36
+ try {
37
+ const raw = fs.readFileSync(filePath, "utf-8");
38
+ const json = JSON.parse(raw);
39
+ const servers = json.mcpServers ?? {};
40
+ return Object.entries(servers).map(([name, def]) => {
41
+ const d = def;
42
+ return {
43
+ name,
44
+ command: typeof d.command === "string" ? d.command : undefined,
45
+ args: Array.isArray(d.args) ? d.args : undefined,
46
+ env: typeof d.env === "object" && d.env !== null
47
+ ? d.env
48
+ : undefined,
49
+ url: typeof d.url === "string" ? d.url : undefined,
50
+ type: typeof d.type === "string" ? d.type : undefined,
51
+ sourceFile: filePath,
52
+ };
53
+ });
54
+ }
55
+ catch {
56
+ return [];
57
+ }
58
+ }
59
+ export function scanConfigs() {
60
+ const results = [];
61
+ for (const filePath of getConfigPaths()) {
62
+ if (fs.existsSync(filePath)) {
63
+ const servers = parseConfig(filePath);
64
+ results.push({ filePath, servers });
65
+ }
66
+ }
67
+ return results;
68
+ }
69
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1,29 @@
1
+ export type RiskFlag = "FILE_SYSTEM_ACCESS" | "KEYCHAIN_ACCESS" | "CREDENTIAL_IN_ENV" | "NETWORK_ACCESS" | "UNVERIFIED";
2
+ export type RiskLevel = "HIGH" | "MEDIUM" | "LOW" | "VERIFIED";
3
+ export interface MCPServerEntry {
4
+ name: string;
5
+ command?: string;
6
+ args?: string[];
7
+ env?: Record<string, string>;
8
+ url?: string;
9
+ type?: string;
10
+ sourceFile: string;
11
+ }
12
+ export interface AnalyzedServer {
13
+ name: string;
14
+ sourceFile: string;
15
+ command?: string;
16
+ flags: RiskFlag[];
17
+ level: RiskLevel;
18
+ npmPackage?: string;
19
+ }
20
+ export interface AuditReport {
21
+ scannedAt: string;
22
+ configFiles: string[];
23
+ totalServers: number;
24
+ high: AnalyzedServer[];
25
+ medium: AnalyzedServer[];
26
+ verified: AnalyzedServer[];
27
+ unverified: AnalyzedServer[];
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/audit/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAChB,oBAAoB,GACpB,iBAAiB,GACjB,mBAAmB,GACnB,gBAAgB,GAChB,YAAY,CAAC;AAEjB,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,UAAU,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/dist/auth.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ export declare const API_URL: string;
2
+ export interface StoredAuth {
3
+ token: string;
4
+ userId?: string;
5
+ email?: string;
6
+ savedAt: string;
7
+ }
8
+ export interface WhoAmI {
9
+ userId: string;
10
+ email?: string | null;
11
+ teams: {
12
+ slug: string;
13
+ name: string;
14
+ role: string;
15
+ }[];
16
+ }
17
+ export declare function loadAuth(): StoredAuth | null;
18
+ export declare function getToken(): string | null;
19
+ export declare function saveAuth(auth: StoredAuth): void;
20
+ export declare function clearAuth(): void;
21
+ /** Validate a token against the control plane. Returns identity + teams or null. */
22
+ export declare function whoami(token: string): Promise<WhoAmI | null>;
23
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,QACyC,CAAC;AAK9D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvD;AAED,wBAAgB,QAAQ,IAAI,UAAU,GAAG,IAAI,CAM5C;AAED,wBAAgB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAExC;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAQ/C;AAED,wBAAgB,SAAS,IAAI,IAAI,CAMhC;AAED,oFAAoF;AACpF,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWlE"}