Files
TanTan fa9c73363d
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
SnowLuma v2.4.2 source snapshot
2026-11-13 09:02:00 +08:00

21 KiB
Raw Permalink Blame History

MCP agent features

These additions preserve the existing tool names, action parameters, text results, and read/write gates. No endpoint is contacted by catalog tools or by a local runtime overview. The action catalog remains a bundled snapshot, not a claim about the connected server's capabilities.

Protocol compatibility and catalog caching

The same snowluma-mcp stdio command accepts MCP 2026-07-28 clients and older clients using the initialize handshake. The official server SDK selects the protocol for the connection. Modern clients can use server/discover and per-request metadata without initialization; legacy clients keep their existing configuration. This does not add a publicly listening MCP HTTP endpoint.

Modern replies carry the protocol's completion and server metadata. Cache hints depend on whether a response contains static catalog data or private live state:

Response Lifetime Scope
tools/list Five minutes private; mode and client capabilities affect tool metadata and availability.
resources/list Five minutes, or zero when events are enabled private; UI capability and active event resources affect the list.
resources/templates/list Five minutes public; the bundled template list is static.
resources/read for the action catalog or message UI Five minutes public; these are bundled static assets.
resources/read for event resources Zero private; these contain account events and connection state.

These are client cache hints, not a server-side result cache. Tool calls, including the runtime overview, never receive these hints. The SDK omits modern-only result fields for legacy clients. Unsupported protocol revisions are rejected before action execution; they do not trigger an action retry.

Call tracing

Every tool invocation gets a W3C trace context. Clients can supply traceparent and optional tracestate in the tool request's _meta:

{
  "name": "query_action",
  "arguments": { "action": "get_status" },
  "_meta": {
    "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
  }
}

MCP preserves a valid caller trace ID and creates a new span ID for each call. Without a valid parent it creates an independent trace. The returned _meta.traceparent identifies that call, including tool errors; the existing text and structured result envelopes remain unchanged. This works with both protocol generations.

Ordinary requests, streams, automatic upload chunks, and upload cleanup forward the call context in HTTP headers, never in OneBot action parameters. Concurrent calls keep independent headers. To follow a call, match its traceparent in MCP stderr diagnostics with SnowLuma's http_trace entry, then follow that entry's existing req# through action lifecycle logs. SnowLuma correlation requires TRACE logging and a valid authenticated HTTP request; the remote trace ID does not replace its local request ID. No telemetry exporter is installed.

MCP's call diagnostics contain the known tool name, traceparent, outcome, failure category and duration, not message contents, tokens, file paths or arbitrary metadata. tracestate is validated with a maximum of 512 characters and 32 members; it is forwarded but not logged. Invalid trace metadata produces a fixed diagnostic and does not reject an otherwise valid action. baggage is neither forwarded nor logged. Trace identifiers are correlation data, not authentication or permission.

Optional execution services

Existing configurations keep working. Input completion and message previews are additive tools; event connections and durable tasks require explicit configuration. For example, set these variables in the MCP client's server environment, replacing addresses, credentials and paths with the intended SnowLuma instance:

{
  "SNOWLUMA_MCP_MODE": "write",
  "SNOWLUMA_MCP_ENDPOINT": "http://127.0.0.1:3000/",
  "SNOWLUMA_MCP_TOKEN": "<your access token>",
  "SNOWLUMA_MCP_EVENTS_URL": "ws://127.0.0.1:3001/event",
  "SNOWLUMA_MCP_TASK_DIR": "/absolute/private/mcp-tasks",
  "SNOWLUMA_MCP_UPLOAD_ROOT": "/absolute/allowed/uploads"
}

The HTTP and WebSocket addresses are independently configured server endpoints; the WebSocket address is never guessed from the HTTP address. Events reuse SNOWLUMA_MCP_TOKEN in an authorization header. Upload and task directories are on the MCP host. Omit services that are not needed. Docs mode remains offline.

Completing missing parameters

