@xpufx/paseo-mcp-tools 0.1.3
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 +21 -0
- package/README.md +84 -0
- package/client/__snapshots__/attribution.test.tsx.snap +170 -0
- package/client/commands.ts +113 -0
- package/client/mcp-query.tsx +39 -0
- package/client/pill.tsx +886 -0
- package/client/timeline.tsx +136 -0
- package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
- package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
- package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
- package/client/vendor/paseo-plugin-helper/host.ts +277 -0
- package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/index.ts +28 -0
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
- package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
- package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
- package/client/vendor/paseo-plugin-helper/query.ts +66 -0
- package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
- package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
- package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
- package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
- package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
- package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
- package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
- package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
- package/docs/TEST_METHODOLOGY.md +29 -0
- package/package.json +41 -0
- package/paseo-plugin.json +4 -0
- package/scripts/version.mjs +4 -0
- package/server/discovery/extract.ts +277 -0
- package/server/discovery/types.ts +23 -0
- package/server/health/health.ts +274 -0
- package/server/inject.ts +32 -0
- package/server/mcp.ts +365 -0
- package/server/providers/README.md +51 -0
- package/server/providers/antigravity.ts +22 -0
- package/server/providers/catalog.ts +6 -0
- package/server/providers/claude.ts +21 -0
- package/server/providers/codex.ts +85 -0
- package/server/providers/family.ts +24 -0
- package/server/providers/index.ts +11 -0
- package/server/providers/opencode.ts +95 -0
- package/server/providers/paseo.ts +112 -0
- package/server/providers/pi.ts +39 -0
- package/server/settings.ts +19 -0
- package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
- package/server/vendor/paseo-plugin-helper/index.ts +18 -0
- package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
- package/server/vendor/paseo-plugin-helper/mcp/client.ts +349 -0
- package/server/vendor/paseo-plugin-helper/mcp/http-client.ts +399 -0
- package/server/vendor/paseo-plugin-helper/mcp/index.ts +5 -0
- package/server/vendor/paseo-plugin-helper/mcp/process-killer.ts +65 -0
- package/server/vendor/paseo-plugin-helper/mcp/ring-buffer.ts +29 -0
- package/server/vendor/paseo-plugin-helper/mcp/types.ts +41 -0
- package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
- package/server/vendor/paseo-plugin-helper/network.ts +91 -0
- package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
- package/server/vendor/paseo-plugin-helper/process.ts +186 -0
- package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
- package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
- package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
- package/server/vendor/paseo-plugin-helper/system.ts +128 -0
- package/server/vendor/paseo-plugin-helper/task.ts +116 -0
- package/server/vendor/paseo-plugin-helper/version.ts +153 -0
- package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
- package/shared/mcp.ts +207 -0
- package/shared/vendor/paseo-plugin-helper/README.md +11 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
- package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
- package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
- package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
- package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
- package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
- package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
- package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
- package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
- package/shared/version.ts +2 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Provider Probes — Contract-First
|
|
2
|
+
|
|
3
|
+
You don't test people's probe logic, you test that they satisfy the interface. Adding a new provider only touches **one file + 1 line in catalog**.
|
|
4
|
+
|
|
5
|
+
## Contract
|
|
6
|
+
|
|
7
|
+
`discovery/types.ts: McpProbe`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
interface McpProbe {
|
|
11
|
+
id: string; // key in registry, e.g. "opencode"
|
|
12
|
+
label: string; // human, e.g. "opencode · live"
|
|
13
|
+
matches(provider: string): bool // does this probe handle this Paseo provider id?
|
|
14
|
+
probe(ctx: ProbeContext): Promise<ProbeResult> // live probe run
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ProbeContext { agentId: string; provider: string; cwd: string; sessionId?: string | null }
|
|
18
|
+
interface ProbeResult { servers: McpServer[]; error?: string | null; steps?: DiagnosticStep[] }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`McpServer` is `shared/mcp.ts: McpServerSchema` — `id, name, transport, source{kind,label,path}, command, url, description, hasSecrets, configPreview`.
|
|
22
|
+
|
|
23
|
+
## Adding a provider
|
|
24
|
+
|
|
25
|
+
1. Create `providers/<id>.ts`:
|
|
26
|
+
```ts
|
|
27
|
+
import type { McpProbe, ProbeContext } from "../discovery/types";
|
|
28
|
+
|
|
29
|
+
const myProbe: McpProbe = {
|
|
30
|
+
id: "mycli",
|
|
31
|
+
label: "mycli · live",
|
|
32
|
+
matches: (p) => p === "mycli",
|
|
33
|
+
async probe(ctx: ProbeContext) {
|
|
34
|
+
// discover servers
|
|
35
|
+
return { servers: [...], error: null };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default myProbe;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
2. Add 1 line to `providers/catalog.ts`:
|
|
43
|
+
```ts
|
|
44
|
+
export { default as mycli } from "./mycli";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
3. Done. The probe is automatically indexed into `probes`, used by `probeForProvider`, reported in diagnostics, and validated by `npm test`.
|
|
48
|
+
|
|
49
|
+
## Tests
|
|
50
|
+
|
|
51
|
+
`providers/providers.test.ts` checks contract compliance — `id` format, `probe` execution, and Zod validation for every returned server.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { matchesFamily } from "./family";
|
|
4
|
+
import { discoverFromCandidates } from "../discovery/extract";
|
|
5
|
+
import type { McpProbe, ProbeContext } from "../discovery/types";
|
|
6
|
+
|
|
7
|
+
export const antigravityProbe: McpProbe = {
|
|
8
|
+
id: "antigravity",
|
|
9
|
+
label: "antigravity · live",
|
|
10
|
+
matches: (provider) => matchesFamily(provider, "antigravity"),
|
|
11
|
+
async probe(ctx: ProbeContext) {
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
return discoverFromCandidates("antigravity", [
|
|
14
|
+
{ path: path.join(home, ".gemini", "config", "mcp_config.json"), label: "antigravity · user global" },
|
|
15
|
+
{ path: path.join(home, ".gemini", "antigravity", "mcp_config.json"), label: "antigravity · app global" },
|
|
16
|
+
{ path: path.join(ctx.cwd, ".gemini", "mcp_config.json"), label: "antigravity · project config" },
|
|
17
|
+
{ path: path.join(ctx.cwd, "mcp_config.json"), label: "antigravity · project root" },
|
|
18
|
+
]);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default antigravityProbe;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as antigravity } from "./antigravity";
|
|
2
|
+
export { default as claude } from "./claude";
|
|
3
|
+
export { default as codex } from "./codex";
|
|
4
|
+
export { default as opencode } from "./opencode";
|
|
5
|
+
export { default as paseo } from "./paseo";
|
|
6
|
+
export { default as pi } from "./pi";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { matchesFamily } from "./family";
|
|
4
|
+
import { discoverFromCandidates } from "../discovery/extract";
|
|
5
|
+
import type { McpProbe, ProbeContext } from "../discovery/types";
|
|
6
|
+
|
|
7
|
+
export const claudeProbe: McpProbe = {
|
|
8
|
+
id: "claude",
|
|
9
|
+
label: "claude · live",
|
|
10
|
+
matches: (provider) => matchesFamily(provider, "claude"),
|
|
11
|
+
async probe(ctx: ProbeContext) {
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
return discoverFromCandidates("claude", [
|
|
14
|
+
{ path: path.join(home, ".claude.json"), label: "claude · user global" },
|
|
15
|
+
{ path: path.join(home, ".claude", "settings.json"), label: "claude · settings global" },
|
|
16
|
+
{ path: path.join(ctx.cwd, ".claude.json"), label: "claude · project shared" },
|
|
17
|
+
]);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default claudeProbe;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { matchesFamily } from "./family";
|
|
6
|
+
import type { McpProbe, McpServer, ProbeContext } from "../discovery/types";
|
|
7
|
+
|
|
8
|
+
export const codexProbe: McpProbe = {
|
|
9
|
+
id: "codex",
|
|
10
|
+
label: "codex · live",
|
|
11
|
+
matches: (provider) => matchesFamily(provider, "codex"),
|
|
12
|
+
async probe(ctx: ProbeContext) {
|
|
13
|
+
const home = os.homedir();
|
|
14
|
+
const candidatePaths = [
|
|
15
|
+
{ path: path.join(home, ".codex", "config.toml"), label: "codex · user global" },
|
|
16
|
+
{ path: path.join(ctx.cwd, ".codex", "config.toml"), label: "codex · project override" },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const merged = new Map<string, McpServer>();
|
|
20
|
+
const steps: Array<{ target: string; status: "found" | "missing" | "error" | "skipped"; details: string; contentPreview: string | null }> = [];
|
|
21
|
+
|
|
22
|
+
for (const cand of candidatePaths) {
|
|
23
|
+
if (!existsSync(cand.path)) {
|
|
24
|
+
steps.push({
|
|
25
|
+
target: cand.path,
|
|
26
|
+
status: "missing",
|
|
27
|
+
details: `${cand.label} does not exist`,
|
|
28
|
+
contentPreview: null,
|
|
29
|
+
});
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const text = await readFile(cand.path, "utf8");
|
|
34
|
+
const sections = text.split(/^\s*\[mcp_servers\./gm).slice(1);
|
|
35
|
+
let count = 0;
|
|
36
|
+
for (const sec of sections) {
|
|
37
|
+
const lines = sec.split("\n");
|
|
38
|
+
const name = (lines[0] || "").replace(/\][\s\S]*$/, "").trim();
|
|
39
|
+
if (!name) continue;
|
|
40
|
+
count++;
|
|
41
|
+
const body = lines.slice(1).join("\n");
|
|
42
|
+
const cmdMatch = body.match(/^\s*command\s*=\s*"([^"]+)"/m);
|
|
43
|
+
const urlMatch = body.match(/^\s*url\s*=\s*"([^"]+)"/m);
|
|
44
|
+
const command = cmdMatch ? cmdMatch[1] : null;
|
|
45
|
+
const url = urlMatch ? urlMatch[1] : null;
|
|
46
|
+
const transport = url ? ("http" as const) : command ? ("stdio" as const) : ("unknown" as const);
|
|
47
|
+
|
|
48
|
+
merged.set(name, {
|
|
49
|
+
id: `session:codex:${name}`,
|
|
50
|
+
name,
|
|
51
|
+
transport,
|
|
52
|
+
source: { kind: "session" as const, label: cand.label, path: cand.path },
|
|
53
|
+
command,
|
|
54
|
+
url,
|
|
55
|
+
description: url ?? command ?? "codex mcp (toml)",
|
|
56
|
+
hasSecrets: /env|token|key|secret/i.test(body),
|
|
57
|
+
configPreview: `[mcp_servers.${name}]\n${body.slice(0, 500).trim()}`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
steps.push({
|
|
61
|
+
target: cand.path,
|
|
62
|
+
status: "found",
|
|
63
|
+
details: `${cand.label} (${text.length} bytes) · Valid TOML with ${count} MCP server(s)`,
|
|
64
|
+
contentPreview: text.slice(0, 1000),
|
|
65
|
+
});
|
|
66
|
+
} catch (e) {
|
|
67
|
+
steps.push({
|
|
68
|
+
target: cand.path,
|
|
69
|
+
status: "error",
|
|
70
|
+
details: `${cand.label} failed to read: ${e instanceof Error ? e.message : String(e)}`,
|
|
71
|
+
contentPreview: null,
|
|
72
|
+
});
|
|
73
|
+
return { servers: [], error: e instanceof Error ? e.message : String(e), steps };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
servers: [...merged.values()],
|
|
79
|
+
error: null,
|
|
80
|
+
steps,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default codexProbe;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const AFFIX_SEPS = ["-", "_", ":", "/"];
|
|
2
|
+
|
|
3
|
+
function norm(value: string): string {
|
|
4
|
+
return value.trim().toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function matchesFamily(provider: string, family: string): boolean {
|
|
8
|
+
const p = norm(provider);
|
|
9
|
+
const f = norm(family);
|
|
10
|
+
if (!p || !f) return false;
|
|
11
|
+
if (p === f) return true;
|
|
12
|
+
return AFFIX_SEPS.some((sep) => p.startsWith(f + sep) || p.endsWith(sep + f));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function matchesAnyFamily(provider: string, families: string[]): boolean {
|
|
16
|
+
return families.some((family) => matchesFamily(provider, family));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const OPENCODE_FORK_IDS = ["example-fork", "sample-fork"];
|
|
20
|
+
|
|
21
|
+
export function matchesOpencodeFamily(provider: string): boolean {
|
|
22
|
+
const p = norm(provider);
|
|
23
|
+
return matchesFamily(p, "opencode") || OPENCODE_FORK_IDS.includes(p);
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { McpProbe } from "../discovery/types";
|
|
2
|
+
import * as catalog from "./catalog";
|
|
3
|
+
|
|
4
|
+
export const probes: McpProbe[] = Object.values(catalog);
|
|
5
|
+
|
|
6
|
+
export function probeForProvider(provider: string, ...labels: Array<string | null | undefined>): McpProbe | null {
|
|
7
|
+
const candidates = [provider, ...labels].filter((c): c is string => typeof c === "string" && c.length > 0);
|
|
8
|
+
return probes.find((p) => candidates.some((c) => p.matches(c))) ?? null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export * from "./catalog";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { safeSpawn } from "paseo-plugin-helper/server";
|
|
4
|
+
import { stripAnsi } from "paseo-plugin-helper/shared";
|
|
5
|
+
import type { McpProbe, ProbeContext, McpServer } from "../discovery/types";
|
|
6
|
+
import { matchesOpencodeFamily } from "./family";
|
|
7
|
+
import { redact } from "../discovery/extract";
|
|
8
|
+
|
|
9
|
+
function parseOpencodeMcpList(output: string, cfgPath: string): McpServer[] {
|
|
10
|
+
const clean = stripAnsi(output);
|
|
11
|
+
const lines = clean.split("\n");
|
|
12
|
+
const servers: McpServer[] = [];
|
|
13
|
+
for (let i = 0; i < lines.length; i++) {
|
|
14
|
+
const line = lines[i].trim();
|
|
15
|
+
const m = line.match(/^[●○•]\s*[✓✗○●]?\s*(\S+)\s+(connected|disabled|failed|connecting)?/);
|
|
16
|
+
if (!m) continue;
|
|
17
|
+
const name = m[1];
|
|
18
|
+
const status = m[2] ?? "unknown";
|
|
19
|
+
const next = lines[i + 1]?.trim().replace(/^│\s*/, "") ?? "";
|
|
20
|
+
const isUrl = next.startsWith("http");
|
|
21
|
+
const isDisabled = status === "disabled";
|
|
22
|
+
if (isDisabled) continue;
|
|
23
|
+
const url = isUrl ? next : null;
|
|
24
|
+
const command = !isUrl && next ? next : null;
|
|
25
|
+
const transport: McpServer["transport"] = url ? "http" : command ? "stdio" : "unknown";
|
|
26
|
+
servers.push({
|
|
27
|
+
id: `session:opencode:${name}`,
|
|
28
|
+
name,
|
|
29
|
+
transport,
|
|
30
|
+
source: { kind: "session", label: "opencode · live", path: cfgPath },
|
|
31
|
+
command,
|
|
32
|
+
url,
|
|
33
|
+
description: `${next} [${status}]`,
|
|
34
|
+
hasSecrets: false,
|
|
35
|
+
configPreview: redact(next),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return servers;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const opencodeProbe: McpProbe = {
|
|
42
|
+
id: "opencode",
|
|
43
|
+
label: "opencode · live",
|
|
44
|
+
matches: (provider) => matchesOpencodeFamily(provider),
|
|
45
|
+
async probe(ctx: ProbeContext) {
|
|
46
|
+
const globalCfg = path.join(os.homedir(), ".config", "opencode", "opencode.jsonc");
|
|
47
|
+
const localCfg = path.join(ctx.cwd, "opencode.json");
|
|
48
|
+
const cfgPath = `${globalCfg} + ${localCfg} (cwd-merged)`;
|
|
49
|
+
const target = "opencode mcp list (CLI)";
|
|
50
|
+
let out: string;
|
|
51
|
+
try {
|
|
52
|
+
const res = await safeSpawn("opencode", ["mcp", "list"], { cwd: ctx.cwd, timeoutMs: 5000 });
|
|
53
|
+
out = res.stdout;
|
|
54
|
+
if (res.code !== 0 && !out) {
|
|
55
|
+
const msg = res.stderr || `opencode mcp list exited ${res.code}`;
|
|
56
|
+
return {
|
|
57
|
+
servers: [],
|
|
58
|
+
error: msg,
|
|
59
|
+
steps: [{ target, status: "error" as const, details: msg, contentPreview: null }],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
64
|
+
return {
|
|
65
|
+
servers: [],
|
|
66
|
+
error: msg,
|
|
67
|
+
steps: [{ target, status: "error" as const, details: msg, contentPreview: null }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const servers = parseOpencodeMcpList(out, cfgPath);
|
|
72
|
+
return {
|
|
73
|
+
servers,
|
|
74
|
+
error: null,
|
|
75
|
+
steps: [
|
|
76
|
+
{
|
|
77
|
+
target,
|
|
78
|
+
status: "found" as const,
|
|
79
|
+
details: `CLI returned ${servers.length} active server(s)`,
|
|
80
|
+
contentPreview: redact(out.slice(0, 1000)),
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
} catch (e) {
|
|
85
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
86
|
+
return {
|
|
87
|
+
servers: [],
|
|
88
|
+
error: msg,
|
|
89
|
+
steps: [{ target, status: "error" as const, details: msg, contentPreview: redact(out.slice(0, 1000)) }],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export default opencodeProbe;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
5
|
+
import { matchesFamily } from "./family";
|
|
6
|
+
import type { McpProbe, ProbeContext, ProbeResult, DiagnosticStep } from "../discovery/types";
|
|
7
|
+
import type { McpServer } from "../../shared/mcp";
|
|
8
|
+
|
|
9
|
+
export async function findStoredAgentRecord(agentId: string): Promise<Record<string, unknown> | null> {
|
|
10
|
+
const base = path.join(os.homedir(), ".paseo", "agents");
|
|
11
|
+
if (!existsSync(base)) return null;
|
|
12
|
+
try {
|
|
13
|
+
const projects = await readdir(base, { withFileTypes: true });
|
|
14
|
+
for (const ent of projects) {
|
|
15
|
+
const cand = ent.isDirectory()
|
|
16
|
+
? path.join(base, ent.name, `${agentId}.json`)
|
|
17
|
+
: path.join(base, ent.name);
|
|
18
|
+
if (ent.isFile() && ent.name !== `${agentId}.json`) continue;
|
|
19
|
+
if (existsSync(cand)) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(await readFile(cand, "utf8")) as Record<string, unknown>;
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
} catch {}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const paseoProbe: McpProbe = {
|
|
30
|
+
id: "paseo",
|
|
31
|
+
label: "Paseo · Built-in",
|
|
32
|
+
matches: (provider) => matchesFamily(provider, "paseo"),
|
|
33
|
+
async probe(ctx: ProbeContext): Promise<ProbeResult> {
|
|
34
|
+
const steps: DiagnosticStep[] = [];
|
|
35
|
+
const record = await findStoredAgentRecord(ctx.agentId);
|
|
36
|
+
const cfg = (record?.config ?? {}) as Record<string, unknown>;
|
|
37
|
+
const meta = (record?.metadata ?? {}) as Record<string, unknown>;
|
|
38
|
+
const mcpServers = ((cfg.mcpServers ?? meta.mcpServers ?? {}) as Record<string, unknown>);
|
|
39
|
+
const paseoDef = mcpServers.paseo as Record<string, unknown> | undefined;
|
|
40
|
+
|
|
41
|
+
if (record) {
|
|
42
|
+
steps.push({
|
|
43
|
+
target: "Paseo Agent Record",
|
|
44
|
+
status: "found",
|
|
45
|
+
details: paseoDef?.url ? `Active session endpoint: ${paseoDef.url}` : "Found record, no explicit paseo MCP entry",
|
|
46
|
+
contentPreview: paseoDef ? JSON.stringify(paseoDef, null, 2) : null,
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
steps.push({
|
|
50
|
+
target: "Paseo Agent Record",
|
|
51
|
+
status: "missing",
|
|
52
|
+
details: `No stored agent record found for ID ${ctx.agentId}`,
|
|
53
|
+
contentPreview: null,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let url: string | null = null;
|
|
58
|
+
let hasSecrets = false;
|
|
59
|
+
if (paseoDef && typeof paseoDef.url === "string") {
|
|
60
|
+
url = paseoDef.url;
|
|
61
|
+
hasSecrets = Boolean(paseoDef.headers);
|
|
62
|
+
} else {
|
|
63
|
+
const configPath = path.join(os.homedir(), ".paseo", "config.json");
|
|
64
|
+
if (existsSync(configPath)) {
|
|
65
|
+
try {
|
|
66
|
+
const configJson = JSON.parse(await readFile(configPath, "utf8")) as Record<string, unknown>;
|
|
67
|
+
const daemon = (configJson.daemon ?? {}) as Record<string, unknown>;
|
|
68
|
+
const mcp = (daemon.mcp ?? {}) as Record<string, unknown>;
|
|
69
|
+
const enabled = mcp.enabled !== false && mcp.injectIntoAgents !== false;
|
|
70
|
+
steps.push({
|
|
71
|
+
target: "Paseo Daemon MCP Config",
|
|
72
|
+
status: enabled ? "found" : "skipped",
|
|
73
|
+
details: `mcp.enabled=${mcp.enabled !== false}, injectIntoAgents=${mcp.injectIntoAgents !== false}, listen=${daemon.listen ?? "default"}`,
|
|
74
|
+
contentPreview: JSON.stringify(daemon.mcp, null, 2),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (enabled) {
|
|
78
|
+
const listen = typeof daemon.listen === "string" ? daemon.listen : "127.0.0.1:6767";
|
|
79
|
+
url = `http://${listen}/mcp/agents?callerAgentId=${ctx.agentId}`;
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
steps.push({
|
|
83
|
+
target: "Paseo Daemon MCP Config",
|
|
84
|
+
status: "error",
|
|
85
|
+
details: e instanceof Error ? e.message : String(e),
|
|
86
|
+
contentPreview: null,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!url) {
|
|
93
|
+
return { servers: [], steps };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const server: McpServer = {
|
|
97
|
+
id: "session:paseo",
|
|
98
|
+
name: "Paseo (Builtin)",
|
|
99
|
+
transport: "http",
|
|
100
|
+
source: { kind: "paseo", label: "Paseo · Built-in", path: url },
|
|
101
|
+
command: null,
|
|
102
|
+
url,
|
|
103
|
+
description: "Paseo control plane, agent automation & browser tools",
|
|
104
|
+
hasSecrets,
|
|
105
|
+
configPreview: JSON.stringify({ name: "Paseo (Builtin)", url }, null, 2),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return { servers: [server], steps };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default paseoProbe;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { matchesFamily } from "./family";
|
|
4
|
+
import { discoverFromCandidates } from "../discovery/extract";
|
|
5
|
+
import type { McpProbe, ProbeContext } from "../discovery/types";
|
|
6
|
+
|
|
7
|
+
export const piProbe: McpProbe = {
|
|
8
|
+
id: "pi",
|
|
9
|
+
label: "pi · live",
|
|
10
|
+
matches: (provider) => matchesFamily(provider, "pi"),
|
|
11
|
+
async probe(ctx: ProbeContext) {
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
const envDir = process.env.PI_CODING_AGENT_DIR?.trim()
|
|
14
|
+
? path.resolve(process.env.PI_CODING_AGENT_DIR.trim().replace(/^~\//, `${home}/`))
|
|
15
|
+
: null;
|
|
16
|
+
|
|
17
|
+
// Precedence: lower index = base default, higher index = override wins
|
|
18
|
+
return discoverFromCandidates("pi", [
|
|
19
|
+
{ path: path.join(home, ".config", "mcp", "mcp.json"), label: "pi · global shared" },
|
|
20
|
+
{ path: path.join(home, ".agents", "mcp.json"), label: "pi · agents global" },
|
|
21
|
+
{ path: path.join(home, ".agents", "mcp", "mcp.json"), label: "pi · agents nested" },
|
|
22
|
+
{ path: path.join(home, ".pi", "agent", "mcp.json"), label: "pi · agent global" },
|
|
23
|
+
{ path: path.join(home, ".pi", "mcp.json"), label: "pi · user config" },
|
|
24
|
+
{ path: path.join(home, ".pi", ".mcp.json"), label: "pi · user config" },
|
|
25
|
+
...(envDir
|
|
26
|
+
? [
|
|
27
|
+
{ path: path.join(envDir, "mcp.json"), label: "pi · env config" },
|
|
28
|
+
{ path: path.join(envDir, ".mcp.json"), label: "pi · env config" },
|
|
29
|
+
]
|
|
30
|
+
: []),
|
|
31
|
+
{ path: path.join(ctx.cwd, "mcp.json"), label: "pi · project shared" },
|
|
32
|
+
{ path: path.join(ctx.cwd, ".mcp.json"), label: "pi · project shared" },
|
|
33
|
+
{ path: path.join(ctx.cwd, ".pi", "mcp.json"), label: "pi · project override" },
|
|
34
|
+
{ path: path.join(ctx.cwd, ".pi", ".mcp.json"), label: "pi · project override" },
|
|
35
|
+
]);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default piProbe;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PluginStorage, createSettingsHandlers } from "paseo-plugin-helper/server";
|
|
2
|
+
import { log } from "./mcp";
|
|
3
|
+
import { mcpToolsSettingsContract, type McpToolsSettings } from "../shared/mcp";
|
|
4
|
+
|
|
5
|
+
export const settingsStorage = new PluginStorage<McpToolsSettings>("mcp-tools", "settings.json", {
|
|
6
|
+
schema: mcpToolsSettingsContract.schema,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// Plain handler functions on purpose: index.server.ts wires them via
|
|
10
|
+
// server.handle, which is the only call form the compiler strips
|
|
11
|
+
// from the client bundle.
|
|
12
|
+
export const settingsHandlers = createSettingsHandlers(mcpToolsSettingsContract, settingsStorage, {
|
|
13
|
+
onUpdate: (next) => {
|
|
14
|
+
log.info("Settings updated", next);
|
|
15
|
+
},
|
|
16
|
+
onReset: () => {
|
|
17
|
+
log.info("Settings reset to defaults");
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { safeExec } from "./process";
|
|
2
|
+
|
|
3
|
+
export interface AgentIdentity {
|
|
4
|
+
id?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
model?: string;
|
|
7
|
+
provider?: string;
|
|
8
|
+
repo?: string;
|
|
9
|
+
branch?: string;
|
|
10
|
+
envelopeText?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AgentIdentityOptions {
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
envelopeCommand?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ENV_KEYS = [
|
|
19
|
+
"AGENT_ID",
|
|
20
|
+
"AGENT_NAME",
|
|
21
|
+
"AGENT_MODEL",
|
|
22
|
+
"AGENT_PROVIDER",
|
|
23
|
+
"PASEO_AGENT_ID",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
function readEnvIdentity(env: NodeJS.ProcessEnv = process.env): AgentIdentity | null {
|
|
27
|
+
const id = env.PASEO_AGENT_ID || env.AGENT_ID || undefined;
|
|
28
|
+
const name = env.AGENT_NAME || undefined;
|
|
29
|
+
const model = env.AGENT_MODEL || undefined;
|
|
30
|
+
const provider = env.AGENT_PROVIDER || undefined;
|
|
31
|
+
if (!id && !name && !model && !provider) return null;
|
|
32
|
+
return { id, name, model, provider };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeEnvelopeJson(raw: unknown): AgentIdentity | null {
|
|
36
|
+
if (!raw || typeof raw !== "object") return null;
|
|
37
|
+
const o = raw as Record<string, unknown>;
|
|
38
|
+
const str = (v: unknown): string | undefined =>
|
|
39
|
+
typeof v === "string" && v.length > 0 ? v : undefined;
|
|
40
|
+
const identity: AgentIdentity = {
|
|
41
|
+
id: str(o.id) ?? str(o.agentId),
|
|
42
|
+
name: str(o.name) ?? str(o.agentName),
|
|
43
|
+
model: str(o.model),
|
|
44
|
+
provider: str(o.provider),
|
|
45
|
+
repo: str(o.repo) ?? str(o.workspace),
|
|
46
|
+
branch: str(o.branch),
|
|
47
|
+
envelopeText: str(o.envelopeText) ?? str(o.envelope),
|
|
48
|
+
};
|
|
49
|
+
if (!identity.id && !identity.name && !identity.model && !identity.provider && !identity.repo && !identity.branch) return null;
|
|
50
|
+
return identity;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function getAgentIdentity(
|
|
54
|
+
options: AgentIdentityOptions = {},
|
|
55
|
+
): Promise<AgentIdentity | null> {
|
|
56
|
+
const timeoutMs = options.timeoutMs ?? 5000;
|
|
57
|
+
const command = options.envelopeCommand ?? "xpufx-tool envelope --format json";
|
|
58
|
+
const envIdentity = readEnvIdentity();
|
|
59
|
+
void ENV_KEYS;
|
|
60
|
+
try {
|
|
61
|
+
const result = await safeExec(command, { timeoutMs });
|
|
62
|
+
if (result.code !== 0) return envIdentity;
|
|
63
|
+
const text = result.stdout.trim();
|
|
64
|
+
if (!text) return envIdentity;
|
|
65
|
+
try {
|
|
66
|
+
const parsed: unknown = JSON.parse(text);
|
|
67
|
+
const cliIdentity = normalizeEnvelopeJson(parsed);
|
|
68
|
+
if (!cliIdentity) return envIdentity;
|
|
69
|
+
if (!envIdentity) return cliIdentity;
|
|
70
|
+
return {
|
|
71
|
+
id: envIdentity.id ?? cliIdentity.id,
|
|
72
|
+
name: envIdentity.name ?? cliIdentity.name,
|
|
73
|
+
model: envIdentity.model ?? cliIdentity.model,
|
|
74
|
+
provider: envIdentity.provider ?? cliIdentity.provider,
|
|
75
|
+
repo: cliIdentity.repo,
|
|
76
|
+
branch: cliIdentity.branch,
|
|
77
|
+
envelopeText: cliIdentity.envelopeText,
|
|
78
|
+
};
|
|
79
|
+
} catch {
|
|
80
|
+
return envIdentity;
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
return envIdentity;
|
|
84
|
+
}
|
|
85
|
+
}
|