lablup / lablup/backend.ai-webui

[wayfinder] Backend.AI agent interface for Claude — CLI (docs + GraphQL) + WebMCP interlink map

Closed
#9,210 0 comments 0 reactions 0 assignees View on GitHub
wayfinder:map
Dominant language
TypeScript
Stars
133
Forks
81
Avg merge
1d 12h
Merged PRs (30d)
355

Description

## Destination

> **Status (2026-08-29): map complete — every child ticket is closed.** Next step is a spec ticket for `bai-agent` + WebMCP interlink, derived from Decisions so far; the items under _Not yet specified_ are deliberately deferred past that spec.

When Claude receives a question or work request about Backend.AI, it can (1) use a CLI in the webui repo to search the user manual, terminology, and GraphQL schema, confirm against live GraphQL, and answer in **user-facing terms (i18n/terminology) with links to the deployed docs**, and (2) **hand off to the user's browser via WebMCP** (e.g. open the detail page of a resource the CLI just created). The map is done when every decision needed to hand this to a spec has been made.

## Notes

- **Plan, don't do.** Tickets on this map are decision tickets. Product code is written after the map closes (spec → implementation tickets). Prototype artifacts are throwaway and only linked.
- Issues and comments are written in English. Resolve a ticket with a resolution comment, transition to Done, and append one line to Decisions so far on this Epic. Research findings live in the ticket's resolution comment (no research branches).
- Skills to consult: `webui-connection-info` (dev server / credentials), `bai-cli` (backend repo's `./bai login` / `./bai gql` — an existing session-cookie raw GraphQL CLI), `docs-lead` (manual structure, terminology precedence), `.claude/rules/graphql-pagination.md`. The **Astryx CLI** (`pnpm exec astryx --help`, `docs cli-integrations`, `docs working-with-ai`) is the reference for the agent-facing surface (FR-3742).
- Baseline findings (2026-08-28 session):
- WebUI GraphQL goes through a single gateway `/admin/gql` (SESSION mode: `{endpoint}/func/admin/gql`). SESSION mode sends no signature — auth is the `AIOHTTP_SESSION` cookie + `X-BackendAI-SessionID` header. No CSRF token, so cookie reuse by an external process is a supported path (`./bai login` implements it).
- `e2e/utils/admin-api.ts` already does plain-JSON `/server/login` → `/func/admin/gql` POST.
- Manager introspection is off by default → tools must read the committed SDL (`data/schema.graphql`, 692 KB, manually updated). Strawberry v2 descriptions are mandatory and CI-enforced (`Added in X.Y.Z. ...`); input fields and enum values are weakly documented.
- Meaning sources: `resources/i18n/*.json` (precedence 1), `packages/backend.ai-webui-docs/terminology.json` (2), the manual (30 docs × 4 languages). Deployed docs: webui.docs.backend.ai (Amplify, FR-3247).
- No prior MCP/WebMCP work in either repo or Jira (greenfield).
- Working mode (decided 2026-08-29): independent frontier tickets may be worked in one session — AFK parts (prototypes, investigations) run as parallel subagents, the human decision for each ticket still happens in the driving session, and each ticket still gets its own resolution comment. The join-point ticket ([grilling] Agent-facing command surface and output contract) is worked alone after the others.

## Decisions so far

- [Deployed docs site URL and anchor structure](https://lablup.atlassian.net/browse/FR-3751) — md→URL is deterministic: `https://webui.docs.backend.ai/{version}/{lang}/{slug}.html#{slug}-{heading-slug`} (version segment mandatory, `/latest/` deep links do not work, Korean anchors are raw). The site ships `search-index.json` per version/lang, reusable as the CLI search base. No `llms.txt`.
- [backend.ai-client Node reuse vs plain fetch](https://lablup.atlassian.net/browse/FR-3754) — both are small: package reuse needs ~25–40 lines of shims (localStorage, document, cookie jar) but drags in per-request logging/timers; plain fetch is ~40–60 lines for SESSION, ~100–120 with HMAC (signing helpers are pure but not exported). Legacy `src/lib/backend.ai-client-node.ts` is a drifting copy, unsuitable.
- [Schema freshness — data/schema.graphql vs backend CI](https://lablup.atlassian.net/browse/FR-3752) — the webui snapshot is a hand-edited accumulation with no version marker (stale `RoleInvitation*` types, pre-applied unreleased 26.9.0 fields); backend publishes `supergraph.graphql` as a release asset on every tag and keeps main's copy current via CI. Fetch-based sync is feasible today; version identification needs a recorded sha/tag.
- [WebMCP bridge/polyfill comparison](https://lablup.atlassian.net/browse/FR-3753) — spec entry point is `document.modelContext` with signal-based unregister (no `unregisterTool`, no `provideContext`); Chrome OT 149–156, Edge 150 OT; Claude has no native support (claude-code#30645 not planned) so a bridge is mandatory. Working combination today: `@mcp-b/global` + `@mcp-b/webmcp-local-relay` (no extension, released 2026-08-23); runner-up MCPCat WebMCP Bridge; others stale. Electron → polyfill path (or `enable-blink-features`), never OT tokens.
- [Mutation scope and safeguards](https://lablup.atlassian.net/browse/FR-3744) — CLI executes non-destructive writes only: static allow-list in CLI source (seeded with compute-session and VFolder creation), explicit `--allow-mutation` flag on every call, no session/config switch. Destructive mutations are never executed by the CLI; they go through WebMCP where the existing confirmation UX applies. Rejection error must point to the WebUI path (shape owned by the handoff ticket).
- [Credential source and session storage policy](https://lablup.atlassian.net/browse/FR-3745) — no credential file: browser-delegated login (`claude login` style). CLI opens a hidden WebUI route `/cli-login?port=&state=`; after an explicit consent notice the page hands the current session's `X-BackendAI-SessionID` (= `AIOHTTP_SESSION` value) + endpoint to a localhost callback, or shows it for manual paste on remote/headless boxes. No backend change. Endpoint is user-configurable (any server incl. production); SESSION mode only, HMAC deferred; stored per endpoint at `~/.config/backend.ai-agent/sessions/.json` (0600), not shared with `./bai`; on 401 → delete file + structured `auth_required` error, no auto re-login; session id masked in logs, `state` nonce checked, `logout` is local-only.
- [Where the CLI lives, its name, packaging, and its relationship to `./bai gql`](https://lablup.atlassian.net/browse/FR-3741) — workspace package `packages/backend.ai-agent-cli`, bin `bai-agent`, Node ≥22 / ESM / tsup (backend.ai-client build chain). Ships code only: **repo mode** — walks up from cwd to the webui checkout and reads `resources/i18n`, `data/schema.graphql`, `packages/backend.ai-webui-docs` live (no data copied into the package, no `workspace:*` on docs). Independent of `./bai` (minimal login/gql duplication accepted, no delegation, not a superset).
- [`explain` output format (schema description + i18n label + terminology + docs link)](https://lablup.atlassian.net/browse/FR-3749) — every piece tagged `derived: auto | heuristic | curated | MISSING`; enum value → label in scope. Measured boundary: schema type/description auto; table-column labels auto via `dataIndex`↔`t()` scoped to files spreading `fragment on `; value labels, type→terminology concept, docs heading and one-line meaning are curated, kept in `packages/backend.ai-agent-cli/mappings/.yaml`. Output default amended by FR-3742 (text default, `--json`; `--md` dropped). Follow-up: FR-3756.
- [Docs search approach and deployed-docs URL mapping rules](https://lablup.atlassian.net/browse/FR-3743) — search English, map hits to the user language by heading parity (zero gaps across 4 langs); result unit = deepest heading (h2–h4) with a verified `{basename}-{slugify(heading)`} anchor; live parse of the markdown per query (toolkit `search-index.json` rejected: page-level, 1000-char body); docs `{version`} from the repo `package.json` minor (prerelease → `next`, config `latest` fallback, `--docs-version` override); synonym table from `terminology.json`.
- [Version alignment strategy (docs · schema snapshot · manager version)](https://lablup.atlassian.net/browse/FR-3747) — gate on `Added in` / `Deprecated since` markers with type-level inheritance (91 % of Strawberry types, 15 % of fields carry one); introspection only opportunistically, skipped silently when disabled; warn by default, `--strict` refuses; `bai-agent schema sync` is suggested, never automatic, and records `data/schema.meta.json {tag, sha256, fetchedAt`} because the `supergraph.graphql` asset carries no version; docs link version comes from the repo, not the manager (docs minors track the WebUI).
- [`/cli-login` browser-delegated login page + headless paste fallback](https://lablup.atlassian.net/browse/FR-3755) — works end to end (branch `proto/FR-3755-cli-login`): session id from `X-BackendAI-SessionID` equals the `AIOHTTP_SESSION` cookie and the header alone authenticates; a dead session is `200 + user_auth_unauthorized`, not 401 (FR-3745 amended). Consent notice + attestation checkbox + short `state`-derived code shown on page and terminal; paste fallback behind Reveal with a password-grade warning; unreachable listener → error + Retry, paste secondary; tab stays open after success; route gated by a `config.toml` flag. Production PNA prompt (public origin → localhost) still unverified.
- [WebMCP interlink demo — create a session via CLI, open its detail page in the browser tab](https://lablup.atlassian.net/browse/FR-3750) — works end to end today with `@mcp-b/global` + `@mcp-b/webmcp-local-relay` 5.0.1 (branch `proto/FR-3750-webmcp-interlink`): auth is free via the tab's `globalThis.backendaiclient`, `whoami` 32 ms, navigation visible in 1.3–3.4 s, route-scoped tools refresh via `list_changed`. Decided: pages register tools through a `useWebMCPTool(tool, {signal})` hook (target contract — the prototype registers imperatively); relay browser assets served by a Vite dev plugin from `node_modules` (no vendoring); dev CSP `frame-src blob:` only under `VITE_WEBMCP=on`, which is the single dev-only switch; tabs self-describe via `document.title` (amended by FR-3746: needs a route-aware title); the developer runs the relay from their Claude Code MCP config. Follow-up: FR-3757.
- [Agent-facing command surface and output contract](https://lablup.atlassian.net/browse/FR-3742) — Astryx CLI conventions adopted. Commands: `login · logout · whoami · version · search · docs show · schema show|sync · explain · query · doctor · manifest · init --features agents` (`docs search`/`schema search` are aliases of `search --domain`). `search` is one ranked list with stable ids and a `command:` line per hit (search → show is the agent loop), top-N per domain guaranteed, single English index with terminology/i18n query normalisation, `--lang` affects labels only. Output: text mirroring JSON by default, `--json` `{apiVersion, type, data`} / `{apiVersion, error, code, suggestions?, hint?`}, `--dense`/`--detail`; `search` 10 hits, `docs show` one section (`--full`), `query` cut at `--max-bytes` 64 KiB with `truncated` paths. Exit codes 0/1/2 usage/3 `auth_required`/4 `mutation_refused`/5 `not_found`; `hint` = next command. `query` takes a raw GraphQL document (arg / `--file` / stdin, `--var`), pre-validated against the local SDL (`schema_mismatch`). Follow-up: FR-3758.
- [Unified search ranking across docs / schema / i18n / terminology](https://lablup.atlassian.net/browse/FR-3758) — prototyped on real data (14 queries incl. ko/ja). **i18n is not a search domain**: `search` ranks docs · schema · terminology; the i18n reverse index only normalises queries and feeds `explain`, shown as a secondary `UI label:` line under the owning hit. Minimal normalisation (exact i18n/terminology match, printed once as a header) and the agent block tells Claude to search in English UI terms. Score formula (100 exact / 85 field / 80 phrase / 40–75 name tokens / 10–35 description, docs bodies up to ~60, schema identifiers ≥ 4 tokens use only the last two segments), 2 reserved slots per domain ≥ 40, fixed-vocabulary `reason` ≤ 60 chars; the 14 queries become the regression set.
- [Curated field-mapping file — format, ownership, and staleness checks](https://lablup.atlassian.net/browse/FR-3756) — `mappings/.yaml` is nested per field (`fields.: {label, meaning, concept?, docs?, values{VALUE: {meaning, label?, concept?, variant?`}}} + top-level `type/concept/docs`), validated by `mappings/schema.json` via ajv; present = curated, absent = auto/heuristic/MISSING. Value labels are curated by necessity (`ComputeSessionNode.status/type` are `String`, UI renders raw). Ownership: the feature PR adds the entry, `mappings/` in CODEOWNERS, no hard gate (nudge = `MISSING` in `explain` + `doctor`). Staleness: `bai-agent doctor --mappings` implements the resolver, `verify.sh` calls it, a path-filtered workflow catches reverse drift (i18n/docs/schema edits). ≤ 63 mapping files ever (~15 worth curating).
- [CLI ↔ WebMCP handoff protocol](https://lablup.atlassian.net/browse/FR-3746) — one tool `open_resource {type, id, view?`} backed by a new shared `resourcePath(type, id, view?)` helper in `react/src/helper/` (also replaces the 4 hand-rolled session-link call sites); first-cut types `session` (views detail / scheduling_history / container_log), `vfolder` (`path?`), `deployment` (detail / revisions / access_tokens), `model_card`, `role`, `artifact`, `list {resource, filter?, statusCategory?`}; agent/user/keypair detail excluded until they get URL params. Every CLI result carries `webui_path` + `webui_url`; `mutation_refused` puts the path in `hint`. The relay cannot target a tab, so the CLI checks `webmcp_list_sources`: 0 → `no_webui_tab` + URL hint (no auto-open), ≥ 2 → `ambiguous_tab` + suggestions, agent retries with `--tab `. Route-aware `document.title` (`Backend.AI · · @ `) is introduced unconditionally. Naming / schema style / error shape → FR-3757.
- [Per-page WebMCP tool catalog and the useWebMCPTool hook contract](https://lablup.atlassian.net/browse/FR-3757) — first cut: global `bai_whoami`, `bai_open_resource`; per page only the read-only vocabulary `bai_list_visible_` / `bai_get_current_` / `bai_get__filter` on 8 screens (sessions, deployments, data, model-store, admin session/agent/users/rbac), all `readOnlyHint:true`; settings/branding/chat/launcher excluded; local actions are URL params via `open_resource {type:'list'`}. Hook `useWebMCPTool(tool | null, deps)` in `react/src/hooks/`, re-registers on deps, no-op unless `VITE_WEBMCP=on`, JSON-Schema literals (`@mcp-b/webmcp-types`, no zod), host state via the caller's closure, errors = throw or `{isError, structuredContent:{code`}}. Names `bai_` + snake_case, never scope/tab. `` from `handle.labelKey` renders `Backend.AI · · @ `.
- [Skill structure and boundaries with existing skills](https://lablup.atlassian.net/browse/FR-3748) — repo-local `.claude/skills/bai-agent/` (name = CLI bin; `webui-graphql` dropped), thin (~100 lines) + `references/query-cookbook.md`; never repeats what `manifest --json`, the generated `BAI-AGENT` CLAUDE.md block and `--help` say; carries trigger, preflight (doctor/whoami → login via `webui-connection-info`), workflow pointer, handoff timing, neighbour table, re-sync sentence, CLI-blind gotchas; rules are linked not restated. Triggers separated from `webui-connection-info` / `backend-ai-guide` / `docs-lead` / `bai-cli`; two neighbour descriptions get one-line edits.

## Not yet specified

- Whether to repackage the CLI as a stdio MCP server (claude.ai / desktop surfaces) — after CLI demand is confirmed. Astryx's own MCP server (`search(query)` + `get(name)`) is the model if it happens.
- Whether the deployed docs site itself should expose an agent-friendly surface (llms.txt or similar).
- How these tools are verified (including ties to the e2e / screenshot agents). The FR-3758 14-query set is one input.
- HMAC (API-mode keypair) auth for headless/CI use, without a browser — deferred from FR-3745 until demand is confirmed.
- Remote mode for the CLI (run without a webui checkout): version-keyed fetch + cache under `~/.cache/backend.ai-agent/` — i18n from the target webui `/resources/i18n/{lng}.json`, schema from the backend `supergraph.graphql` release asset, manual from the docs-site `search-index.json`. Deferred from FR-3741; verified feasible.

## Out of scope

- Exposing WebMCP tools permanently in production WebUI (a separate effort once the standard settles).
- Passing authentication (cookies) from WebMCP to the CLI. (This does not cover the dedicated `/cli-login` page decided in FR-3745, which is a plain WebUI route, not a WebMCP tool.)
- Improving GraphQL description quality in the backend repo.
- MCP host / tool calling as a product feature (FR-1032, FR-1141 line).
- Consolidating `bai-agent` with the backend repo's `./bai` CLI (FR-3741 chose independence).
- Indexing non-English manual bodies for search recall, and morphological / synonym-dictionary query normalisation (FR-3743/FR-3742/FR-3758 chose a single English index; the agent translates).
- Deep links for agent / user / keypair detail and the session drawer on `/admin/session` (FR-2944) — WebUI features, not this map; handoff excludes them until they exist.

JIRA Issue: FR-3740

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the Decisions so far and the linked FR tickets, especially FR-3742, FR-3743, FR-3747, and FR-3758; inspect packages/backend.ai-agent-cli, data/schema.graphql, resources/i18n, and packages/backend.ai-webui-docs. The map is complete, so the next deliverable is a separate bai-agent plus WebMCP specification; implementation is explicitly deferred until that spec is accepted.

Written by the indexing model from the issue text.

Assessment

Tech stack
graphql, node.js, typescript, vite
Domain
api, cli, documentation, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.