Skip to content

feat(cli): MCP list_peers + runner hub auth for peer discovery - #1372

Merged
tiann merged 12 commits into
tiann:mainfrom
heavygee:feat/a2a-list-peers
Aug 5, 2026
Merged

feat(cli): MCP list_peers + runner hub auth for peer discovery#1372
tiann merged 12 commits into
tiann:mainfrom
heavygee:feat/a2a-list-peers

Conversation

@heavygee

@heavygee heavygee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add MCP list_peers (HTTP MCP + stdio bridge + flavor tool lists) so runner-spawned agents can discover same-hub peers without shelling hapi or sitting on the hub host.
  • After initializeToken(), export HAPI_API_URL / CLI_API_TOKEN into process.env so agent Shell fallbacks inherit the session hub credentials (web terminal PTYs still strip them).
  • Clearer auth/URL failure text for --list / JWT exchange pointing at hapi auth login and MCP list_peers.
  • Docs for split hub + remote runner peer discovery; system prompts teach list_peers before inspect/ping when no citation is available.

Test plan

  • bun x vitest run on pingPeer, hapiSessionEnv, BasePermissionHandler, startHappyServer, happyMcpStdioBridge, buildHapiMcpBridge, runAgentSession, tokenInit, pingPeer command suites
  • bun typecheck at repo root
  • Dogfood on runner-spawned session: MCP list_peers returns peers; inspect_peer / ping_peer with a listed id
  • Dogfood shell: after session start, hapi ping-peer --list works on runner host with matching hub auth

Issues

Fixes #1371

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Preserve default-hub auto-start after exporting child credentials — initializeToken() now calls exportHapiHubAuthEnv(), which always writes the resolved default http://localhost:3006 into process.env.HAPI_API_URL. Every agent command calls maybeAutoStartServer() immediately afterward, and its existing guard treats any value in that env var as an explicit override, so installations relying on the bundled hub no longer auto-start it and proceed to connection failure. Evidence: cli/src/ui/tokenInit.ts:30; related guard cli/src/utils/autoStartServer.ts:94.
    Suggested fix:
    // Capture whether the user supplied the URL before initializeToken() mirrors it.
    const apiUrlWasExplicitlyConfigured = Boolean(process.env.HAPI_API_URL)
    
    async function shouldAutoStartServer(): Promise<boolean> {
        if (apiUrlWasExplicitlyConfigured) {
            return false
        }
        // existing settings/default checks...
    }

Summary
Review mode: initial

One Major regression found. The new MCP/list formatting paths did not produce another finding above the confidence threshold.

Testing
Not run (automation): bun is unavailable in this runner (/bin/bash: bun: command not found). Add a regression test covering initializeToken() followed by maybeAutoStartServer() with no explicit API URL and a saved local token.

HAPI Bot

Comment thread cli/src/ui/tokenInit.ts Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Keep settings-backed CLI_API_TOKEN out of the wrapped agent environment — the new assignment copies the hub's shared secret from settings/prompt-backed configuration into process.env, and all wrapped agent launchers inherit that environment. The secret can be exchanged for a namespace JWT covering the hub API, bypassing the deliberate approval boundary around inspect_peer and ping_peer. Evidence cli/src/agent/hapiSessionEnv.ts:64; related context cli/src/agent/runAgentPty.ts:160, hub/src/web/routes/auth.ts:28.
    Suggested fix:

export function exportHapiHubAuthEnv(options: ExportHapiHubAuthEnvOptions = {}): void {
if (options.exportApiUrl) {
const apiUrl = configuration.apiUrl?.trim()
if (apiUrl) process.env.HAPI_API_URL = apiUrl
}
// Do not mirror configuration.cliApiToken into process.env.
// Settings/prompt-backed helper CLIs can reread settings; env-backed tokens are already inherited.
}

