Skip to main content
Glama
pc439527

jumpserver-mcp

by pc439527

jumpserver-mcp

JumpServer (KoKo) bastion connector for WorkBuddy as an MCP stdio server. Ported from dsh-jumpserver (v0.3.1) core. From v0.4.0 it is no longer just "AI safely executes JumpServer commands" — it is an AI infrastructure investigation / fault-analysis platform: fixed read-only probes, structured host inventory, evidence-annotated topology, streaming job model, and an embedded ops console (8 tabs). v0.4.1 adds the fleet layer: bounded-concurrency surveys, config-driven runbooks, multi-host diffing, baseline drift detection, an assets tab, and console access tokens. v0.4.2 adds assertions (runbooks return a verdict), scope checks at session entry, and console-token expiry + rotation. v0.4.3 is a correctness/security release: profile-step assertions aggregate instead of false-failing, jumpserver_job_start runs through the shared permission gate, commandStatus separates "exchange completed" from "command succeeded", the console token is no longer written to disk, maxSessions is actually enforced, and target scope matches exactly unless it declares otherwise. v0.4.4 makes the job stop path correct (one Ctrl+C, PTY held through recovery, maxDuration via stop()) and adds deferred approval for job start. v0.4.5 is the job-lifecycle / isolation release: one interrupt entry point (no more double Ctrl+C), concurrent-safe and idempotent stop(), PTY ownership bound to the session lifecycle (no ghost SESSION_BUSY after a reconnect), a real cursor for jumpserver_job_read, per-conversation job isolation, a corrected sessionScope, and one shared commandStatus → error mapping. v0.5.0 is the distribution / configuration release: the connection four-tuple (host / port / username / password) can come from the WorkBuddy connector form as environment variables (ENV > config.json > default), config.json became OPTIONAL and now carries policy only, the SSH handshake verifies the bastion's host key (TOFU + optional pinned fingerprint), the console gained a 设置 tab showing where every value came from, CI covers Windows × Node 20/22, and the command-classifier corpus was extended to the operator's real command surface. v0.5.1 is the console-stability / distribution-tooling release: the console address is fixed at http://127.0.0.1:8765/ with the access token injected into the page rather than the URL, so it is short, bookmarkable and identical in every process — a second MCP process now adopts that address instead of falling back to a random port whose URL died with it, an already-open tab self-heals after a restart, the handover hint repeats until the page has actually been fetched once, and the listener is released exactly once on shutdown. It also adds the connector package toolchain (validate:connector incl. a de-identification scan, pack:connector) and a local-market verification path so the install-by-form flow can be exercised before submitting for review.

v0.5.2 is the honesty / diagnosability release. Every tool now declares real MCP annotations (a write tool can no longer advertise readOnlyHint), an incomplete connection no longer kills the process before the handshake — it is reported as CONFIG_INCOMPLETE by the tools that actually need the bastion, while jumpserver_status stays answerable and says WHAT is missing, a new --doctor self-check answers "why is this not working" without starting the server, per-target progress notifications reach the host during long multi-host surveys, the ops console stops polling a hidden/backgrounded page, and the command classifier recognises SAP HANA hdbsql read-only queries. v0.5.3 teaches the connector KoKo's multi-username ID> selection screen: assets with more than one authorised bastion user no longer fail with ASSET_ENTER_TIMEOUT — pass accountIndex to jumpserver_enter / jumpserver_run / jumpserver_batch (per task) / jumpserver_job_start, or accountByTarget: { "<ip>": <index> } to jumpserver_inspect / jumpserver_topology / jumpserver_compare / jumpserver_baseline_* / jumpserver_profile_run. Omit and the connector fails with ACCOUNT_SELECTION_REQUIRED carrying the parsed accounts=0:admin | 1:ops | 2:deploy list in detail — fail-closed, never auto-picks. v0.5.4 makes the ops console multi-instance aware: the stable address is shared by every MCP process of a workspace, so a process that loses the port race used to serve nothing — two WorkBuddy conversations showed one terminal and the 中断 button hit the wrong process. Now the loser binds its own private loopback port and publishes { role, port, summary } in its discovery file, the page's instance picker lists every live console and embeds the selected one (each embedded page injects its own token, so no shared secret is introduced), and the handover URL carries ?inst=<pid> so a conversation opens its own sessions. The error detail field is also rendered now — a swallowed detail had hidden every ASSET_ENTER_TIMEOUT / ACCOUNT_SELECTION_REQUIRED diagnosis from the model.

v0.5.5 is the stop-don't-spin release. An asset the bastion cannot dial used to cost far more than the failure itself. KoKo answers a dead dial with 开始连接到 root(root)@10.0.0.10 error: 网络不通(连接超时) and re-paints its own prompt one line below; the connector read that as a generic failure, reported detail: error (the regex token, not the banner), and collapsed the session to UNKNOWN. The model could not tell a dead network from a wrong accountIndex, and every retry needed a full reconnect — measured on a real conversation: 8 enter/connect cycles, 2m53s, 8.32 tokens, for one unreachable asset. Now: the network banner is classified ASSET_UNREACHABLE with the whole banner line in detail plus a retryAfterMs cooldown, the session stays at the (real) KoKo menu so no reconnect is needed, a repeat enter on that target is refused before a single byte reaches the PTY, the result carries an explicit next: STOP line, and the failure detection is scoped to the current attempt so a stale banner is never re-matched against a healthy new one. Fixable failures (ACCOUNT_SELECTION_REQUIRED, a non-network ASSET_ENTER_TIMEOUT) stay retryable and get no STOP line.

