jui-tools-mcp-server 2.15.11 → 2.17.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/.github/workflows/test.yml +2 -1
- package/README.md +30 -1
- package/dist/index.js +30 -2
- package/dist/tools/infra/infra_check.d.ts +10 -0
- package/dist/tools/infra/infra_check.js +43 -0
- package/dist/tools/infra/infra_common.d.ts +40 -0
- package/dist/tools/infra/infra_common.js +44 -0
- package/dist/tools/infra/infra_cost.d.ts +3 -0
- package/dist/tools/infra/infra_cost.js +118 -0
- package/dist/tools/infra/infra_load_validate.d.ts +8 -0
- package/dist/tools/infra/infra_load_validate.js +39 -0
- package/dist/tools/infra/infra_prices_validate.d.ts +7 -0
- package/dist/tools/infra/infra_prices_validate.js +34 -0
- package/dist/tools/infra/infra_seed.d.ts +27 -0
- package/dist/tools/infra/infra_seed.js +81 -0
- package/dist/tools/infra/infra_validate.d.ts +8 -0
- package/dist/tools/infra/infra_validate.js +37 -0
- package/dist/tools/viz/viz_common.d.ts +13 -0
- package/dist/tools/viz/viz_common.js +50 -0
- package/dist/tools/viz/viz_doctor.d.ts +2 -0
- package/dist/tools/viz/viz_doctor.js +14 -0
- package/dist/tools/viz/viz_open.d.ts +2 -0
- package/dist/tools/viz/viz_open.js +14 -0
- package/dist/tools/viz/viz_start.d.ts +2 -0
- package/dist/tools/viz/viz_start.js +23 -0
- package/dist/tools/viz/viz_status.d.ts +2 -0
- package/dist/tools/viz/viz_status.js +21 -0
- package/dist/tools/viz/viz_stop.d.ts +2 -0
- package/dist/tools/viz/viz_stop.js +13 -0
- package/package.json +1 -1
- package/scripts/record-infra-fixtures.sh +78 -0
- package/src/index.ts +33 -2
- package/src/tools/infra/infra_check.ts +49 -0
- package/src/tools/infra/infra_common.ts +69 -0
- package/src/tools/infra/infra_cost.ts +122 -0
- package/src/tools/infra/infra_load_validate.ts +46 -0
- package/src/tools/infra/infra_prices_validate.ts +41 -0
- package/src/tools/infra/infra_seed.ts +88 -0
- package/src/tools/infra/infra_validate.ts +44 -0
- package/src/tools/viz/viz_common.ts +61 -0
- package/src/tools/viz/viz_doctor.ts +20 -0
- package/src/tools/viz/viz_open.ts +20 -0
- package/src/tools/viz/viz_start.ts +27 -0
- package/src/tools/viz/viz_status.ts +27 -0
- package/src/tools/viz/viz_stop.ts +19 -0
- package/tests/fixtures/infra/RECORDED_FROM +1 -0
- package/tests/fixtures/infra/check-infra.json +101 -0
- package/tests/fixtures/infra/cost-infra.day.json +10080 -0
- package/tests/fixtures/infra/cost-infra.month-by-node.json +484 -0
- package/tests/fixtures/infra/cost-infra.month.json +381 -0
- package/tests/fixtures/infra/cost-infra.refused.json +5 -0
- package/tests/fixtures/infra/seed-infra.draft.json +233 -0
- package/tests/fixtures/infra/seed-infra.report.json +46 -0
- package/tests/fixtures/infra/validate-infra-load.errors.json +39 -0
- package/tests/fixtures/infra/validate-infra-load.ok.json +29 -0
- package/tests/fixtures/infra/validate-infra-load.unreadable.json +6 -0
- package/tests/fixtures/infra/validate-infra-prices.ok.json +9 -0
- package/tests/fixtures/infra/validate-infra.errors.json +39 -0
- package/tests/fixtures/infra/validate-infra.ok.json +30 -0
- package/tests/fixtures/infra/validate-infra.unreadable.json +6 -0
- package/tests/tool_count.test.ts +53 -0
- package/tests/tools_infra.test.ts +332 -0
- package/tests/tools_viz.test.ts +154 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { failure, need, problems, runDocJson, text } from "./infra_common.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `jsonui-doc validate infra-prices <project> --json`: the room's own price files (<infra.dir>/prices, or
|
|
8
|
+
* infra.prices_dir). Exit 0: no error; 1: errors; 2: the folder cannot be read (reason).
|
|
9
|
+
*/
|
|
10
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
11
|
+
server.tool(
|
|
12
|
+
"infra_prices_validate",
|
|
13
|
+
"Validate the server room's own price files (docs/infra/prices/*.json — only for what the bundled prices lack) with `jsonui-doc validate infra-prices --json`. Returns the files used, how many prices, and the problems by severity and code with the first 20. Needs jsonui-cli 2.3.15+.",
|
|
14
|
+
{
|
|
15
|
+
project_dir: z.string().optional().describe("Project directory with jui.config.json (overrides JUI_PROJECT_DIR env)"),
|
|
16
|
+
},
|
|
17
|
+
async ({ project_dir }) => {
|
|
18
|
+
try {
|
|
19
|
+
const projectDir = config.resolveProjectDir(project_dir);
|
|
20
|
+
const { exitCode, data } = await runDocJson(["validate", "infra-prices", projectDir, "--json"], projectDir);
|
|
21
|
+
need(data, ["ok"], "validate infra-prices");
|
|
22
|
+
if (!data.ok) {
|
|
23
|
+
need(data, ["reason", "message"], "validate infra-prices");
|
|
24
|
+
return text({ ok: false, exit: exitCode, readable: false, reason: data.reason, message: data.message });
|
|
25
|
+
}
|
|
26
|
+
need(data, ["dir", "files", "problems", "count"], "validate infra-prices");
|
|
27
|
+
return text({
|
|
28
|
+
ok: exitCode === 0,
|
|
29
|
+
exit: exitCode,
|
|
30
|
+
readable: true,
|
|
31
|
+
dir: data.dir,
|
|
32
|
+
files: data.files,
|
|
33
|
+
count: data.count,
|
|
34
|
+
problems: problems(data.problems, "validate infra-prices"),
|
|
35
|
+
});
|
|
36
|
+
} catch (e: any) {
|
|
37
|
+
return failure(e);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "fs";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { ServerConfig } from "../../config.js";
|
|
7
|
+
import { ContractError, FIRST, failure, need, text } from "./infra_common.js";
|
|
8
|
+
import { runCli } from "../../cli_runner.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `jsonui-doc seed infra <project> -o <dir>`: a draft model from what the project already says (Compose, Terraform /
|
|
12
|
+
* OpenTofu, Kubernetes, CloudFormation / SAM / CDK, wrangler, PaaS files, SDK imports and env names; never state,
|
|
13
|
+
* tfvars or .env values), and its report (what it read, what it could not place). By default the draft is written to
|
|
14
|
+
* a temp folder and read back — nothing in the project changes; write: true writes <infra.dir>/seed (the draft is a
|
|
15
|
+
* starting point, never read as the model). Exit 0: drafted; 2: refused (bad config, the draft fails its own check,
|
|
16
|
+
* or a draft is already there without force).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function infraDir(projectDir: string): string {
|
|
20
|
+
try {
|
|
21
|
+
const cfg = JSON.parse(readFileSync(join(projectDir, "jui.config.json"), "utf-8"));
|
|
22
|
+
if (typeof cfg?.infra?.dir === "string") return cfg.infra.dir;
|
|
23
|
+
} catch {
|
|
24
|
+
// no config, or not JSON: the CLI says so itself
|
|
25
|
+
}
|
|
26
|
+
return "docs/infra";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readJson(path: string, what: string): any {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
32
|
+
} catch {
|
|
33
|
+
throw new ContractError(`seed infra wrote no readable ${what} at ${path}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function summarize(draft: any, report: any) {
|
|
38
|
+
need(draft, ["nodes", "groups", "edges"], "seed infra draft");
|
|
39
|
+
need(report, ["inputs", "unmatched", "skipped", "skippedCount", "notes", "withheld"], "seed infra report");
|
|
40
|
+
return {
|
|
41
|
+
counts: { nodes: draft.nodes.length, groups: draft.groups.length, edges: draft.edges.length,
|
|
42
|
+
flows: Array.isArray(draft.flows) ? draft.flows.length : 0 },
|
|
43
|
+
environments: (draft.environments ?? []).map((e: any) => e.id),
|
|
44
|
+
nodes: draft.nodes.slice(0, 50).map((n: any) => ({
|
|
45
|
+
id: n.id, category: n.category, provider: n.provider, service: n.service, iac: n.iac, environments: n.environments,
|
|
46
|
+
})),
|
|
47
|
+
inputs: report.inputs,
|
|
48
|
+
unmatched: Object.fromEntries(Object.entries(report.unmatched).map(([provider, u]: [string, any]) =>
|
|
49
|
+
[provider, { count: u.count, items: (u.items ?? []).slice(0, FIRST) }])),
|
|
50
|
+
notDrawn: report.notDrawn ?? {},
|
|
51
|
+
skipped: { count: report.skippedCount, first: report.skipped.slice(0, FIRST) },
|
|
52
|
+
notes: report.notes.slice(0, FIRST),
|
|
53
|
+
withheld: report.withheld,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
58
|
+
server.tool(
|
|
59
|
+
"infra_seed",
|
|
60
|
+
"Draft the server room's model from the project's IaC, config files and code with `jsonui-doc seed infra` (Compose, Terraform/OpenTofu, Kubernetes, CloudFormation/SAM/CDK, wrangler, Firebase/PaaS files, SDK imports and env var names — never Terraform state, tfvars or .env values). Returns the draft's counts and nodes (id, category, provider, service, iac), what was read, what could not be placed (unmatched, by provider) or was skipped, and notes. By default nothing in the project is written; write: true writes <infra.dir>/seed/draft.json and seed-report.json (a starting point to copy from — the model is docs/infra/*.json).",
|
|
61
|
+
{
|
|
62
|
+
project_dir: z.string().optional().describe("Project directory with jui.config.json (overrides JUI_PROJECT_DIR env)"),
|
|
63
|
+
write: z.boolean().optional().describe("Write the draft to <infra.dir>/seed in the project (default false: read only)"),
|
|
64
|
+
force: z.boolean().optional().describe("With write: replace a draft already there"),
|
|
65
|
+
},
|
|
66
|
+
async ({ project_dir, write, force }) => {
|
|
67
|
+
const temp = write ? null : mkdtempSync(join(tmpdir(), "jui-mcp-seed-"));
|
|
68
|
+
try {
|
|
69
|
+
const projectDir = config.resolveProjectDir(project_dir);
|
|
70
|
+
const out = temp ?? join(projectDir, infraDir(projectDir), "seed");
|
|
71
|
+
const args = ["seed", "infra", projectDir, "-o", out];
|
|
72
|
+
if (write && force) args.push("--force");
|
|
73
|
+
const result = await runCli("jsonui-doc", args, { cwd: projectDir, timeout: 180_000 });
|
|
74
|
+
if (result.exitCode !== 0 || !existsSync(join(out, "draft.json"))) {
|
|
75
|
+
return text({ ok: false, exit: result.exitCode,
|
|
76
|
+
message: (result.stderr || result.stdout).trim().split("\n").slice(-5).join("\n") });
|
|
77
|
+
}
|
|
78
|
+
const summary = summarize(readJson(join(out, "draft.json"), "draft.json"),
|
|
79
|
+
readJson(join(out, "seed-report.json"), "seed-report.json"));
|
|
80
|
+
return text({ ok: true, exit: 0, written: write ? out : null, ...summary });
|
|
81
|
+
} catch (e: any) {
|
|
82
|
+
return failure(e);
|
|
83
|
+
} finally {
|
|
84
|
+
if (temp) rmSync(temp, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ServerConfig } from "../../config.js";
|
|
4
|
+
import { failure, need, problems, runDocJson, text } from "./infra_common.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `jsonui-doc validate infra <project> --json`: the room's model (docs/infra/*.json, or jui.config.json's infra.dir)
|
|
8
|
+
* read as jui viz reads it — each file against the schema and the shapes of secrets, then merged, and its flows
|
|
9
|
+
* walked. Exit 0: no error (warnings alone are 0); 1: errors; 2: nothing could be read (reason).
|
|
10
|
+
*/
|
|
11
|
+
export function register(server: McpServer, config: ServerConfig) {
|
|
12
|
+
server.tool(
|
|
13
|
+
"infra_validate",
|
|
14
|
+
"Validate the server room's infra model (docs/infra/*.json: environments, groups, nodes, edges, flows) with `jsonui-doc validate infra --json`. Returns whether it could be read (and why not), the files used, the counts, and the problems by severity and code with the first 20 (file, path, code, message). Flow problems (flow.*) name the rule a step most likely missed. Needs jsonui-cli 2.3.15+.",
|
|
15
|
+
{
|
|
16
|
+
project_dir: z.string().optional().describe("Project directory with jui.config.json (overrides JUI_PROJECT_DIR env)"),
|
|
17
|
+
},
|
|
18
|
+
async ({ project_dir }) => {
|
|
19
|
+
try {
|
|
20
|
+
const projectDir = config.resolveProjectDir(project_dir);
|
|
21
|
+
const { exitCode, data } = await runDocJson(["validate", "infra", projectDir, "--json"], projectDir);
|
|
22
|
+
need(data, ["ok"], "validate infra");
|
|
23
|
+
if (!data.ok && data.reason !== "invalid") {
|
|
24
|
+
need(data, ["reason", "message"], "validate infra");
|
|
25
|
+
return text({ ok: false, exit: exitCode, readable: false, reason: data.reason, message: data.message });
|
|
26
|
+
}
|
|
27
|
+
need(data, ["dir", "files", "problems"], "validate infra");
|
|
28
|
+
return text({
|
|
29
|
+
ok: exitCode === 0,
|
|
30
|
+
exit: exitCode,
|
|
31
|
+
readable: data.ok,
|
|
32
|
+
dir: data.dir,
|
|
33
|
+
files: data.files,
|
|
34
|
+
environments: (data.environments ?? []).map((e: any) => e.id),
|
|
35
|
+
counts: data.counts ?? null,
|
|
36
|
+
flows: Array.isArray(data.flows) ? data.flows.length : null,
|
|
37
|
+
problems: problems(data.problems, "validate infra"),
|
|
38
|
+
});
|
|
39
|
+
} catch (e: any) {
|
|
40
|
+
return failure(e);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { runCli, CliResult } from "../../cli_runner.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared by the viz_* tools (`jui viz`, the local viewer of Claude Code
|
|
6
|
+
* sessions). jui viz reads ~/.claude and ~/.jsonui-cli/viz, not a project, so
|
|
7
|
+
* every call runs in the home folder — never JUI_PROJECT_DIR.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Said in every viz_* description: other agents' sessions (Codex, …) are not shown. */
|
|
11
|
+
export const CLAUDE_CODE_ONLY =
|
|
12
|
+
"jui viz shows Claude Code sessions only (Claude Code のセッションだけを見せる).";
|
|
13
|
+
|
|
14
|
+
export const UPDATE_JUI =
|
|
15
|
+
"curl -fsSL https://raw.githubusercontent.com/Tai-Kimura/jsonui-cli/main/installer/bootstrap.sh | bash";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The installed jui predates what the tool asks for. Matched on argparse's
|
|
19
|
+
* own words, as printed by the jui versions that lack each piece (measured
|
|
20
|
+
* 2026-10-18: jui 1.9.37 has no `viz`; feature/jui-viz before `start` has
|
|
21
|
+
* neither `start` nor `status --with-relay`).
|
|
22
|
+
*/
|
|
23
|
+
const OUTDATED: { pattern: RegExp; missing: string }[] = [
|
|
24
|
+
{ pattern: /invalid choice: 'viz'/, missing: "`jui viz`" },
|
|
25
|
+
{ pattern: /invalid choice: 'start'/, missing: "`jui viz start`" },
|
|
26
|
+
{ pattern: /unrecognized arguments: .*--with-relay/, missing: "`jui viz status --with-relay`" },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export function outdatedJui(result: CliResult): string | undefined {
|
|
30
|
+
const hit = OUTDATED.find((o) => o.pattern.test(result.stderr));
|
|
31
|
+
if (!hit) return undefined;
|
|
32
|
+
return `The installed jui has no ${hit.missing}. Update jui, then call this tool again: ${UPDATE_JUI}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function runViz(args: string[], timeout?: number): Promise<CliResult> {
|
|
36
|
+
return runCli("jui", ["viz", ...args], { cwd: homedir(), timeout });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The tool's text: the CLI's JSON (when it printed one) inside the usual envelope. */
|
|
40
|
+
export function vizResult(result: CliResult): string {
|
|
41
|
+
let parsed: unknown = undefined;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(result.stdout);
|
|
44
|
+
} catch {
|
|
45
|
+
// not JSON (stop / open print a line)
|
|
46
|
+
}
|
|
47
|
+
const notInstalled = (result.exitCode as unknown) === "ENOENT";
|
|
48
|
+
return JSON.stringify(
|
|
49
|
+
{
|
|
50
|
+
success: result.exitCode === 0,
|
|
51
|
+
result: parsed,
|
|
52
|
+
output: parsed === undefined ? result.stdout || undefined : undefined,
|
|
53
|
+
errors: result.stderr || undefined,
|
|
54
|
+
update_jui: notInstalled
|
|
55
|
+
? `jui is not installed (not found on PATH). Install it: ${UPDATE_JUI}`
|
|
56
|
+
: outdatedJui(result),
|
|
57
|
+
},
|
|
58
|
+
null,
|
|
59
|
+
2
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CLAUDE_CODE_ONLY, runViz, vizResult } from "./viz_common.js";
|
|
3
|
+
|
|
4
|
+
export function register(server: McpServer) {
|
|
5
|
+
server.tool(
|
|
6
|
+
"viz_doctor",
|
|
7
|
+
"Check the environment for jui viz (the local viewer of Claude Code sessions): Python packages, the hooks and " +
|
|
8
|
+
"the Python they run, the port, the claude CLI. Each check has name, ok and detail. " +
|
|
9
|
+
CLAUDE_CODE_ONLY,
|
|
10
|
+
{},
|
|
11
|
+
async () => {
|
|
12
|
+
try {
|
|
13
|
+
const result = await runViz(["doctor", "--json"], 30_000);
|
|
14
|
+
return { content: [{ type: "text", text: vizResult(result) }] };
|
|
15
|
+
} catch (e: any) {
|
|
16
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CLAUDE_CODE_ONLY, runViz, vizResult } from "./viz_common.js";
|
|
3
|
+
|
|
4
|
+
export function register(server: McpServer) {
|
|
5
|
+
server.tool(
|
|
6
|
+
"viz_open",
|
|
7
|
+
"Open the running jui viz page (the local viewer of Claude Code sessions) in the user's browser. " +
|
|
8
|
+
"Fails when no server runs: call viz_start first. " +
|
|
9
|
+
CLAUDE_CODE_ONLY,
|
|
10
|
+
{},
|
|
11
|
+
async () => {
|
|
12
|
+
try {
|
|
13
|
+
const result = await runViz(["open"], 30_000);
|
|
14
|
+
return { content: [{ type: "text", text: vizResult(result) }] };
|
|
15
|
+
} catch (e: any) {
|
|
16
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CLAUDE_CODE_ONLY, runViz, vizResult } from "./viz_common.js";
|
|
4
|
+
|
|
5
|
+
export function register(server: McpServer) {
|
|
6
|
+
server.tool(
|
|
7
|
+
"viz_start",
|
|
8
|
+
"Start the jui viz server (the local viewer of Claude Code sessions) in the background and return its URL " +
|
|
9
|
+
"on 127.0.0.1. A server already running is reported (alreadyRunning), not started again. Hooks and the " +
|
|
10
|
+
"relay are installed with the jui CLI, not here. " +
|
|
11
|
+
CLAUDE_CODE_ONLY,
|
|
12
|
+
{
|
|
13
|
+
port: z.number().int().min(1).max(65535).optional().describe("Port on 127.0.0.1 (default: the viz config, 7788)"),
|
|
14
|
+
},
|
|
15
|
+
async (params) => {
|
|
16
|
+
try {
|
|
17
|
+
const args = ["start", "--json"];
|
|
18
|
+
if (params.port !== undefined) { args.push("--port", String(params.port)); }
|
|
19
|
+
// jui waits up to 15 s for the server to answer.
|
|
20
|
+
const result = await runViz(args, 45_000);
|
|
21
|
+
return { content: [{ type: "text", text: vizResult(result) }] };
|
|
22
|
+
} catch (e: any) {
|
|
23
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CLAUDE_CODE_ONLY, runViz, vizResult } from "./viz_common.js";
|
|
4
|
+
|
|
5
|
+
export function register(server: McpServer) {
|
|
6
|
+
server.tool(
|
|
7
|
+
"viz_status",
|
|
8
|
+
"Report jui viz (the local viewer of Claude Code sessions): whether its server runs (pid, URL), the hooks in " +
|
|
9
|
+
"~/.claude/settings.json (installed of 18), and whether the permission relay is registered. " +
|
|
10
|
+
CLAUDE_CODE_ONLY,
|
|
11
|
+
{
|
|
12
|
+
relay: z.boolean().optional().describe(
|
|
13
|
+
"Also ask claude whether the permission relay is registered (default true; runs `claude mcp get`, a few seconds)"
|
|
14
|
+
),
|
|
15
|
+
},
|
|
16
|
+
async (params) => {
|
|
17
|
+
try {
|
|
18
|
+
const args = ["status", "--json"];
|
|
19
|
+
if (params.relay !== false) { args.push("--with-relay"); }
|
|
20
|
+
const result = await runViz(args, 30_000);
|
|
21
|
+
return { content: [{ type: "text", text: vizResult(result) }] };
|
|
22
|
+
} catch (e: any) {
|
|
23
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CLAUDE_CODE_ONLY, runViz, vizResult } from "./viz_common.js";
|
|
3
|
+
|
|
4
|
+
export function register(server: McpServer) {
|
|
5
|
+
server.tool(
|
|
6
|
+
"viz_stop",
|
|
7
|
+
"Stop the jui viz server (the local viewer of Claude Code sessions). Only the server jui recorded is stopped. " +
|
|
8
|
+
CLAUDE_CODE_ONLY,
|
|
9
|
+
{},
|
|
10
|
+
async () => {
|
|
11
|
+
try {
|
|
12
|
+
const result = await runViz(["stop"], 30_000);
|
|
13
|
+
return { content: [{ type: "text", text: vizResult(result) }] };
|
|
14
|
+
} catch (e: any) {
|
|
15
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
7479faaf infra arms: a load with warnings alone is cost infra's 0 (still counted), and a price dropped by two rows warns once
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ok": true,
|
|
3
|
+
"exit": 0,
|
|
4
|
+
"checks": [
|
|
5
|
+
{
|
|
6
|
+
"name": "infra-load",
|
|
7
|
+
"type": "builtin:infra-load",
|
|
8
|
+
"status": "ok",
|
|
9
|
+
"summary": {
|
|
10
|
+
"ok": 10,
|
|
11
|
+
"mismatch": 0,
|
|
12
|
+
"missing_in_impl": 0,
|
|
13
|
+
"missing_in_doc": 0,
|
|
14
|
+
"skipped": 6,
|
|
15
|
+
"warning": 2,
|
|
16
|
+
"unit": "node"
|
|
17
|
+
},
|
|
18
|
+
"results": [
|
|
19
|
+
{
|
|
20
|
+
"target": "load/ordinary/stripe",
|
|
21
|
+
"status": "skipped",
|
|
22
|
+
"confidence": "metadata",
|
|
23
|
+
"message": "its kind reads no load"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"target": "load/ordinary/api-role",
|
|
27
|
+
"status": "skipped",
|
|
28
|
+
"confidence": "metadata",
|
|
29
|
+
"message": "its kind reads no load"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"target": "load/ordinary/api-5xx",
|
|
33
|
+
"status": "skipped",
|
|
34
|
+
"confidence": "metadata",
|
|
35
|
+
"message": "its kind reads no load"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"target": "load/sale/stripe",
|
|
39
|
+
"status": "skipped",
|
|
40
|
+
"confidence": "metadata",
|
|
41
|
+
"message": "its kind reads no load"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"target": "load/sale/mailer",
|
|
45
|
+
"status": "warning",
|
|
46
|
+
"confidence": "metadata",
|
|
47
|
+
"expected": "emails",
|
|
48
|
+
"message": "billed by use, but this load definition gives it no load (undefined, not 0)"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"target": "load/sale/assets",
|
|
52
|
+
"status": "warning",
|
|
53
|
+
"confidence": "metadata",
|
|
54
|
+
"expected": "get_requests, put_requests, storage_gb, egress_gb",
|
|
55
|
+
"message": "billed by use, but this load definition gives it no load (undefined, not 0)"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"target": "load/sale/api-role",
|
|
59
|
+
"status": "skipped",
|
|
60
|
+
"confidence": "metadata",
|
|
61
|
+
"message": "its kind reads no load"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"target": "load/sale/api-5xx",
|
|
65
|
+
"status": "skipped",
|
|
66
|
+
"confidence": "metadata",
|
|
67
|
+
"message": "its kind reads no load"
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"warnings": [],
|
|
71
|
+
"report": "/private<ROOT>/room/docs/infra/load/.check-report.infra-load.json"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "infra-code",
|
|
75
|
+
"type": "builtin:infra-code",
|
|
76
|
+
"status": "ok",
|
|
77
|
+
"summary": {
|
|
78
|
+
"ok": 0,
|
|
79
|
+
"mismatch": 0,
|
|
80
|
+
"missing_in_impl": 0,
|
|
81
|
+
"missing_in_doc": 0,
|
|
82
|
+
"skipped": 0,
|
|
83
|
+
"warning": 1,
|
|
84
|
+
"unit": "reference"
|
|
85
|
+
},
|
|
86
|
+
"results": [
|
|
87
|
+
{
|
|
88
|
+
"target": "stripe/stripe#evidence/0",
|
|
89
|
+
"status": "warning",
|
|
90
|
+
"confidence": "proof",
|
|
91
|
+
"expected": "backend/app/config.py",
|
|
92
|
+
"message": "the evidence file is not there"
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"warnings": [
|
|
96
|
+
"[unverified] *: only Python and JavaScript / TypeScript imports, AWS SDK clients and environment variable names are read (not values, not calls): a service reached another way is not seen"
|
|
97
|
+
],
|
|
98
|
+
"report": "/private<ROOT>/room/docs/infra/.check-report.infra-code.json"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|