Skip to content

feat(sicore-a2a-mcp): support multiple named A2A keys selected by alias#443

Open
LikiosSedo wants to merge 1 commit into
mainfrom
feat/mcp-multi-key-alias
Open

feat(sicore-a2a-mcp): support multiple named A2A keys selected by alias#443
LikiosSedo wants to merge 1 commit into
mainfrom
feat/mcp-multi-key-alias

Conversation

@LikiosSedo

Copy link
Copy Markdown
Collaborator

Problem

One sicore-a2a-mcp adapter process binds a single A2A key, i.e. a single Siclaw agent. Switching agents means editing config and restarting. The obvious shortcut — passing the key as a tool argument — is exactly the thing we must not do: A2A keys are credentials and must never flow through the model context.

Approach

Put multiple named keys in configuration, and let the model select an agent by a short alias passed as an optional agent tool argument. The alias is the only agent selector that crosses the model boundary; keys stay in config/env only.

Changes

  • Config — new SICLAW_A2A_KEYS env, a JSON object {"alias":"sk-..."}. Aliases must match ^[a-z0-9][a-z0-9_-]{0,31}$.
    • Backward compatible: SICLAW_A2A_KEY / SICLAW_A2A_KEY_FILE keep working and map to the reserved alias default. The two forms may be combined; a default alias inside SICLAW_A2A_KEYS is a collision error.
    • SICLAW_AGENT_ID still pins the default single key; combining it with SICLAW_A2A_KEYS is rejected (each named key resolves its own agent).
    • Every key is self-resolved once at startup via GET /api/v1/a2a/self; any failure aborts boot with the failing alias named (fail-fast, no silent partial key).
  • Tool surface — all five tools gain an optional agent alias argument (never a key). Descriptions are populated at startup with the configured aliases and their resolved agent ids. One agent → agent optional; several → omitting it on a create/list call errors with the alias list instead of guessing.
  • Routingtask_id/context_id are per-key server resources. The adapter records task_id -> alias in process memory, so siclaw_wait_task / siclaw_get_task / siclaw_cancel_task auto-route to the creating key. A mismatched agent argument is overridden by the recorded creator and a routing_note records the override. siclaw_list_tasks without agent aggregates across every key, tags each row with its alias, and rebuilds the map after a restart.
  • Security — keys live only in configuration. They are never a tool parameter, never logged, and never present in an error message; errors and the stderr ready line carry aliases and resolved agent ids only.

Implementation routes the new logic through a small AgentRouter; SicoreA2aClient stays per-key and unchanged.

Scope

The remote Sicore MCP endpoint (/api/v1/mcp) is out of scope: each remote client config carries its own Authorization header, so multi-agent there is just multiple client configs. This alias multiplexing is only for the local stdio adapter that injects credentials from env/files. The README documents this split.

Tests

npm test — 56 passing (was ~30).

  • Config: multi-key parse/order, single-key default mapping, merge, alias validation, collision, SICLAW_AGENT_ID + SICLAW_A2A_KEYS rejection, non-string/malformed key values not echoed.
  • Router unit tests: selection rules, task→creator precedence + override note, untracked-task attribution.
  • Tool tests: single-key back-compat behavior preserved, multi-key requires agent, unknown alias, named routing, auto-route follow-ups, override note, aggregated list, ownership recovery.
  • stdio e2e: existing single-key + self-resolve tests, plus a new two-alias cold-start smoke asserting per-key /self resolution, description injection, alias routing, and no key leakage in descriptions or the stderr ready line.

Manual fail-fast smoke confirmed for missing key / bad alias / collision / SICLAW_AGENT_ID+SICLAW_A2A_KEYS — each exits 1 with a clear message and no key echoed.

Not published: no npm publish, no tag — release is left to the maintainer.

One adapter process could previously bind only a single A2A key, so
switching Siclaw agents meant editing config and restarting. Passing the
key as a tool argument is not an option: credentials must never flow
through the model context.

Add configuration-side named keys, selected at call time by alias:

- New SICLAW_A2A_KEYS env: JSON {alias: key}. Aliases match
  ^[a-z0-9][a-z0-9_-]{0,31}$. The single-key SICLAW_A2A_KEY /
  SICLAW_A2A_KEY_FILE form is unchanged and maps to the reserved "default"
  alias; the two forms may be combined. SICLAW_AGENT_ID still pins the
  default key and is rejected alongside SICLAW_A2A_KEYS. Every key is
  self-resolved once at startup and any failure aborts boot (fail-fast).
- Every tool gains an optional "agent" alias argument (never a key). Tool
  descriptions are populated at startup with the configured aliases and
  their resolved agent ids. With one agent "agent" is optional; with
  several, a create/list call that omits it errors with the alias list
  rather than guessing.
- task_id -> alias is tracked in process for the server lifetime, so
  wait/get/cancel auto-route to the creating key. A mismatched "agent"
  argument is overridden by the recorded creator and noted in the result.
  siclaw_list_tasks without "agent" aggregates across every key, tags each
  task with its alias, and rebuilds the map after a restart.

Keys stay in configuration only: never a tool parameter, never logged,
never present in an error message (errors carry aliases and agent ids).

Route the new logic through an AgentRouter; keep SicoreA2aClient per-key.
Existing tests updated to the router API; add router unit tests, multi-key
routing tool tests, and a two-alias stdio e2e cold-start smoke.
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.

1 participant