@snowluma/mcp 1.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @snowluma/mcp
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server for the **SnowLuma OneBot action
4
+ catalog**. It runs in two modes from one binary:
5
+
6
+ - **docs** (default) — read-only: every action's docs, parameters, cross-field
7
+ constraints, and a ready-to-use **JSON Schema**, so an LLM can answer "what
8
+ params does `set_group_ban` take?" without holding the whole catalog in context.
9
+ - **execution** (opt-in) — when pointed at a running OneBot HTTP endpoint, the LLM
10
+ can also *call* actions: read-only ones freely, write ones behind a gate.
11
+
12
+ ## Docs only (default)
13
+
14
+ Add to your MCP client (Claude Desktop, Cline, …) — no endpoint, no execution:
15
+
16
+ ```json
17
+ {
18
+ "mcpServers": {
19
+ "snowluma": { "command": "npx", "args": ["-y", "@snowluma/mcp"] }
20
+ }
21
+ }
22
+ ```
23
+
24
+ ### Docs tools
25
+
26
+ - `list_actions({ category? })` — lightweight index (name / category / summary / aliases / `readOnly`).
27
+ - `get_action({ name })` — full doc for one action incl. `inputSchema` and `readOnly` (accepts aliases).
28
+ - `search_actions({ query })` — fuzzy match over name / summary / aliases.
29
+ - `list_categories()` — categories and their action counts.
30
+
31
+ Also exposes the whole catalog as a resource: `snowluma://onebot/actions`.
32
+
33
+ ## Execution (opt-in)
34
+
35
+ Point the server at a running SnowLuma instance's **OneBot HTTP endpoint** (the
36
+ `httpServer` network adapter) and it gains two execution tools:
37
+
38
+ - `query_action({ action, params? })` — calls a **read-only** action (e.g. `get_*`,
39
+ `can_*`) and returns the full OneBot response. Annotated `readOnlyHint`. Refuses
40
+ write actions (points you to `invoke_action`).
41
+ - `invoke_action({ action, params? })` — calls **any known** action, including ones
42
+ with side effects (send a message, change a group, …). Annotated `destructiveHint`
43
+ + `openWorldHint`. **Only available in write mode.**
44
+
45
+ Both pass the OneBot envelope through verbatim — a logical failure (`retcode≠0`)
46
+ comes back as data with its `wording`; only a transport failure is an error.
47
+
48
+ ### Configuration (env)
49
+
50
+ | Variable | Meaning |
51
+ | --- | --- |
52
+ | `SNOWLUMA_MCP_ENDPOINT` | OneBot HTTP endpoint, e.g. `http://127.0.0.1:3000/`. **Absent → docs-only** (execution tools hidden). |
53
+ | `SNOWLUMA_MCP_TOKEN` | Access token (sent as `Authorization: Bearer …`), if the endpoint requires one. |
54
+ | `SNOWLUMA_MCP_TIMEOUT_MS` | Per-request timeout (default `30000`). |
55
+ | `SNOWLUMA_MCP_MODE` | `docs` \| `read` \| `write`. Default: `read` when an endpoint is set, else `docs`. |
56
+
57
+ **Read mode** — the LLM can query read-only actions, but cannot perform any write:
58
+
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "snowluma": {
63
+ "command": "npx",
64
+ "args": ["-y", "@snowluma/mcp"],
65
+ "env": {
66
+ "SNOWLUMA_MCP_ENDPOINT": "http://127.0.0.1:3000/",
67
+ "SNOWLUMA_MCP_TOKEN": "your-access-token"
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ **Write mode** — also enables `invoke_action` (the bot can send messages, manage
75
+ groups, etc.). Enable deliberately:
76
+
77
+ ```json
78
+ {
79
+ "mcpServers": {
80
+ "snowluma": {
81
+ "command": "npx",
82
+ "args": ["-y", "@snowluma/mcp"],
83
+ "env": {
84
+ "SNOWLUMA_MCP_ENDPOINT": "http://127.0.0.1:3000/",
85
+ "SNOWLUMA_MCP_TOKEN": "your-access-token",
86
+ "SNOWLUMA_MCP_MODE": "write"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### Safety model
94
+
95
+ - **Read/write is classified per action** in the source specs (by what the action
96
+ actually does, not its name) and baked into the catalog. The default is *write*:
97
+ an action is callable via `query_action` **only** if it is explicitly read-only.
98
+ - **The mode gate is enforced on every call**, not just by hiding tools — calling
99
+ `invoke_action` outside write mode is refused even if a client sends it directly.
100
+ - **Unknown actions are rejected** by both tools (only catalog actions are callable),
101
+ so typos and non-catalog internal actions can't be driven.
102
+ - A well-behaved client can auto-approve `query_action` (read-only) and prompt for
103
+ `invoke_action` (destructive) using the MCP tool annotations.
104
+
105
+ ## How it stays in sync
106
+
107
+ The catalog — including each action's `readOnly` flag — is a **build-time snapshot**
108
+ generated from `@snowluma/onebot`'s live action specs (`collectActionDocs()`) on
109
+ every build, so it auto-tracks action add/remove and read/write reclassification.
110
+ The snapshot is pinned to the SnowLuma version it was built from; a new SnowLuma
111
+ release republishes a fresh catalog.
112
+
113
+ This package is generated; do not hand-edit `src/generated/catalog.ts`.
@@ -0,0 +1,24 @@
1
+ /** OneBot v11 response envelope, passed through to the LLM verbatim. */
2
+ export interface OneBotEnvelope {
3
+ status: string;
4
+ retcode: number;
5
+ data?: unknown;
6
+ message?: string;
7
+ wording?: string;
8
+ echo?: unknown;
9
+ [k: string]: unknown;
10
+ }
11
+ export interface ActionClient {
12
+ /** Send one OneBot action; resolves the full envelope (even on retcode≠0),
13
+ * rejects only on transport-level failure (timeout / connection / bad body). */
14
+ call(action: string, params: Record<string, unknown>): Promise<OneBotEnvelope>;
15
+ }
16
+ export interface HttpClientOptions {
17
+ /** OneBot HTTP endpoint, e.g. http://127.0.0.1:3000/. */
18
+ endpoint: string;
19
+ accessToken?: string;
20
+ timeoutMs?: number;
21
+ /** Injectable fetch for tests; defaults to the global fetch. */
22
+ fetch?: typeof fetch;
23
+ }
24
+ export declare function makeHttpClient(opts: HttpClientOptions): ActionClient;
package/dist/client.js ADDED
@@ -0,0 +1,42 @@
1
+ // Thin HTTP bridge to a running OneBot instance.
2
+ //
3
+ // The OneBot v11 HTTP action protocol is trivial — POST `{ action, params }` to
4
+ // the endpoint, read back the JSON envelope — so the MCP talks to it directly
5
+ // with `fetch`. We deliberately do NOT depend on @snowluma/sdk here: its
6
+ // published dist is bundler-targeted ESM (extensionless relative imports) that
7
+ // native `node` cannot resolve without a bundler, and this package is a plain
8
+ // `tsc`-built stdio bin. The wire shape — not a shared client type — is the seam
9
+ // (ADR-0005); tests inject a fake `ActionClient` (or a fake `fetch`).
10
+ const DEFAULT_TIMEOUT_MS = 30_000;
11
+ export function makeHttpClient(opts) {
12
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
13
+ const timeoutMs = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : DEFAULT_TIMEOUT_MS;
14
+ const headers = {
15
+ 'Content-Type': 'application/json',
16
+ Accept: 'application/json',
17
+ };
18
+ if (opts.accessToken)
19
+ headers.Authorization = `Bearer ${opts.accessToken}`;
20
+ return {
21
+ async call(action, params) {
22
+ const res = await fetchImpl(opts.endpoint, {
23
+ method: 'POST',
24
+ headers,
25
+ body: JSON.stringify({ action, params }),
26
+ signal: AbortSignal.timeout(timeoutMs),
27
+ });
28
+ const text = await res.text();
29
+ let parsed;
30
+ try {
31
+ parsed = text ? JSON.parse(text) : null;
32
+ }
33
+ catch {
34
+ throw new Error(`OneBot returned non-JSON (HTTP ${res.status})`);
35
+ }
36
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
37
+ throw new Error(`OneBot returned an unexpected response (HTTP ${res.status})`);
38
+ }
39
+ return parsed;
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,3 @@
1
+ import type { CatalogAction, CatalogCategory } from '../types.js';
2
+ export declare const ACTIONS: CatalogAction[];
3
+ export declare const CATEGORIES: CatalogCategory[];