query_action_with_input and invoke_action_with_input use the same arguments, action lookup and mode gates as query_action and invoke_action. The existing tools continue to behave exactly as before. The additional tools ask for input only when a field listed in the action catalog's top-level required array is absent; they do not infer conditional requirements or optional values.

For example, invoke_action_with_input with {"action":"send_group_msg","params":{"group_id":20001}} asks for the missing message. The form contains one params_json string, whose contents are a JSON object such as {"message":"Hello"}. The answer is validated against the missing fields' catalog schemas before normal action execution. Already provided fields, the action name and execution options cannot be overwritten; extra answer fields are rejected. A declined, cancelled or invalid answer does not execute the action.

Clients must advertise form elicitation support. Modern clients receive resultType: "input_required" and resume using the SDK's request-state and input response mechanism. Legacy clients with elicitation support use the SDK's elicitation/create flow. Clients without form support receive a tool error and can provide complete arguments to the original tools instead.

Continuation state is signed, tied to the original tool and arguments, expires after ten minutes and is consumed once before execution. Concurrent or repeated answers cannot execute the same continuation twice. A runtime holds at most 256 pending prompts; restart invalidates them rather than replaying work. Completed arguments bypass the input round entirely. This feature is not a general approval gate and does not add confirmation prompts to existing action calls.

Live event resources

With SNOWLUMA_MCP_EVENTS_URL configured, read and write modes expose subscribe_events and unsubscribe_events. The URL must use ws: or wss: without embedded credentials, query parameters or fragments. Listing tools or resources does not connect; only subscribe_events opens the event connection.

{
  "self_id": 10001,
  "types": ["message", "notice"],
  "group_ids": [20001],
  "user_ids": [30001]
}

Use the expected logged-in account for required self_id; obtain it through get_login_info if necessary. Every incoming event must match this account. An account mismatch or malformed event stops the connection and is surfaced. The optional types are message, notice and request; omitted types mean all three. message_sent is included in the message category. Group and user filters are combined with AND, so a group filter excludes events without a matching group_id. Each ID filter accepts at most 100 distinct positive integers.

The result contains a canonical snowluma://onebot/events/<id> resource URI. Read it with resources/read; use ?after=<next_cursor> on subsequent reads to obtain newer buffered entries. Cursors belong to that one resource. The response includes status, next_cursor, oldest_cursor, dropped_events, gap and notification_failed. Reads do not consume events. Only events received after subscription creation are available; this is not message-history storage.

Each resource retains at most 256 events and 1 MiB of event data. One runtime supports at most 16 resources, sharing one account connection. Incoming frames are limited to 256 KiB. Buffer eviction and disconnection remain visible through the cursor/gap and status fields; the service does not silently reconnect or claim missing events were recovered. Release existing resources before changing accounts or creating a new connection after disconnection.

For updates, use the canonical URI without ?after:

  • Modern clients use subscriptions/listen with resourceSubscriptions: [uri] (the TypeScript SDK exposes client.listen(...)). Closing that notification stream does not release the event buffer; call unsubscribe_events explicitly.
  • Legacy clients first call resources/subscribe with the canonical URI, then receive notifications/resources/updated. resources/unsubscribe also releases that event resource. The explicit unsubscribe tool works for both generations.

An update notification indicates that the resource changed; fetch the resource to read its data. Releasing the last resource closes the WebSocket. Closing the MCP connection releases its event resources and buffers. No event operation executes a OneBot business action or sends a QQ message.

Persistent background transfers

Set an absolute SNOWLUMA_MCP_TASK_DIR and use write mode to enable persistent tasks. Task tools additionally require MCP 2026-07-28 clients advertising extensions: {"io.modelcontextprotocol/tasks": {}}. Legacy clients keep the existing synchronous execution tools; this server does not expose the obsolete tasks/list or tasks/result protocol methods.

