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.
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/audit/catalog.d.ts +5 -0
- package/dist/audit/catalog.d.ts.map +1 -0
- package/dist/audit/catalog.js +69 -0
- package/dist/audit/index.d.ts +10 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +32 -0
- package/dist/audit/report.d.ts +6 -0
- package/dist/audit/report.d.ts.map +1 -0
- package/dist/audit/report.js +79 -0
- package/dist/audit/risk.d.ts +3 -0
- package/dist/audit/risk.d.ts.map +1 -0
- package/dist/audit/risk.js +68 -0
- package/dist/audit/scanner.d.ts +8 -0
- package/dist/audit/scanner.d.ts.map +1 -0
- package/dist/audit/scanner.js +69 -0
- package/dist/audit/types.d.ts +29 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +2 -0
- package/dist/auth.d.ts +23 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +52 -0
- package/dist/cli/add.d.ts +18 -0
- package/dist/cli/add.d.ts.map +1 -0
- package/dist/cli/add.js +114 -0
- package/dist/cli/audit.d.ts +2 -0
- package/dist/cli/audit.d.ts.map +1 -0
- package/dist/cli/audit.js +58 -0
- package/dist/cli/guard.d.ts +2 -0
- package/dist/cli/guard.d.ts.map +1 -0
- package/dist/cli/guard.js +58 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +44 -0
- package/dist/cli/list.d.ts +5 -0
- package/dist/cli/list.d.ts.map +1 -0
- package/dist/cli/list.js +33 -0
- package/dist/cli/login.d.ts +6 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +43 -0
- package/dist/cli/remove.d.ts +6 -0
- package/dist/cli/remove.d.ts.map +1 -0
- package/dist/cli/remove.js +15 -0
- package/dist/cli/sync.d.ts +2 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +104 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +132 -0
- package/dist/guard/broker.d.ts +62 -0
- package/dist/guard/broker.d.ts.map +1 -0
- package/dist/guard/broker.js +147 -0
- package/dist/guard/dashboard.d.ts +14 -0
- package/dist/guard/dashboard.d.ts.map +1 -0
- package/dist/guard/dashboard.js +428 -0
- package/dist/guard/default-policy.json +33 -0
- package/dist/guard/index.d.ts +20 -0
- package/dist/guard/index.d.ts.map +1 -0
- package/dist/guard/index.js +61 -0
- package/dist/guard/logger.d.ts +30 -0
- package/dist/guard/logger.d.ts.map +1 -0
- package/dist/guard/logger.js +118 -0
- package/dist/guard/policy.d.ts +19 -0
- package/dist/guard/policy.d.ts.map +1 -0
- package/dist/guard/policy.js +108 -0
- package/dist/guard/proxy.d.ts +29 -0
- package/dist/guard/proxy.d.ts.map +1 -0
- package/dist/guard/proxy.js +109 -0
- package/dist/guard/types.d.ts +70 -0
- package/dist/guard/types.d.ts.map +1 -0
- package/dist/guard/types.js +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +259 -0
- package/dist/proxy.d.ts +122 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +165 -0
- package/dist/stack.d.ts +45 -0
- package/dist/stack.d.ts.map +1 -0
- package/dist/stack.js +93 -0
- package/dist/telemetry.d.ts +15 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +71 -0
- package/dist/tools/get-details.d.ts +14 -0
- package/dist/tools/get-details.d.ts.map +1 -0
- package/dist/tools/get-details.js +27 -0
- package/dist/tools/install.d.ts +2 -0
- package/dist/tools/install.d.ts.map +1 -0
- package/dist/tools/install.js +74 -0
- package/dist/tools/list-categories.d.ts +2 -0
- package/dist/tools/list-categories.d.ts.map +1 -0
- package/dist/tools/list-categories.js +13 -0
- package/dist/tools/search.d.ts +16 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +17 -0
- 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
|
+
[](https://www.npmjs.com/package/@curatedmcp/launcher)
|
|
4
|
+
[](https://www.npmjs.com/package/@curatedmcp/launcher)
|
|
5
|
+
[](https://github.com/oneprofile-dev/mcp-launcher/actions/workflows/test.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
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"}
|