@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 +113 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.js +42 -0
- package/dist/generated/catalog.d.ts +3 -0
- package/dist/generated/catalog.js +5884 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +86 -0
- package/dist/tools.d.ts +11 -0
- package/dist/tools.js +160 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.js +5 -0
- package/package.json +58 -0
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`.
|
package/dist/client.d.ts
ADDED
|
@@ -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
|
+
}
|