@chainpatrol/mcp 1.10.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/CHANGELOG.md +17 -0
- package/README.md +69 -0
- package/dist/chainpatrol-config.d.ts +29 -0
- package/dist/chainpatrol-config.js +15 -0
- package/dist/chainpatrol-config.js.map +1 -0
- package/dist/chunk-4VHYP4ZH.js +39 -0
- package/dist/chunk-4VHYP4ZH.js.map +1 -0
- package/dist/chunk-AHMNJBKL.js +4468 -0
- package/dist/chunk-AHMNJBKL.js.map +1 -0
- package/dist/index.d.ts +279 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/stdio-bin.d.ts +1 -0
- package/dist/stdio-bin.js +54 -0
- package/dist/stdio-bin.js.map +1 -0
- package/package.json +63 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @chainpatrol/mcp
|
|
2
|
+
|
|
3
|
+
## 1.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8d8861e: Add `@chainpatrol/mcp`, an MCP server exposing every public API operation as a
|
|
8
|
+
tool, and a `chainpatrol mcp` command that serves it over stdio.
|
|
9
|
+
|
|
10
|
+
The tool surface is generated from the external API router rather than written
|
|
11
|
+
by hand, so agents reach the same capabilities the API exposes. Large value
|
|
12
|
+
lists and long explanatory prose are served as MCP resources instead of being
|
|
13
|
+
inlined in every schema, and workflow guides (organization healthcheck, trend
|
|
14
|
+
search, weekly customer-success sweep) ship as MCP prompts.
|
|
15
|
+
|
|
16
|
+
Point an MCP client at `chainpatrol mcp` after `chainpatrol login`, or set
|
|
17
|
+
`CHAINPATROL_API_KEY` for a service account.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @chainpatrol/mcp
|
|
2
|
+
|
|
3
|
+
The official ChainPatrol [MCP](https://modelcontextprotocol.io) server. Every
|
|
4
|
+
public API operation is available as a tool, so an agent never has to drop down
|
|
5
|
+
to raw HTTP.
|
|
6
|
+
|
|
7
|
+
## Use it
|
|
8
|
+
|
|
9
|
+
The server ships with the CLI, which already handles login:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @chainpatrol/cli
|
|
13
|
+
chainpatrol login
|
|
14
|
+
chainpatrol mcp # speaks MCP on stdio
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Point an MCP client at that command. For Claude Code:
|
|
18
|
+
|
|
19
|
+
```jsonc
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"chainpatrol": {
|
|
23
|
+
"command": "chainpatrol",
|
|
24
|
+
"args": ["mcp"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For a service account, set `CHAINPATROL_API_KEY` instead of logging in.
|
|
31
|
+
|
|
32
|
+
## What it exposes
|
|
33
|
+
|
|
34
|
+
- **Tools** — one per public API operation: asset checks and search, reports,
|
|
35
|
+
proposal review, detections and detection configs, takedowns, threats,
|
|
36
|
+
metrics, healthchecks, and organization management.
|
|
37
|
+
- **Resources** — the large value lists (`chainpatrol://enums/asset_type`) and
|
|
38
|
+
the guides explaining what a proposal label or reject reason means.
|
|
39
|
+
- **Prompts** — the organization healthcheck, trend search, and weekly
|
|
40
|
+
customer-success sweep.
|
|
41
|
+
|
|
42
|
+
Most tools are scoped to one organization and there is no implicit default, so
|
|
43
|
+
call `user_orgs` first and pass the slug explicitly.
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
| Variable | Purpose |
|
|
48
|
+
| ------------------------ | ------------------------------------------------------------- |
|
|
49
|
+
| `CHAINPATROL_API_KEY` | API key; takes precedence over a stored login. |
|
|
50
|
+
| `CHAINPATROL_API_URL` | API base URL. Defaults to `https://app.chainpatrol.io`. |
|
|
51
|
+
| `CHAINPATROL_CONFIG_DIR` | Where credentials are read from. |
|
|
52
|
+
| `CHAINPATROL_MCP_TOOLS` | Comma-separated tool names to expose. Everything, by default. |
|
|
53
|
+
|
|
54
|
+
`CHAINPATROL_MCP_TOOLS` exists only to trade breadth for context budget — it is
|
|
55
|
+
not an access control. Authorization is enforced by the API against the
|
|
56
|
+
credential in use. Both entry points read it: `chainpatrol-mcp`, and
|
|
57
|
+
`chainpatrol mcp`, where an explicit `--tools` takes precedence.
|
|
58
|
+
|
|
59
|
+
## The tool surface is generated
|
|
60
|
+
|
|
61
|
+
`src/generated/tools.json` is produced from the external tRPC router by
|
|
62
|
+
`yarn tools:generate`, so a tool exists for every public operation by
|
|
63
|
+
construction rather than by hand. `yarn tools:check` fails when the committed
|
|
64
|
+
manifest no longer matches the router, and runs in CI — a new API endpoint
|
|
65
|
+
cannot ship without the MCP surface catching up.
|
|
66
|
+
|
|
67
|
+
Two deliberate gaps, both listed with reasons in the manifest's `excluded`
|
|
68
|
+
array: `/internal/*` paths, and the three inbound webhook receivers
|
|
69
|
+
(Cloudflare, GitHub, Notion) that third-party services call and users do not.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
declare const DEFAULT_API_URL = "https://app.chainpatrol.io";
|
|
2
|
+
interface StoredConfig {
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
defaultOrg?: string;
|
|
5
|
+
}
|
|
6
|
+
declare function configDir(): string;
|
|
7
|
+
declare function readJsonFile<T>(path: string): T | null;
|
|
8
|
+
/**
|
|
9
|
+
* The configuration as written on disk, with no environment applied.
|
|
10
|
+
*
|
|
11
|
+
* Use this for read-modify-write: saving the *effective* config would persist a
|
|
12
|
+
* one-off `CHAINPATROL_API_URL` as the stored default, so a later command run
|
|
13
|
+
* without the variable would keep talking to the wrong API.
|
|
14
|
+
*/
|
|
15
|
+
declare function readStoredConfig(): StoredConfig;
|
|
16
|
+
/**
|
|
17
|
+
* The effective API URL.
|
|
18
|
+
*
|
|
19
|
+
* `CHAINPATROL_API_URL` takes precedence over a stored `apiUrl` — MCP clients
|
|
20
|
+
* launch a server with an environment rather than a config file — and is read
|
|
21
|
+
* on each call rather than once at module load, so a value set after import
|
|
22
|
+
* still applies. A blank value counts as unset.
|
|
23
|
+
*
|
|
24
|
+
* Takes an already-read `stored` config when the caller has one, so resolving
|
|
25
|
+
* the whole configuration does not read the same file twice.
|
|
26
|
+
*/
|
|
27
|
+
declare function resolveApiUrl(stored?: StoredConfig): string;
|
|
28
|
+
|
|
29
|
+
export { DEFAULT_API_URL, type StoredConfig, configDir, readJsonFile, readStoredConfig, resolveApiUrl };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_API_URL,
|
|
3
|
+
configDir,
|
|
4
|
+
readJsonFile,
|
|
5
|
+
readStoredConfig,
|
|
6
|
+
resolveApiUrl
|
|
7
|
+
} from "./chunk-4VHYP4ZH.js";
|
|
8
|
+
export {
|
|
9
|
+
DEFAULT_API_URL,
|
|
10
|
+
configDir,
|
|
11
|
+
readJsonFile,
|
|
12
|
+
readStoredConfig,
|
|
13
|
+
resolveApiUrl
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=chainpatrol-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/chainpatrol-config.ts
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var DEFAULT_API_URL = "https://app.chainpatrol.io";
|
|
6
|
+
function configDir() {
|
|
7
|
+
if (process.env.CHAINPATROL_CONFIG_DIR) return process.env.CHAINPATROL_CONFIG_DIR;
|
|
8
|
+
const legacy = join(homedir(), ".chainpatrol");
|
|
9
|
+
const xdg = join(
|
|
10
|
+
process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"),
|
|
11
|
+
"chainpatrol"
|
|
12
|
+
);
|
|
13
|
+
return existsSync(legacy) && !existsSync(xdg) ? legacy : xdg;
|
|
14
|
+
}
|
|
15
|
+
function readJsonFile(path) {
|
|
16
|
+
if (!existsSync(path)) return null;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function readStoredConfig() {
|
|
24
|
+
const stored = readJsonFile(join(configDir(), "config.json"));
|
|
25
|
+
return { apiUrl: DEFAULT_API_URL, ...stored };
|
|
26
|
+
}
|
|
27
|
+
function resolveApiUrl(stored = readStoredConfig()) {
|
|
28
|
+
const fromEnv = process.env.CHAINPATROL_API_URL?.trim();
|
|
29
|
+
return fromEnv || stored.apiUrl;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
DEFAULT_API_URL,
|
|
34
|
+
configDir,
|
|
35
|
+
readJsonFile,
|
|
36
|
+
readStoredConfig,
|
|
37
|
+
resolveApiUrl
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=chunk-4VHYP4ZH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/chainpatrol-config.ts"],"sourcesContent":["/**\n * The on-disk configuration shared by every ChainPatrol entry point.\n *\n * `chainpatrol`, `chainpatrol mcp` and the standalone `chainpatrol-mcp` binary\n * all read the same `config.json`, so the rules for finding it and for letting\n * `CHAINPATROL_API_URL` override it live here rather than in each of them. They\n * were duplicated once and drifted on exactly that precedence, which is the\n * reason this module exists.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const DEFAULT_API_URL = \"https://app.chainpatrol.io\";\n\nexport interface StoredConfig {\n apiUrl: string;\n defaultOrg?: string;\n}\n\nexport function configDir(): string {\n if (process.env.CHAINPATROL_CONFIG_DIR) return process.env.CHAINPATROL_CONFIG_DIR;\n\n // The legacy path wins only when it is the one that exists.\n const legacy = join(homedir(), \".chainpatrol\");\n const xdg = join(\n process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"),\n \"chainpatrol\",\n );\n return existsSync(legacy) && !existsSync(xdg) ? legacy : xdg;\n}\n\nexport function readJsonFile<T>(path: string): T | null {\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf-8\")) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * The configuration as written on disk, with no environment applied.\n *\n * Use this for read-modify-write: saving the *effective* config would persist a\n * one-off `CHAINPATROL_API_URL` as the stored default, so a later command run\n * without the variable would keep talking to the wrong API.\n */\nexport function readStoredConfig(): StoredConfig {\n const stored = readJsonFile<Partial<StoredConfig>>(join(configDir(), \"config.json\"));\n return { apiUrl: DEFAULT_API_URL, ...stored };\n}\n\n/**\n * The effective API URL.\n *\n * `CHAINPATROL_API_URL` takes precedence over a stored `apiUrl` — MCP clients\n * launch a server with an environment rather than a config file — and is read\n * on each call rather than once at module load, so a value set after import\n * still applies. A blank value counts as unset.\n *\n * Takes an already-read `stored` config when the caller has one, so resolving\n * the whole configuration does not read the same file twice.\n */\nexport function resolveApiUrl(stored: StoredConfig = readStoredConfig()): string {\n const fromEnv = process.env.CHAINPATROL_API_URL?.trim();\n return fromEnv || stored.apiUrl;\n}\n"],"mappings":";AASA,SAAS,YAAY,oBAAoB;AACzC,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,IAAM,kBAAkB;AAOxB,SAAS,YAAoB;AAClC,MAAI,QAAQ,IAAI,uBAAwB,QAAO,QAAQ,IAAI;AAG3D,QAAM,SAAS,KAAK,QAAQ,GAAG,cAAc;AAC7C,QAAM,MAAM;AAAA,IACV,QAAQ,IAAI,mBAAmB,KAAK,QAAQ,GAAG,SAAS;AAAA,IACxD;AAAA,EACF;AACA,SAAO,WAAW,MAAM,KAAK,CAAC,WAAW,GAAG,IAAI,SAAS;AAC3D;AAEO,SAAS,aAAgB,MAAwB;AACtD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASO,SAAS,mBAAiC;AAC/C,QAAM,SAAS,aAAoC,KAAK,UAAU,GAAG,aAAa,CAAC;AACnF,SAAO,EAAE,QAAQ,iBAAiB,GAAG,OAAO;AAC9C;AAaO,SAAS,cAAc,SAAuB,iBAAiB,GAAW;AAC/E,QAAM,UAAU,QAAQ,IAAI,qBAAqB,KAAK;AACtD,SAAO,WAAW,OAAO;AAC3B;","names":[]}
|