invoke_action_background takes the same arguments as invoke_action, but only accepts catalog actions marked as streams. For example, it can run upload_file_stream with execution.input_file inside the configured upload root. It writes the task record before starting work and returns a resultType: "task" handle. Ordinary actions continue through the existing tools. The current background transfer entry does not automatically combine with _with_input; provide the required transfer parameters before starting it.

  • tasks/get with { "taskId": "..." } returns current state and, when finished, the saved result or error. A completed task can contain a failed business response: inspect the nested result's diagnostics, status and retcode.
  • tasks/cancel requests cooperative cancellation. Cancellation does not undo remote effects, and a successful completion already in flight may win the race. Poll the task again for its final state.
  • tasks/update accepts input responses only for outstanding task input requests. It is the protocol lifecycle endpoint, not an arbitrary task-editing API. The current background transfer entry does not itself request interactive input.
  • list_tasks is an MCP tool returning task metadata without result bodies. It accepts optional cursor and limit (1–100; default 50) and returns nextCursor when another page is available.

Task handles advertise pollIntervalMs: 1000 and ttlMs: null. Results survive restart and are not automatically expired or deleted. Restart marks unfinished work failed with an explicit unknown remote outcome message; it never replays the operation. This persists transfer status/results, not resumable upload or download execution. There are at most four concurrent tasks and a 16 MiB limit on each persisted task record.

Storage uses an exclusive runtime lease, private directories/files (0700/0600 on POSIX) and validated records. Corruption, permission errors and write failures remain errors. The directory is bound to a digest of the effective mode, endpoint and token; changing that configuration rejects reuse of the directory. Choose a new private directory for the new configuration and retain the old history. Credentials are not stored in the scope record, but task results can contain private account data and should be protected accordingly.

Message preview panel

preview_message is available in every mode and only constructs a draft:

{
  "message_type": "group",
  "target_id": 20001,
  "text": "Hello",
  "reply_to": -123456
}

Use private for a private conversation. target_id must be a positive safe integer; optional reply_to is a non-zero signed 32-bit message ID. The preview supports literal text and optional quoted replies, not media rendering. Text that resembles message control codes remains literal text.

Every client receives the same readable text draft, can_send flag and corresponding invoke_action arguments. Only clients advertising the MCP Apps extension io.modelcontextprotocol/ui with MIME type text/html;profile=mcp-app receive the tool's UI metadata and listed UI resource. The panel is a bundled, self-contained HTML asset built with the official Apps client. It loads no external scripts, fonts or media, opens no network connection of its own and displays external text without interpreting it as HTML. A missing packaged asset is a visible packaging error.

Loading the panel sends nothing. Its send button calls the existing invoke_action through the host only after the user clicks, and the server's write gate still applies. Read/docs mode or a host without server-tool support keeps sending disabled. The panel prevents duplicate clicks and does not retry failed or uncertain sends; create a new draft only after checking the outcome. Successful acknowledgement is not proof of delivery or rendering in QQ.

Action result schemas and examples

get_action and snowluma://onebot/actions include returnsSchema and examples for every bundled action, including aliases. Both are authored beside the OneBot action definition and copied into the generated catalog. List/search responses remain compact; use get_action to retrieve the complete contract.

returnsSchema describes successful OneBot data, not the full OneBot envelope or the MCP structuredContent wrapper. Check status and retcode before reading it. For stream actions, the schema describes each successful frame's data; MCP downloads instead return saved-file metadata and generic streams return a frame collection. Error frames and failed envelopes are not successful results. Unsupported actions use { "not": {} } because they have no successful result. Compatibility placeholders explicitly document their limits.

Each example has summary, params, and optional notes explaining prerequisites and identifier sources. These are illustrative calls, never run by catalog tools. Replace example account/group IDs, opaque handles and paths with values from the current account before execution. Notes identify operations with multiple steps, such as completing an upload after all chunks have been acknowledged.

For example, get_action({ "name": "get_msg" }) documents signed message IDs:

{
  "action": "get_msg",
  "params": { "message_id": -123456 }
}

Pass this object to query_action with a real message ID. Writes and streams use invoke_action in write mode. For automatic upload, pass { "action": "upload_file_stream", "execution": { "input_file": "/allowed/hello.txt" } } to invoke_action, with a real file inside the configured upload root. Ordinary action paths refer to the SnowLuma server, whereas execution.input_file and MCP download paths refer to the MCP host.

