@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.
@@ -0,0 +1,279 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ export { DEFAULT_API_URL, StoredConfig, configDir, readJsonFile, readStoredConfig, resolveApiUrl } from './chainpatrol-config.js';
3
+
4
+ /**
5
+ * The shape of the generated tool manifest.
6
+ *
7
+ * The manifest is produced by `scripts/generate-tools.ts`, which walks the
8
+ * external tRPC router and emits one entry per public API operation. It is
9
+ * committed to the repo so the published package carries no dependency on the
10
+ * private router package, and `yarn tools:check` fails CI when the two drift.
11
+ */
12
+ /** A JSON Schema object, as served to MCP clients in `tools/list`. */
13
+ type JsonSchema = {
14
+ type?: string;
15
+ properties?: Record<string, JsonSchema>;
16
+ required?: string[];
17
+ [key: string]: unknown;
18
+ };
19
+ type HttpMethod = "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
20
+ interface ToolDefinition {
21
+ /** MCP tool name, e.g. `asset_check`. Snake case of the tRPC procedure key. */
22
+ name: string;
23
+ /** tRPC procedure key on the external router, e.g. `assetCheck`. */
24
+ procedure: string;
25
+ /** REST method and path, used by the HTTP invoker. */
26
+ method: HttpMethod;
27
+ path: string;
28
+ /** Path parameter names extracted from `path`, e.g. `["assetId"]`. */
29
+ pathParams: string[];
30
+ /** Short human title, from the procedure's OpenAPI `summary`. */
31
+ title: string;
32
+ /** Tool description, from the procedure's OpenAPI `description`. */
33
+ description: string;
34
+ tags: string[];
35
+ /** True for tRPC queries — surfaced to clients as `readOnlyHint`. */
36
+ readOnly: boolean;
37
+ /**
38
+ * True when the API marks the operation deprecated. The tool is still served
39
+ * — it is a real capability — but the description says so up front so agents
40
+ * reach for the replacement instead.
41
+ */
42
+ deprecated: boolean;
43
+ /**
44
+ * True when the procedure's input parser accepts `undefined`.
45
+ *
46
+ * MCP has no way to say "this tool takes no arguments" other than an empty
47
+ * object schema, so `z.void()` and `z.object({})` are indistinguishable in
48
+ * `inputSchema` — and each rejects what the other accepts. Recorded here at
49
+ * generation time, where the router is available and `tools:check` makes a
50
+ * change visible in CI, so the in-process transport does not have to
51
+ * introspect tRPC internals at runtime to tell them apart.
52
+ *
53
+ * Absent rather than `false` when the input is required, to keep the
54
+ * manifest diff to the procedures this actually applies to.
55
+ */
56
+ acceptsNoInput?: true;
57
+ inputSchema: JsonSchema;
58
+ }
59
+ /**
60
+ * A large enum lifted out of the tool schemas.
61
+ *
62
+ * The asset-type enum alone is 92 values and appears in ten tools; inlining
63
+ * every copy cost ~17% of the whole `tools/list` payload. Schemas keep a few
64
+ * example values and point at these, which are served as MCP resources so the
65
+ * full list stays one fetch away.
66
+ */
67
+ interface EnumDefinition {
68
+ name: string;
69
+ values: string[];
70
+ /** Tool/property pairs the enum was lifted from, for the resource text. */
71
+ usedBy: string[];
72
+ }
73
+ /**
74
+ * Long explanatory prose moved out of an inline schema description.
75
+ *
76
+ * Nothing is lost: the text here is the original description verbatim, the
77
+ * schema keeps its first sentence plus a pointer to `uri`, and the enum of
78
+ * accepted values stays inline on the property.
79
+ */
80
+ interface GlossaryDefinition {
81
+ uri: string;
82
+ title: string;
83
+ text: string;
84
+ }
85
+ interface ToolManifest {
86
+ /** Bumped when the manifest's own shape changes. */
87
+ manifestVersion: 1;
88
+ tools: ToolDefinition[];
89
+ /** Operations deliberately not exposed, with the reason why. */
90
+ excluded: {
91
+ path: string;
92
+ method: string;
93
+ reason: string;
94
+ }[];
95
+ /** Large enums, served as `chainpatrol://enums/<name>` resources. */
96
+ enums: EnumDefinition[];
97
+ /** Long descriptions moved into `chainpatrol://glossary/<name>` resources. */
98
+ glossaries: GlossaryDefinition[];
99
+ }
100
+
101
+ /**
102
+ * Dispatches one tool call.
103
+ *
104
+ * Everything above this line — the tool list, the schemas, the descriptions,
105
+ * the prompts and resources — is shared by every deployment. Only dispatch
106
+ * differs: the remote server holds a tRPC caller and invokes the procedure in
107
+ * process, while the local server posts to the public REST API. Keeping that
108
+ * behind one function is what stops the two from drifting apart the way the
109
+ * SDK drifted from the API.
110
+ */
111
+ type ToolInvoker = (tool: ToolDefinition, args: Record<string, unknown>) => Promise<unknown>;
112
+ /** Raised for an API response that carries a message worth showing the model. */
113
+ declare class ToolInvocationError extends Error {
114
+ readonly status: number | undefined;
115
+ readonly details: unknown;
116
+ constructor(message: string, options?: {
117
+ status?: number;
118
+ details?: unknown;
119
+ cause?: unknown;
120
+ });
121
+ }
122
+
123
+ declare const manifest: ToolManifest;
124
+ interface CreateServerOptions {
125
+ invoker: ToolInvoker;
126
+ /**
127
+ * Restricts the exposed tools by name. Every public API operation is served
128
+ * by default — this exists only so a caller can trade breadth for context
129
+ * budget, never to gate capabilities.
130
+ */
131
+ only?: string[];
132
+ serverName?: string;
133
+ version?: string;
134
+ }
135
+ /**
136
+ * Above this, a result is cut short rather than handed to the model whole.
137
+ *
138
+ * A broad `asset_list` or `detection_list` can otherwise fill a client's
139
+ * context in a single call, with nothing in the response telling the model it
140
+ * should have paginated.
141
+ */
142
+ declare const MAX_RESULT_CHARS = 100000;
143
+ /**
144
+ * Builds a ChainPatrol MCP server over the generated tool manifest.
145
+ *
146
+ * The transport and the dispatch strategy are the caller's choice: pass an
147
+ * in-process invoker and a streamable-HTTP transport for the remote server, or
148
+ * an HTTP invoker and a stdio transport for the local one. Both see exactly the
149
+ * same tools.
150
+ */
151
+ declare function createChainPatrolMcpServer(options: CreateServerOptions): Server;
152
+
153
+ /** How the caller authenticates: an org/user API key, or a bearer token. */
154
+ type ApiCredential = {
155
+ kind: "api-key";
156
+ value: string;
157
+ } | {
158
+ kind: "bearer";
159
+ value: string;
160
+ };
161
+ type CredentialProvider = () => ApiCredential | Promise<ApiCredential>;
162
+ interface HttpInvokerOptions {
163
+ /** Defaults to `https://app.chainpatrol.io`. */
164
+ baseUrl?: string;
165
+ credential: CredentialProvider;
166
+ timeoutMs?: number;
167
+ fetchImpl?: typeof fetch;
168
+ }
169
+ /**
170
+ * Dispatches tool calls to the public REST API.
171
+ *
172
+ * Used by the local (stdio) server, which has no in-process access to the
173
+ * router. It talks to exactly the endpoints a customer's own script would, with
174
+ * the credentials the CLI already stores.
175
+ */
176
+ declare function createHttpInvoker(options: HttpInvokerOptions): ToolInvoker;
177
+
178
+ interface McpResource {
179
+ uri: string;
180
+ name: string;
181
+ title: string;
182
+ description: string;
183
+ mimeType: string;
184
+ text: string;
185
+ }
186
+ /**
187
+ * Builds the resource set served alongside the tools.
188
+ *
189
+ * Two kinds, both holding content that used to be inlined into every schema:
190
+ * the large enums (asset types, detection sources) and the glossaries that
191
+ * explain what a proposal label or reject reason means. Resources are fetched
192
+ * on demand, so this detail costs nothing until an agent needs it.
193
+ *
194
+ * Every resource exists because some tool's schema points at it, so `tools`
195
+ * narrows the set alongside the tool filter: with the referring tools gone the
196
+ * resource is unreachable prose, and listing it only spends the context budget
197
+ * the filter was set to save.
198
+ */
199
+ declare function buildResources(manifest: ToolManifest, tools?: ToolDefinition[]): McpResource[];
200
+
201
+ /**
202
+ * Workflow prompts.
203
+ *
204
+ * These carry over the guides that live in the CLI skill file today — the
205
+ * organization healthcheck and the trend search. There they are always resident
206
+ * in the model's context; here a client fetches one only when the user is
207
+ * actually doing that job.
208
+ *
209
+ * The procedure is the same, restated in tool vocabulary rather than shell
210
+ * commands.
211
+ */
212
+ interface PromptArgument {
213
+ name: string;
214
+ description: string;
215
+ required?: boolean;
216
+ }
217
+ interface PromptDefinition {
218
+ name: string;
219
+ title: string;
220
+ description: string;
221
+ arguments: PromptArgument[];
222
+ /**
223
+ * Every tool the workflow tells the agent to call, by name.
224
+ *
225
+ * A prompt is only advertised when all of them are exposed: narrowing the
226
+ * tool surface with `only` / `CHAINPATROL_MCP_TOOLS` would otherwise leave a
227
+ * prompt instructing the agent to call tools that are not there.
228
+ *
229
+ * `tests/prompts.unit.test.ts` keeps this in step with `template` — every
230
+ * manifest tool named in the prose has to appear here, and every entry has to
231
+ * be a real tool.
232
+ */
233
+ tools: string[];
234
+ /** `{{arg}}` placeholders are substituted by `renderPrompt`. */
235
+ template: string;
236
+ }
237
+ declare const PROMPTS: PromptDefinition[];
238
+ /** Substitutes `{{name}}` placeholders, filling defaults for absent arguments. */
239
+ declare function renderPrompt(prompt: PromptDefinition, args: Record<string, unknown>): string;
240
+ /**
241
+ * Narrows the prompt set to the workflows every one of whose tools is exposed.
242
+ *
243
+ * A prompt is a script for calling tools. Advertising one whose tools have been
244
+ * filtered out hands the agent instructions it cannot follow, and the failure
245
+ * surfaces mid-workflow as an unknown-tool error rather than at discovery.
246
+ */
247
+ declare function selectPrompts(prompts: PromptDefinition[], available: Iterable<string>): PromptDefinition[];
248
+
249
+ /**
250
+ * The one knob that narrows the exposed tool surface.
251
+ *
252
+ * MCP clients configure servers by command plus environment, not by argv they
253
+ * can always control, so the variable is the interface that has to work — both
254
+ * entry points (`chainpatrol-mcp` and `chainpatrol mcp`) read it from here so
255
+ * there is one parser and one documented name.
256
+ *
257
+ * It trades breadth for context budget and is not access control: the API
258
+ * still authorizes every call against the credential in use.
259
+ */
260
+ declare const TOOLS_ENV_VAR = "CHAINPATROL_MCP_TOOLS";
261
+ /** The environment shape this reads, so a test can pass one in. */
262
+ type ToolsEnvironment = Record<string, string | undefined>;
263
+ /**
264
+ * Reads `CHAINPATROL_MCP_TOOLS` as a list of tool names.
265
+ *
266
+ * Returns `undefined` — meaning "expose everything" — for an unset, blank, or
267
+ * all-separator value, so a misconfigured variable widens the surface rather
268
+ * than silently serving no tools at all.
269
+ */
270
+ declare function toolsFromEnvironment(env?: ToolsEnvironment): string[] | undefined;
271
+
272
+ /**
273
+ * Package version, reported to MCP clients in the initialize handshake.
274
+ * Kept as a literal so the bundled server needs no filesystem lookup; the
275
+ * `version.unit.test.ts` check keeps it in step with package.json.
276
+ */
277
+ declare const PACKAGE_VERSION = "1.10.0";
278
+
279
+ export { type ApiCredential, type CreateServerOptions, type CredentialProvider, type EnumDefinition, type GlossaryDefinition, type HttpInvokerOptions, type HttpMethod, type JsonSchema, MAX_RESULT_CHARS, type McpResource, PACKAGE_VERSION, PROMPTS, type PromptDefinition, TOOLS_ENV_VAR, type ToolDefinition, ToolInvocationError, type ToolInvoker, type ToolManifest, type ToolsEnvironment, buildResources, createChainPatrolMcpServer, createHttpInvoker, manifest, renderPrompt, selectPrompts, toolsFromEnvironment };
package/dist/index.js ADDED
@@ -0,0 +1,41 @@
1
+ import {
2
+ MAX_RESULT_CHARS,
3
+ PACKAGE_VERSION,
4
+ PROMPTS,
5
+ TOOLS_ENV_VAR,
6
+ ToolInvocationError,
7
+ buildResources,
8
+ createChainPatrolMcpServer,
9
+ createHttpInvoker,
10
+ manifest,
11
+ renderPrompt,
12
+ selectPrompts,
13
+ toolsFromEnvironment
14
+ } from "./chunk-AHMNJBKL.js";
15
+ import {
16
+ DEFAULT_API_URL,
17
+ configDir,
18
+ readJsonFile,
19
+ readStoredConfig,
20
+ resolveApiUrl
21
+ } from "./chunk-4VHYP4ZH.js";
22
+ export {
23
+ DEFAULT_API_URL,
24
+ MAX_RESULT_CHARS,
25
+ PACKAGE_VERSION,
26
+ PROMPTS,
27
+ TOOLS_ENV_VAR,
28
+ ToolInvocationError,
29
+ buildResources,
30
+ configDir,
31
+ createChainPatrolMcpServer,
32
+ createHttpInvoker,
33
+ manifest,
34
+ readJsonFile,
35
+ readStoredConfig,
36
+ renderPrompt,
37
+ resolveApiUrl,
38
+ selectPrompts,
39
+ toolsFromEnvironment
40
+ };
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PACKAGE_VERSION,
4
+ createChainPatrolMcpServer,
5
+ createHttpInvoker,
6
+ toolsFromEnvironment
7
+ } from "./chunk-AHMNJBKL.js";
8
+ import {
9
+ configDir,
10
+ readJsonFile,
11
+ resolveApiUrl
12
+ } from "./chunk-4VHYP4ZH.js";
13
+
14
+ // src/stdio.ts
15
+ import { DateTime } from "luxon";
16
+ import { join } from "path";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ function resolveCredential() {
19
+ const envKey = process.env.CHAINPATROL_API_KEY?.trim();
20
+ if (envKey) return { kind: "api-key", value: envKey };
21
+ const stored = readJsonFile(
22
+ join(configDir(), "credentials.json")
23
+ );
24
+ if (!stored?.accessToken) {
25
+ throw new Error(
26
+ "Not signed in to ChainPatrol. Run `chainpatrol login`, or set CHAINPATROL_API_KEY."
27
+ );
28
+ }
29
+ if (stored.expiresAt) {
30
+ const expiresAt = DateTime.fromISO(stored.expiresAt, { zone: "utc" });
31
+ if (expiresAt.isValid && expiresAt < DateTime.now().toUTC()) {
32
+ throw new Error("ChainPatrol session expired. Run `chainpatrol login` again.");
33
+ }
34
+ }
35
+ return { kind: "bearer", value: stored.accessToken };
36
+ }
37
+ async function main() {
38
+ const server = createChainPatrolMcpServer({
39
+ invoker: createHttpInvoker({
40
+ baseUrl: resolveApiUrl(),
41
+ credential: resolveCredential
42
+ }),
43
+ only: toolsFromEnvironment(),
44
+ version: PACKAGE_VERSION
45
+ });
46
+ await server.connect(new StdioServerTransport());
47
+ }
48
+
49
+ // src/stdio-bin.ts
50
+ main().catch((error) => {
51
+ console.error(error instanceof Error ? error.message : String(error));
52
+ process.exit(1);
53
+ });
54
+ //# sourceMappingURL=stdio-bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stdio.ts","../src/stdio-bin.ts"],"sourcesContent":["/**\n * Local MCP server, spoken over stdio.\n *\n * This module is side-effect free so its resolvers can be tested directly;\n * `stdio-bin.ts` is the executable that calls `main()`.\n *\n * Launched by desktop MCP clients (Claude Code, Claude Desktop, Cursor) and by\n * `chainpatrol mcp`. It dispatches through the public REST API, using the same\n * credentials the CLI stores, so it needs no access to ChainPatrol internals.\n *\n * Credentials, in order:\n * 1. `CHAINPATROL_API_KEY` — for service accounts and CI.\n * 2. The token stored by `chainpatrol login`.\n */\nimport { DateTime } from \"luxon\";\nimport { join } from \"node:path\";\n\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nimport { configDir, readJsonFile, resolveApiUrl } from \"./chainpatrol-config\";\nimport { createHttpInvoker, type ApiCredential } from \"./http-invoker\";\nimport { createChainPatrolMcpServer } from \"./server\";\nimport { toolsFromEnvironment } from \"./tools-filter\";\nimport { PACKAGE_VERSION } from \"./version\";\n\n/**\n * Resolved per call rather than once at startup, so a `chainpatrol login` in\n * another terminal takes effect without restarting the MCP client.\n */\nexport function resolveCredential(): ApiCredential {\n const envKey = process.env.CHAINPATROL_API_KEY?.trim();\n if (envKey) return { kind: \"api-key\", value: envKey };\n\n const stored = readJsonFile<{ accessToken?: string; expiresAt?: string }>(\n join(configDir(), \"credentials.json\"),\n );\n\n if (!stored?.accessToken) {\n throw new Error(\n \"Not signed in to ChainPatrol. Run `chainpatrol login`, or set CHAINPATROL_API_KEY.\",\n );\n }\n\n if (stored.expiresAt) {\n // Luxon in UTC, per AGENTS.md — no `Date.now()` in application code.\n const expiresAt = DateTime.fromISO(stored.expiresAt, { zone: \"utc\" });\n if (expiresAt.isValid && expiresAt < DateTime.now().toUTC()) {\n throw new Error(\"ChainPatrol session expired. Run `chainpatrol login` again.\");\n }\n }\n\n return { kind: \"bearer\", value: stored.accessToken };\n}\n\nexport async function main(): Promise<void> {\n const server = createChainPatrolMcpServer({\n invoker: createHttpInvoker({\n baseUrl: resolveApiUrl(),\n credential: resolveCredential,\n }),\n only: toolsFromEnvironment(),\n version: PACKAGE_VERSION,\n });\n\n await server.connect(new StdioServerTransport());\n}\n","#!/usr/bin/env node\n/**\n * Executable entrypoint for the local MCP server.\n *\n * Kept separate from `stdio.ts` so that module stays importable — and testable\n * — without starting a server as a side effect.\n */\nimport { main } from \"./stdio\";\n\nmain().catch((error: unknown) => {\n // stdout carries the protocol, so diagnostics have to go to stderr.\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAS,gBAAgB;AACzB,SAAS,YAAY;AAErB,SAAS,4BAA4B;AAY9B,SAAS,oBAAmC;AACjD,QAAM,SAAS,QAAQ,IAAI,qBAAqB,KAAK;AACrD,MAAI,OAAQ,QAAO,EAAE,MAAM,WAAW,OAAO,OAAO;AAEpD,QAAM,SAAS;AAAA,IACb,KAAK,UAAU,GAAG,kBAAkB;AAAA,EACtC;AAEA,MAAI,CAAC,QAAQ,aAAa;AACxB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,WAAW;AAEpB,UAAM,YAAY,SAAS,QAAQ,OAAO,WAAW,EAAE,MAAM,MAAM,CAAC;AACpE,QAAI,UAAU,WAAW,YAAY,SAAS,IAAI,EAAE,MAAM,GAAG;AAC3D,YAAM,IAAI,MAAM,6DAA6D;AAAA,IAC/E;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,UAAU,OAAO,OAAO,YAAY;AACrD;AAEA,eAAsB,OAAsB;AAC1C,QAAM,SAAS,2BAA2B;AAAA,IACxC,SAAS,kBAAkB;AAAA,MACzB,SAAS,cAAc;AAAA,MACvB,YAAY;AAAA,IACd,CAAC;AAAA,IACD,MAAM,qBAAqB;AAAA,IAC3B,SAAS;AAAA,EACX,CAAC;AAED,QAAM,OAAO,QAAQ,IAAI,qBAAqB,CAAC;AACjD;;;ACxDA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAE/B,UAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AACpE,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@chainpatrol/mcp",
3
+ "description": "The official ChainPatrol MCP server — every public API capability as an MCP tool",
4
+ "author": "ChainPatrol <support@chainpatrol.io>",
5
+ "version": "1.10.0",
6
+ "license": "UNLICENSED",
7
+ "homepage": "https://chainpatrol.com/docs/mcp",
8
+ "keywords": [
9
+ "chainpatrol",
10
+ "mcp",
11
+ "model-context-protocol",
12
+ "agent"
13
+ ],
14
+ "type": "module",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./config": {
21
+ "types": "./dist/chainpatrol-config.d.ts",
22
+ "import": "./dist/chainpatrol-config.js"
23
+ }
24
+ },
25
+ "bin": {
26
+ "chainpatrol-mcp": "./dist/stdio-bin.js"
27
+ },
28
+ "files": [
29
+ "./dist/**"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "provenance": false
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "tools:generate": "tsx scripts/generate-tools.ts",
39
+ "tools:check": "tsx scripts/generate-tools.ts --check",
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "vitest run --config vitest.config.unit.ts",
42
+ "lint:eslint": "eslint . --flag unstable_native_nodejs_ts_config --cache --cache-location .cache/.eslintcache",
43
+ "lint": "npx oxlint ."
44
+ },
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.30.0",
47
+ "luxon": "^3.4.4",
48
+ "zod": "^3.25.76"
49
+ },
50
+ "devDependencies": {
51
+ "@chainpatrol/eslint-config": "0.0.1",
52
+ "@chainpatrol/external-trpc": "0.0.0",
53
+ "@chainpatrol/tsconfig": "0.0.0",
54
+ "@types/luxon": "^3.4.2",
55
+ "@types/node": "^22.0.0",
56
+ "eslint": "^9.39.1",
57
+ "tsup": "^8.5.0",
58
+ "tsx": "^4.19.4",
59
+ "typescript": "6.0.2",
60
+ "vitest": "^3.2.4",
61
+ "zod-to-json-schema": "^3.24.5"
62
+ }
63
+ }