- [Minor] Exclude the calling session from `list_peers` — `/api/sessions` returns every session in the namespace, and this new path formats that result without removing `client.sessionId`. The caller is normally active and recently updated, so it is likely to appear first and can be mistaken for a peer, causing `inspect_peer` or `ping_peer` to self-target. Evidence `cli/src/claude/utils/startHappyServer.ts:289`; related context `hub/src/web/routes/sessions.ts:79`.
Suggested fix:
```ts
const sessions = (await listPeerSessions({ limit: limit + 1 }))
  .filter((session) => session.id !== client.sessionId)
  .slice(0, limit)

Summary
Review mode: follow-up after new commits.

The previous auto-start finding is no longer present. One Major security issue and one Minor correctness issue remain.

Testing
Not run (automation): PR code was not executed. Add coverage proving settings-backed tokens are absent from spawned agent environments and list_peers excludes the current session while respecting limit.

HAPI Bot

Comment thread cli/src/agent/hapiSessionEnv.ts Outdated
Comment thread cli/src/claude/utils/startHappyServer.ts Outdated
@heavygee

heavygee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed in follow-up push:

  1. Major (token export): exportHapiHubAuthEnv no longer mirrors CLI_API_TOKEN into process.env. Settings/prompt-backed secrets stay in-memory/settings; env-backed tokens already inherit; fresh hapi re-reads settings. Explicit non-default hub URL export only.

  2. Minor (self in list): MCP list_peers excludes client.sessionId via formatPeerSessionsList({ excludeSessionId }), with a unit test for newest-self omitted.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Use HAPI's effective session title and normalize it to one line — the new formatter reads only metadata.name, but normal session bootstrap leaves that field unset and agent-generated titles are persisted in metadata.summary.text. As a result, most discoverable sessions appear as (unnamed), which defeats title-based peer discovery. Raw whitespace also lets a title forge extra output rows. Evidence cli/src/modules/pingPeer/pingPeer.ts:394; related context cli/src/api/apiSession.ts:834, web/src/lib/sessionTitle.ts:10.
    Suggested fix:

metadata?: {
name?: string
summary?: { text: string }
// existing fields...
}

const rawName = session.metadata?.name
?? session.metadata?.summary?.text
?? session.metadata?.path?.split('/').filter(Boolean).pop()
?? session.id.slice(0, 8)
const name = rawName.replace(/\s+/g, ' ').trim().slice(0, 255)


**Summary**
Review mode: follow-up after new commits.

One Major correctness issue remains. The earlier token-exposure and self-listing findings are no longer present at the current head.

**Testing**
Not run (automation): PR code was not executed. Add formatter coverage for a summary-only session and a title containing newlines/tabs.

*HAPI Bot*

Comment thread cli/src/modules/pingPeer/pingPeer.ts Outdated
@heavygee

heavygee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed: resolvePeerSessionLabel uses name → summary.text → path basename → id prefix (web parity), collapses whitespace to one line; tests cover summary-only + multiline titles.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Minor] Return resolvable peer identifiers — the shortlist emits only eight characters, but the resolver rejects ambiguous prefixes. Two sessions sharing that prefix become indistinguishable and neither can be selected from list_peers. Evidence cli/src/modules/pingPeer/pingPeer.ts:417; related context cli/src/modules/pingPeer/pingPeer.ts:169.
    Suggested fix:
    return `  ${session.id}  active=${session.active}  flavor=${flavor}  ${name}`
  • [Minor] Enforce the discovery limit at the hub — the new auto-approved tool passes a limit, but GET /api/sessions ignores it and computes/serializes every session summary, including scheduled-count lookups, before the client discards all but 30–100 rows. Repeated agent calls therefore remain unbounded by the advertised limit. Evidence cli/src/claude/utils/startHappyServer.ts:289; related context hub/src/web/routes/sessions.ts:79.
    Suggested fix:
    // Add a lightweight, server-limited peer query.
    const limit = Math.min(101, Math.max(1, Number(c.req.query('limit')) || 31))
    const sessions = engine.getSessionsByNamespace(c.get('namespace'))
        .sort((a, b) => b.updatedAt - a.updatedAt)
        .slice(0, limit)
        .map(({ id, active, updatedAt, metadata }) => ({ id, active, updatedAt, metadata }))

Summary
Review mode: follow-up after new commits.

Two Minor correctness/performance issues remain.

Testing
Not run (automation): PR code was not executed; CI checks were pending at review time. Add a formatter test with colliding eight-character prefixes and a route-level test proving the peer limit is applied before summary/count work.

HAPI Bot

Comment thread cli/src/modules/pingPeer/pingPeer.ts Outdated
Comment thread cli/src/claude/utils/startHappyServer.ts Outdated
@heavygee

heavygee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed remaining Minor threads in e22c7f6:

  1. Full session ids in peer listformatPeerSessionsList now prints the full UUID so colliding 8-char prefixes stay resolvable via resolveSessionByPrefix (plus collision regression test).
  2. Server-side limitGET /api/sessions?limit=N now slices after the existing active/pending/updatedAt sort (cap 500; omitted = full list for the web UI). Scheduled-count lookups run only on the limited set. Hub route test covers limited vs unlimited.

Please re-run review on the tip.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Preserve full-session resolution for ping/inspect - the new server-side slice makes the existing default limit=500 effective for every listSessions caller. Both pingPeer and inspectPeer call it without an override, so a cited full UUID outside the first 500 active-first rows now returns not_found even though the route previously returned all namespace sessions. Evidence hub/src/web/routes/sessions.ts:101; related context cli/src/modules/pingPeer/pingPeer.ts:195, cli/src/modules/pingPeer/pingPeer.ts:449, cli/src/modules/pingPeer/pingPeer.ts:644.
    Suggested fix:
    async function listSessions(
        apiUrl: string,
        jwt: string,
        http: AxiosInstance,
        limit?: number
    ): Promise<PingPeerSessionSummary[]> {
        // ...
        params: limit === undefined ? undefined : { limit },
    }
    Keep the bounded argument only in listPeerSessions; let prefix/full-ID resolution omit it.
  • [Minor] Apply newest-first ordering before discovery truncation - list_peers promises newest updatedAt rows, but it requests a limited response from an endpoint that sorts active sessions first and slices before the formatter re-sorts. With enough active sessions, a newer inactive peer is never returned. Evidence cli/src/claude/utils/startHappyServer.ts:289; related context hub/src/web/routes/sessions.ts:85.
    Suggested fix:
    const sessions = await listPeerSessions({
        limit: Math.min(101, limit + 1),
        order: 'updatedAt'
    })
    Add the query option and have the hub apply updatedAt ordering before slice.
  • [Minor] Strip Windows separators from fallback labels - splitting only on / means a nameless Windows session emits its full absolute path as the auto-approved list_peers label instead of the documented basename. Evidence cli/src/modules/pingPeer/pingPeer.ts:386.
    Suggested fix:
    const pathLabel = meta?.path?.split(/[\\/]/).filter(Boolean).pop()?.trim()
    const raw = meta?.name?.trim()
        || meta?.summary?.text?.trim()
        || pathLabel
        || session.id.slice(0, 8)

Summary
Review mode: follow-up after new commits.

Three findings remain: one Major resolver regression and two Minor discovery correctness/privacy issues.

Testing
Not run (automation): PR code was not executed. GitHub test was pending at review time. Add coverage for resolving a session beyond row 500, a newer inactive peer beyond an active-first limit, and a Windows path fallback.

HAPI Bot

Comment thread hub/src/web/routes/sessions.ts
Comment thread cli/src/claude/utils/startHappyServer.ts Outdated
Comment thread cli/src/modules/pingPeer/pingPeer.ts Outdated
@heavygee

heavygee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed tip review findings:

  1. Major — ping/inspect resolution: listSessions only sends limit when callers pass one. listPeerSessions keeps a bound; pingPeer / inspectPeer omit it so full UUIDs outside the first 500 rows still resolve (regression test with 501 sessions).
  2. Minor — discovery ordering: GET /api/sessions?order=updatedAt sorts newest-first before limit; listPeerSessions sends that order by default.
  3. Minor — Windows path labels: basename fallback splits on [\\/].

Tip: this commit.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Minor] Auto-approve the ACP title form — ACP permission derivation prefers the registered tool title, but the new allowlist only includes identifier aliases. Default-mode ACP sessions can therefore prompt or block on peer discovery. Evidence cli/src/modules/common/permission/BasePermissionHandler.ts:38; related context cli/src/agent/utils.ts:24, cli/src/claude/utils/startHappyServer.ts:283.
    Suggested fix:
    "list peer sessions",

Summary
Review mode: follow-up after new commits.

One Minor finding remains. The three findings from the prior bot review no longer reproduce in the current diff.

Testing
Not run locally (automation): PR code was not executed. GitHub test passed. Add the registered title (List Peer Sessions) to the auto-approval test matrix.

HAPI Bot

Comment thread cli/src/modules/common/permission/BasePermissionHandler.ts Outdated
@heavygee

heavygee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed Minor: auto-approve ACP title form List Peer Sessions (exact-name set + test matrix).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Minor] Preserve overflow after excluding the caller — list_peers fetches only limit + 1 rows and caps that fetch at 100, then removes the current session. If the caller occupies the prefetched row, the result can contain fewer than the requested number of peers and omit the “more” marker even when additional sessions exist; at limit=100, it can return only 99 peers. The formatter’s numeric omitted count is also only the count inside this bounded sample, not the namespace total. Evidence cli/src/claude/utils/startHappyServer.ts:289; related context cli/src/modules/pingPeer/pingPeer.ts:433.
    Suggested fix:
    const sessions = await listPeerSessions({ limit: limit + 2 })
    const peers = sessions.filter((session) => session.id !== client.sessionId)
    const hasMore = peers.length > limit
    
    text: formatPeerSessionsList(peers, {
        maxRows: limit,
        hasMore
    })
    // In the formatter: if (hasMore) rows.push('  … more sessions available')
  • [Minor] Separate URL and token recovery instructions — the new hint says configuring both HAPI_API_URL and CLI_API_TOKEN can be replaced by hapi auth login, but that command only saves cliApiToken; it never sets the remote hub URL. On a split-hub runner that is still using localhost, following the advertised recovery leaves the command pointed at the wrong host. Evidence cli/src/modules/pingPeer/pingPeer.ts:87; related context cli/src/commands/auth.ts:61.
    Suggested fix:
    const AUTH_RECOVERY_HINT =
        'On a remote runner, set HAPI_API_URL to the runner hub, and set CLI_API_TOKEN ' +
        'or run `hapi auth login` to save the token. Inside a HAPI session prefer MCP ...'

Summary
Review mode: follow-up after new commits.

Two Minor findings remain. The prior HAPI Bot finding about the ACP title form is fixed in the latest commits.

Testing
Not run (automation/security constraint: PR code was not executed). GitHub test and pr-review checks were pending at review time. Add coverage for a full limit=100 request with the caller in the result and for the no-overflow-marker case when more peers exist beyond the bounded fetch.

HAPI Bot

Comment thread cli/src/claude/utils/startHappyServer.ts Outdated
Comment thread cli/src/modules/pingPeer/pingPeer.ts Outdated
@heavygee

heavygee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed tip Minors:

  1. Overflow after excludepeerListFetchLimit(limit, { excludeCaller: true }) fetches limit+2 (hub cap 500); formatter takes explicit hasMore so we do not claim an exact omitted count from a bounded sample.
  2. Auth recovery copy — URL and token instructions are separate (hapi auth login only persists the token).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Minor] Avoid an exact overflow count from a truncated shell shortlist — listPeerSessions() now fetches at most 200 rows, while formatPeerSessionsList() derives the … N more count only from that bounded sample. With more than 200 sessions, hapi ping-peer --list reports … 170 more after showing 30 even though the namespace contains additional sessions, which makes the new discovery output misleading. Evidence cli/src/commands/pingPeer.ts:141; related context cli/src/modules/pingPeer/pingPeer.ts:377 and cli/src/modules/pingPeer/pingPeer.ts:448.
    Suggested fix:
    const maxRows = 30
    const sessions = await listPeerSessions({
        limit: peerListFetchLimit(maxRows)
    })
    console.log(formatPeerSessionsList(sessions, {
        maxRows,
        hasMore: sessions.length > maxRows
    }))

Summary
Review mode: follow-up after new commits.

One Minor finding remains. The two findings from the prior HAPI Bot review are addressed by the latest commits.

Testing
Not run (automation/security constraint: PR code was not executed). git diff --check passed. GitHub test and pr-review checks were pending at review time. Add command-level coverage for a namespace larger than the bounded fetch and assert that --list uses a non-numeric overflow marker.

HAPI Bot

Comment thread cli/src/commands/pingPeer.ts Outdated
@heavygee

heavygee commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed Minor: hapi ping-peer --list now uses peerListFetchLimit + hasMore (same as MCP) instead of an exact omitted count from a truncated sample.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • None.

Summary
Review mode: follow-up after new commits.

No reportable issues found in the latest full PR diff. The prior HAPI Bot Minor about reporting an exact overflow count from a bounded peer fetch is addressed by the new bounded fetch plus boolean overflow marker.

Residual risk: the live runner → MCP bridge → remote hub peer-discovery path was not exercised in this review.

Testing
Not run (automation/security constraint: PR code was not executed). git diff --check passed. GitHub test and pr-review checks were pending at review time.

HAPI Bot

@heavygee
heavygee force-pushed the feat/a2a-list-peers branch from 4774315 to 3a03f36 Compare August 5, 2026 01:03

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • None.

Summary
Review mode: follow-up after new commits.

No reportable issues found in the latest full PR diff. The only change since the prior HAPI Bot pass guards the inactivity tick against incomplete machineCache test doubles and adds the missing stubs; it does not introduce a production behavior regression above the reporting threshold.

Residual risk: the live runner to MCP bridge to remote hub peer-discovery path was not exercised in this review.

Testing
Not run (automation/security constraint: PR code was not executed). git diff --check passed. GitHub test and pr-review checks were pending at review time.

HAPI Bot

@heavygee
heavygee force-pushed the feat/a2a-list-peers branch from 3a03f36 to 85f6fff Compare August 5, 2026 02:04

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • None.

Summary
Review mode: follow-up after new commits.

No reportable issues found in the latest full PR diff. The previous reviewed commit and current head have diverged, so the current 29-file diff was reviewed independently; the prior review and compare diff were used only as follow-up context.

Residual risk: the live remote-runner → MCP bridge → remote-hub peer-discovery path was not exercised in this review.

Testing
Not run (automation/security constraint: contributor code was not executed). git diff --check passed for the PR head. GitHub test and pr-review checks were pending at review time.

HAPI Bot

heavygee and others added 10 commits August 5, 2026 09:04
Runner-spawned agents could not discover same-hub peers without
sitting on the hub host or pasting a session id. Add MCP list_peers
(in-process credentials), export HAPI_API_URL/CLI_API_TOKEN after
auth init for shell fallbacks, and clearer auth failure hints.

Co-authored-by: Cursor <cursoragent@cursor.com>
exportHapiHubAuthEnv was writing the implicit localhost default into
process.env, which made maybeAutoStartServer skip starting the bundled
hub. Only export HAPI_API_URL when the URL came from env or settings;
always still export CLI_API_TOKEN. Also fill missing deliveryMode on
abort restore so web typecheck matches RawSendError (main tip unblock).

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep settings/prompt-backed hub secrets out of wrapped agent env so
shell JWT+curl cannot bypass peer-tool approval. Fresh hapi re-reads
settings; env-backed tokens already inherit. list_peers omits the
calling session from the shortlist.

Co-authored-by: Cursor <cursoragent@cursor.com>
list_peers was showing (unnamed) for ordinary sessions because titles
live in metadata.summary.text. Match web getSessionTitle and collapse
whitespace so each peer stays one agent-readable line.

Co-authored-by: Cursor <cursoragent@cursor.com>
Short 8-char prefixes collide across UUID namespaces; print full ids so
resolveSessionByPrefix stays unambiguous. Honor optional limit after sort
so listPeerSessions stops loading the whole namespace for scheduled counts.

Co-authored-by: Cursor <cursoragent@cursor.com>
CI tsc rejected Map<string, null> for getNextScheduledAtBySessionIds.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep GET /sessions?limit only for discovery callers. ping/inspect omit
limit so full UUIDs outside the first 500 stay resolvable. Peer lists
pass order=updatedAt so truncation matches newest-first. Basename
fallback splits Windows paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
Permission derivation prefers request.title; match the MCP tool title
form so default-mode ACP sessions do not prompt on discovery.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fetch limit+2 when excluding the caller so overflow still surfaces at
limit=100. Clarify that auth login only saves the token, not HAPI_API_URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
heavygee and others added 2 commits August 5, 2026 09:05
Match MCP list_peers: fetch limit+1 and mark hasMore instead of claiming
an exact omitted count from a 200-row sample.

Co-authored-by: Cursor <cursoragent@cursor.com>
CI flake: 5s inactivity tick hit test doubles that only stubbed
getOnlineMachinesByNamespace. Optional-call + stub the method.

Co-authored-by: Cursor <cursoragent@cursor.com>
@heavygee
heavygee force-pushed the feat/a2a-list-peers branch from 85f6fff to b5b3489 Compare August 5, 2026 09:06

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • None.

Questions

  • None.

Summary
Review mode: follow-up after new commits.

No reportable issues found after reviewing the full latest PR diff. The prior HAPI Bot review and the intervening commit comparison were used only as follow-up context.

Residual risk: the split-hub/remote-runner MCP list_peers path and shell fallback were not exercised end-to-end; coverage remains unit/mock based.

Testing
Not run (automation/security constraint: contributor code was not executed). git diff --check passed for the reviewed head.

HAPI Bot

@tiann
tiann merged commit c0b30bf into tiann:main Aug 5, 2026
2 checks passed
@heavygee
heavygee deleted the feat/a2a-list-peers branch August 5, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(a2a): list peers from runner context (MCP + CLI auth)

2 participants