plumb-mcp-server 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shashank Gupta
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,294 @@
1
+ # plumb-mcp-server
2
+
3
+ An LLM-free [MCP](https://modelcontextprotocol.io/) server that exposes Plumb's boundary-analysis tools — dependency-cruiser, ts-morph public-surface, and madge cycle detection — so any MCP client (Claude Desktop, Cursor, or any SDK host) can call them as structured tools.
4
+
5
+ This server ships the **tools**, not an agent or LLM. No API key. No inference. It returns grounded structural facts that an agent can then reason over — separating the mechanical data-gathering from the judgment layer.
6
+
7
+ ---
8
+
9
+ ## Install & run
10
+
11
+ Build the package first (from the repo root):
12
+
13
+ ```sh
14
+ pnpm build
15
+ ```
16
+
17
+ The server binary is `plumb-mcp-server`. Run it directly to start the stdio server:
18
+
19
+ ```sh
20
+ plumb-mcp-server
21
+ ```
22
+
23
+ ### MCP client config
24
+
25
+ Add to your MCP client config (e.g. `claude_desktop_config.json` or `mcp.json`):
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "plumb": {
31
+ "command": "plumb-mcp-server"
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ ### Sandbox with `PLUMB_MCP_ALLOWED_ROOT`
38
+
39
+ By default the server accepts any existing directory. Set `PLUMB_MCP_ALLOWED_ROOT` to restrict analysis to a directory tree and block path-traversal attempts:
40
+
41
+ ```json
42
+ {
43
+ "mcpServers": {
44
+ "plumb": {
45
+ "command": "plumb-mcp-server",
46
+ "env": {
47
+ "PLUMB_MCP_ALLOWED_ROOT": "/path/to/your/repos"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Tools
57
+
58
+ ### `dependency-graph`
59
+
60
+ Analyses module dependencies in a TypeScript repository using dependency-cruiser and returns a flat edge list.
61
+
62
+ **Input**
63
+
64
+ | Field | Type | Required | Notes |
65
+ |---|---|---|---|
66
+ | `repoPath` | `string` | yes | Absolute path to the repository root |
67
+ | `doNotFollow` | `string` | no | **Accepted but not yet forwarded to dependency-cruiser — currently a no-op stub.** Ignored at runtime. |
68
+
69
+ **Output shape**
70
+
71
+ ```ts
72
+ {
73
+ repoPath: string;
74
+ modules: string[]; // repo-relative source paths, sorted
75
+ edges: {
76
+ from: string; // repo-relative source file
77
+ to: string | null; // resolved target (null for unresolvable/external)
78
+ importPath: string; // the import specifier as written in source
79
+ line: number | null; // line number of the import statement
80
+ external: boolean; // true for node_modules / unresolvable deps
81
+ dynamic: boolean; // true for dynamic import()
82
+ }[];
83
+ generatedWith: {
84
+ tool: 'dependency-cruiser';
85
+ version: string;
86
+ };
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ### `public-surface`
93
+
94
+ Maps each package in the repository to its public API surface: the entrypoint (`src/index.ts`) and the set of modules reachable from it, using ts-morph static resolution.
95
+
96
+ **Input**
97
+
98
+ | Field | Type | Required |
99
+ |---|---|---|
100
+ | `repoPath` | `string` | yes |
101
+
102
+ **Output shape**
103
+
104
+ ```ts
105
+ {
106
+ repoPath: string;
107
+ packages: {
108
+ packageRoot: string; // repo-relative package directory
109
+ entrypoint: string | null; // repo-relative path to src/index.ts, or null
110
+ publicModules: string[]; // modules reachable from the entrypoint
111
+ }[];
112
+ generatedWith: string; // e.g. "ts-morph@28.0.0"
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ### `detect-cycles`
119
+
120
+ Finds circular dependencies in a TypeScript repository using madge. Returns cycles as lists of repo-relative file paths.
121
+
122
+ **Input**
123
+
124
+ | Field | Type | Required |
125
+ |---|---|---|
126
+ | `repoPath` | `string` | yes |
127
+
128
+ **Output shape**
129
+
130
+ ```ts
131
+ {
132
+ repoPath: string;
133
+ cycles: {
134
+ members: string[]; // repo-relative paths forming the cycle
135
+ }[];
136
+ generatedWith: string; // e.g. "madge@8.0.0"
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Worked example
143
+
144
+ The repository includes a synthetic labeled fixture at `packages/agent/test/fixtures/flawed-monorepo`. It contains seeded violations (deep-internal imports, cyclic dependencies, inverted-layer dependencies, etc.) used by the eval harness.
145
+
146
+ ### Tool: `detect-cycles`
147
+
148
+ Call:
149
+
150
+ ```json
151
+ {
152
+ "tool": "detect-cycles",
153
+ "arguments": {
154
+ "repoPath": "<repo-root>/packages/agent/test/fixtures/flawed-monorepo"
155
+ }
156
+ }
157
+ ```
158
+
159
+ Actual output (captured from a real run):
160
+
161
+ ```json
162
+ {
163
+ "repoPath": "<repo-root>/packages/agent/test/fixtures/flawed-monorepo",
164
+ "cycles": [
165
+ {
166
+ "members": [
167
+ "cycle3/src/x.ts",
168
+ "cycle3/src/y.ts",
169
+ "cycle3/src/z.ts"
170
+ ]
171
+ },
172
+ {
173
+ "members": [
174
+ "graph/src/a.ts",
175
+ "graph/src/b.ts"
176
+ ]
177
+ }
178
+ ],
179
+ "generatedWith": "madge@8.0.0"
180
+ }
181
+ ```
182
+
183
+ This surfaces both seeded cycles: the 3-node `cycle3/` ring (P14 in the fixture's violation catalog) and the 2-node `graph/` mutual import (P3).
184
+
185
+ ### Tool: `public-surface`
186
+
187
+ Call:
188
+
189
+ ```json
190
+ {
191
+ "tool": "public-surface",
192
+ "arguments": {
193
+ "repoPath": "<repo-root>/packages/agent/test/fixtures/flawed-monorepo"
194
+ }
195
+ }
196
+ ```
197
+
198
+ Actual output (captured from a real run):
199
+
200
+ ```json
201
+ {
202
+ "repoPath": "<repo-root>/packages/agent/test/fixtures/flawed-monorepo",
203
+ "packages": [
204
+ {
205
+ "packageRoot": "billing",
206
+ "entrypoint": "billing/src/index.ts",
207
+ "publicModules": [
208
+ "billing/src/index.ts",
209
+ "billing/src/internal/ledger.ts"
210
+ ]
211
+ },
212
+ {
213
+ "packageRoot": "catalog",
214
+ "entrypoint": "catalog/src/index.ts",
215
+ "publicModules": [
216
+ "catalog/src/index.ts",
217
+ "catalog/src/products.ts"
218
+ ]
219
+ },
220
+ {
221
+ "packageRoot": "orders",
222
+ "entrypoint": "orders/src/index.ts",
223
+ "publicModules": [
224
+ "orders/src/checkout.ts",
225
+ "orders/src/index.ts"
226
+ ]
227
+ },
228
+ {
229
+ "packageRoot": "payments",
230
+ "entrypoint": "payments/src/index.ts",
231
+ "publicModules": [
232
+ "payments/src/index.ts"
233
+ ]
234
+ },
235
+ {
236
+ "packageRoot": "remotes/analytics",
237
+ "entrypoint": "remotes/analytics/src/index.ts",
238
+ "publicModules": [
239
+ "remotes/analytics/src/index.ts"
240
+ ]
241
+ },
242
+ {
243
+ "packageRoot": "remotes/dashboard",
244
+ "entrypoint": "remotes/dashboard/src/index.ts",
245
+ "publicModules": [
246
+ "remotes/dashboard/src/index.ts"
247
+ ]
248
+ },
249
+ {
250
+ "packageRoot": "shared",
251
+ "entrypoint": "shared/src/index.ts",
252
+ "publicModules": [
253
+ "shared/src/format.ts",
254
+ "shared/src/index.ts"
255
+ ]
256
+ },
257
+ {
258
+ "packageRoot": "vendor/react-lite",
259
+ "entrypoint": "vendor/react-lite/src/index.ts",
260
+ "publicModules": [
261
+ "vendor/react-lite/src/index.ts"
262
+ ]
263
+ }
264
+ ],
265
+ "generatedWith": "ts-morph@28.0.0"
266
+ }
267
+ ```
268
+
269
+ This shows that `shared/src/format.ts` is reachable from `shared/src/index.ts` — so it is "public" in the ts-morph sense — yet violations P6, P7, and P8 import it directly from outside `shared/`, bypassing the entrypoint. The `dependency-graph` tool provides the edge list; an agent composes the two to classify each cross-package edge as `via-entrypoint` or `bypasses-entrypoint`.
270
+
271
+ ---
272
+
273
+ ## The honest boundary
274
+
275
+ This server returns structural facts. The `surfaceClass`-per-edge labeling that powers Plumb's violation findings — classifying each dependency edge as `via-entrypoint`, `bypasses-entrypoint`, `intra-package`, or `not-a-package-import` — is the **agent's composition** of `public-surface` and `dependency-graph`, not a single tool. That composition layer is intentionally outside this server: it requires LLM judgment to map surface classes to violation categories given an architectural description.
276
+
277
+ Put differently: `plumb-mcp-server` draws the line at facts. Everything beyond that is reasoning.
278
+
279
+ ---
280
+
281
+ ## Security
282
+
283
+ - `repoPath` is validated as an existing directory before any tool runs. Empty or whitespace-only values are rejected immediately.
284
+ - Set `PLUMB_MCP_ALLOWED_ROOT` to restrict which directory trees are analyzable and to block path-traversal attempts. The guard resolves real paths (following symlinks) before comparing, so a symlink inside the allowed root that points outside it is caught and rejected.
285
+ - **Path disclosure:** error messages and successful result payloads contain absolute on-disk paths (e.g. `repoPath` is echoed in every Facts object). If the server runs with access to sensitive paths, hosts should filter or redact tool output before surfacing it to end users.
286
+ - **Resource bounds:** the server imposes no timeout or file-count limit on analysis. Point it at a developer-sized repository; avoid enormous monorepos or pathologically deep trees. The underlying tools already skip `node_modules` by default.
287
+ - The server reads files only within the given `repoPath`; it does not execute shell commands or make network requests.
288
+ - No credentials or API keys are required or read.
289
+
290
+ ---
291
+
292
+ ## License & status
293
+
294
+ `plumb-mcp-server` is part of the [Plumb](https://plumb.sgupta.dev) project — the OSS companion deliverable to the measured Plumb case study. Licensed MIT — see `LICENSE`. The static analysis engine it depends on is published separately as [`plumb-tools`](https://www.npmjs.com/package/plumb-tools).
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createServer } from './server.js';
4
+ const server = createServer();
5
+ const transport = new StdioServerTransport();
6
+ await server.connect(transport);
7
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;AAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { createServer } from './server.js';
2
+ export { resolveRepoPath } from './resolve-repo-path.js';
3
+ export type { ResolveResult } from './resolve-repo-path.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createServer } from './server.js';
2
+ export { resolveRepoPath } from './resolve-repo-path.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,15 @@
1
+ export type ResolveResult = {
2
+ ok: true;
3
+ value: string;
4
+ } | {
5
+ ok: false;
6
+ error: string;
7
+ };
8
+ /**
9
+ * Resolves and validates a repoPath from MCP client input.
10
+ *
11
+ * Footgun: repoPath crosses a trust boundary here (MCP client is external).
12
+ * The underlying analysis tools do no path validation themselves.
13
+ */
14
+ export declare function resolveRepoPath(input: string): ResolveResult;
15
+ //# sourceMappingURL=resolve-repo-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-repo-path.d.ts","sourceRoot":"","sources":["../src/resolve-repo-path.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAgD5D"}
@@ -0,0 +1,56 @@
1
+ import { existsSync, realpathSync, statSync } from 'node:fs';
2
+ import { isAbsolute, relative, resolve } from 'node:path';
3
+ /**
4
+ * Resolves and validates a repoPath from MCP client input.
5
+ *
6
+ * Footgun: repoPath crosses a trust boundary here (MCP client is external).
7
+ * The underlying analysis tools do no path validation themselves.
8
+ */
9
+ export function resolveRepoPath(input) {
10
+ if (input.trim() === '') {
11
+ return { ok: false, error: 'repoPath must be a non-empty path' };
12
+ }
13
+ const abs = resolve(input);
14
+ if (!existsSync(abs)) {
15
+ return { ok: false, error: `repoPath does not exist: ${abs}` };
16
+ }
17
+ if (!statSync(abs).isDirectory()) {
18
+ return { ok: false, error: `repoPath is not a directory: ${abs}` };
19
+ }
20
+ const allowedRoot = process.env['PLUMB_MCP_ALLOWED_ROOT'];
21
+ if (allowedRoot !== undefined && allowedRoot !== '') {
22
+ let realRoot;
23
+ let realAbs;
24
+ try {
25
+ realRoot = realpathSync(resolve(allowedRoot));
26
+ }
27
+ catch {
28
+ return { ok: false, error: `PLUMB_MCP_ALLOWED_ROOT does not exist or is not accessible` };
29
+ }
30
+ try {
31
+ realAbs = realpathSync(abs);
32
+ }
33
+ catch {
34
+ return { ok: false, error: `repoPath does not exist: ${abs}` };
35
+ }
36
+ const rel = relative(realRoot, realAbs);
37
+ // rel starts with '..' or is absolute when realAbs is outside realRoot
38
+ if (rel.startsWith('..') || isAbsolute(rel)) {
39
+ return {
40
+ ok: false,
41
+ error: `repoPath is not within the allowed root (PLUMB_MCP_ALLOWED_ROOT=${realRoot}): ${realAbs}`,
42
+ };
43
+ }
44
+ return { ok: true, value: realAbs };
45
+ }
46
+ else {
47
+ process.stderr.write('[plumb-mcp-server] PLUMB_MCP_ALLOWED_ROOT is not set — any existing directory is accepted\n');
48
+ try {
49
+ return { ok: true, value: realpathSync(abs) };
50
+ }
51
+ catch {
52
+ return { ok: false, error: `repoPath does not exist: ${abs}` };
53
+ }
54
+ }
55
+ }
56
+ //# sourceMappingURL=resolve-repo-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-repo-path.js","sourceRoot":"","sources":["../src/resolve-repo-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAM1D;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC;IACnE,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAE3B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,4BAA4B,GAAG,EAAE,EAAE,CAAC;IACjE,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACjC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,GAAG,EAAE,EAAE,CAAC;IACrE,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAC1D,IAAI,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,EAAE,EAAE,CAAC;QACpD,IAAI,QAAgB,CAAC;QACrB,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,4DAA4D,EAAE,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC;YACH,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,4BAA4B,GAAG,EAAE,EAAE,CAAC;QACjE,CAAC;QACD,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxC,uEAAuE;QACvE,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5C,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,mEAAmE,QAAQ,MAAM,OAAO,EAAE;aAClG,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACtC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,6FAA6F,CAC9F,CAAC;QACF,IAAI,CAAC;YACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,4BAA4B,GAAG,EAAE,EAAE,CAAC;QACjE,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createServer(): McpServer;
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAgEpE,wBAAgB,YAAY,IAAI,SAAS,CAQxC"}
package/dist/server.js ADDED
@@ -0,0 +1,48 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { dependencyGraphTool, publicSurfaceTool, detectCyclesTool, } from 'plumb-tools';
3
+ import { resolveRepoPath } from './resolve-repo-path.js';
4
+ // Generic mapping: one AnalysisTool → one registered MCP tool.
5
+ // Keeps the binding DRY; the name and description come from the tool object
6
+ // so they stay in sync without hardcoding.
7
+ function registerAnalysisTool(server, tool) {
8
+ // ZodRawShapeCompat = Record<string, AnySchema>; .shape gives us that directly.
9
+ // We cast through unknown here because TypeScript cannot align the generic
10
+ // ZodRawShapeCompat inference between our dynamic shape and the SDK's callback
11
+ // type; the runtime contract is correct.
12
+ const shape = tool.inputSchema
13
+ .shape;
14
+ const handler = async (args) => {
15
+ const validation = resolveRepoPath(String(args['repoPath'] ?? ''));
16
+ if (!validation.ok) {
17
+ return {
18
+ content: [{ type: 'text', text: validation.error }],
19
+ isError: true,
20
+ };
21
+ }
22
+ try {
23
+ const input = { ...args, repoPath: validation.value };
24
+ const facts = await tool.run(input);
25
+ return {
26
+ content: [{ type: 'text', text: JSON.stringify(facts, null, 2) }],
27
+ };
28
+ }
29
+ catch (err) {
30
+ const message = err instanceof Error ? err.message : String(err);
31
+ return {
32
+ content: [{ type: 'text', text: `Tool execution failed: ${message}` }],
33
+ isError: true,
34
+ };
35
+ }
36
+ };
37
+ // Cast handler to satisfy the SDK's generic ToolCallback inference.
38
+ // The deprecated .tool() overload is simpler to type-check for dynamic shapes.
39
+ server.tool(tool.name, tool.description, shape, handler);
40
+ }
41
+ export function createServer() {
42
+ const server = new McpServer({ name: 'plumb-mcp-server', version: '0.1.0' });
43
+ registerAnalysisTool(server, dependencyGraphTool);
44
+ registerAnalysisTool(server, publicSurfaceTool);
45
+ registerAnalysisTool(server, detectCyclesTool);
46
+ return server;
47
+ }
48
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAMzD,+DAA+D;AAC/D,4EAA4E;AAC5E,2CAA2C;AAC3C,SAAS,oBAAoB,CAC3B,MAAiB,EACjB,IAAmC;IAEnC,gFAAgF;IAChF,2EAA2E;IAC3E,+EAA+E;IAC/E,yCAAyC;IACzC,MAAM,KAAK,GAAI,IAAI,CAAC,WAA6D;SAC9E,KAAgC,CAAC;IAEpC,MAAM,OAAO,GAAG,KAAK,EAAE,IAA6B,EAAuB,EAAE;QAC3E,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;YACnB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,KAAK,EAAE,CAAC;gBACnD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,KAAK,EAAY,CAAC;YAChE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACpC,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aAClE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,OAAO,EAAE,EAAE,CAAC;gBACtE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;IAEF,oEAAoE;IACpE,+EAA+E;IAC9E,MAOC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAE7E,oBAAoB,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAClD,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAChD,oBAAoB,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAE/C,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "plumb-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "LLM-free MCP server exposing Plumb's boundary-analysis tools: dependency graph, public surface, and cycle detection",
5
+ "author": "Shashank Gupta",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=18"
9
+ },
10
+ "keywords": [
11
+ "mcp",
12
+ "model-context-protocol",
13
+ "architecture",
14
+ "dependency-analysis",
15
+ "typescript",
16
+ "monorepo",
17
+ "boundaries",
18
+ "dependency-cruiser",
19
+ "madge",
20
+ "ts-morph"
21
+ ],
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "bin": {
26
+ "plumb-mcp-server": "./dist/bin.js"
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "default": "./dist/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "plumb-tools": "^0.1.0",
44
+ "@modelcontextprotocol/sdk": "1.29.0",
45
+ "zod": "4.4.3"
46
+ },
47
+ "scripts": {
48
+ "typecheck": "tsc --noEmit",
49
+ "build": "tsc -p tsconfig.build.json"
50
+ }
51
+ }