@agentprojectcontext/apx 1.53.7 → 1.55.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/package.json +1 -1
- package/src/core/mcp/runner.js +21 -2
- package/src/core/runtime-skills/apx/SKILL.md +1 -1
- package/src/core/runtime-skills/apx-mcp/SKILL.md +11 -4
- package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +9 -2
- package/src/core/stores/messages.js +69 -0
- package/src/host/daemon/api/conversations.js +25 -0
- package/src/host/daemon/api/mcps.js +15 -0
- package/src/interfaces/cli/commands/code.js +56 -0
- package/src/interfaces/cli/commands/mcp.js +122 -5
- package/src/interfaces/cli/index.js +28 -7
- package/src/interfaces/web/dist/assets/{index-Bks35ks-.js → index-J3_Jhr99.js} +126 -126
- package/src/interfaces/web/dist/assets/{index-Bks35ks-.js.map → index-J3_Jhr99.js.map} +1 -1
- package/src/interfaces/web/dist/index.html +1 -1
- package/src/interfaces/web/src/components/chat/ChatList.tsx +73 -18
- package/src/interfaces/web/src/hooks/useChat.ts +29 -1
- package/src/interfaces/web/src/i18n/en.ts +1 -0
- package/src/interfaces/web/src/i18n/es.ts +1 -0
- package/src/interfaces/web/src/lib/api/conversations.ts +7 -1
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +35 -18
- package/src/interfaces/web/src/types/daemon.ts +16 -0
- package/src/interfaces/cli/commands/sys.js +0 -862
- package/src/interfaces/cli/terminal-chat/renderer.js +0 -520
package/package.json
CHANGED
package/src/core/mcp/runner.js
CHANGED
|
@@ -153,7 +153,9 @@ class McpProcess {
|
|
|
153
153
|
|
|
154
154
|
async listTools() {
|
|
155
155
|
await this._ensureInitialized();
|
|
156
|
-
return
|
|
156
|
+
return collectToolPages((cursor) =>
|
|
157
|
+
this._send("tools/list", cursor ? { cursor } : {})
|
|
158
|
+
);
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
async callTool(name, args) {
|
|
@@ -374,7 +376,9 @@ class HttpMcpClient {
|
|
|
374
376
|
|
|
375
377
|
async listTools() {
|
|
376
378
|
await this._ensureInitialized();
|
|
377
|
-
return
|
|
379
|
+
return collectToolPages((cursor) =>
|
|
380
|
+
this._rpc("tools/list", cursor ? { cursor } : {})
|
|
381
|
+
);
|
|
378
382
|
}
|
|
379
383
|
|
|
380
384
|
async callTool(name, args) {
|
|
@@ -399,6 +403,21 @@ class HttpMcpClient {
|
|
|
399
403
|
}
|
|
400
404
|
}
|
|
401
405
|
|
|
406
|
+
// tools/list is paginated (nextCursor). Follow every page and hand back a
|
|
407
|
+
// single merged { tools } result so callers never see partial catalogs.
|
|
408
|
+
const MAX_TOOL_PAGES = 32;
|
|
409
|
+
async function collectToolPages(fetchPage) {
|
|
410
|
+
const tools = [];
|
|
411
|
+
let cursor;
|
|
412
|
+
for (let i = 0; i < MAX_TOOL_PAGES; i++) {
|
|
413
|
+
const result = await fetchPage(cursor);
|
|
414
|
+
if (Array.isArray(result?.tools)) tools.push(...result.tools);
|
|
415
|
+
cursor = result?.nextCursor;
|
|
416
|
+
if (!cursor) break;
|
|
417
|
+
}
|
|
418
|
+
return { tools };
|
|
419
|
+
}
|
|
420
|
+
|
|
402
421
|
function parseFirstSseJson(raw) {
|
|
403
422
|
for (const block of raw.split(/\r?\n\r?\n/)) {
|
|
404
423
|
const dataLines = [];
|
|
@@ -21,7 +21,7 @@ If you can spawn a subagent natively in the current IDE (Claude Code, Cursor,
|
|
|
21
21
|
|-------|-----------|------|
|
|
22
22
|
| Delegate to external coding CLI | **apx-runtime** | `apx run <agent> --runtime claude-code\|codex\|...` |
|
|
23
23
|
| List/read/resume/summarise/continue sessions | **apx-sessions** | `apx session resume`, `apx sessions list`, "import a codex session" |
|
|
24
|
-
| Use a registered MCP tool | **apx-mcp** | `apx mcp run`, "call MCP filesystem", "MCP failing" |
|
|
24
|
+
| Use a registered MCP tool | **apx-mcp** | `apx mcp tools`, `apx mcp run`, "call MCP filesystem", "MCP failing" |
|
|
25
25
|
| Add/configure/use a project agent | **apx-agent** | "add an agent", vault import, per-agent model, agent memory |
|
|
26
26
|
| Register/list/configure a project | **apx-project** | "register this project", `apx project list`, per-project config |
|
|
27
27
|
| Per-project TODO list | **apx-task** | "add a task", "remind me to…", "what's pending" |
|
|
@@ -47,7 +47,12 @@ apx mcp remove github --scope runtime --project iacrmar
|
|
|
47
47
|
apx mcp enable filesystem --project iacrmar
|
|
48
48
|
apx mcp disable filesystem --project iacrmar
|
|
49
49
|
|
|
50
|
-
#
|
|
50
|
+
# Discover tools — list catalog, then inspect one tool's schema
|
|
51
|
+
apx mcp tools filesystem # table: tool name + description
|
|
52
|
+
apx mcp tools filesystem read_file # params (types, required) + run example
|
|
53
|
+
apx mcp tools filesystem --json # raw JSON with full inputSchema
|
|
54
|
+
|
|
55
|
+
# Call a tool through the daemon
|
|
51
56
|
apx mcp run filesystem read_file '{"path":"README.md"}'
|
|
52
57
|
```
|
|
53
58
|
|
|
@@ -95,13 +100,15 @@ apx mcp remove github # errors if github lives in runtime
|
|
|
95
100
|
|
|
96
101
|
```bash
|
|
97
102
|
apx mcp check --project iacrmar # scopes seen + which files exist
|
|
98
|
-
apx mcp
|
|
103
|
+
apx mcp tools <name> # spawn server + list its tools (proves init works)
|
|
104
|
+
apx mcp logs <name> # spawn/init event log + stderr tail
|
|
105
|
+
apx mcp run <name> <tool> '{...}' # call a tool for real
|
|
99
106
|
apx log -f # tail unified log for spawn errors
|
|
100
107
|
```
|
|
101
108
|
|
|
102
|
-
"Doesn't show tools" = command failed to start (missing env vars, package not found) or crashed during initialize.
|
|
109
|
+
"Doesn't show tools" = command failed to start (missing env vars, package not found) or crashed during initialize. `apx mcp logs <name>` shows the stderr tail; the unified log has the rest.
|
|
103
110
|
|
|
104
|
-
|
|
111
|
+
Standard workflow to use any MCP: `apx mcp tools <name>` → `apx mcp tools <name> <tool>` (copy the run example) → `apx mcp run <name> <tool> '<json>'`.
|
|
105
112
|
|
|
106
113
|
## Don't
|
|
107
114
|
|
|
@@ -151,10 +151,17 @@ apx mcp add github \
|
|
|
151
151
|
## Debugging
|
|
152
152
|
|
|
153
153
|
```bash
|
|
154
|
-
# Smoke test
|
|
154
|
+
# Smoke test — spawn the server and list its tool catalog
|
|
155
|
+
apx mcp tools my-server
|
|
156
|
+
|
|
157
|
+
# Inspect one tool's schema + copy-paste run example
|
|
158
|
+
apx mcp tools my-server search_inventory
|
|
159
|
+
|
|
160
|
+
# Call it for real
|
|
155
161
|
apx mcp run my-server search_inventory '{"query":"shoes"}'
|
|
156
162
|
|
|
157
|
-
# Spawn errors / stderr
|
|
163
|
+
# Spawn errors / stderr tail
|
|
164
|
+
apx mcp logs my-server
|
|
158
165
|
apx log -f
|
|
159
166
|
|
|
160
167
|
# Scopes / files / env APX sees
|
|
@@ -591,6 +591,75 @@ export function readGlobalMessages({ channel, limit = 100, since } = {}) {
|
|
|
591
591
|
return all.slice(-limit);
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
// Global channel threads (super-agent chats surfaced in the web Chats sidebar)
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
// The global ledger is the source of truth for every super-agent turn outside
|
|
598
|
+
// exec (telegram, web quick-chat, desktop, deck …). A "thread" is one
|
|
599
|
+
// channel+day JSONL file — the same granularity the context window reads.
|
|
600
|
+
|
|
601
|
+
const CHANNEL_NAME_RE = /^[a-z0-9_-]+$/i;
|
|
602
|
+
|
|
603
|
+
// List every non-empty channel+day thread, newest-last-activity first.
|
|
604
|
+
export function listGlobalThreads({ channels, _globalMessagesDir } = {}) {
|
|
605
|
+
const base = _globalMessagesDir || GLOBAL_MESSAGES_DIR;
|
|
606
|
+
if (!fs.existsSync(base)) return [];
|
|
607
|
+
const chans = (channels && channels.length
|
|
608
|
+
? channels
|
|
609
|
+
: fs.readdirSync(base).filter((f) => {
|
|
610
|
+
try { return fs.statSync(path.join(base, f)).isDirectory(); } catch { return false; }
|
|
611
|
+
})
|
|
612
|
+
).filter((c) => CHANNEL_NAME_RE.test(c));
|
|
613
|
+
|
|
614
|
+
const out = [];
|
|
615
|
+
for (const ch of chans) {
|
|
616
|
+
const dir = path.join(base, ch);
|
|
617
|
+
let files;
|
|
618
|
+
try { files = fs.readdirSync(dir); } catch { continue; }
|
|
619
|
+
for (const f of files) {
|
|
620
|
+
const m = f.match(/^(\d{4}-\d{2}-\d{2})\.jsonl$/);
|
|
621
|
+
if (!m) continue;
|
|
622
|
+
const msgs = parseDayJsonl(fs.readFileSync(path.join(dir, f), "utf8")).filter(
|
|
623
|
+
(r) => r.type === "user" || r.type === "agent"
|
|
624
|
+
);
|
|
625
|
+
if (!msgs.length) continue;
|
|
626
|
+
const firstUser = msgs.find((r) => r.type === "user");
|
|
627
|
+
const title = String((firstUser || msgs[0]).body || "")
|
|
628
|
+
.replace(/\s+/g, " ")
|
|
629
|
+
.trim()
|
|
630
|
+
.slice(0, 80);
|
|
631
|
+
out.push({
|
|
632
|
+
id: m[1],
|
|
633
|
+
channel: ch,
|
|
634
|
+
title: title || `${ch} · ${m[1]}`,
|
|
635
|
+
messages: msgs.length,
|
|
636
|
+
started_at: msgs[0].ts,
|
|
637
|
+
last_ts: msgs[msgs.length - 1].ts,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
out.sort((a, b) => (b.last_ts || "").localeCompare(a.last_ts || ""));
|
|
642
|
+
return out;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Read one channel+day thread shaped for the web chat viewer:
|
|
646
|
+
// { id, channel, messages: [{ role, content, ts }] } — or null when missing.
|
|
647
|
+
export function readGlobalThread({ channel, date, _globalMessagesDir } = {}) {
|
|
648
|
+
if (!CHANNEL_NAME_RE.test(String(channel || ""))) return null;
|
|
649
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(String(date || ""))) return null;
|
|
650
|
+
const base = _globalMessagesDir || GLOBAL_MESSAGES_DIR;
|
|
651
|
+
const file = path.join(base, channel, `${date}.jsonl`);
|
|
652
|
+
if (!fs.existsSync(file)) return null;
|
|
653
|
+
const messages = parseDayJsonl(fs.readFileSync(file, "utf8"))
|
|
654
|
+
.filter((r) => r.type === "user" || r.type === "agent")
|
|
655
|
+
.map((r) => ({
|
|
656
|
+
role: r.type === "user" ? "user" : "assistant",
|
|
657
|
+
content: r.body || "",
|
|
658
|
+
ts: r.ts,
|
|
659
|
+
}));
|
|
660
|
+
return { id: date, channel, messages };
|
|
661
|
+
}
|
|
662
|
+
|
|
594
663
|
// Wipe the cache and re-populate from APX project messages. Reads BOTH `.jsonl`
|
|
595
664
|
// (current format) and `.md` (legacy). Called by rebuild.
|
|
596
665
|
export function rebuildMessagesFromFs(db, projectRoot) {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// Per-agent conversation surface: list, fetch, compact, and a2a /send.
|
|
2
2
|
// GET /projects/:pid/agents/:slug/conversations
|
|
3
3
|
// GET /projects/:pid/agents/:slug/conversations/:id
|
|
4
|
+
// GET /projects/:pid/super-agent/threads (channel ledger)
|
|
5
|
+
// GET /projects/:pid/super-agent/threads/:channel/:id
|
|
4
6
|
// POST /projects/:pid/agents/:slug/compact
|
|
5
7
|
// POST /projects/:pid/agents/:slug/conversations/:id/compact
|
|
6
8
|
// POST /projects/:pid/send (agent-to-agent)
|
|
7
9
|
import { readAgents } from "#core/apc/parser.js";
|
|
8
10
|
import { listConversations, readConversation } from "#core/stores/conversations.js";
|
|
11
|
+
import { listGlobalThreads, readGlobalThread } from "#core/stores/messages.js";
|
|
9
12
|
import { compactConversation } from "#core/stores/conversations-compactor.js";
|
|
10
13
|
import { replyAsAgent } from "#core/agent/a2a/reply.js";
|
|
11
14
|
import { nowIso } from "./shared.js";
|
|
@@ -48,6 +51,28 @@ export function register(app, { project, config }) {
|
|
|
48
51
|
});
|
|
49
52
|
});
|
|
50
53
|
|
|
54
|
+
// ---- Super-agent channel threads ----
|
|
55
|
+
// The super-agent's chats (telegram, web quick-chat, desktop, deck …) live
|
|
56
|
+
// in the global per-channel ledger, not in per-agent conversation files.
|
|
57
|
+
// These endpoints surface that ledger as day-threads so the web Chats
|
|
58
|
+
// sidebar can list and reopen them.
|
|
59
|
+
app.get("/projects/:pid/super-agent/threads", (req, res) => {
|
|
60
|
+
const p = project(req, res);
|
|
61
|
+
if (!p) return;
|
|
62
|
+
res.json(listGlobalThreads());
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
app.get("/projects/:pid/super-agent/threads/:channel/:id", (req, res) => {
|
|
66
|
+
const p = project(req, res);
|
|
67
|
+
if (!p) return;
|
|
68
|
+
const thread = readGlobalThread({
|
|
69
|
+
channel: req.params.channel,
|
|
70
|
+
date: req.params.id,
|
|
71
|
+
});
|
|
72
|
+
if (!thread) return res.status(404).json({ error: "thread not found" });
|
|
73
|
+
res.json(thread);
|
|
74
|
+
});
|
|
75
|
+
|
|
51
76
|
async function handleCompact(req, res, filename) {
|
|
52
77
|
const p = project(req, res);
|
|
53
78
|
if (!p) return;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// POST /projects/:pid/mcps?scope=shared|runtime|global (default: shared)
|
|
6
6
|
// DELETE /projects/:pid/mcps/:name?scope=… (default: shared)
|
|
7
7
|
// GET /projects/:pid/mcps/check
|
|
8
|
+
// GET /projects/:pid/mcps/:name/tools
|
|
8
9
|
// POST /projects/:pid/mcps/:name/call
|
|
9
10
|
import fs from "node:fs";
|
|
10
11
|
import path from "node:path";
|
|
@@ -190,6 +191,20 @@ export function register(app, { projects, registries, project }) {
|
|
|
190
191
|
});
|
|
191
192
|
});
|
|
192
193
|
|
|
194
|
+
// Full tool catalog — tools/list with input schemas, all pages merged.
|
|
195
|
+
// This is what `apx mcp tools` renders; /test below stays as the
|
|
196
|
+
// lightweight smoke check for the web UI card.
|
|
197
|
+
app.get("/projects/:pid/mcps/:name/tools", async (req, res) => {
|
|
198
|
+
const p = project(req, res);
|
|
199
|
+
if (!p) return;
|
|
200
|
+
try {
|
|
201
|
+
const result = await registries.for(p).listTools(req.params.name);
|
|
202
|
+
res.json({ tools: Array.isArray(result?.tools) ? result.tools : [] });
|
|
203
|
+
} catch (e) {
|
|
204
|
+
res.status(500).json({ error: e.message });
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
193
208
|
app.post("/projects/:pid/mcps/:name/call", async (req, res) => {
|
|
194
209
|
const p = project(req, res);
|
|
195
210
|
if (!p) return;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// `apx code` — launch the APX terminal coding assistant (Solid.js TUI).
|
|
2
|
+
// The TUI runs its TypeScript source directly under bun; there is no
|
|
3
|
+
// legacy readline fallback anymore (removed with the old sys.js chat).
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname, resolve } from "node:path";
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { resolveProjectId } from "./project.js";
|
|
9
|
+
import { readConfig } from "#core/config/index.js";
|
|
10
|
+
import { readIdentity } from "#core/identity/index.js";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const TUI_SRC = resolve(__dirname, "../../tui/run.ts");
|
|
14
|
+
|
|
15
|
+
export async function cmdCode(args) {
|
|
16
|
+
const pid = await resolveProjectId(args?.flags?.project);
|
|
17
|
+
const cfg = readConfig();
|
|
18
|
+
const id = readIdentity();
|
|
19
|
+
|
|
20
|
+
// Optional --agent <slug>: route chat to a project agent instead of the APX
|
|
21
|
+
// default. Empty / missing flag means default ("super-agent" mode).
|
|
22
|
+
const agentFlag = typeof args?.flags?.agent === "string" ? args.flags.agent.trim() : "";
|
|
23
|
+
const routedAgentSlug = agentFlag || null;
|
|
24
|
+
const defaultAgentLabel = id?.agent_name || cfg.super_agent?.name || "APX";
|
|
25
|
+
|
|
26
|
+
if (!existsSync(TUI_SRC)) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"apx code: TUI source not found at src/interfaces/tui/run.ts — reinstall with `npm i -g @agentprojectcontext/apx`."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// bun must resolve node_modules/tsconfig from the apx package root, so the
|
|
33
|
+
// spawn cwd stays there — but we pass the user's actual working directory
|
|
34
|
+
// (where they ran `apx code`) via --cwd so the TUI shows the real project
|
|
35
|
+
// path + git branch instead of apx/src.
|
|
36
|
+
const bunBin = process.env.BUN_PATH || "bun";
|
|
37
|
+
const userCwd = process.cwd();
|
|
38
|
+
const result = spawnSync(bunBin, [
|
|
39
|
+
"--preload", "@opentui/solid/preload",
|
|
40
|
+
TUI_SRC,
|
|
41
|
+
"--pid", pid,
|
|
42
|
+
"--agent", routedAgentSlug || defaultAgentLabel,
|
|
43
|
+
"--model", cfg.super_agent?.model || "claude-3-5-sonnet",
|
|
44
|
+
"--cwd", userCwd,
|
|
45
|
+
], { stdio: "inherit", cwd: resolve(__dirname, "../../..") });
|
|
46
|
+
|
|
47
|
+
if (result.error?.code === "ENOENT") {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"apx code: bun is required to run the TUI but was not found. Install it (https://bun.sh) or set BUN_PATH to the binary."
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (result.error) throw result.error;
|
|
53
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
54
|
+
process.exitCode = result.status;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -159,13 +159,130 @@ export async function cmdMcpRun(args) {
|
|
|
159
159
|
process.stdout.write(JSON.stringify(result.result, null, 2) + "\n");
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// Turn a JSON-Schema type into a short placeholder for the run-example JSON.
|
|
163
|
+
function placeholderFor(schema) {
|
|
164
|
+
const t = Array.isArray(schema?.type) ? schema.type[0] : schema?.type;
|
|
165
|
+
if (schema?.enum?.length) return schema.enum[0];
|
|
166
|
+
if (t === "number" || t === "integer") return 0;
|
|
167
|
+
if (t === "boolean") return false;
|
|
168
|
+
if (t === "array") return [];
|
|
169
|
+
if (t === "object") return {};
|
|
170
|
+
return `<${t || "string"}>`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function schemaTypeLabel(schema) {
|
|
174
|
+
if (schema?.enum?.length) return schema.enum.join("|");
|
|
175
|
+
const t = Array.isArray(schema?.type) ? schema.type.join("|") : schema?.type;
|
|
176
|
+
return t || "any";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function firstLine(s) {
|
|
180
|
+
return String(s || "").split("\n")[0].trim();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function printToolDetail(mcpName, tool) {
|
|
184
|
+
console.log(`${mcpName} · ${tool.name}`);
|
|
185
|
+
if (tool.description) console.log(` ${tool.description.trim().replace(/\n/g, "\n ")}`);
|
|
186
|
+
|
|
187
|
+
const props = tool.inputSchema?.properties || {};
|
|
188
|
+
const required = new Set(tool.inputSchema?.required || []);
|
|
189
|
+
const keys = Object.keys(props);
|
|
190
|
+
console.log("");
|
|
191
|
+
if (keys.length === 0) {
|
|
192
|
+
console.log(" Params: (none)");
|
|
193
|
+
} else {
|
|
194
|
+
console.log(" Params:");
|
|
195
|
+
const nameW = Math.max(...keys.map((k) => k.length), 4) + 2;
|
|
196
|
+
const typeW = Math.max(...keys.map((k) => schemaTypeLabel(props[k]).length), 4) + 2;
|
|
197
|
+
for (const k of keys) {
|
|
198
|
+
const req = required.has(k) ? "(required)" : "(optional)";
|
|
199
|
+
console.log(
|
|
200
|
+
` ${k.padEnd(nameW)}${schemaTypeLabel(props[k]).padEnd(typeW)}${req} ${firstLine(props[k]?.description)}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Example invocation with the required params stubbed in.
|
|
206
|
+
const example = {};
|
|
207
|
+
for (const k of keys) {
|
|
208
|
+
if (required.has(k)) example[k] = placeholderFor(props[k]);
|
|
209
|
+
}
|
|
210
|
+
console.log("");
|
|
211
|
+
console.log(" Run:");
|
|
212
|
+
console.log(` apx mcp run ${mcpName} ${tool.name} '${JSON.stringify(example)}'`);
|
|
213
|
+
}
|
|
214
|
+
|
|
162
215
|
export async function cmdMcpTools(args) {
|
|
163
216
|
const name = args._[0];
|
|
164
|
-
if (!name) throw new Error("apx mcp tools:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
217
|
+
if (!name) throw new Error("apx mcp tools: usage: apx mcp tools <name> [<tool>] [--json]");
|
|
218
|
+
const toolFilter = args._[1];
|
|
219
|
+
const pid = await resolveProjectId(args?.flags?.project);
|
|
220
|
+
const data = await http.get(`/projects/${pid}/mcps/${name}/tools`);
|
|
221
|
+
const tools = data.tools || [];
|
|
222
|
+
|
|
223
|
+
if (toolFilter) {
|
|
224
|
+
const tool = tools.find((t) => t.name === toolFilter);
|
|
225
|
+
if (!tool) {
|
|
226
|
+
const hint = tools.length
|
|
227
|
+
? `Available: ${tools.map((t) => t.name).join(", ")}`
|
|
228
|
+
: "(server reported no tools)";
|
|
229
|
+
throw new Error(`MCP "${name}" has no tool "${toolFilter}". ${hint}`);
|
|
230
|
+
}
|
|
231
|
+
if (args?.flags?.json) {
|
|
232
|
+
process.stdout.write(JSON.stringify(tool, null, 2) + "\n");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
printToolDetail(name, tool);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (args?.flags?.json) {
|
|
240
|
+
process.stdout.write(JSON.stringify(tools, null, 2) + "\n");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (tools.length === 0) {
|
|
244
|
+
console.log(`(MCP "${name}" reported no tools)`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const nameW = Math.max(...tools.map((t) => t.name.length), 4) + 2;
|
|
248
|
+
console.log(`${tools.length} tool${tools.length === 1 ? "" : "s"} — apx mcp tools ${name} <tool> for schema\n`);
|
|
249
|
+
console.log("TOOL".padEnd(nameW) + " DESCRIPTION");
|
|
250
|
+
for (const t of tools) {
|
|
251
|
+
console.log(t.name.padEnd(nameW) + " " + firstLine(t.description).slice(0, 100));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export async function cmdMcpLogs(args) {
|
|
256
|
+
const name = args._[0];
|
|
257
|
+
if (!name) throw new Error("apx mcp logs: missing <name>");
|
|
258
|
+
const pid = await resolveProjectId(args?.flags?.project);
|
|
259
|
+
const logs = await http.get(`/projects/${pid}/mcps/${name}/logs`);
|
|
260
|
+
if (args?.flags?.json) {
|
|
261
|
+
process.stdout.write(JSON.stringify(logs, null, 2) + "\n");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const target = logs.transport === "http"
|
|
265
|
+
? logs.url
|
|
266
|
+
: [logs.command, ...(logs.args || [])].filter(Boolean).join(" ");
|
|
267
|
+
console.log(`${name} (${logs.transport})${target ? " — " + target : ""}`);
|
|
268
|
+
if (logs.transport === "stdio") {
|
|
269
|
+
console.log(` running: ${logs.running ? "yes" : "no"} started: ${logs.started_at || "-"} last exit: ${logs.last_exit_code ?? "-"}`);
|
|
270
|
+
} else {
|
|
271
|
+
console.log(` started: ${logs.started_at || "-"} last error: ${logs.last_error || "-"}`);
|
|
272
|
+
}
|
|
273
|
+
if (logs.note) console.log(` ${logs.note}`);
|
|
274
|
+
if (logs.events?.length) {
|
|
275
|
+
console.log("\nEvents:");
|
|
276
|
+
for (const e of logs.events) {
|
|
277
|
+
console.log(` ${e.ts} [${e.level}] ${e.msg}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (logs.stderr_tail?.trim()) {
|
|
281
|
+
console.log("\nstderr tail:");
|
|
282
|
+
for (const line of logs.stderr_tail.trim().split("\n")) {
|
|
283
|
+
console.log(` ${line}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
169
286
|
}
|
|
170
287
|
|
|
171
288
|
export async function cmdMcpCheck(args = {}) {
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
cmdMcpDisable,
|
|
47
47
|
cmdMcpRun,
|
|
48
48
|
cmdMcpTools,
|
|
49
|
+
cmdMcpLogs,
|
|
49
50
|
cmdMcpCheck,
|
|
50
51
|
} from "./commands/mcp.js";
|
|
51
52
|
import {
|
|
@@ -89,7 +90,7 @@ import {
|
|
|
89
90
|
cmdConversationsList,
|
|
90
91
|
cmdConversationsGet,
|
|
91
92
|
} from "./commands/chat.js";
|
|
92
|
-
import {
|
|
93
|
+
import { cmdCode } from "./commands/code.js";
|
|
93
94
|
import { cmdRun, cmdEnvDetect } from "./commands/runtime.js";
|
|
94
95
|
import { cmdSend, cmdConnections } from "./commands/a2a.js";
|
|
95
96
|
import {
|
|
@@ -713,7 +714,8 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
713
714
|
["enable <name>", "Enable a project-owned MCP server."],
|
|
714
715
|
["disable <name>", "Disable a project-owned MCP server."],
|
|
715
716
|
["run <name> <tool>", "Call one MCP tool."],
|
|
716
|
-
["tools <name>", "
|
|
717
|
+
["tools <name> [<tool>]", "List a server's tools, or show one tool's schema."],
|
|
718
|
+
["logs <name>", "Show spawn/init logs and stderr tail for a server."],
|
|
717
719
|
["check", "Audit source files, merge order, and conflicts."],
|
|
718
720
|
],
|
|
719
721
|
options: [["--project <name|id|path>", "Pin command to a specific project."]],
|
|
@@ -785,10 +787,27 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
785
787
|
}),
|
|
786
788
|
"mcp tools": topic({
|
|
787
789
|
title: "apx mcp tools",
|
|
788
|
-
summary: "
|
|
789
|
-
usage: ["apx mcp tools <name> [--project <name|id|path>]"],
|
|
790
|
-
options: [
|
|
791
|
-
|
|
790
|
+
summary: "List an MCP server's tools, or show one tool's input schema with a ready-to-run example.",
|
|
791
|
+
usage: ["apx mcp tools <name> [<tool>] [--json] [--project <name|id|path>]"],
|
|
792
|
+
options: [
|
|
793
|
+
["--json", "Raw JSON output (full tool objects with inputSchema)."],
|
|
794
|
+
["--project <name|id|path>", "Pin command to a specific project."],
|
|
795
|
+
],
|
|
796
|
+
examples: [
|
|
797
|
+
"apx mcp tools filesystem",
|
|
798
|
+
"apx mcp tools filesystem read_file",
|
|
799
|
+
"apx mcp tools dokploy-mcp --json",
|
|
800
|
+
],
|
|
801
|
+
}),
|
|
802
|
+
"mcp logs": topic({
|
|
803
|
+
title: "apx mcp logs",
|
|
804
|
+
summary: "Show an MCP server's spawn/init event log and stderr tail — first stop when a server doesn't list tools.",
|
|
805
|
+
usage: ["apx mcp logs <name> [--json] [--project <name|id|path>]"],
|
|
806
|
+
options: [
|
|
807
|
+
["--json", "Raw JSON output."],
|
|
808
|
+
["--project <name|id|path>", "Pin command to a specific project."],
|
|
809
|
+
],
|
|
810
|
+
examples: ["apx mcp logs dokploy-mcp"],
|
|
792
811
|
}),
|
|
793
812
|
"mcp check": topic({
|
|
794
813
|
title: "apx mcp check",
|
|
@@ -2022,7 +2041,8 @@ function buildHelp(version) {
|
|
|
2022
2041
|
hCmd("apx mcp remove <name>", 36, ""),
|
|
2023
2042
|
hCmd("apx mcp enable/disable", 36, "<name>"),
|
|
2024
2043
|
hCmd("apx mcp run <name> <tool>", 36, "[<json-args>] call a tool through the daemon"),
|
|
2025
|
-
hCmd("apx mcp tools <name>", 36, "list
|
|
2044
|
+
hCmd("apx mcp tools <name>", 36, "[<tool>] list tools, or one tool's schema + run example"),
|
|
2045
|
+
hCmd("apx mcp logs <name>", 36, "spawn/init log + stderr tail"),
|
|
2026
2046
|
hCmd("apx mcp check", 36, "audit multi-source merge"),
|
|
2027
2047
|
|
|
2028
2048
|
hSec("Daemon Service"),
|
|
@@ -2372,6 +2392,7 @@ async function dispatch(cmd, rest) {
|
|
|
2372
2392
|
else if (sub === "disable") await cmdMcpDisable(a);
|
|
2373
2393
|
else if (sub === "run") await cmdMcpRun(a);
|
|
2374
2394
|
else if (sub === "tools") await cmdMcpTools(a);
|
|
2395
|
+
else if (sub === "logs") await cmdMcpLogs(a);
|
|
2375
2396
|
else if (sub === "check") await cmdMcpCheck(a);
|
|
2376
2397
|
else die(`unknown mcp subcommand: ${sub || "(none)"}`);
|
|
2377
2398
|
break;
|