v0.5.6 closes the loop V0.5.3 opened: raising the account prompt worked, answering it did not. enter() collapsed the session to UNKNOWN on ACCOUNT_SELECTION_REQUIRED, so the very next call — the one carrying the accountIndex the connector had just asked for — was refused with CONNECTION_LOST: a live PTY reported as a lost connection, and no way forward except reconnecting before it could type one character. Measured on a real conversation (console pid 36612, one asset, 3m56s): enter → ACCOUNT_SELECTION_REQUIRED → enter(accountIndex=1) → CONNECTION_LOST → connect → enter → and only then the asset's actual failure (the SSH reconnect and a second KoKo dial, ~40 s, spent on a session that was never dead). Now the session stays live at the prompt (state ENTERING_ASSET, so the console shows no misleading → UNKNOWN marker), the follow-up call answers it in place — same transport, one target line, exactly the displayed ID, no reconnect — jumpserver_run / jumpserver_batch / jumpserver_job_start resume through the same path, and canResumeAccountSelection() fails closed: prompt gone → ACCOUNT_SELECTION_EXPIRED, transport dead → the normal reconnect. The ops console also now lands on 实时终端 (#terminal) instead of 审计日志 — the page exists to be watched while the agent works, and the audit table is a lookup, not the reason to open it. v0.5.8 is the identity / collection-speed release. Two fixes, one per half of "why is this slow / why does this look wrong": (1) the console no longer calls your own conversation a stranger — the handover URL is the shared stable address plus ?inst=<pid>, and with two conversations open the process that owns 8765 answers it, so the page used to label the process that asked for the console 「另一个实例」 and offer a 「切回本实例」 button pointing at somebody else's session. Now ?inst= is the authority: the page hands the browser over to that instance's own listener (probed first, so a dead port keeps the wrapper), the asking conversation is 「本实例」, the process that happened to answer is 「本页服务进程」, and a vanished instance is reported instead of silently showing another conversation's console. (2) Jev can now shorten a collection run: riskJudge.autoAllow lets an unambiguously read-only verdict skip the per-host confirm cycle on commands the rule classifier cannot name — behind six thresholds, three structural vetoes (no composition, no opaque shell syntax, no interpreter/remote head), an honest "自动放行未通过:…" line when it refuses, and an AUTO_ALLOWED audit record when it does not. v0.5.9 is the audit-completeness release. (1) Refusals are now audited. Until now the JSONL only ever contained commands that RAN — one the rules blocked, or one nobody approved, left no record at all, so "who tried to run rm -rf /var and was stopped" was unanswerable. Every refusal path now writes exactly one entry: BLOCKED when the deterministic gate refused it (the rule's own reason in refusalReason), DENIED when approval was not granted — across exec, run, batch and job. The console renders them as 「已拦截」 / 「未批准」, deliberately distinct from the red failures, because a refusal is the policy working rather than a crash; jumpserver_audit returns the reason as a trailing why= column (absent on entries that ran, so nothing downstream has to learn a new column for the common case); and because the write is best-effort, a broken audit sink can never turn a refusal into an execution. (2) Five verb-aware classifier rules close the KNOWN GAPs the corpus had recorded: sapcontrol -function Get*, HDB info|version|status|proc, lsnrctl status|services|version|show, showmount, and jstack (plain form only — -F force-attaches and is MODIFY, -m stays UNKNOWN). Each is fail-closed: an unrecognised verb returns UNKNOWN instead of guessing, and the mutating forms (HDB stop, sapcontrol … Stop, lsnrctl reload) are never READ.

v0.5.10 / v0.5.11 are the long-session hygiene release, built from three field complaints. (1) The console got slower the longer a conversation ran, and the audit log only ever grew. The audit tab now reads through an absolute-index cursor and renders only the newest 300 rows, and POST /api/audit/archive rotates the JSONL atomically — the rotated file is kept, never deleted. (2) The console showed eight tabs whether or not they were wanted. auditViewer.tabs now hides the unused ones. It is a visibility switch, not a permission switch: the audit sink keeps writing, every /api/* keeps its access token and same-origin guard, and settings cannot be hidden. (3) The connector form's single credential could not be changed after binding, and only one bastion login could exist. The console's 设置 tab now stores and switches several accounts, AES-256-GCM encrypted under ~/.workbuddy/jumpserver-mcp/; the selected account takes precedence over the injected credential and falls back safely when it is missing or cannot be decrypted, so an account problem can never be the reason a connection fails. Switching accounts tears down existing sessions and asset caches, because a PTY authenticated with the previous credential no longer represents the identity the operator selected.

v0.5.12 makes every failure renderer carry its diagnostic. A saved account whose password was wrong reached the model as code: AUTH_FAILED / JumpServer authentication failed and nothing else, so the only possible next step was to guess. The transport was not at fault — guardValue() attaches JumpServerError.detail correctly — but three hand-written projections dropped it: the assets renderer, the ops text tools, and the nested errors inside renderBatchResult(). The batch case had a second, quieter cause: BatchCommandResult.error / TargetBatchResult.error were declared { code, message } while errorDetail() actually returned a detail, so the compiler blessed a shape that was incapable of carrying the diagnostic. All three now delegate to renderResult() (plus a shared NestedFailure type), which also restores the connection: INCOMPLETE — missing … line those tools had been dropping. --doctor learned the same lesson from the other end: it inspected only config.json and the environment, so on a machine with a healthy account selected it declared the credential "NOT found in any layer" and could print a host the connector was never going to dial — it now reads the account layer first (redacted ProfileView, never the token), and the harness pins HOME/USERPROFILE to a throwaway directory in spawnServer(), because a single saved account on the developer's machine was enough to turn "no configuration at all" into connection: complete and fail a suite whose code was correct.

v0.5.13 makes the tab switches real. V0.5.10 answered "can I switch these tabs off" with auditViewer.tabs in config.json and a read-only report in the 设置 tab — which is a file the operator owns and the connector form rewrites, not a control, and the report named the rule without saying how to use it. Reported as "I can't find where to turn this off". The 设置 tab now renders one checkbox per tab, persisted to the console's own ~/.workbuddy/jumpserver-mcp/console-prefs.json, and a write returns the authoritative new list (nav, router and switches all redraw from it, so the nav that was rendered can never disagree with the router that guards it). Precedence is one rule: an explicit value in config.json wins, so those rows render locked and disabled rather than offering a click that cannot win; settings can never be hidden by either layer, and the store refuses such a write instead of accepting-and-ignoring it. Still a visibility switch and nothing more — no credential is written and no permission is granted, so it is not behind allowCredentialWrite. A corrupt preference file degrades to defaults (it is read while rendering a page) and refuses further writes (so recoverable data is not replaced).

Tools (23)

Tool

Purpose

jumpserver_status

Session state / gateway / current target / permission mode / runtime version / conversation scope; live console URL + token expiry. Strictly read-only

jumpserver_console_rotate_token

Invalidate the console access token. In stable mode (default) the address is unchanged — the open page picks up a fresh token on its next reload; set auditViewer.stableUrl: false to get the old "new URL, old link dies" behaviour. Side-effecting, so it is its own tool

jumpserver_connect

Establish the persistent SSH/PTY session, wait for the KoKo menu

jumpserver_enter

Enter an asset through the KoKo menu (verified by probe). Pass accountIndex for multi-user assets

jumpserver_assets

List authorized assets (p only — read-only, footer-verified, cached)

jumpserver_exec

Execute one simple command on the currently-entered asset

jumpserver_run

Auto-navigate + verify + execute (one target, one command)

jumpserver_batch

Multi-target multi-command batch (target affinity)

jumpserver_leave

Leave the current asset, return to the KoKo menu

jumpserver_close

Close the session, release resources

jumpserver_snapshot

Terminal mirror: read the recent PTY event stream

jumpserver_audit

Read recent audit entries (in-chat; time displayed in the configured zone)

jumpserver_inspect

Fixed read-only profile probes -> structured HostInventory

jumpserver_topology

Build an evidence-annotated relationship graph between hosts

jumpserver_profile_run

Run a NAMED runbook (config runbooks) across targets — same steps, every host; expect assertions yield PASS/FAIL

jumpserver_compare

Run one command/profile on N hosts and report the DIFF (groups + outliers)

jumpserver_baseline_capture

Snapshot a named baseline to data/baselines/<name>.json

jumpserver_baseline_compare

Re-inspect and report DRIFT vs a saved baseline

jumpserver_interrupt

Ctrl+C the remote shell NOW, re-verify, out-of-band (does not wait for the running op)

jumpserver_job_start

Start a streaming job (tail -f / journalctl -f / tcpdump)

jumpserver_job_read

Cursor read of a job: what arrived since the last read + nextSeq to resume (or full:true)

jumpserver_job_stop

Ctrl+C the job, release the shell

jumpserver_jobs

List this conversation's jobs

When requireArm: true, jumpserver_arm / jumpserver_disarm (30-minute authorization window) are also registered.

Related MCP server: SSH MCP Server

Architecture highlights

  • Explicit state machine (V0.1): illegal transitions always collapse to UNKNOWN; never guess.

  • Command classifier (V0.3.1, 5 risks: READ / PRIVILEGED_READ / UNKNOWN / MODIFY / DANGEROUS); DANGEROUS always requires human approval, even in FULL_ACCESS.

  • Advisory risk judge (V0.5.7, opt-in, riskJudge.enabled): when a command lands on UNKNOWN, the connector can ask a TypeSafe AI System One model (Jev) for a typed second opinion — read-only probability, impact, a rubric score plus its distribution, and a confidence — and prints it into the approval prompt. By default it is advisory only: it never allows, denies or downgrades anything, MODIFY / DANGEROUS / READ commands are never sent anywhere, and every failure (disabled, no credential, timeout, non-2xx, malformed payload) silently falls back to today's prompt. Commands leave the process redacted. Off by default.

  • Jev auto-allow for collection (V0.5.8, opt-in, riskJudge.autoAllow.enabled): during a multi-host survey almost every probe the classifier cannot name (lsnrctl status, showmount -e, hdb info, …) costs one confirmation per host. With auto-allow on, a verdict that clears every threshold lets that command run without the round-trip. It is the only path by which the judge can change an outcome, and it is narrow by construction: only where the deterministic gate had already decided to ask a human (never where it decided to block), only UNKNOWN, only a single non-composed command whose head is not an interpreter/remote tool, and only when read-only probability, confidence, rubric position, distribution mass, privilege probability and impact band all pass. A miss is reported in the prompt (自动放行未通过:…) and every auto-allowed command is written to the audit with an AUTO_ALLOWED line — visible in the console's audit tab and in the tool result. Default off.

  • Every refusal is recorded (V0.5.9): the audit trail used to contain only what actually ran — a command the rules blocked, or one nobody approved, vanished without a trace. All four execution paths (exec / run / batch / job) now write exactly one entry per refusal: BLOCKED when the deterministic gate refused it, carrying the rule's own reason in refusalReason; DENIED when approval was not granted. The records satisfy the storage schema, are searchable with jumpserver_audit, and the console shows them as 「已拦截」 / 「未批准」 rather than as red failures. The write is best-effort by design: a broken audit sink can never turn a refusal into an execution.

  • Abort fix (V0.4.0 P0): MCP cancellation / jumpserver_interrupt / the console's "Interrupt current command" button all send Ctrl+C to the remote shell and re-run the probe — the connector never declares the asset usable while a foreground job is still running.

  • Structured inspection (V0.4.0 P0): jumpserver_inspect runs fixed read-only profiles (basic / network / process / service / web / java / database / container / full). A probe that regresses to a non-READ rule is skipped, not silently downgraded. jumpserver_topology builds a graph with per-edge confidence (HIGH/MEDIUM/LOW) and evidence (nginx upstream / ESTABLISHED … / /etc/hosts / same upstream cluster).

  • Streaming jobs (V0.4.0): jumpserver_job_* covers tail -f / journalctl -f / tcpdump style commands that never end on their own. Output is harvested from the TerminalObserver stream, maxDuration enforces an upper bound.

  • Job lifecycle & isolation (V0.4.5): one PTY owner, always. jumpserver_interrupt and the console's 中断 button go through a single entry point, so a streaming job is interrupted by exactly ONE Ctrl+C (V0.4.4 sent manager.interrupt() and then jobs.stop() = two). JobStore.stop() is idempotent and concurrent-safe: a maxDuration stop racing a manual jumpserver_job_stop shares one probe and one verdict, so a failed verify can no longer be masked as STOPPED. PTY ownership is bound to the session lifecycle — close(), transport loss and a reconnect all release it, so a reconnected conversation is not permanently SESSION_BUSY; a job whose shell is gone is reported LOST even when the reconnect completed within one pump interval. Jobs are scoped to their conversation (list / read / stop / the RUNNING cap), so a multiplexing host cannot let one conversation read or stop another's job. jumpserver_job_read is a real cursor read (nextSeq → sinceSeq), not a tail of the buffer.

  • Incremental audit (V0.4.0): async append + bounded memory ring (5000) + file-offset incremental tail. The console no longer readFileSyncs the whole JSONL every second.

  • Audit timezone (V0.4.0): JSONL stays UTC; display uses timeZone (default Asia/Shanghai).

  • Target scope (V0.4.0): allowedTargets / deniedTargets; deny always wins; every navigation and every exec against an already-entered asset is checked. From V0.4.2 the check runs at the ENTRY of jumpserver_connect / jumpserver_enter too, so a denied target never gets an SSH/PTY session opened at all. From V0.4.3 entries declare their intent — exact (192.168.79.10), prefix (trailing ./-/:), glob (oa-*) or CIDR (192.168.79.0/24); a bare entry is EXACT, so 192.168.79.10 no longer also matches 192.168.79.100.

  • Version single source (V0.4.0): package.json is the only truth; runtime PLUGIN_VERSION reads from there. npm version and the runtime can no longer drift.

  • Bounded-concurrency surveys (V0.4.1): batchConcurrency (default 1 = strictly sequential) lets inspect / topology / compare / profile_run visit several targets at once. Result order always matches input order; one target's failure never aborts its siblings. One conversation still owns ONE bastion PTY — raising batchConcurrency interleaves target turns on that single session, it does not open N sessions. A process-wide Semaphore (maxSessions, default 4) caps how many targets are entered simultaneously, which only binds when batchConcurrency > 1.

  • Runbooks (V0.4.1): jumpserver_profile_run executes a named, reviewed recipe from config runbooks. Each step is an inspect profile or an explicit READ command; a non-READ step (or a typo'd profile) is SKIPPED and reported — never silently executed.

  • Runbook assertions (V0.4.2): a step may carry expect (contains / notContains / matches / exitCode / notEmpty / minLines / message, all AND). The runbook then reports PASS/FAIL per step, per target, and overall — turning a survey into a verdict. An unreachable target auto-fails any assertion it carried. V0.4.3 fixes how a profile step is judged: a profile step is ONE logical step reported once (its per-probe detail stays in commands[] / probes[]), and its expect is evaluated against the AGGREGATE of every probe — so expect: { contains: "LISTEN" } no longer FAILs a healthy host just because only the ss -lntp probe prints it. To assert one specific probe use expect: { probe: "listen", contains: "LISTEN" }; a probe name that does not exist is a config error and skips the step.

  • Command outcome (V0.4.3): executionState says whether the transport exchange completed; commandStatus says what happened to the COMMAND — SUCCESS / EXIT_NONZERO / TIMEOUT / INTERRUPTED / CONNECTION_LOST / UNKNOWN. A completed exchange with a non-zero exit is ok: false, and the audit/console report it as a failure (jps -lv exiting 127 reads READ / EXIT_NONZERO, never READ / COMPLETED). V0.4.5 decodes it through ONE shared mapping, so TIMEOUT / CONNECTION_LOST / UNKNOWN keep their own error code in exec, batch AND compare (V0.4.4 reported every compare failure as COMMAND_EXIT_NONZERO).

  • Conversation scope (V0.4.3): the scope id is resolved as transport sessionId → JUMPSERVER_MCP_SESSION → process isolation. WorkBuddy's stdio model spawns one server per conversation, so process isolation IS the boundary; jumpserver_status reports which mode is active. A host that multiplexes conversations over one process MUST supply a sessionId, otherwise they share one bastion session — the startup log says so explicitly rather than pretending otherwise. V0.4.5 reports the scope the CALLER is actually on: transport only when the transport itself supplied a sessionId, otherwise the runtime mode (env / process) — V0.4.4 asked whether the resolved scope ID was non-empty, which is always true, so a plain stdio conversation claimed transport.

  • Multi-host diff (V0.4.1): jumpserver_compare runs the SAME read-only command/profile on every target and groups them by signature, reporting each outlier's missing/extra lines versus the majority. Order-insensitive, so ss/ps line ordering does not create false diffs.

  • Baseline drift (V0.4.1): jumpserver_baseline_capture persists a compact state snapshot to data/baselines/<name>.json; jumpserver_baseline_compare re-inspects and reports real drift (ports, disks, services, roles, kernel, cores; load/memory jitter is suppressed). An unreachable host is reported as unreachable, never as "unchanged".

  • SSH host-key verification (V0.5.0): the handshake no longer trusts whoever answers. With hostFingerprint set, only a byte-identical host key is accepted; without it the first contact is recorded in data/known_hosts.json (TOFU) and every later contact must match. A contradiction is refused as HOST_KEY_MISMATCH carrying both fingerprints — never auto-re-trusted. The old algorithms.serverHostKey override (RSA variants only) is gone, so an ed25519 / ecdsa host key negotiates again.

  • Connection provenance (V0.5.0): every resolved connection value carries the layer it came from (env / config / default), reported in the console's 设置 tab and in the startup log — "which config am I actually running with" is answerable without opening a file.

  • Console access token (V0.4.1): the embedded console binds to 127.0.0.1 but every request must present a per-process random token (/?token=… or the X-Console-Token header) — otherwise 403. The token is handed over only in the model's console URL.

  • Token expiry + rotation (V0.4.2): the console token now expires (default 12h; auditViewer.tokenTtlMinutes, 0 = never). A stale token gets 403 plus an x-console-token-expired: 1 marker, so the page shows a "token expired — ask the model to reopen the console" overlay instead of retrying forever. jumpserver_status returns the live consoleUrl + expiry, and jumpserver_console_rotate_token mints a new token on demand (the old link dies immediately).

  • Paged audit reads + rotation (V0.5.10): the console audit tab used to fetch the whole 5000-record window every second and re-render every matching row, and once the ring hit its cap the table stopped updating at all because the page only re-rendered when the array length changed. Reads now go through an absolute-index cursor (page(since, limit), O(limit)), the page fetches only what is new and renders the newest 300 rows with a "matched / showing / total" line, and POST /api/audit/archive rotates the JSONL: it flushes queued appends, atomically renames the file to <name>.<UTC>.jsonl in the same directory, then resets the tail offset. The rotated file is never deleted — archiving moves the evidence, it does not destroy it.

  • Configurable console tabs (V0.5.10): auditViewer.tabs decides which of the eight tabs the console shows. Hiding a tab removes its nav button and its client-side polling, but the section markup stays (the page script binds handlers inside it) and the hash router refuses to select it, so it is unreachable rather than absent. It is a visibility switch, not a permission switch: the audit sink keeps writing and every /api/* keeps its token + same-origin guard. settings cannot be hidden.

  • Multiple bastion accounts (V0.5.11): several JumpServer logins can be stored locally and switched from the console's 设置 tab. A selected account supplies the whole identity — host, port, username and password — and takes precedence over the connector form's single injected credential; if the account is missing, its port is out of range, or its secret cannot be decrypted, resolution falls back per field to the original ENV → config.json → default chain, so an account problem can never be the reason a connection fails. Secrets are AES-256-GCM encrypted under ~/.workbuddy/jumpserver-mcp/profiles.json (key vault.key) and are never echoed to the page; a corrupt store is reported and refused for writing instead of being silently overwritten. Switching accounts tears down every existing session, asset cache and pending grant, because a PTY authenticated with the previous credential no longer represents the identity the operator selected. jumpserver_status reports where the live credential and host came from (env / config / profile) without ever printing a value. Console write routes require an same-origin request as well as a valid token: a cross-origin POST is refused outright, because a leaked token (which stableUrl=false puts in the URL) must not be enough on its own to switch accounts or interrupt sessions.

  • Honest annotations (V0.5.2): every registered tool declares MCP annotations, and the values are treated as a contract, not decoration — a tool that writes anything may not claim readOnlyHint. jumpserver_baseline_capture (writes data/baselines/<name>.json) used to advertise read-only, the same class of bug V0.4.3 fixed for jumpserver_status; it now declares readOnlyHint: false. jumpserver_interrupt / jumpserver_job_start / jumpserver_job_stop declare destructiveHint: true. A test freezes the whole table, so a tool cannot quietly change its contract — or be added without one.

  • Diagnosable startup (V0.5.2): an incomplete connection no longer calls process.exit(1) — the MCP handshake completes, and only the tools that actually need the bastion refuse, with CONFIG_INCOMPLETE carrying the same wording the startup log uses. jumpserver_status stays answerable and reports connection: INCOMPLETE — missing host, username, because "ask what is wrong" is exactly what a user does first. A malformed config is still fatal: a missing value is recoverable at runtime, a broken file is an operator mistake. --doctor covers the rest — see Diagnosing a broken setup.

  • Progress notifications (V0.5.2): the long multi-target tools (inspect / topology / compare / profile_run / baseline_* / batch) emit notifications/progress per finished target when the host supplies a progressToken. A 20-host survey is no longer indistinguishable from a hang.

  • Tool-table budget (V0.5.2): the tools/list payload is a permanent tax on EVERY conversation, including ones that never touch JumpServer. It is now measured (npm run measure:context) and gated by a test at 23 KB (~5.4 k tokens). The remaining large lever — merging the six multi-target survey tools behind one mode parameter — would save ~3-4 KB but renames the public tool surface, so it is left as a deliberate decision rather than taken silently.

  • Idle console is idle (V0.5.2): each console poller already self-gated on its own tab, but a page hidden in a background tab kept fetching every second and re-reading audit.jsonl with nobody looking. One visibility gate now covers all six pollers, and returning to the page refreshes the active tab immediately.

  • hdbsql read-only queries (V0.5.2): SAP HANA has no -e, so hdbsql -n host:30013 -u SYSTEM -d SAPHANADB "SELECT ..." — the query a BASIS actually runs — was an unknown executable and prompted on every call. A narrow HANA rule now confirms SELECT / EXPLAIN PLAN FOR SELECT / read-only meta-commands, while -o / -I / -L (file targets), CALL / DO / WITH (opaque semantics) and every unproven statement type stay UNKNOWN. mysql / mariadb keep their existing verdicts, and the classifier now has tests.

Configuration

V0.5.0 splits configuration into two layers.

Connection — owned by the WorkBuddy connector form. Installing the connector pops a form (address / port / username / password); the values are stored on this machine and injected into the stdio process as environment variables, so there is nothing to hand-edit. Resolution order:

ENV  >  config.json  >  built-in default

Env var

Default

Description

JUMPSERVER_HOST

—

Bastion gateway host (falls back to config.json host)

JUMPSERVER_PORT

2222

Bastion SSH port

JUMPSERVER_USERNAME

—

Bastion login (falls back to config.json username)

JUMPSERVER_PASSWORD

—

Password, resolved per connect; never written to config.json

JUMPSERVER_MCP_CONFIG

<root>/config.json

Config file path

JUMPSERVER_MCP_SESSION

—

Pin one session scope per process

Policy — still in config.json. Permission mode, target scope, concurrency, asset groups, runbooks and console settings live here. config.json is now OPTIONAL: when the connector form supplies the connection, the file may be absent and every policy field falls back to its default. Copy config.example.json to config.json only when you need those fields.

Field

Default

Description

host / port / username

see ENV

Bastion SSH gateway (overridden by ENV)

hostFingerprint

—

Pinned SSH host key, e.g. SHA256:AbCdEf…. When set, only a byte-identical key is accepted; otherwise the first contact is recorded and checked afterwards (TOFU)

knownHostsPath

<root>/data/known_hosts.json

TOFU store used when no fingerprint is pinned

passwordEnv

JUMPSERVER_PASSWORD

Env-var name holding the password

permissionMode

READ_ONLY

READ_ONLY / AUTO / FULL_ACCESS

timeZone

Asia/Shanghai

Display zone for audit timestamps (storage is still UTC)

allowedTargets

[]

Allow-list; each entry is exact (192.168.79.10), prefix (trailing . / - / :), glob (oa-*) or CIDR (192.168.79.0/24) — see target scope

deniedTargets

[]

Deny-list (checked first, always wins); same exact / prefix / glob / CIDR syntax

batchConcurrency

1

Targets interleaved per batch/inspect/compare call (1..8; 1 = sequential). One conversation = one PTY; this does not multiply sessions

maxSessions

4

Cap on targets entered simultaneously (1..16). Enforced process-wide by one Semaphore shared by every conversation in the process; only binds when batchConcurrency > 1

assetGroups

{}

Named group -> keywords, used by jumpserver_assets

runbooks

{}

Named runbook -> { title, steps[] }; each step is a profile or a READ command, optionally with expect assertions

auditPath

<root>/data/audit.jsonl

JSONL audit sink

auditViewer.{enabled,port,autoOpen,portFallback}

true/8765/true/true

Embedded console

auditViewer.stableUrl

true

Serve one token-free, restart-stable address (http://127.0.0.1:<port>/, token injected into the page) instead of a per-process ?token= URL on a possibly-ephemeral port. Set false only for the legacy behaviour

auditViewer.tokenTtlMinutes

720

Console access-token lifetime in minutes (0 = never expires)

auditViewer.tabs

{} (all on)

Per-tab visibility of the console navigation, e.g. {"stats": false}. Hiding a tab removes its nav button and stops its client-side polling; it does NOT stop the audit sink, does NOT reduce any permission check (every /api/* keeps its console token + same-origin guard), and the section markup stays in the page because the script binds handlers inside it. settings is always-on — it is the only diagnostics entry point and documents this very switch. Since V0.5.13 the 设置 tab also offers these as checkboxes, persisted to ~/.workbuddy/jumpserver-mcp/console-prefs.json; precedence is an explicit value here > the console checkbox > visible, so a tab pinned in this file is rendered locked in the UI

auditViewer.allowCredentialWrite

true

Whether the local console may add / edit / delete bastion account credentials. The console binds to 127.0.0.1 and every request carries the per-process access token, and secrets are stored AES-256-GCM encrypted under ~/.workbuddy/jumpserver-mcp/profiles.json (key vault.key) and never echoed back — set false to reduce the settings tab to a read-only list and have every write route refuse

requireArm

false

Require jumpserver_arm before any tool runs

riskJudge.{enabled,endpoint,apiKeyEnv,apiKeyFile,model,timeoutMs,cacheTtlSeconds,redactNetwork}

false / https://api.typesafe.ai/v1/systemone / TYPESAFE_API_KEY / ~/.workbuddy/typesafe/api_key / jev-latest / 1500 / 3600 / true

Semantic second opinion for UNKNOWN commands (V0.5.7) — only UNKNOWN commands are ever sent; MODIFY / DANGEROUS / READ / PRIVILEGED_READ never are. The key is resolved per call: env var apiKeyEnv first, then the file apiKeyFile (empty or unreadable = judge silently off). Only the env-var name and the path live in the config object, never the secret. The state is redacted first: credentials always, IP / FQDN / home account by default, system paths deliberately kept

riskJudge.autoAllow.{enabled,minReadOnly,minConfidence,maxRiskScore,minSafeProbability,maxNeedsPrivilege}

false / 0.9 / 0.7 / 1 / 0.85 / 0.3

V0.5.8, opt-in: let a verdict that clears every threshold skip the human prompt for an UNKNOWN command the gate had already sent to approval — the friction that dominates multi-host collection. maxRiskScore is the expected rubric position on the API's 0..4 scale (1 = the two safest bands only); minSafeProbability is the mass the rubric distribution must put on those two bands, so an average cannot hide a fat tail. Everything else is unchanged: the guard refuses composed commands, opaque shell syntax, interpreter/remote heads, a missing distribution and every non-finite value, and MODIFY / DANGEROUS / a hard block are never eligible. Default off

Diagnosing a broken setup

Run the self-check before reading any source. It starts no MCP server, opens no SSH connection, binds no port, prints no credential value, and exits non-zero when something must be fixed:

npx -y jumpserver-mcp --doctor      # or: npm run doctor
jumpserver-mcp 0.5.2 — self-check

[OK  ] node runtime: v22.22.2 on linux/x64
[OK  ] config file: none at /opt/jumpserver-mcp/config.json — the connector form alone can supply the connection
[FAIL] connection: INCOMPLETE — missing host / username
           jumpserver-mcp: no JumpServer host / username configured.
           Provide it in either place — ENV wins over config.json:
             1. WorkBuddy → 连接器 → JumpServer: fill 地址 / 端口 / 用户名 / 密码
             2. config.json: copy config.example.json and set "host", "username"
           Environment keys read at startup: JUMPSERVER_HOST, JUMPSERVER_PORT, JUMPSERVER_USERNAME, JUMPSERVER_PASSWORD
[WARN] credential: NOT found in any layer — the value itself is never read by this command
[OK  ] audit sink: /opt/jumpserver-mcp/data/audit.jsonl — writable
[OK  ] baseline store: /opt/jumpserver-mcp/data/baselines — writable
[OK  ] ops console: port 8765 is already serving a console (expected while another conversation is open — reload the page)
[OK  ] effective policy: READ_ONLY, requireArm=false, maxSessions=4, batchConcurrency=1, ...
[OK  ] ssh2 transport: module loads

Result: 1 problem must be fixed before the connector can reach the bastion

What each verdict maps to:

Check

A failure here means

node runtime

The host is running an older Node than engines requires

config file

config.json is not valid JSON, or violates the schema. Both are fatal at startup (unlike a missing value)

connector env

A JUMPSERVER_* variable is malformed (e.g. a non-numeric port). Blamed on the environment, not on the file

connection

host / username are missing from every layer. The fix text is byte-identical to the one the tools return as CONFIG_INCOMPLETE

credential

No password in any layer (WARN, not FAIL — the connection itself may still be complete)

audit sink / baseline store

The install directory is not writable (read-only mount, no space, discarded container layer)

ops console

Reported for information: an occupied port is expected, since a stable address outlives any one process

ssh2 transport

The only non-trivial dependency does not load

config file and connection are deliberately separate checks: a file that parses but violates the schema used to surface as a raw zod dump, and a bad JUMPSERVER_PORT used to look like a config problem.

When an asset is unreachable (V0.5.5)

A reachable bastion does not imply reachable assets. When KoKo cannot dial the target it prints the reason and returns to its own menu:

开始连接到 root(root)@192.168.138.56  0.0 … 14.4
开始连接到 root(root)@192.168.138.56 error: 网络不通(连接超时)
[Host]>

The connector answers:

Field

Value

code

ASSET_UNREACHABLE

message

names the target and states it is not a configuration / credential / accountIndex problem

detail

koko=<the full banner line> retryAfterMs=<cooldown>

next

STOP — do not retry …; report to the user that the server is temporarily unreachable

What the connector does with that knowledge:

  • The session survives. KoKo is back at its own prompt, so the state is JUMPSERVER_MENU, not UNKNOWN — the next jumpserver_enter/jumpserver_run for a different asset needs no reconnect. The check is tail-anchored and refuses to accept an ID> prompt, where an asset name would be read as an account choice.

  • The target is refused for 2 minutes (retryAfterMs), before any byte reaches the PTY: KoKo would otherwise spend its own ~15 s dial timeout to fail identically. The cooldown is per-target, and an explicit jumpserver_close clears it (the usual reason to close is that the network was just fixed).

  • Failures are scoped to one attempt. The detector tail is reset when a dial starts, so a banner left over from a previous attempt is never re-matched against a healthy new one.

  • Only network failures stop. A non-network banner (e.g. 资产不存在) still returns ASSET_ENTER_TIMEOUT and stays retryable — a wrong guess must not be turned into a dead end.

  • Multi-target tools keep the reason. errorDetail() now forwards detail (V0.5.5: it used to drop it between the session and the tool result), so jumpserver_batch / inspect / topology / compare / profile_run / baseline_* print the banner and the STOP line per target instead of a bare code: message.

For multi-host surveys this means the unreachable host is reported once with its banner and the rest of the fleet is surveyed normally; repeated entries for the same host are refused instantly instead of re-dialling.

Answering KoKo's account prompt (V0.5.6)

When a target has more than one authorised bastion user, KoKo paints a numbered list and waits at ID>. The connector never picks for you — without accountIndex it answers ACCOUNT_SELECTION_REQUIRED with the parsed list in detail:

ACCOUNT_SELECTION_REQUIRED: target 192.168.138.56 has 2 accounts; pass accountIndex in
jumpserver_enter/jumpserver_run/jumpserver_batch — the session stays live at the KoKo ID>
prompt: call again with accountIndex to answer it (no reconnect needed)
  detail: accounts=1) root | 2) 手动输入

At that moment the session is deliberately still live and still mid-entry (ENTERING_ASSET, not UNKNOWN), parked on the prompt that asked the question. What happens to the answer:

Situation

Behaviour

accountIndex is one of the displayed IDs

the ID is written, KoKo dials, the shell is verified — same transport, same target line, no reconnect

accountIndex is not in the list

ACCOUNT_SELECTION_REQUIRED naming the bad choice; the prompt is still answerable, so retry with a valid ID

the caller omits accountIndex again

the selection is re-raised (previously this answer was NOT_AT_MENU)

the prompt is gone — KoKo timed the selection out, or the transport died

ACCOUNT_SELECTION_EXPIRED → reconnect and enter again; nothing is typed into a screen the connector no longer recognises

an explicit jumpserver_close

the parked prompt is dropped, like the unreachable cooldown

No byte reaches the ID> prompt except an explicit accountIndex from the caller, and canResumeAccountSelection() requires the list to still be on screen — looksLikeMenuPrompt() keeps refusing ID> for the same reason, so a parked prompt can never be mistaken for navigable menu.

Register with WorkBuddy

Two paths. They differ in who has to edit a file.

Connector package — for other people. Install from connector/. connector/token-schema.json declares the four connection fields, so a user fills a form once and never touches a config file. connector/mcp.json pins no machine paths — it declares a Node runtime and runs the published package:

{
  "mcpServers": {
    "jumpserver": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "jumpserver-mcp"],
      "runtime": { "type": "node", "version": "20" },
      "env": {
        "JUMPSERVER_HOST": "${JUMPSERVER_HOST}",
        "JUMPSERVER_PORT": "${JUMPSERVER_PORT}",
        "JUMPSERVER_USERNAME": "${JUMPSERVER_USERNAME}",
        "JUMPSERVER_PASSWORD": "${JUMPSERVER_PASSWORD}"
      }
    }
  }
}

Two prerequisites must be met before this path works on anyone else's machine:

  • The package has to be on npm. mcp.json launches npx -y jumpserver-mcp; until npm publish has run, that name resolves to nothing and the server dies at startup. Confirm with npm view jumpserver-mcp version.

  • The connector has to pass review. WorkBuddy provides no local install entry for auth_mode: "token" connectors — zip connector/ and submit it to the WorkBuddy team; it shows up in the connector market once approved. npm run validate:connector checks the package against the submission rules (required manifest fields, ${VAR} placeholders that must match a form field key exactly, minWorkbuddyVersion high enough for the features used, no hardcoded credentials, no machine-specific paths or internal addresses) and exits non-zero on any failure. To exercise the install-by-form flow on your own machine before review, see Verifying the connector locally.

Local checkout — while developing. Point command / args at node <repo>/lib/server.js and put the connection values directly in env. Works immediately, but never ship this form: an absolute path only works on the machine it was written on.

Embedded ops console

Once the MCP process starts it serves a local ops page at a stable address — http://127.0.0.1:8765/ by default (V0.5.1). /api/* still requires the per-process access token, but the server injects it into the page instead of the URL, so the address is short, bookmarkable and byte-identical in every process. Consequences:

  • No ephemeral fallback. If another MCP process already holds the port, this one adopts the same address (instead of inventing a random port whose URL dies with it) and takes the listener over the moment the owner exits.

  • Every instance is reachable (V0.5.4). A process that loses the port race binds its own private loopback port and publishes { role, port, summary } in data/consoles/<pid>.json. The header's instance picker lists every live console of the workspace (pid · role · target · sessions · state) and embeds the selected one in full; the embedded page injects its own token, so no secret is shared and the per-process token model is unchanged.

  • The handover URL is per-instance. Each process hands the model http://127.0.0.1:8765/?inst=<pid>, so opening the console from a second conversation lands on that conversation's sessions. The bare address (no ?inst=) keeps showing whichever process owns the port. Before V0.5.4 both conversations showed one terminal and the 中断 button acted on the other process's sessions — a real cross-conversation side effect.

  • …and it is followed (V0.5.8). Whoever owns 8765 answers the handover request, which is often not the conversation that asked: a fresh conversation is a follower on a private port, so the page it received was served by another conversation's process. The page read that as "this is me" and labelled its own author 「另一个实例」 — correct terminal, inverted identity, and a 「切回本实例」 button that led somewhere else. Now the requesting pid (?inst=) is the authority: SELFPID is reported as 本页服务进程, OPENPID as 本实例, and the page probes that instance's own address (/ping, 204, token-free) before handing the browser over — so the tabs, audit and terminal all belong to the right conversation. It stays put (and says 「本对话实例(pid X)已退出」) when the instance is gone, instead of quietly displaying another conversation's console as yours.

  • The page self-heals. A restarted process re-binds the same address and an already-open tab resumes polling by itself; the 工作台已失效 overlay clears on the first successful poll.

  • Token expiry is transparent. A 403 marked expired makes the page reload once to pick up a freshly injected token.

  • Cross-origin requests are refused, so a web page in the local browser cannot read the audit trail or drive /api/interrupt.

The model receives the URL in a tool response and must open it with present_files (WorkBuddy's built-in preview panel) — do not use the system browser. The hint repeats on every response until the page has actually been fetched once, because a single first-response handover got parked until the end of the task in practice.

Set auditViewer.stableUrl: false to restore the v0.5.0 form: 127.0.0.1:<random>/?token=…, one console per conversation, invalidated when the conversation ends. That form has no shared address, hence no instance picker — each conversation simply owns its own console.

Tabs:

  • Instance picker (header) — switch between this workspace's live MCP instances (one per conversation). Selecting another instance embeds its whole console behind a warning bar; its 中断 / 断开 buttons act on that instance, never on yours.

  • Live terminal — real-time PTY mirror, input echo, state-transition markers; "Interrupt current command (Ctrl+C)" / Clear / Auto-scroll. This is the landing tab (V0.5.6): a bare/bookmarked address and any unknown #hash open here, since the console is normally opened to be watched while the agent works. Deep links still work — #audit, #assets, #topology, #jobs, #stats, #sessions, #settings.

  • Assets — last jumpserver_assets listing with search, group/platform/status filters, and name/IP/platform/node/role/status columns; roles come from the last topology, status from the last 30 minutes of audit activity. Also lists saved baselines.

  • Topology — last jumpserver_topology result: node cards + edge list (with confidence + evidence) + per-node detail (roles, OS, ports, IPs, in/out edges, memory, load).

  • Jobs — streaming jobs (jsjob_xxxx) of this conversation: progress bar, Stop button, output tail.

  • Audit log — AuditStore memory ring (5000) in the configured timezone; supports callId / batchId; JSONL / CSV export.

  • Stats — risk / target / operation / 14-day activity bars.

  • Sessions — active sessions + Interrupt / Disconnect buttons.

  • Settings (V0.5.0) — the live connection and where every value came from (WorkBuddy environment / config.json / default), the policy knobs that only config.json can set, and the recorded known_hosts entries. The password appears as "configured / not configured" — its value is never rendered.

Approval flow

READ_ONLY auto-runs reads. MODIFY / DANGEROUS / UNKNOWN return COMMAND_APPROVAL_REQUIRED with a redacted reason on the first attempt; the model shows it to the user and retries the same call with confirm: true only after explicit agreement. requireArm: true adds the 30-minute jumpserver_arm / jumpserver_disarm window on top.

allowedTargets / deniedTargets are an independent gate: even with a human-approved READ, a target outside the allow-list still gets TARGET_DENIED.

Development

npm install
npm run typecheck   # tsc --noEmit
npm run build       # tsc -p tsconfig.json
npm test            # builds + runs tests/**/*.test.mjs (node:test)
npm run smoke       # MCP stdio JSON-RPC smoke
npm run doctor      # self-check; no server, no SSH, no port bound (see above)
npm run measure:context   # tools/list payload size + per-tool breakdown

Connector package toolchain (V0.5.1):

npm run validate:connector   # submission rules + de-identification scan; non-zero on failure
npm run pack:connector       # validate, then build .workbuddy/artifacts/jumpserver-connector-v<version>.zip
npm run verify:entry         # do the registry AND every install source still point at a live server?
npm run link:stable          # pin ~/WorkBuddy/jumpserver-mcp at this checkout (see below)

When the connector shows as enabled but no tool appears in the list, run npm run verify:entry before anything else. The host caches the absolute path of the checkout that was installed, so moving the project folder (this repo used to live under ~/WorkBuddy) or switching the managed node version leaves a registry entry that spawns a process which dies at startup — and MCP reports the empty tool list without saying why. The script boots that exact command line, completes a handshake and prints the server version and tool count; when a path is gone it names the file and the line to fix. Fix it, then reconnect the connector — a stale path is cached per session.

Two places freeze that path, and fixing only the registry is not enough:

  • the connector registry (~/.workbuddy/connectors/<account>/mcp.json), and

  • any install source the host can re-copy from — the marketplace cache and a locally built package. A dev-mode marketplace package bakes the absolute checkout path in at build time, so "reconnect from the connector centre" pushes the old path straight back and silently undoes a manual fix. verify:entry checks both sides and fails with LATENT FAILURE when only the registry is currently fine.

npm run link:stable is the durable answer to the same problem: it creates a directory junction at ~/WorkBuddy/jumpserver-mcp (the path the original package was built against) pointing at wherever the checkout lives now, so frozen artifacts keep resolving without a rebuild. It is idempotent, never deletes the checkout, and --status / --remove are there for inspection. npm run market:build prints the path it just froze, because the artifact is location-bound by design.

Test coverage (tests/):

  • security/command-classifier.test.mjs — the classifier matrix (incl. every fixed case from the review).

  • security/permission-gate.test.mjs — 3 modes x 5 risks + target allow/deny.

  • security/risk-judge.test.mjs (V0.5.7) — the advisory judge against a mock System One endpoint: disabled / missing credential / timeout / non-2xx / malformed payload all degrade to the plain UNKNOWN prompt, credentials and target IPs proven absent from the outgoing state, a MODIFY command proven never to leave the process, and only a cached repeat avoiding a second call.

  • security/risk-judge-autoallow.test.mjs (V0.5.8) — the ONE path by which the judge may change an outcome: the guard refuses a missing number, a composed command, an interpreter head and every unmet threshold, an auto-allowed command still verifies its target, and the thresholds are asserted against verdicts measured from the live model.

  • security/refusal-audit.test.mjs (V0.5.9) — every refusal path (exec / run / batch, BLOCKED and DENIED) writes exactly one audit record carrying the reason, an approved command writes none, the record satisfies the storage schema, and a broken audit sink cannot turn a refusal into an execution. Sample UNKNOWN commands come from tests/helpers/unknown-command.mjs, which fails on import the moment a rule makes one of them classified — a classifier improvement must never look like a gate regression.

  • security/target-scope.test.mjs — deny beats allow, exact / prefix / glob / CIDR matching, blank targets never gated.

  • security/job-start-gate.test.mjs — the streaming-job tool shares the permission gate: READ_ONLY cannot be bypassed with confirm, AUTO asks, FULL_ACCESS still gates DANGEROUS, an unverified target is refused.

  • jumpserver/state-machine.test.mjs — legal/illegal transitions.

  • jumpserver/session-abort.test.mjs — AbortSignal and out-of-band interrupt() must Ctrl+C + re-verify.

  • jumpserver/asset-list.test.mjs — p parse / footer / filter / groups.

  • jumpserver/host-parse.test.mjs — every parser.

  • jumpserver/topology.test.mjs — edge construction and evidence.

  • inspect/profile-safety.test.mjs — every profile command is still READ.

  • jumpserver/concurrency.test.mjs — bounded concurrency order/limit, abort, Semaphore.

  • jumpserver/session-gate.test.mjs — the maxSessions gate really caps concurrent holders.

  • jumpserver/runbook.test.mjs — non-READ / unknown-profile steps are skipped; evaluateExpect PASS/FAIL matrix; runRunbook integration (profile step reported ONCE, aggregated assertion, expect.probe).

  • jumpserver/compare.test.mjs — order-insensitive grouping, count-aware (multiset) outliers, failed hosts excluded.

  • runtime/baseline.test.mjs — baseline save/load, name safety, drift diff, jitter suppression.

  • runtime/console-token.test.mjs — 403 without token, 200 with token / header, TTL expiry marker, rotation, and the discovery file never leaking the token.

  • runtime/command-status.test.mjs — exit 0 vs exit 127 (jps -lv must not be ok=true), timeout, connection lost.

  • runtime/conversation-lifecycle.test.mjs — two conversations never share a bundle/grant; transport sessionId wins; a shared process is reported honestly.

  • runtime/job-lifecycle-v045.test.mjs — one Ctrl+C per interrupt (job vs bare shell), concurrent stop() shares one verdict, PTY ownership released on close / transport loss / reconnect, per-conversation job isolation + cap, cursor job_read (nextSeq / sinceSeq / droppedChars).

  • runtime/session-scope.test.mjs — jumpserver_status.sessionScope projection: a stdio conversation (or the ANONYMOUS_SESSION / env fallback) must never report transport.

  • runtime/job-stop-correctness.test.mjs — one Ctrl+C, PTY held through recovery, maxDuration through stop().

  • runtime/compare-command-status.test.mjs — a failed probe is excluded from the diff and keeps its own error code.

  • runtime/terminal-markers.test.mjs — connector-internal markers are hidden from the live terminal view.

  • runtime/audit-store.test.mjs — async append, ring cap, incremental tail.

  • runtime/time.test.mjs — timezone formatting.

  • config/config-env.test.mjs — ENV > config.json > default resolution, per-value provenance, config.json optional, blank ENV never overriding a real value.

  • jumpserver/host-key.test.mjs — pinned fingerprint accepted / rejected, TOFU first-contact record, a contradiction surfaces as HOST_KEY_MISMATCH instead of re-trusting.

  • runtime/console-stable-url.test.mjs — the console address is byte-identical across processes, a second process adopts the port instead of falling back, the page self-heals after a restart, /api/* stays token-gated, cross-origin refused.

  • runtime/console-exit-listener.test.mjs — the console listener is released exactly once and survives a restart (a leaked exit listener used to keep the port bound).

  • runtime/console-instances.test.mjs (V0.5.4) — a process that loses the stable-port race still binds its own loopback console and publishes role: follower; /api/instances lists every live instance with self marked and puts yours first; a stale discovery file is not offered; the endpoint stays token-gated; the handover URL carries ?inst=<pid>; the page exposes the picker + embed target and no longer labels a PTY session 「当前对话」.

  • runtime/console-instance-identity.test.mjs (V0.5.8) — ?inst= is the authority, not the process that answers: OPENPID is 「本实例」 and SELFPID is 「本页服务进程」, the page hands over to that instance's own listener, and a vanished instance is reported instead of showing another conversation's console.

  • security/command-classifier-corpus/*.test.mjs — a wider classifier corpus (SAP / databases, containers / services, filesystem / process / storage, network / dangerous) guarding the READ / MODIFY / DANGEROUS boundary.

  • security/sql-command-classifier.test.mjs (V0.5.2) — the database-CLI chain (mysql / mariadb + SAP HANA hdbsql) through the AUTHORITATIVE classifier: read-only forms confirmed, -o / -I / -L file targets and CALL / DO / WITH held at UNKNOWN, shell chaining unable to smuggle a second command past the rule, and the HANA rule proven to run before the generic one.

  • runtime/tool-annotations.test.mjs (V0.5.2) — freezes the annotation table for all 23 tools (+ the requireArm arm/disarm pair), asserts no write tool claims readOnlyHint, that every annotation key is one the MCP spec defines, and that the README tool table matches what is registered.

  • runtime/audit-refusal-render.test.mjs (V0.5.9) — the refusal survives the projection: jumpserver_audit (real server over MCP stdio, fabricated sink) lists a BLOCKED / DENIED entry with its why= reason, while an entry that ran keeps its previous shape byte-for-byte.

  • runtime/config-incomplete.test.mjs (V0.5.2) — boots with no configuration at all, asserts jumpserver_status names what is missing, that connect-needing tools refuse with CONFIG_INCOMPLETE and actionable text, that local-only tools keep working, and that a MALFORMED config is still fatal.

  • runtime/context-budget.test.mjs (V0.5.2) — gates the tools/list payload at 23 KB and every individual tool at 1.7 KB, so context cost cannot drift one description at a time.

  • runtime/doctor.test.mjs (V0.5.2) — --doctor never throws on the input it diagnoses, attributes a bad JUMPSERVER_* variable to the environment rather than the file, translates zod issues into path: message, and never renders a credential value.

  • jumpserver/enter-unreachable.test.mjs (V0.5.5) — a real 网络不通(连接超时) banner yields ASSET_UNREACHABLE with the full banner in detail (not the matched token), the session lands back at the menu so the next asset needs no reconnect, a repeat enter on the same target is refused with zero PTY writes and the cooldown restated, the cooldown is per-target and cleared by an explicit close, a stale banner is not re-matched, a non-network failure stays retryable, looksLikeMenuPrompt accepts [Host]> / Opt> but refuses ID>, and renderResult prints the STOP line for ASSET_UNREACHABLE only, and one fleet-level case drives SessionManager.runTargetBatch over three hosts (the middle one down) to pin that the down host reports once with its banner while the other two are surveyed normally — the fleet wire answers the target line, not a timer, because runTargetBatch leaves the previous asset first and a pre-timed banner is wiped by the attempt-scoped detector reset.

  • jumpserver/session-account-selection.test.mjs (V0.5.3, extended in V0.5.6) — the ID> screen answered with the displayed ID, ACCOUNT_SELECTION_REQUIRED carrying the parsed list when it is omitted, an out-of-range index refused at the boundary, and a single-user asset left untouched. V0.5.6 adds the part V0.5.3 got wrong: the session stays live at the prompt so the answer lands on the same PTY — one transport, one target line, exactly one digit written, asserted at both the session and the SessionManager.runTargetBatch level (the manager had its own fail-closed guards, and those are what rejected the answer before); a rejected index keeps the prompt answerable; re-asking without an index re-raises the selection instead of NOT_AT_MENU; and a prompt that is gone (ACCOUNT_SELECTION_EXPIRED) or a closed session puts nothing on the wire.

CI: .github/workflows/ci.yml — typecheck → build → validate:connector → smoke → test, on Ubuntu and Windows × Node 20/22.

Verifying the connector locally

A auth_mode: "token" connector has no local install button — it reaches the market only after review. To exercise the form-and-install path on your own machine first, point WorkBuddy's connector market at a locally served zip:

npm run market:build     # inject connector/ into a copy of the official market index
npm run market:serve     # serve it on 127.0.0.1
npm run market:enable    # write ~/.workbuddy/connectors/connector-marketplace.json
# restart WorkBuddy, then install "JumpServer" from the connector list
npm run market:status    # what is currently overridden
npm run market:disable   # restore the official market (also removes the override file)

market:enable backs up whatever it overwrites and market:disable restores it. Do not put files directly into ~/.workbuddy/connectors/connectors-marketplace/ — the client rebuilds that directory on every sync (every 10 minutes) and will delete them.

Available Tools

23 tools
jumpserver_assetsA
Read-only

List the assets this account is authorized for WITHOUT entering any of them: KoKo menu command "p" (display-only), parsed locally, cached ~5 min. Filter by substring or by a configured asset group. Call this before jumpserver_run/jumpserver_batch — never guess IPs, and never type an asset name into the menu (KoKo auto-logs-in on a unique hit).

NameRequiredDescriptionDefault
groupNoConfigured asset group name (assetGroups setting). Unknown groups error instead of returning "0 assets".
filterNoSubstring filter, matched case-insensitively across name / IP / platform / node / comment.
refreshNoForce a fresh capture instead of the cache (default false). Use after a failed or incomplete capture.
includeRawTextNoInclude the raw captured screen in rawText, bounded (default false) — diagnostics only.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

readOnlyHint=true already tells the agent this is a safe read. The description adds meaningful behavior beyond that: capture is via KoKo menu command 'p', parsed locally, and cached ~5 minutes, plus the auto-login hazard. It stops short of describing the result shape or pagination/truncation limits for large asset sets.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense but front-loaded: the core capability and its 'without entering' constraint lead, followed by mechanism, then warnings. Every clause carries information, though the parenthetical stacking makes it slightly hard to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only listing tool with full annotation and schema coverage and no output schema, the description supplies the mechanism, caching window, filter options, and the critical safety warning. Only the result shape/size behavior is unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all four parameters are already documented. The description restates the filter/group capability but adds no syntax or semantics beyond the schema, and says nothing about refresh or includeRawText. Baseline 3 is appropriate when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List the assets this account is authorized for') and immediately scopes it ('WITHOUT entering any of them'), which distinguishes it from jumpserver_enter, jumpserver_run, and jumpserver_batch. An agent knows exactly what this returns without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names when to call it ('Call this before jumpserver_run/jumpserver_batch') and names the alternatives it feeds. It also gives negative guidance ('never guess IPs', 'never type an asset name into the menu') with the reason (KoKo auto-logs-in on a unique hit), which is actionable routing advice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_auditA
Read-only

Read the local audit trail of THIS connector in-chat: recent audited events (time, operation, target asset, redacted command, risk, result). Use it when the user asks what the AI ran through JumpServer. Purely local file read — never touches the bastion, and works with no session and no configuration.

NameRequiredDescriptionDefault
limitNoHow many recent entries to return (default 20, oldest first).
filterNoSubstring filter matched case-insensitively against each raw audit entry (e.g. an IP or a command fragment).

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, but the description adds genuinely behavioral context beyond them: purely local file read, no bastion contact, no session or configuration required, and an enumeration of the returned fields. Missing only edge behavior such as what an empty trail returns.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences, front-loaded with the verb and resource, then the usage trigger, then the safety/operational caveat. No filler and nothing repeated from the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by listing the returned fields (time, operation, target asset, redacted command, risk, result). Combined with the annotations and fully documented params, an agent has everything needed to call this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the limit and filter parameters (including default, ordering, and case-insensitive substring semantics) are already fully documented in the schema. The description adds no parameter-level detail, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (read) and resource (this connector's local audit trail), and enumerates the returned event fields. The phrase 'of THIS connector' and 'in-chat' distinguishes it from bastion-facing siblings like jumpserver_assets or jumpserver_exec.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit trigger — 'Use it when the user asks what the AI ran through JumpServer' — and rules out the bastion path ('never touches the bastion, works with no session and no configuration'). It does not name a specific sibling alternative, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_baseline_captureA

Capture a NAMED baseline snapshot of one or more servers: the read-only inspect probes run and a compact state (OS, kernel, cores, memory, disks, listening ports, services, roles) is SAVED to data/baselines/.json. Pair it with jumpserver_baseline_compare before and after a change window to prove what actually changed. Writes a file, so this is NOT a read-only operation.

NameRequiredDescriptionDefault
nameYesBaseline name (letters/digits/._-, max 64), e.g. "oa-prechange-20260910".
groupNoInstead of targets: a configured asset group whose assets are snapshotted.
profileNoProbe profile used for the snapshot (default "full").
targetsYesTarget IPs or asset names to snapshot (max 20).
overwriteNoAllow replacing an existing baseline of the same name (default false).
accountByTargetNo{ [target]: displayedAccountId } for multi-user assets (KoKo `ID>` prompt).

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=false, idempotentHint=false, destructiveHint=false; the description reinforces this by explicitly stating it writes a file and is NOT read-only. It also discloses the concrete side effect and its location (data/baselines/<name>.json), which adds value beyond the annotations. It stops short of noting that a second capture with the same name fails without overwrite=true, which is the key non-idempotency behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the action and artifact, followed by the pairing workflow and the write warning. Every sentence carries distinct information with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully explains where results land (data/baselines/<name>.json) and what state is included. It is essentially complete for invocation, though it omits failure behavior on duplicate names and any auth/permission requirement for writing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all six parameters including the name format, profile enum, target limit, and overwrite flag. The description only gestures at 'NAMED' and 'one or more servers', adding no syntax or format detail beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Capture) and resource (a NAMED baseline snapshot of one or more servers), then enumerates exactly what is captured (OS, kernel, cores, memory, disks, listening ports, services, roles). An agent can distinguish it from jumpserver_baseline_compare and jumpserver_snapshot without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: pair with jumpserver_baseline_compare before and after a change window. That names the sibling and the workflow condition that selects it. It does not state when not to use it or the overwrite/re-capture prerequisite, so it falls short of a full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_baseline_compareA
Read-only

Compare a saved baseline (jumpserver_baseline_capture) against the CURRENT state of the same servers and report DRIFT: which hosts changed and exactly what changed (added/removed listening ports, disk usage, services, roles, kernel, cores, memory %, load). Use it after a change window, or periodically, to prove what moved. Hosts that cannot be reached now are reported as unreachable, never as unchanged.

NameRequiredDescriptionDefault
nameYesBaseline name to compare against.
formatNotext (default, compact) or json (full structured drift).
profileNoProbe profile for the fresh capture (default: the profile stored in the baseline).
accountByTargetNo{ [target]: displayedAccountId } for multi-user assets (KoKo `ID>` prompt).

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint=true already declaring a safe read, the description adds meaningful behavioral context: unreachable hosts are reported as unreachable, never as unchanged, which prevents a false 'no drift' reading. It also implies a fresh probe of live servers rather than a stored diff.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, each earning its place: the first defines the operation and its output fields, the second gives timing guidance, the third discloses the unreachable-host behavior. Front-loaded with the core purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by enumerating the drift dimensions reported (ports, disk, services, roles, kernel, cores, memory, load) and the unreachable case. It is nearly complete, though output shape/format differences between text and json are left to the schema enum.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all four parameters are already documented, including the enum values for format and profile. The description adds no syntax or format detail beyond the schema, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource pair (compare a saved baseline against current state) and names the exact sibling that produces the baseline, jumpserver_baseline_capture. It also distinguishes itself from the generic jumpserver_compare by specifying drift reporting on the same servers.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear usage context: 'after a change window, or periodically, to prove what moved.' It implies the baseline must already exist but does not explicitly state prerequisites or when to prefer this over the sibling jumpserver_compare.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_batchA
Destructive

Run simple commands across one or more targets in a single call. All commands of a target run before the next target is entered (target affinity). Put ONE simple read-only command per commands[] entry — no for/while loops, if branches, $(...) substitution or chained redirection; each entry is classified independently and statically.

NameRequiredDescriptionDefault
tasksYesOne or more target tasks.
confirmNoSet true only after the user approved these command(s). The first attempt returns COMMAND_APPROVAL_REQUIRED with the reason.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds real behavioral detail beyond the annotations: target affinity ordering ('all commands of a target run before the next target is entered') and per-entry static classification, which is non-obvious and affects how commands are authored. There is a mild tension with destructiveHint=true — the tool is flagged destructive while the description instructs read-only commands — but that reads as caller guidance rather than a claim about the tool's capability, so it is not a contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense sentences: the first front-loads purpose and scope, the second carries the authoring constraints. No filler, no restatement of the title or schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description does note that results come back per command, which is the key return-behavior signal for a batch tool. The approval gate (confirm -> COMMAND_APPROVAL_REQUIRED) is only explained in the schema, so the description is slightly thin on the destructive-execution workflow, but it is otherwise adequate for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so target, timeout (max 600), accountIndex and commands are already documented at the schema level. The description adds one rule not present in the schema (exactly one simple command per entry, no shell metacharacters), but otherwise does not enrich the parameters further.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Run') and resource ('simple commands') plus the scope that makes it distinct: 'across one or more targets in a single call'. This implicitly separates it from single-target siblings like jumpserver_exec/jumpserver_run, but no sibling is named, so an agent still has to infer which one to prefer.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives strong in-call constraints (one simple read-only command per commands[] entry, no loops/branches/substitution/chained redirection, each entry statically classified), which tells the agent how to compose input. It does not, however, say when to choose batch over jumpserver_exec/jumpserver_run or when multi-target batching is inappropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_closeA
Idempotent

Close the JumpServer session: PTY, SSH channel, buffers and timers are released and the connector returns to the DISCONNECTED state.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare idempotentHint=true, destructiveHint=false, and readOnlyHint=false, so safety is partly covered. The description still adds real value by naming the released resources and the resulting DISCONNECTED state, giving the agent a clear post-condition beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with the verb and the cleanup effects stated immediately. No filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-arg lifecycle tool with no output schema, the description covers the action and its resulting state, which is what an agent needs. It omits edge-case behavior (what happens when no session is active) and any auth/permission requirement, leaving a small gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters, so there is no parameter semantics to convey; the schema is trivially complete at 100% coverage. Baseline of 4 applies for a parameterless tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (close) and resource (JumpServer session) and enumerates exactly what is torn down (PTY, SSH channel, buffers, timers). It does not, however, distinguish itself from sibling lifecycle tools such as jumpserver_leave or jumpserver_interrupt, whose boundaries with 'close' are left for the agent to infer.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use or when-not-to-use guidance, no prerequisites (e.g. must a session be active first?), and no routing to alternatives like jumpserver_leave or jumpserver_interrupt. The agent must guess which teardown tool applies.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_compareA
Read-only

Run the SAME read-only command or inspect profile across several servers and report what DIFFERS — which host has a different config, a missing listener, a full disk. Returns groups of identical hosts plus, for each outlier, the lines it is missing or has extra versus the majority. Use it for "are these nodes configured the same?" instead of eyeballing raw dumps. Only READ is accepted; a mutating command refuses the whole call.

NameRequiredDescriptionDefault
formatNotext (default, compact) or json (full structured diff).
commandNoOne read-only command to run on every target (mutually exclusive with profile).
profileNoInspect profile to run on every target (mutually exclusive with command).
targetsYesTarget IPs or asset names to compare (2..20).
concurrencyNoTargets at once (default: batchConcurrency setting).
stripPrefixesNoLine prefixes to strip before diffing (e.g. volatile timestamps).
accountByTargetNo{ [target]: displayedAccountId } for multi-user assets (KoKo `ID>` prompt).

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=true, and the description adds a genuinely non-obvious behavior: a mutating command causes the whole call to be refused rather than partially executed. It also discloses the diff-oriented output structure. It does not cover partial-target failure, timeout, or concurrency semantics beyond what the schema already states.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core operation is front-loaded in the first clause, and the follow-on sentences carry the return shape, the use case, and the read-only constraint without padding. It is slightly dense with em-dash asides but every sentence contributes something.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 7-parameter diff tool with no output schema, the description covers the operation, the intended question it answers, the return structure, and the hard read-only constraint. With full schema coverage handling parameters, nothing material an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with command/profile/profiles, format, targets bounds, concurrency, stripPrefixes and accountByTarget all documented in the schema itself. The description only reinforces command-vs-profile mutual exclusivity and adds no syntax or format detail the schema lacks, so the baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific compound operation — run the same read-only command or inspect profile across many hosts and report only the differences — and even sketches the return shape (groups of identical hosts plus per-outlier missing/extra lines). It is clear and non-tautological, but it never names or distinguishes the overlapping siblings such as jumpserver_baseline_compare, jumpserver_inspect, or jumpserver_profile_run.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a concrete trigger ('are these nodes configured the same?') and an explicit anti-pattern to avoid ('instead of eyeballing raw dumps'), which is real routing value. It stops short of exclusions against the near-neighbor sibling tools, so the agent gets context but not a decision rule for choosing between this and baseline_compare/inspect.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_connectA
Idempotent

Open the persistent JumpServer SSH session and wait until the menu is detected. Idempotent — if a session already exists this does NOT create a second one, it returns the current state. One session per conversation.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare idempotentHint=true and destructiveHint=false, so the bar is lower, yet the description adds genuine context: it explains what idempotency means ('does NOT create a second one, it returns the current state'), discloses blocking behavior ('wait until the menu is detected'), and adds a constraint not in the annotations ('one session per conversation'). It stops short of failure/timeout or auth behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences with zero filler; the core action and completion condition are front-loaded, followed by the idempotency and cardinality constraints. Every sentence carries distinct information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a no-parameter, no-output-schema tool, the description covers the action, blocking semantics, idempotency, and session cardinality, which is most of what an agent needs. It omits failure/timeout handling and any authentication precondition, which are the remaining gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4; there is nothing for the description to disambiguate beyond behavior. No parameters are misdescribed or left ambiguous.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Open the persistent JumpServer SSH session') plus the completion condition ('wait until the menu is detected'). It is distinct from lifecycle siblings like jumpserver_close/jumpserver_leave, but it never names an alternative such as jumpserver_enter or jumpserver_status, so differentiation is left partly to inference from the sibling list.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implicitly this is the session-start tool and 'one session per conversation' hints at its place in the lifecycle, but there is no explicit when-to-use or when-not-to-use guidance relative to siblings like jumpserver_enter or jumpserver_status. The idempotency note is the closest thing to routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_console_rotate_tokenA

Revoke the ops-console access token and issue a fresh one. In stable mode (the default) the console address is unchanged — reload the page and it picks the new token up. Use when the console reports 令牌已过期. Side-effecting (the old token dies immediately), so not read-only.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: it explains that the old token dies immediately, that stable mode leaves the console address unchanged, that a page reload picks up the new token, and explicitly restates the non-read-only nature for emphasis. The annotations only carry readOnlyHint=false and idempotentHint=false; the description adds the immediate invalidation semantics and recovery behavior, which is exactly the context an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, each earning its place: the action, the post-condition/recovery path, and the trigger plus safety note. Front-loaded with the verb and object.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter mutation with no output schema, the description supplies the action, side effect, recovery workflow, and trigger. Nothing material is missing for an agent to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters, so the baseline is 4. The description does not need to explain any argument semantics, and it correctly focuses on behavior instead.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Revoke the ops-console access token and issue a fresh one') with clear scope and side effects. No sibling tool deals with token rotation, so it is unambiguous in its domain.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names the concrete trigger ('Use when the console reports 令牌已过期') and describes the stable-mode default behavior. It does not mention alternatives or when not to use it, but no obvious alternative exists among siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_enterA
Idempotent

Enter a target asset THROUGH the JumpServer menu by its IP or name. Requires the session to be at the menu. A probe (hostname/whoami/pwd) runs internally and the asset is reported as entered only after it verifies. When the target has multiple authorised bastion users, KoKo shows a numbered list ending in ID>; pass accountIndex (the displayed ID, usually 1 or 2) to pick one, or omit to fail with ACCOUNT_SELECTION_REQUIRED and the parsed list in detail. An asset the bastion cannot reach (网络不通(连接超时)) fails fast as ASSET_UNREACHABLE and is then refused for 2 minutes — report it to the user instead of retrying.

NameRequiredDescriptionDefault
targetYesTarget asset IP or name, e.g. 203.0.113.101
accountIndexNoThe account ID shown by KoKo at the `ID>` prompt (usually 1 or 2; matches the number on the screen).

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare non-readOnly, idempotent, non-destructive; the description goes well beyond them by disclosing an internal hostname/whoami/pwd probe with verification, failure codes (ACCOUNT_SELECTION_REQUIRED, ASSET_UNREACHABLE with the parsed list in `detail`), and a 2-minute unreachable-asset refusal window. That is exactly the operational context an agent needs and cannot get from structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with what the tool does, then prerequisites, then the account-selection and unreachable-asset edge cases. It is dense and longer than typical, but each sentence carries actionable information (error codes, retry prohibition) rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema and only 2 params, the description supplies the missing pieces itself: the verification probe, the shape of the ACCOUNT_SELECTION_REQUIRED detail (parsed account list), and the transient refusal behavior. Nothing an agent needs to call this correctly appears to be absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description earns more by explaining why accountIndex exists (the KoKo `ID>` numbered prompt), what its value corresponds to on screen, and what omitting it produces. This adds real semantic meaning beyond the schema's terse 'usually 1 or 2'.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Enter'), resource ('target asset'), and mechanism ('THROUGH the JumpServer menu by its IP or name'), which separates it from a generic 'connect'. It does not name the sibling it is contrasted with (e.g. jumpserver_connect/exec), so differentiation must be inferred from the menu-based framing.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit precondition ('Requires the session to be at the menu') and prescribes behavior in edge cases: pass accountIndex when KoKo shows a numbered list, and do not retry an unreachable asset because it is refused for 2 minutes. It stops short of naming an alternative tool to use instead, so the when-not guidance is behavioral rather than routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_execA
Destructive

Run ONE simple command on the CURRENTLY entered asset and return stdout plus the real exit code (completion is detected by a unique marker, never by waiting a fixed time). Send one simple read-only command — no for/if loops, no $(...) substitution, no multi-statement one-liners, because READ_ONLY classifies every command statically and blocks shell structure. For several checks on one target use jumpserver_batch.

NameRequiredDescriptionDefault
commandYesShell command to execute on the remote asset.
confirmNoSet true only after the user approved these command(s). The first attempt returns COMMAND_APPROVAL_REQUIRED with the reason.
timeoutNoTimeout in seconds (default: configured command timeout, max 600).

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds real behavior beyond the annotations: completion is detected by a unique marker rather than a fixed wait, the real exit code is returned, and a static READ_ONLY classifier blocks shell structure. It does not describe the approval flow or failure modes in prose (that lives in the schema), and the conservative destructiveHint=true / readOnlyHint=false annotations are left unreconciled with the 'send a read-only command' instruction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense sentences with zero filler; the primary action and its scoping constraint are front-loaded, then the command-shape restrictions, then the sibling routing. Every clause carries information an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description correctly discloses the return shape (stdout plus real exit code) and the completion-detection mechanism, which is exactly what an agent needs. It leaves the approval prerequisite (COMMAND_APPROVAL_REQUIRED / confirm) to the schema and never states explicitly that jumpserver_enter must precede it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline would be 3, but the description meaningfully constrains the `command` parameter (one simple command, no shell structure of any kind) far beyond the schema's generic 'Shell command to execute on the remote asset'. `confirm` and `timeout` are left entirely to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb+resource+scope: run ONE simple command on the CURRENTLY entered asset, returning stdout and the real exit code. The 'CURRENTLY entered asset' scoping and the explicit pointer to jumpserver_batch cleanly separate it from the batch and run siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use (single command, several checks -> use jumpserver_batch) and when-not (no for/if loops, no $(...) substitution, no multi-statement one-liners, because READ_ONLY blocks shell structure). The alternative tool is named with the condition that selects it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_inspectA
Read-only

Collect a STRUCTURED host inventory from one or more servers using FIXED read-only probes (the connector decides the commands — do not invent shell). Returns parsed facts (OS, kernel, uptime, load, memory, disks, IPs, listening ports, connections, processes, services, nginx upstreams, java, docker, roles) instead of raw shell text. Use this rather than a pile of jumpserver_exec calls. Profiles: basic / network / process / service / web / java / database / container / full.

NameRequiredDescriptionDefault
groupNoInstead of targets: a configured asset group whose assets are all inspected.
formatNotext (default, compact) or json (full structured inventory).
profileNoProbe profile(s), default "basic". "full" runs every profile.
targetsYesTarget IPs or asset names (max 20).
concurrencyNoTargets surveyed at once (default: batchConcurrency setting).
accountByTargetNo{ [target]: displayedAccountId } for multi-user assets (KoKo `ID>` prompt).

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond readOnlyHint=true, the description discloses that probes are fixed and connector-chosen, that output is parsed facts rather than raw shell, and what categories of facts come back. This is rich behavioral context an agent needs to trust and interpret the call.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action, then the return contents, then the reason to prefer it and the profile list. It is dense and largely earns its space, though the inline enumeration of return facts is long enough to slightly dilute focus.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by listing the returned facts, and it covers targets, groups, profiles, format, and safety. Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all six parameters, including the profile enum and defaults. The description repeats the profile list but adds no format, syntax, or interaction detail beyond what the schema provides, matching the baseline for full coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (collect) and resource (structured host inventory) with a clear scope (one or more servers). It enumerates the exact facts returned and names the sibling it replaces (jumpserver_exec), so the agent can distinguish it without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: 'Use this rather than a pile of jumpserver_exec calls' names the alternative and the condition. It also warns against inventing shell and points to profiles for scoping, leaving little to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_interruptA
Destructive

Send Ctrl+C to the remote shell NOW to interrupt whatever is running (a stuck command, a tail that will not end, a long find). Out-of-band: it does NOT wait for the current operation to finish. Afterwards the connector re-probes the shell and reports whether it is usable again (ASSET_SHELL) or collapsed to UNKNOWN. Use it instead of jumpserver_close when you only want to stop the remote job and keep the session.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations: it discloses that the interrupt is out-of-band and does NOT wait for the current operation to finish, and that afterwards the connector re-probes the shell and reports ASSET_SHELL or UNKNOWN. That post-condition and result-state information is exactly the behavioral context an agent needs and is not derivable from destructiveHint/readOnlyHint.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the action and urgency ('NOW'), then adds the out-of-band caveat, the post-condition, and the sibling routing in three tight sentences. No filler; every clause carries decision-relevant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the burden of return semantics and does so by naming the two possible resulting states. Combined with the annotations covering the safety profile, an agent has everything needed to call this correctly and interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate; baseline for a no-parameter tool is 4. No parameter syntax is needed or missing.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific action (send Ctrl+C) against a specific resource (the remote shell) with the intent spelled out (interrupt whatever is running), and gives concrete examples of the target situation (stuck command, endless tail, long find). It also names a sibling, jumpserver_close, and explains the distinction, so an agent can route correctly without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use it ('instead of jumpserver_close when you only want to stop the remote job and keep the session'), which is a direct when/when-not rule against the most likely alternative. The contrast with closing the session is unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_job_readA
Read-only

Read the output of a streaming job (jumpserver_job_start) as a CURSOR READ: by default it returns only what arrived after the previous read, plus nextSeq to pass back as sinceSeq so you resume exactly where you left off (safe to retry, never re-consumes old lines). full:true returns the whole buffered output instead.

NameRequiredDescriptionDefault
fullNoReturn the whole buffered output instead of only what arrived since the cursor (default false).
jobIdYesJob id returned by jumpserver_job_start, e.g. jsjob_8fd11a
maxCharsNoCap the returned characters (default 8000).
sinceSeqNoAbsolute cursor to resume from — pass the nextSeq of the previous read. Omit to continue from the last read.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true. The description adds real behavioral context beyond that: cursor semantics, 'never re-consumes old lines', and 'safe to retry' — valuable idempotency and dedup guarantees. It doesn't cover truncation/exit-status behavior, keeping it below 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense clauses with zero filler; the cursor default is front-loaded before the full:true override. Every phrase carries information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description must (and mostly does) explain the return shape via nextSeq. It leaves the pagination/truncation interaction with maxChars and end-of-stream signalling unaddressed, a minor residual gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description goes further by linking nextSeq (return) to sinceSeq (input) as a resume contract and clarifying full vs. default, adding meaning beyond the per-field schema text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb+resource ('Read the output of a streaming job') paired with the originating sibling (jumpserver_job_start), and it names the distinguishing mode ('CURSOR READ'). An agent can tell this apart from jumpserver_jobs or jumpserver_job_start without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explains the default cursor behavior and the explicit alternative (full:true returns the whole buffer), giving clear when-to-use context. It does not name other sibling read tools or state when NOT to use cursor mode, so it stops short of full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_jobsA
Read-only

List streaming jobs of this conversation (id, target, command, state, elapsed, buffered bytes).

NameRequiredDescriptionDefault

No parameters

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnlyHint=true), so the lower bar applies. The description adds real value beyond that by enumerating the returned per-job fields (id, target, command, state, elapsed, buffered bytes), which is the only source of return-shape information since there is no output schema. It omits pagination/ordering behavior, keeping it short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One sentence, front-loaded with verb and resource, with the field list tucked into a parenthetical. Every element earns its place and nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-param, read-only list tool with no output schema, the agent has enough to call it correctly and knows the shape of the response. Only pagination/ordering and any failure conditions are unaddressed, a minor gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4; there is nothing for the description to disambiguate. The parenthetical field list appears to describe outputs rather than inputs, which is appropriate given the empty schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (List) and resource (streaming jobs) scoped to 'this conversation', which sharpens it against the pool of job_* siblings. It does not explicitly name or contrast with jumpserver_job_read or job_start/stop, so differentiation is inferred rather than stated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The scope phrase 'of this conversation' implies when this tool applies, but there is no explicit when-to-use instruction and no alternative named (e.g. job_read for a single job's detail). Usage is only implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_job_startA
Destructive

Start a STREAMING job on a server — for commands that never return on their own: tail -f /…/stdout.log, journalctl -f -u nginx, tcpdump, top -b, ping. Returns a jobId; then poll jumpserver_job_read(jobId) for the new output and call jumpserver_job_stop(jobId) when done. While a job runs the shell is reserved for it (other commands get SESSION_BUSY), and it auto-stops at maxDuration.

NameRequiredDescriptionDefault
targetYesTarget asset IP or name.
commandYesStreaming command, e.g. "tail -f /usr/weaver/Resin4/log/stdout.log".
confirmNoSet true after the user approved this command (non-READ classifications require it).
maxDurationNoAuto-stop after N seconds (default 300, max 900).
accountIndexNoThe account ID shown by KoKo at the `ID>` prompt (usually 1 or 2; matches the number on the screen).

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already disclose destructiveHint=true/readOnlyHint=false, but the description adds real behavioral context beyond that: the shell is reserved while a job runs (other commands get SESSION_BUSY) and the job auto-stops at maxDuration. It also notes the jobId return value. Not exhaustive (no auth/permission notes), but materially useful.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense sentences that are front-loaded with the purpose, then the return/next-step workflow, then the runtime constraints. Every clause earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, but the description explains the return (a jobId) and how to consume it via jumpserver_job_read. Combined with the shell-reservation and auto-stop notes, an agent has everything needed to invoke and manage the job correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all five parameters are already documented in the schema. The description reinforces maxDuration auto-stop behavior and gives a command example, but adds little beyond the structured fields. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Start a STREAMING job on a server') and immediately scopes it to a class of never-returning commands, distinguishing it from siblings like jumpserver_exec and jumpserver_run that handle one-shot commands.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly defines when to use it ('commands that never return on their own') with concrete examples, and lays out the full workflow: poll jumpserver_job_read(jobId), then jumpserver_job_stop(jobId). The agent knows both the trigger condition and the follow-up calls.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_job_stopA
Destructive

Stop a streaming job: sends Ctrl+C to the remote shell and releases it for normal commands.

NameRequiredDescriptionDefault
jobIdYesJob id returned by jumpserver_job_start.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true and non-idempotent, so the safety profile is covered. The description adds genuinely useful context beyond that: the stop is implemented as Ctrl+C to the remote shell and the session is returned to normal command mode, which explains the mechanism and the post-condition an agent should expect.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single tight sentence with the action front-loaded and the mechanism/effect following. No filler and nothing that needs trimming.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter control tool with annotations carrying the destructive/non-idempotent profile and no output schema, the description is nearly sufficient: action, mechanism, and resulting shell state are all given. It would be fully complete with a note on what happens if the job is already stopped or the jobId is invalid.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is a single parameter with 100% schema description coverage, and the schema already documents jobId as the id returned by jumpserver_job_start. The description adds nothing about the parameter beyond that, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Stop a streaming job") plus the mechanism used to do it, so an agent knows exactly what operation this is. It does not, however, differentiate itself from plausible siblings such as jumpserver_interrupt or jumpserver_close, which are not named or contrasted.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: the description notes the shell is "released for normal commands," which suggests this is the counterpart to jumpserver_job_start, and the schema ties jobId to that tool. There is no explicit statement of when to prefer this over jumpserver_interrupt/close, nor any precondition or exclusion.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_leaveA
Idempotent

Leave the currently entered asset and return to the JumpServer menu. Sends exit at most once, and reports success only after the menu is detected again.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare idempotentHint=true and destructiveHint=false; the description adds real value by specifying the at-most-once exit send and the exact success condition (menu re-detected), which an agent cannot infer from structured fields.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, both front-loaded, no redundant restatement of the name. Every clause earns its place by adding action, mechanism, or success criteria.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the burden and does state how success is determined. It falls short only on failure/precondition behavior (e.g., what is reported when no asset is currently entered).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Zero parameters, so the baseline of 4 applies — there is nothing for the description to disambiguate beyond the implicit requirement of being inside an entered asset.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource: leaving the currently entered asset and returning to the JumpServer menu. The enter/leave pairing makes it distinguishable from siblings like jumpserver_connect and jumpserver_close, but those alternatives are not named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by 'the currently entered asset' — it presumes a prior jumpserver_enter — but it never states when to prefer this over jumpserver_close or jumpserver_connect, nor what happens if no asset is entered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_profile_runA
Read-only

Run a NAMED runbook (a reviewed, reusable recipe from config "runbooks") across one or more servers: the SAME steps run on every host, and the results come back grouped per target and per step. Each step is an inspect profile or an explicit READ command; a non-READ step is SKIPPED and reported, never executed. Steps carrying expect assertions yield a PASS/FAIL verdict per step, per target and overall.

NameRequiredDescriptionDefault
groupNoConfigured asset group — the runbook runs on every asset of that group.
formatNotext (default, compact) or json (full structured result).
runbookYesRunbook name as defined in config.json under "runbooks", e.g. "oa-health".
targetsNoTarget IPs or asset names (max 20). Omit when using group.
concurrencyNoTargets run at once (default: batchConcurrency setting).
accountByTargetNo{ [target]: displayedAccountId } for multi-user assets (KoKo `ID>` prompt).

TDQS

A4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnlyHint annotation, the description discloses substantive behavior: the same steps execute on every host, results are grouped per target and per step, non-READ steps are SKIPPED and reported rather than executed, and `expect` assertions produce PASS/FAIL verdicts. This tells an agent exactly what happens to unsafe steps and what shape of verdict to expect — context the annotation alone cannot convey.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single dense but well-structured block: it leads with the core action, then qualifies step types and result grouping, with backticked keywords and caps for emphasis. Every clause carries information, though the packed prose is slightly heavy for a quick scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description compensates by explaining the return shape (results grouped per target and per step, PASS/FAIL per step/target/overall). Combined with a fully documented 6-parameter schema and a safety annotation, an agent has everything needed to invoke this tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents runbook, targets, group, format, concurrency and accountByTarget thoroughly. The description reinforces the runbook name and multi-host targeting but adds no format, limit, or semantics beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Run a NAMED runbook ... across one or more servers') and immediately qualifies what a runbook is ('a reviewed, reusable recipe from config "runbooks"'). It implicitly separates itself from ad-hoc execution siblings (jumpserver_exec, jumpserver_run, jumpserver_batch) by emphasizing a named, pre-reviewed recipe, but never names those alternatives, so the differentiation is left to inference.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes clear you need a runbook name and can scope by targets or group, which is implied usage context. However, it gives no explicit when-to-use vs. when-not guidance and never routes the agent to jumpserver_exec/jumpserver_run/jumpserver_batch for one-off commands, leaving the choice between runbook and ad-hoc execution unstated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_runA
Destructive

Run ONE simple command on a target reachable through JumpServer: connects (or reuses the session), switches asset when necessary, verifies the target, executes, and returns structured output. Same command-style limits as jumpserver_exec. For MULTIPLE metrics on one target call jumpserver_batch ONCE instead of calling this repeatedly.

NameRequiredDescriptionDefault
targetYesTarget asset IP or name, e.g. 203.0.113.101
commandYesShell command to execute on the remote asset.
confirmNoSet true only after the user approved these command(s). The first attempt returns COMMAND_APPROVAL_REQUIRED with the reason.
timeoutNoTimeout in seconds (default: configured command timeout, max 600).
accountIndexNoThe account ID shown by KoKo at the `ID>` prompt (usually 1 or 2; matches the number on the screen).

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructive=true and readOnlyHint=false, so the write profile is already covered. The description adds useful behavior beyond that: session reuse, asset switching, target verification, and structured output, which tells the agent this is a stateful, multi-step operation rather than a one-shot call. It does not, however, say what gets destroyed or how failures/session state are left behind.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero filler. The primary action and its workflow are front-loaded, and the sibling routing rule is packed into a single clarifying clause at the end.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, but the description states that structured output is returned and describes the exact sequence an agent should expect, including in-session asset switching. Combined with a fully documented 5-parameter schema and clear annotations, an agent has enough to invoke this correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the schema already documents target, command, confirm (with the COMMAND_APPROVAL_REQUIRED behavior), timeout defaults/max, and accountIndex semantics. The description adds no parameter-level meaning beyond what the schema provides, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Run) and resource (ONE simple command on a target reachable through JumpServer) and enumerates the internal steps: connect/reuse session, switch asset, verify target, execute, return structured output. It also names the sibling it is not (jumpserver_batch for multiple metrics), so an agent can separate it from the nearest alternative without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes multi-metric work to jumpserver_batch and warns against calling this repeatedly. It gives clear context for the single-command case, but the boundary with the very similarly named jumpserver_exec is only gestured at ('Same command-style limits'), leaving the agent to infer which of the two to pick.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_snapshotA
Read-only

Read-only terminal mirror: the recent PTY event stream (output chunks, state transitions, target changes) plus the current state. Poll incrementally with sinceSeq. Use it to show the user what the terminal looks like now, or to diagnose a stuck menu. Credentials are redacted by the observer.

NameRequiredDescriptionDefault
maxCharsNoCap the returned text length in characters (default 8000).
sinceSeqNoReturn only events with seq > sinceSeq (incremental polling). Omit for the full scrollback.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, so safety is covered. The description adds genuinely useful context beyond that: credentials are redacted by the observer, and it returns an event stream plus current state, implying freshness/refresh semantics rather than a static read.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences, front-loaded with what the tool is, then how to poll, then when to use it. Every sentence carries information with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description still conveys what comes back (event stream contents + current state) and the redaction behavior. It never sketches the shape/ordering of returned events, so an agent knows the contents but not the structure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so both parameters (sinceSeq, maxChars) are already documented in the schema. The description reinforces sinceSeq's incremental-polling purpose but adds no syntax or default detail beyond it ('omit for the full scrollback' is already in the schema), so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource: reads the recent PTY event stream (output chunks, state transitions, target changes) plus current state. This is far more specific than a generic 'status' or 'inspect', but it never explicitly names which sibling it replaces, so it stops short of full sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete when-to-use scenarios ('show the user what the terminal looks like now', 'diagnose a stuck menu') and how to poll incrementally. It does not name alternatives (e.g. inspect/status) or state when not to use it, so no exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_statusA
Read-only

Report connector state: session, gateway, entered target (probe-verified), permission mode, the ops-console URL and token expiry, and whether the connection settings are complete. Never returns credentials. Strictly read-only — to invalidate the console token use jumpserver_console_rotate_token.

NameRequiredDescriptionDefault

No parameters

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, but the description goes beyond them by guaranteeing 'Never returns credentials' and by exposing token-expiry and probe-verified target state, which the agent cannot infer from the schema. It does not describe behavior when no connector session exists, so it is strong but not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tightly packed sentences with the reported fields front-loaded and the safety/caveat clauses trailing. No filler and no redundancy against the schema or annotations.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description correctly compensates by enumerating the returned state fields and the credential-exclusion guarantee. The only gap is the absence of any statement about the response when no session/gateway is active.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline is 4; there is no parameter surface for the description to clarify or obscure.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb ('Report') plus a precise resource ('connector state') followed by the exact fields surfaced: session, gateway, entered target, permission mode, console URL, token expiry, and settings completeness. No sibling in the list offers the same read-only state report, so an agent can select it unambiguously.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implicitly scopes use to inspecting connection/session state and explicitly routes a related need elsewhere ('to invalidate the console token use jumpserver_console_rotate_token'). It does not state a positive trigger condition (e.g., 'call before entering a target'), so it falls short of a full when/when-not statement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

jumpserver_topologyA
Read-only

Build the relationship graph between servers: which host proxies or connects to which, on which port, with what evidence (nginx upstream, ESTABLISHED socket, /etc/hosts). Internally runs the read-only inspect probes and returns nodes (roles, ports, OS, load, memory) plus edges (type, port, confidence, evidence). Use it for "what is the relationship between these hosts" instead of guessing from names. depth>=2 also links app nodes sharing one upstream.

NameRequiredDescriptionDefault
depthNo1 = direct evidence only (default); 2+ also links nodes sharing one upstream.
groupNoConfigured asset group — every asset of that group is surveyed.
formatNotext (default, tree) or json (nodes + edges).
targetsNoTarget IPs or asset names (max 20). Omit when using group.
profilesNoProbe profiles (default: network, process, web).
concurrencyNoTargets surveyed at once (default: batchConcurrency setting).
accountByTargetNo{ [target]: displayedAccountId } for multi-user assets (KoKo `ID>` prompt).

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Consistent with readOnlyHint=true and additive: it discloses that probes are read-only, enumerates the evidence types (nginx upstream, ESTABLISHED socket, /etc/hosts), and explains the depth>=2 linking behavior. Auth/permission requirements and max-20 target behavior are only lightly implied.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences, front-loaded with the core action before the usage guidance. Minor redundancy between the description and the schema's depth wording, but no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description carries the return-value burden and does so adequately by naming nodes (roles, ports, OS, load, memory) and edges (type, port, confidence, evidence). For a 7-param, nested-object tool this is sufficiently complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so most meaning already lives in the schema. The description restates depth semantics rather than adding new parameter detail, so it earns the baseline rather than a bonus.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Build the relationship graph between servers') and defines the graph's contents (hosts, ports, evidence sources). An agent can immediately distinguish it from jumpserver_inspect or jumpserver_assets, since the output is relationships, not inventory.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit usage trigger ('what is the relationship between these hosts') and warns against guessing from names, plus notes the internal inspect probes it relies on. It stops short of naming sibling alternatives or stating when NOT to use it versus jumpserver_inspect directly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 23 tool updatesv0.5.13
    • First observedjumpserver_assets
    • First observedjumpserver_audit
    • First observedjumpserver_baseline_capture
    • First observedjumpserver_baseline_compare
    • First observedjumpserver_batch
    • First observedjumpserver_close
    • First observedjumpserver_compare
    • First observedjumpserver_connect
    • First observedjumpserver_console_rotate_token
    • First observedjumpserver_enter
    • First observedjumpserver_exec
    • First observedjumpserver_inspect
    • First observedjumpserver_interrupt
    • First observedjumpserver_job_read
    • First observedjumpserver_job_start
    • First observedjumpserver_job_stop
    • First observedjumpserver_jobs
    • First observedjumpserver_leave
    • First observedjumpserver_profile_run
    • First observedjumpserver_run
    • First observedjumpserver_snapshot
    • First observedjumpserver_status
    • First observedjumpserver_topology

TDQS

A4/5.0

Scored across 23 tools

Disambiguation4/5

Most tools are clearly distinct (connect/enter/leave/close lifecycle, snapshot vs status vs audit, baseline_capture vs baseline_compare). However exec, run, and batch overlap substantially and are only separable by careful reading of their descriptions (current asset vs target-through-bastion vs multi-target). interrupt vs job_stop vs close also require attention, though each description draws a clear line.

Naming Consistency4/5

All tools share the jumpserver_ prefix and use a mostly consistent verb_noun pattern (job_start, job_read, job_stop, baseline_capture, baseline_compare, profile_run). Minor deviations like console_rotate_token (noun_verb_noun) and the noun-only inspect/compare/topology break strict verb-noun form but remain readable and predictable.

Tool Count4/5

23 tools is on the heavy side but the domain (SSH bastion lifecycle, execution, streaming jobs, inspection, baselines, topology, audit) is genuinely broad and nearly every tool maps to a distinct capability. It stays just under the 'too many' threshold and no tool looks redundant.

Completeness4/5

The surface covers connection lifecycle, single/multi-target execution, streaming jobs, structured inspection, topology, comparison, and baseline drift detection with strong lifecycle coverage. Minor gaps like file transfer/upload or interactive command support are not present but agents can work around them through exec.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables secure remote access operations through SSH, SFTP, rsync, VPN, and tunneling with enterprise-grade policy enforcement and audit logging. Provides AI assistants with secure, policy-driven access to remote systems while maintaining comprehensive audit trails and zero-trust security.
    1
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables SSH connections and remote command execution with persistent session management and real-time browser-based terminal monitoring. Supports multiple simultaneous SSH sessions with command history tracking and live WebSocket streaming of terminal output.
    6
    MIT
  • A
    license
    B
    quality
    A
    maintenance
    Provides policy-driven, auditable SSH access to server fleets for AI assistants with zero-trust security controls, command whitelisting, and comprehensive audit logging to safely manage infrastructure.
    13
    28
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to securely execute SSH commands on remote servers with connection pooling, session isolation, and a web audit panel.
    4
    MIT