@printwithsynergy/synergy-mcp 0.1.1
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/CLAUDE.md +119 -0
- package/LICENSE +661 -0
- package/README.md +160 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/blast-radius.d.ts +33 -0
- package/dist/tools/blast-radius.d.ts.map +1 -0
- package/dist/tools/blast-radius.js +205 -0
- package/dist/tools/blast-radius.js.map +1 -0
- package/dist/tools/floor-pin-grid.d.ts +23 -0
- package/dist/tools/floor-pin-grid.d.ts.map +1 -0
- package/dist/tools/floor-pin-grid.js +133 -0
- package/dist/tools/floor-pin-grid.js.map +1 -0
- package/dist/tools/pattern-audit.d.ts +30 -0
- package/dist/tools/pattern-audit.d.ts.map +1 -0
- package/dist/tools/pattern-audit.js +184 -0
- package/dist/tools/pattern-audit.js.map +1 -0
- package/dist/tools/stack-health-grid.d.ts +35 -0
- package/dist/tools/stack-health-grid.d.ts.map +1 -0
- package/dist/tools/stack-health-grid.js +110 -0
- package/dist/tools/stack-health-grid.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# synergy-mcp
|
|
2
|
+
|
|
3
|
+
Cross-stack dev-audit MCP server for the [Print With Synergy](https://printwithsynergy.com)
|
|
4
|
+
stack.
|
|
5
|
+
|
|
6
|
+
Serves four tools over the MCP stdio transport, scoped to the six repos in the
|
|
7
|
+
stack — `codex-pdf`, `lint-pdf`, `lens-pdf`, `compile-pdf`, `synergy`, `platform`:
|
|
8
|
+
|
|
9
|
+
| Tool | What it answers |
|
|
10
|
+
| -------------------- | --------------------------------------------------------------------- |
|
|
11
|
+
| `blast_radius` | Who breaks if I change this symbol / route / env var? |
|
|
12
|
+
| `floor_pin_grid` | Which repo pins which org package, at which version? |
|
|
13
|
+
| `pattern_audit` | Which repos have CI / consume-surface / RFC 7807 / deep CLAUDE.md? |
|
|
14
|
+
| `stack_health_grid` | What does `/healthz` + `/v1/contract` say across the prod URLs? |
|
|
15
|
+
|
|
16
|
+
Built to close gaps the cross-stack architecture audit surfaced — pin drift,
|
|
17
|
+
pattern divergence, "lens vs lens-server" naming collisions, ad-hoc health
|
|
18
|
+
endpoint shapes.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @printwithsynergy/synergy-mcp
|
|
24
|
+
# or use npx directly from your MCP client config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Configure
|
|
28
|
+
|
|
29
|
+
The server needs to know where your six repo working trees live. Two ways:
|
|
30
|
+
|
|
31
|
+
### 1. Stack root (recommended)
|
|
32
|
+
|
|
33
|
+
If you clone every repo into a single parent directory:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
export SYNERGY_MCP_STACK_ROOT=$HOME/code/printwithsynergy
|
|
37
|
+
# expects $HOME/code/printwithsynergy/{codex-pdf,lint-pdf,lens-pdf,compile-pdf,synergy,platform}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Per-repo overrides
|
|
41
|
+
|
|
42
|
+
For non-uniform paths:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
export SYNERGY_MCP_PATH_CODEX_PDF=/path/to/codex-pdf
|
|
46
|
+
export SYNERGY_MCP_PATH_LINT_PDF=/path/to/lint-pdf
|
|
47
|
+
# ... one per repo
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Per-repo env wins over `SYNERGY_MCP_STACK_ROOT` for the same repo. Repos that
|
|
51
|
+
don't resolve are simply skipped — tools return rows for whatever is available
|
|
52
|
+
plus a `notes[]` entry naming the missing repo.
|
|
53
|
+
|
|
54
|
+
### Prod URLs (for `stack_health_grid` only)
|
|
55
|
+
|
|
56
|
+
Defaults match the production hostnames the audit catalogued; override per
|
|
57
|
+
service:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
export SYNERGY_MCP_URL_CODEX_PDF=https://codex.lintpdf.com
|
|
61
|
+
export SYNERGY_MCP_URL_LINT_PDF=https://lintpdf.com
|
|
62
|
+
export SYNERGY_MCP_URL_COMPILE_PDF=https://compilepdf.com
|
|
63
|
+
export SYNERGY_MCP_URL_SYNERGY=https://... # not set by default
|
|
64
|
+
export SYNERGY_MCP_URL_PLATFORM=https://... # not set by default
|
|
65
|
+
# lens-pdf has no HTTP surface (npm library only) — no URL needed.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Use from Claude Code
|
|
69
|
+
|
|
70
|
+
Add to your Claude Code MCP config (`~/.config/claude-code/mcp.json` or
|
|
71
|
+
project-local equivalent):
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mcpServers": {
|
|
76
|
+
"synergy-mcp": {
|
|
77
|
+
"command": "npx",
|
|
78
|
+
"args": ["-y", "@printwithsynergy/synergy-mcp"],
|
|
79
|
+
"env": {
|
|
80
|
+
"SYNERGY_MCP_STACK_ROOT": "/Users/you/code/printwithsynergy"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Tools then show up as `mcp__synergy_mcp__blast_radius`,
|
|
88
|
+
`mcp__synergy_mcp__floor_pin_grid`, etc.
|
|
89
|
+
|
|
90
|
+
## Tools
|
|
91
|
+
|
|
92
|
+
### `blast_radius`
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"target": "CodexDocument",
|
|
97
|
+
"kind": "symbol"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Kinds: `symbol` | `route` | `env_var`. Default `symbol`.
|
|
102
|
+
|
|
103
|
+
Returns per-hit `{ repo, file, line, text, kind }` where `kind` is one of
|
|
104
|
+
`import` | `usage` | `route` | `env`.
|
|
105
|
+
|
|
106
|
+
Implementation note: regex-based (Python `from X import Y`, TS
|
|
107
|
+
`import { Y } from 'X'`, plus bare-name usage). Not AST.
|
|
108
|
+
[`ctxo`](https://github.com/anthropics/code-review-graph) ships a real AST
|
|
109
|
+
backed blast-radius for TS/Go/C# but doesn't have a Python plugin yet; this
|
|
110
|
+
tool fills the gap with the working substitute that lint-pdf's `CLAUDE.md`
|
|
111
|
+
already calls out.
|
|
112
|
+
|
|
113
|
+
### `floor_pin_grid`
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{ "package": "codex-pdf" }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Or omit `package` to return rows for every detected org package.
|
|
120
|
+
|
|
121
|
+
Audit finding #2 — the bug it would have caught on day one: lint-pdf pinning
|
|
122
|
+
`codex-pdf>=1.19.0` with no upper bound while compile-pdf pinned `<2.0`.
|
|
123
|
+
|
|
124
|
+
### `pattern_audit`
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{ "patterns": ["has_ci", "has_problem_details_errors"] }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Omit `patterns` to run every known probe. Each row is
|
|
131
|
+
`{ repo, pattern, present, evidence }`. Available patterns are listed in the
|
|
132
|
+
result's `available_patterns` field.
|
|
133
|
+
|
|
134
|
+
Probes are intentionally cheap (file existence, shallow grep). Audit findings
|
|
135
|
+
#1, #6, #13, and #4 each get a probe.
|
|
136
|
+
|
|
137
|
+
### `stack_health_grid`
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{ "timeout_ms": 5000 }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Optional `repos: ["codex-pdf", "synergy"]` to subset.
|
|
144
|
+
|
|
145
|
+
Each row: `{ repo, url, endpoint, status, latency_ms, body_summary }`. Endpoint
|
|
146
|
+
mapping per repo matches audit finding #15.
|
|
147
|
+
|
|
148
|
+
## Develop
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
pnpm install
|
|
152
|
+
pnpm dev # tsx watch
|
|
153
|
+
pnpm typecheck # tsc --noEmit
|
|
154
|
+
pnpm test # vitest run
|
|
155
|
+
pnpm build # tsc → dist/
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
AGPL-3.0-or-later, matching the rest of the printwithsynergy engine stack.
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type RepoName = "codex-pdf" | "lint-pdf" | "lens-pdf" | "compile-pdf" | "synergy" | "platform";
|
|
2
|
+
export declare const KNOWN_REPOS: RepoName[];
|
|
3
|
+
/** Language tag drives which AST / pattern set to use. */
|
|
4
|
+
export type RepoLang = "python" | "typescript";
|
|
5
|
+
export declare const REPO_LANG: Record<RepoName, RepoLang>;
|
|
6
|
+
/** Prod URLs for stack_health_grid. Override via env. */
|
|
7
|
+
export declare const PROD_URLS: Record<RepoName, string | null>;
|
|
8
|
+
/** Resolve the working-tree path for a single repo, or null. */
|
|
9
|
+
export declare function resolveRepoPath(repo: RepoName): string | null;
|
|
10
|
+
/** Resolve all six repo paths. Missing repos appear as null. */
|
|
11
|
+
export declare function resolveAllRepoPaths(): Record<RepoName, string | null>;
|
|
12
|
+
/** True if any repo resolves; the caller can surface `config_missing`. */
|
|
13
|
+
export declare function hasAnyRepo(): boolean;
|
|
14
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,QAAQ,GAChB,WAAW,GACX,UAAU,GACV,UAAU,GACV,aAAa,GACb,SAAS,GACT,UAAU,CAAC;AAEf,eAAO,MAAM,WAAW,EAAE,QAAQ,EAOjC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE/C,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAOhD,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CASrD,CAAC;AAUF,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAW7D;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,IAAI,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC,CAMrE;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,IAAI,OAAO,CAEpC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack configuration.
|
|
3
|
+
*
|
|
4
|
+
* synergy-mcp serves a cross-stack dev-audit MCP over six repos in
|
|
5
|
+
* the Print With Synergy stack. It needs to know where each repo's
|
|
6
|
+
* working tree is on the host. Two ways to tell it:
|
|
7
|
+
*
|
|
8
|
+
* 1. Env vars (per-repo): SYNERGY_MCP_PATH_CODEX_PDF=/path/to/codex-pdf
|
|
9
|
+
* (...one per repo). Highest priority.
|
|
10
|
+
* 2. Stack root env: SYNERGY_MCP_STACK_ROOT=/path/to/clones — assumes
|
|
11
|
+
* each repo is a sibling directory named after the repo. This is
|
|
12
|
+
* the common case when you clone all six into a single
|
|
13
|
+
* `printwithsynergy/` directory.
|
|
14
|
+
*
|
|
15
|
+
* If neither is set, the server starts but every tool returns an
|
|
16
|
+
* empty result with a `config_missing` flag so the caller can prompt
|
|
17
|
+
* the user to configure.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
export const KNOWN_REPOS = [
|
|
22
|
+
"codex-pdf",
|
|
23
|
+
"lint-pdf",
|
|
24
|
+
"lens-pdf",
|
|
25
|
+
"compile-pdf",
|
|
26
|
+
"synergy",
|
|
27
|
+
"platform",
|
|
28
|
+
];
|
|
29
|
+
export const REPO_LANG = {
|
|
30
|
+
"codex-pdf": "python",
|
|
31
|
+
"lint-pdf": "python",
|
|
32
|
+
"compile-pdf": "python",
|
|
33
|
+
"lens-pdf": "typescript",
|
|
34
|
+
synergy: "typescript",
|
|
35
|
+
platform: "typescript",
|
|
36
|
+
};
|
|
37
|
+
/** Prod URLs for stack_health_grid. Override via env. */
|
|
38
|
+
export const PROD_URLS = {
|
|
39
|
+
"codex-pdf": process.env.SYNERGY_MCP_URL_CODEX_PDF ?? "https://codex.lintpdf.com",
|
|
40
|
+
"lint-pdf": process.env.SYNERGY_MCP_URL_LINT_PDF ?? "https://lintpdf.com",
|
|
41
|
+
"compile-pdf": process.env.SYNERGY_MCP_URL_COMPILE_PDF ?? "https://compilepdf.com",
|
|
42
|
+
"lens-pdf": null, // npm library — no prod URL
|
|
43
|
+
synergy: process.env.SYNERGY_MCP_URL_SYNERGY ?? null,
|
|
44
|
+
platform: process.env.SYNERGY_MCP_URL_PLATFORM ?? null,
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Map a RepoName to its env-var-friendly UPPERCASE_SNAKE form, e.g.
|
|
48
|
+
* "codex-pdf" → "CODEX_PDF". Used to look up SYNERGY_MCP_PATH_<NAME>.
|
|
49
|
+
*/
|
|
50
|
+
function envKey(repo) {
|
|
51
|
+
return repo.toUpperCase().replace(/-/g, "_");
|
|
52
|
+
}
|
|
53
|
+
/** Resolve the working-tree path for a single repo, or null. */
|
|
54
|
+
export function resolveRepoPath(repo) {
|
|
55
|
+
const perRepoEnv = process.env[`SYNERGY_MCP_PATH_${envKey(repo)}`];
|
|
56
|
+
if (perRepoEnv && existsSync(perRepoEnv))
|
|
57
|
+
return perRepoEnv;
|
|
58
|
+
const stackRoot = process.env.SYNERGY_MCP_STACK_ROOT;
|
|
59
|
+
if (stackRoot) {
|
|
60
|
+
const guess = join(stackRoot, repo);
|
|
61
|
+
if (existsSync(guess))
|
|
62
|
+
return guess;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/** Resolve all six repo paths. Missing repos appear as null. */
|
|
67
|
+
export function resolveAllRepoPaths() {
|
|
68
|
+
const result = {};
|
|
69
|
+
for (const repo of KNOWN_REPOS) {
|
|
70
|
+
result[repo] = resolveRepoPath(repo);
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/** True if any repo resolves; the caller can surface `config_missing`. */
|
|
75
|
+
export function hasAnyRepo() {
|
|
76
|
+
return KNOWN_REPOS.some((r) => resolveRepoPath(r) !== null);
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAUjC,MAAM,CAAC,MAAM,WAAW,GAAe;IACrC,WAAW;IACX,UAAU;IACV,UAAU;IACV,aAAa;IACb,SAAS;IACT,UAAU;CACX,CAAC;AAKF,MAAM,CAAC,MAAM,SAAS,GAA+B;IACnD,WAAW,EAAE,QAAQ;IACrB,UAAU,EAAE,QAAQ;IACpB,aAAa,EAAE,QAAQ;IACvB,UAAU,EAAE,YAAY;IACxB,OAAO,EAAE,YAAY;IACrB,QAAQ,EAAE,YAAY;CACvB,CAAC;AAEF,yDAAyD;AACzD,MAAM,CAAC,MAAM,SAAS,GAAoC;IACxD,WAAW,EACT,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,2BAA2B;IACtE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,qBAAqB;IACzE,aAAa,EACX,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,wBAAwB;IACrE,UAAU,EAAE,IAAI,EAAE,4BAA4B;IAC9C,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,IAAI;IACpD,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,IAAI;CACvD,CAAC;AAEF;;;GAGG;AACH,SAAS,MAAM,CAAC,IAAc;IAC5B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,eAAe,CAAC,IAAc;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnE,IAAI,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IAE5D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IACrD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACpC,IAAI,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;IACtC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,mBAAmB;IACjC,MAAM,MAAM,GAA6C,EAAE,CAAC;IAC5D,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,MAAyC,CAAC;AACnD,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,UAAU;IACxB,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC9D,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* synergy-mcp — cross-stack dev-audit MCP server for the Print With
|
|
4
|
+
* Synergy stack. Serves four tools over stdio:
|
|
5
|
+
*
|
|
6
|
+
* - blast_radius — find every call site of a symbol / route / env
|
|
7
|
+
* var across the six configured repos.
|
|
8
|
+
* - floor_pin_grid — the (repo × org-package × version-spec) matrix.
|
|
9
|
+
* - pattern_audit — the (repo × architectural-pattern) matrix.
|
|
10
|
+
* - stack_health_grid — parallel /healthz + /v1/contract probes
|
|
11
|
+
* across the prod URLs.
|
|
12
|
+
*
|
|
13
|
+
* Configuration is via environment variables — see ./config.ts.
|
|
14
|
+
* Designed to run from Claude Code via `npx @printwithsynergy/synergy-mcp`
|
|
15
|
+
* (or installed globally) over the stdio transport.
|
|
16
|
+
*/
|
|
17
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { hasAnyRepo } from "./config.js";
|
|
22
|
+
import { BlastRadiusInput, blastRadius } from "./tools/blast-radius.js";
|
|
23
|
+
import { FloorPinGridInput, floorPinGrid } from "./tools/floor-pin-grid.js";
|
|
24
|
+
import { PatternAuditInput, patternAudit } from "./tools/pattern-audit.js";
|
|
25
|
+
import { StackHealthGridInput, stackHealthGrid, } from "./tools/stack-health-grid.js";
|
|
26
|
+
const SERVER_NAME = "synergy-mcp";
|
|
27
|
+
const SERVER_VERSION = "0.1.0";
|
|
28
|
+
const TOOLS = [
|
|
29
|
+
{
|
|
30
|
+
name: "blast_radius",
|
|
31
|
+
description: "Find every call site of a symbol, HTTP route, or env var across the printwithsynergy stack's six repos (codex-pdf, lint-pdf, lens-pdf, compile-pdf, synergy, platform). Use to answer 'who breaks if I change X?' before refactoring across the seam.",
|
|
32
|
+
schema: BlastRadiusInput,
|
|
33
|
+
handler: blastRadius,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "floor_pin_grid",
|
|
37
|
+
description: "Return the (repo × org-package × version-spec) matrix across pyproject.toml dependencies + package.json {dependencies,devDependencies,peerDependencies}. Detects drift — e.g. one repo pinning codex-pdf>=1.19.0 with no upper bound while another caps at <2.0.",
|
|
38
|
+
schema: FloorPinGridInput,
|
|
39
|
+
handler: floorPinGrid,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "pattern_audit",
|
|
43
|
+
description: "Return the (repo × pattern) matrix for the architectural patterns the cross-stack audit found inconsistently applied. Patterns include presence of CI workflows, the /v1/contract endpoint, consume-surface-audit / engine-purity tripwires, RFC 7807 Problem Details, and CLAUDE.md depth.",
|
|
44
|
+
schema: PatternAuditInput,
|
|
45
|
+
handler: patternAudit,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "stack_health_grid",
|
|
49
|
+
description: "Fetch /healthz and /v1/contract (where exposed) across the configured prod URLs for the stack, in parallel, returning status code + latency + body summary per probe. Branches per-repo for the five different endpoint shapes the audit cataloged.",
|
|
50
|
+
schema: StackHealthGridInput,
|
|
51
|
+
handler: stackHealthGrid,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
/** Render a Zod object schema to a minimal JSON Schema. */
|
|
55
|
+
function toJsonSchema(zod) {
|
|
56
|
+
const shape = zod.shape;
|
|
57
|
+
const properties = {};
|
|
58
|
+
const required = [];
|
|
59
|
+
for (const [key, raw] of Object.entries(shape)) {
|
|
60
|
+
const field = raw;
|
|
61
|
+
const description = field.description ?? undefined;
|
|
62
|
+
const entry = { description };
|
|
63
|
+
let inner = field;
|
|
64
|
+
if (field instanceof z.ZodOptional || field instanceof z.ZodDefault) {
|
|
65
|
+
inner = field._def.innerType;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
required.push(key);
|
|
69
|
+
}
|
|
70
|
+
if (inner instanceof z.ZodString) {
|
|
71
|
+
entry.type = "string";
|
|
72
|
+
}
|
|
73
|
+
else if (inner instanceof z.ZodNumber) {
|
|
74
|
+
entry.type = "number";
|
|
75
|
+
}
|
|
76
|
+
else if (inner instanceof z.ZodBoolean) {
|
|
77
|
+
entry.type = "boolean";
|
|
78
|
+
}
|
|
79
|
+
else if (inner instanceof z.ZodEnum) {
|
|
80
|
+
entry.type = "string";
|
|
81
|
+
entry.enum = inner._def.values;
|
|
82
|
+
}
|
|
83
|
+
else if (inner instanceof z.ZodArray) {
|
|
84
|
+
entry.type = "array";
|
|
85
|
+
entry.items = { type: "string" };
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
entry.type = "object";
|
|
89
|
+
}
|
|
90
|
+
properties[key] = entry;
|
|
91
|
+
}
|
|
92
|
+
const out = {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties,
|
|
95
|
+
};
|
|
96
|
+
if (required.length > 0)
|
|
97
|
+
out.required = required;
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
async function main() {
|
|
101
|
+
const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
|
|
102
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
103
|
+
const tools = TOOLS.map((t) => ({
|
|
104
|
+
name: t.name,
|
|
105
|
+
description: t.description,
|
|
106
|
+
inputSchema: toJsonSchema(t.schema),
|
|
107
|
+
}));
|
|
108
|
+
return { tools };
|
|
109
|
+
});
|
|
110
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
111
|
+
const { name, arguments: args } = request.params;
|
|
112
|
+
const tool = TOOLS.find((t) => t.name === name);
|
|
113
|
+
if (!tool) {
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (!hasAnyRepo() && name !== "stack_health_grid") {
|
|
120
|
+
return {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: "No repos configured. Set SYNERGY_MCP_STACK_ROOT to the directory containing your six repo clones, or SYNERGY_MCP_PATH_<REPO> per-repo (e.g. SYNERGY_MCP_PATH_CODEX_PDF=/path/to/codex-pdf).",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const parsed = tool.schema.safeParse(args ?? {});
|
|
131
|
+
if (!parsed.success) {
|
|
132
|
+
return {
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: `Invalid arguments for ${name}: ${parsed.error.message}`,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
isError: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const result = await tool.handler(parsed.data);
|
|
144
|
+
return {
|
|
145
|
+
content: [
|
|
146
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
152
|
+
return {
|
|
153
|
+
content: [{ type: "text", text: `Tool error: ${msg}` }],
|
|
154
|
+
isError: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
const transport = new StdioServerTransport();
|
|
159
|
+
await server.connect(transport);
|
|
160
|
+
// Lifecycle: server runs until stdio closes.
|
|
161
|
+
}
|
|
162
|
+
main().catch((err) => {
|
|
163
|
+
console.error("synergy-mcp fatal:", err);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type RepoName } from "../config.js";
|
|
3
|
+
export declare const BlastRadiusInput: z.ZodObject<{
|
|
4
|
+
target: z.ZodString;
|
|
5
|
+
kind: z.ZodDefault<z.ZodEnum<["symbol", "route", "env_var"]>>;
|
|
6
|
+
repos: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
7
|
+
max_results_per_repo: z.ZodDefault<z.ZodNumber>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
target: string;
|
|
10
|
+
kind: "symbol" | "route" | "env_var";
|
|
11
|
+
max_results_per_repo: number;
|
|
12
|
+
repos?: string[] | undefined;
|
|
13
|
+
}, {
|
|
14
|
+
target: string;
|
|
15
|
+
kind?: "symbol" | "route" | "env_var" | undefined;
|
|
16
|
+
repos?: string[] | undefined;
|
|
17
|
+
max_results_per_repo?: number | undefined;
|
|
18
|
+
}>;
|
|
19
|
+
export interface BlastRadiusHit {
|
|
20
|
+
repo: RepoName;
|
|
21
|
+
file: string;
|
|
22
|
+
line: number;
|
|
23
|
+
text: string;
|
|
24
|
+
kind: "import" | "usage" | "route" | "env";
|
|
25
|
+
}
|
|
26
|
+
export interface BlastRadiusResult {
|
|
27
|
+
target: string;
|
|
28
|
+
kind: z.infer<typeof BlastRadiusInput>["kind"];
|
|
29
|
+
hits: BlastRadiusHit[];
|
|
30
|
+
notes: string[];
|
|
31
|
+
}
|
|
32
|
+
export declare function blastRadius(input: z.infer<typeof BlastRadiusInput>): Promise<BlastRadiusResult>;
|
|
33
|
+
//# sourceMappingURL=blast-radius.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blast-radius.d.ts","sourceRoot":"","sources":["../../src/tools/blast-radius.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAIL,KAAK,QAAQ,EAEd,MAAM,cAAc,CAAC;AAEtB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;EAyB3B,CAAC;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;CAC5C;AA0JD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,GACtC,OAAO,CAAC,iBAAiB,CAAC,CA2B5B"}
|