Do not substitute zero for a signed message ID. Resource-only forward uploads and flash shares have a documented message_id: 0 sentinel; it cannot be used for message lookup or recall. Also preserve null versus {}, optional fields, and decimal strings such as media file_size exactly as documented.

Structured results

Completed tool results use the advertised outputSchema and return structuredContent alongside the existing content text block. Modern input_required and background task handles are protocol continuations; inspect their resultType first and obtain the completed result through the relevant continuation or task lifecycle:

{
  "result": { "status": "ok", "retcode": 0, "data": { "message_id": -123 } },
  "diagnostics": { "outcome": "success" }
}

result is the original payload, including arrays such as the category list. It is null for an MCP tool error. diagnostics.outcome is success, failed, or unknown; failures can include kind, message, and next_step.

Ordinary OneBot business failures still return their original envelope without setting isError. Their structured diagnostics indicate failure. An asynchronous acknowledgement or a response that does not confirm success has outcome unknown. Transport errors and existing tool/stream errors still set isError: true. Error categories describe known causes; unclassified execution errors are not guessed from message strings. Neither tool execution nor connection checks retry automatically. Negative message IDs remain valid and are preserved unchanged.

Search, filters, and pagination

search_actions retains its original case-insensitive substring matching and catalog order unless match: "keywords" is requested. Keyword mode combines space-separated terms with AND, recognizes a small set of common Chinese/English synonyms, and ranks exact action names or aliases first.

{
  "query": "群 禁言",
  "match": "keywords",
  "read_only": false,
  "stream": false,
  "limit": 10,
  "offset": 0
}

Both list_actions and search_actions accept optional category, read_only, stream, limit (1–200), and offset (non-negative integer). Omitting pagination returns the complete matching list and preserves {count, actions}. With limit or offset, count is the returned page size, total is the matching count, and next_offset is the next page offset or null. Pagination applies to the bundled catalog; it does not issue server requests.

Runtime overview

get_runtime_info({}) returns:

  • The effective mode, execution_enabled, and write_enabled.
  • The bundled catalog version and action count. server_support remains unknown; a matching version alone does not prove action support.
  • In execution mode, effective request timeout, file size and directory quota limits, concurrent stream limit, download directory, upload root, and whether automatic upload is enabled. In docs mode, transport is null.
  • connection_checked: false. Local configuration is not proof of a live login.

The overview does not expose the endpoint, access token, or authorization headers. Configured file paths are paths on the MCP host. All stream actions still require write mode; upload_enabled describes the configured file root, not permission to bypass that gate.

get_runtime_info({"check_connection": true}) is available only in read/write mode with a client. It explicitly queries get_login_info, get_status, and get_version_info, using the ordinary read-only execution gate. Each entry under checks contains its original response and diagnostics. Partial failures remain visible. Check the returned status data for online state; a successful HTTP exchange alone does not establish that the account is online.

Cancellation and progress

MCP cancellation now reaches ordinary HTTP calls as well as stream actions, including response-body reads. Ordinary calls retain their request timeout; streams retain separate connection and per-read idle timeouts. A cancelled call does not automatically retry. Cancellation or timeout can leave a remote write's outcome unknown: stopping the wait does not undo an operation already received by SnowLuma.

When a caller includes _meta.progressToken, stream execution emits standard notifications/progress notifications with the same token. File downloads report decoded bytes written; automatic uploads report bytes acknowledged by the server. Generic streams report accepted frame counts. A total is included only when a positive file size is known. Empty files and unknown-size downloads omit it.

Notifications are limited to one per 250 ms, plus the final pending update before a successful response. Values increase, notifications stop on cancellation or completion, and callers without a progress token receive none. Notification delivery failures are surfaced. Progress describes transferred data, not final commit: only a successful tool result confirms file validation and completion.

The existing file containment, quotas, partial-file cleanup and upload-reset behavior are unchanged. File bytes never appear in progress messages.