snowbll-mcp 0.2.3 → 0.2.4
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/LICENSE +21 -21
- package/README.md +122 -122
- package/dist/api.js +6 -0
- package/dist/bin/snowbll.js +74 -19
- package/dist/shape.js +91 -0
- package/package.json +2 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Snowbll
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Snowbll
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
# Snowbll MCP + CLI
|
|
2
|
-
|
|
3
|
-
**Search the Snowbll game catalog, read your gaming persona, and follow Forge campaigns — from any MCP-capable AI agent or your terminal.**
|
|
4
|
-
|
|
5
|
-
Everything in this package talks to the **live Snowbll API** (`https://www.snowbll.com`). There is no mock data.
|
|
6
|
-
|
|
7
|
-
Status: the search and Forge tools hit live public endpoints; the MCP servers, API keys, and the keyed v1 endpoints are in **developer preview** — names, fields, auth, and quotas may change before launch.
|
|
8
|
-
|
|
9
|
-
Two binaries ship in one package:
|
|
10
|
-
|
|
11
|
-
| Binary | What it is |
|
|
12
|
-
| --- | --- |
|
|
13
|
-
| `snowbll-mcp` | [Model Context Protocol](https://modelcontextprotocol.io) server over stdio — launched by agents (Claude, Cursor, Hermes, …) |
|
|
14
|
-
| `snowbll` | CLI for humans — search, persona, Forge, key management |
|
|
15
|
-
|
|
16
|
-
There is also a **hosted remote MCP server (developer preview)** at `https://www.snowbll.com/api/mcp` (Streamable HTTP) for clients that connect to URLs instead of launching processes — ChatGPT connectors, Claude custom connectors, Hermes remote MCP. It is an **OAuth 2.1 protected resource**: MCP clients start the sign-in flow automatically on connect (no manual key needed); persona access uses the `persona.read` scope.
|
|
17
|
-
|
|
18
|
-
## Tools
|
|
19
|
-
|
|
20
|
-
The MCP surface is deliberately read-only: tools recommend, explain, and retrieve. Nothing here buys games, backs campaigns, or moves money.
|
|
21
|
-
|
|
22
|
-
| Tool | What it does | Needs API key? |
|
|
23
|
-
| --- | --- | --- |
|
|
24
|
-
| `search_games` | Natural-language catalog search with ranked candidates + reasons | No |
|
|
25
|
-
| `get_game` | Full metadata for one game, incl. human-verification status | No |
|
|
26
|
-
| `get_persona` | The key owner's gaming persona — a playstyle + taste summary from their library and playtime (consent-scoped) | Yes — data visible from the **Observer** rank up; free accounts get a locked marker |
|
|
27
|
-
| `list_forge_campaigns` | Forge campaigns with live funding totals | No |
|
|
28
|
-
| `get_forge_campaign` | One campaign: tiers, stretch goals, progress | No |
|
|
29
|
-
|
|
30
|
-
Honesty model: `verification` is `verified` only after third-party human testing; quality fields (`graphicsFidelity`, `economyComplexity`) are `null` until a human rated them. AI recommends, humans judge.
|
|
31
|
-
|
|
32
|
-
## Quick start (agents)
|
|
33
|
-
|
|
34
|
-
### Claude Code
|
|
35
|
-
|
|
36
|
-
Hosted (remote, OAuth — no install):
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
claude mcp add --transport http snowbll https://www.snowbll.com/api/mcp
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Then run `/mcp` and complete the OAuth sign-in when prompted — no manual key needed. Persona access uses the `persona.read` scope.
|
|
43
|
-
|
|
44
|
-
Or run it locally over stdio (uses `SNOWBLL_API_KEY` if set — see below):
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
claude mcp add snowbll -- npx -y snowbll-mcp
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Claude Desktop / Cursor / Windsurf / Hermes (stdio)
|
|
51
|
-
|
|
52
|
-
```json
|
|
53
|
-
{
|
|
54
|
-
"mcpServers": {
|
|
55
|
-
"snowbll": {
|
|
56
|
-
"command": "npx",
|
|
57
|
-
"args": ["-y", "snowbll-mcp"],
|
|
58
|
-
"env": { "SNOWBLL_API_KEY": "sb_YOUR_API_KEY" }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Omit the `env` block to run keyless (search, game lookups, and Forge tools still work — only `get_persona` needs a key).
|
|
65
|
-
|
|
66
|
-
### ChatGPT (remote connector — no install)
|
|
67
|
-
|
|
68
|
-
1. Settings → Connectors (requires a plan with connectors/developer mode).
|
|
69
|
-
2. Add a custom connector with URL `https://www.snowbll.com/api/mcp`, then complete the OAuth sign-in when prompted — no manual key needed. Persona access uses the `persona.read` scope.
|
|
70
|
-
3. Ask: *"Use Snowbll to find an economy sim like Lemonade Tycoon with a deep economy."*
|
|
71
|
-
|
|
72
|
-
The remote server also implements OpenAI's `search` / `fetch` connector contract, so it works in ChatGPT deep research too.
|
|
73
|
-
|
|
74
|
-
### Claude (claude.ai custom connector / remote)
|
|
75
|
-
|
|
76
|
-
Add a custom connector with URL `https://www.snowbll.com/api/mcp` and complete the OAuth sign-in when prompted — no manual key needed. The catalog and Forge tools (`search_games`, `get_game`, `list_forge_campaigns`, `get_forge_campaign`) are free once connected; `get_persona` is consent-scoped via `persona.read`.
|
|
77
|
-
|
|
78
|
-
### Verify any setup
|
|
79
|
-
|
|
80
|
-
Ask the agent:
|
|
81
|
-
|
|
82
|
-
> Use search_games to find: "economy sim like Lemonade Tycoon with a deep economy"
|
|
83
|
-
|
|
84
|
-
A working setup returns a short candidate list, each with `reasons`.
|
|
85
|
-
|
|
86
|
-
## Quick start (humans)
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
npm install -g snowbll-mcp
|
|
90
|
-
|
|
91
|
-
snowbll search "cozy farming game with deep crafting"
|
|
92
|
-
snowbll forge # campaigns on The Forge
|
|
93
|
-
snowbll login # mint + store an API key (email+password account)
|
|
94
|
-
snowbll whoami
|
|
95
|
-
snowbll persona # your gaming persona
|
|
96
|
-
snowbll setup # wizard + ready-to-paste agent configs
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## API keys (developer preview)
|
|
100
|
-
|
|
101
|
-
Keys (`sb_…`) belong to Snowbll accounts and are hashed at rest — the plaintext is shown once.
|
|
102
|
-
|
|
103
|
-
- `snowbll login` — sign in with an email+password Snowbll account; mints and stores a key in `~/.snowbll/config.json`.
|
|
104
|
-
- Accounts created with Google/Discord have no password: ask the Snowbll team for a preview key, then `snowbll setup` to paste it.
|
|
105
|
-
- `snowbll keys list|create|revoke` manages keys; `SNOWBLL_API_KEY` env always wins over the stored key.
|
|
106
|
-
|
|
107
|
-
Keep keys out of browser code, public repos, and shared transcripts.
|
|
108
|
-
|
|
109
|
-
## Configuration
|
|
110
|
-
|
|
111
|
-
| Env var | Default | Purpose |
|
|
112
|
-
| --- | --- | --- |
|
|
113
|
-
| `SNOWBLL_API_KEY` | – | Developer-preview API key |
|
|
114
|
-
| `SNOWBLL_API_BASE` | `https://www.snowbll.com` | API origin (point at a local dev server while developing) |
|
|
115
|
-
|
|
116
|
-
## REST instead?
|
|
117
|
-
|
|
118
|
-
Everything the tools do is plain HTTP — `POST https://www.snowbll.com/api/search` is live with no key. Full docs: <https://www.snowbll.com/docs>.
|
|
119
|
-
|
|
120
|
-
## License
|
|
121
|
-
|
|
122
|
-
MIT
|
|
1
|
+
# Snowbll MCP + CLI
|
|
2
|
+
|
|
3
|
+
**Search the Snowbll game catalog, read your gaming persona, and follow Forge campaigns — from any MCP-capable AI agent or your terminal.**
|
|
4
|
+
|
|
5
|
+
Everything in this package talks to the **live Snowbll API** (`https://www.snowbll.com`). There is no mock data.
|
|
6
|
+
|
|
7
|
+
Status: the search and Forge tools hit live public endpoints; the MCP servers, API keys, and the keyed v1 endpoints are in **developer preview** — names, fields, auth, and quotas may change before launch.
|
|
8
|
+
|
|
9
|
+
Two binaries ship in one package:
|
|
10
|
+
|
|
11
|
+
| Binary | What it is |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `snowbll-mcp` | [Model Context Protocol](https://modelcontextprotocol.io) server over stdio — launched by agents (Claude, Cursor, Hermes, …) |
|
|
14
|
+
| `snowbll` | CLI for humans — search, persona, Forge, key management |
|
|
15
|
+
|
|
16
|
+
There is also a **hosted remote MCP server (developer preview)** at `https://www.snowbll.com/api/mcp` (Streamable HTTP) for clients that connect to URLs instead of launching processes — ChatGPT connectors, Claude custom connectors, Hermes remote MCP. It is an **OAuth 2.1 protected resource**: MCP clients start the sign-in flow automatically on connect (no manual key needed); persona access uses the `persona.read` scope.
|
|
17
|
+
|
|
18
|
+
## Tools
|
|
19
|
+
|
|
20
|
+
The MCP surface is deliberately read-only: tools recommend, explain, and retrieve. Nothing here buys games, backs campaigns, or moves money.
|
|
21
|
+
|
|
22
|
+
| Tool | What it does | Needs API key? |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| `search_games` | Natural-language catalog search with ranked candidates + reasons | No |
|
|
25
|
+
| `get_game` | Full metadata for one game, incl. human-verification status | No |
|
|
26
|
+
| `get_persona` | The key owner's gaming persona — a playstyle + taste summary from their library and playtime (consent-scoped) | Yes — data visible from the **Observer** rank up; free accounts get a locked marker |
|
|
27
|
+
| `list_forge_campaigns` | Forge campaigns with live funding totals | No |
|
|
28
|
+
| `get_forge_campaign` | One campaign: tiers, stretch goals, progress | No |
|
|
29
|
+
|
|
30
|
+
Honesty model: `verification` is `verified` only after third-party human testing; quality fields (`graphicsFidelity`, `economyComplexity`) are `null` until a human rated them. AI recommends, humans judge.
|
|
31
|
+
|
|
32
|
+
## Quick start (agents)
|
|
33
|
+
|
|
34
|
+
### Claude Code
|
|
35
|
+
|
|
36
|
+
Hosted (remote, OAuth — no install):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
claude mcp add --transport http snowbll https://www.snowbll.com/api/mcp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then run `/mcp` and complete the OAuth sign-in when prompted — no manual key needed. Persona access uses the `persona.read` scope.
|
|
43
|
+
|
|
44
|
+
Or run it locally over stdio (uses `SNOWBLL_API_KEY` if set — see below):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
claude mcp add snowbll -- npx -y snowbll-mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Claude Desktop / Cursor / Windsurf / Hermes (stdio)
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"snowbll": {
|
|
56
|
+
"command": "npx",
|
|
57
|
+
"args": ["-y", "snowbll-mcp"],
|
|
58
|
+
"env": { "SNOWBLL_API_KEY": "sb_YOUR_API_KEY" }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Omit the `env` block to run keyless (search, game lookups, and Forge tools still work — only `get_persona` needs a key).
|
|
65
|
+
|
|
66
|
+
### ChatGPT (remote connector — no install)
|
|
67
|
+
|
|
68
|
+
1. Settings → Connectors (requires a plan with connectors/developer mode).
|
|
69
|
+
2. Add a custom connector with URL `https://www.snowbll.com/api/mcp`, then complete the OAuth sign-in when prompted — no manual key needed. Persona access uses the `persona.read` scope.
|
|
70
|
+
3. Ask: *"Use Snowbll to find an economy sim like Lemonade Tycoon with a deep economy."*
|
|
71
|
+
|
|
72
|
+
The remote server also implements OpenAI's `search` / `fetch` connector contract, so it works in ChatGPT deep research too.
|
|
73
|
+
|
|
74
|
+
### Claude (claude.ai custom connector / remote)
|
|
75
|
+
|
|
76
|
+
Add a custom connector with URL `https://www.snowbll.com/api/mcp` and complete the OAuth sign-in when prompted — no manual key needed. The catalog and Forge tools (`search_games`, `get_game`, `list_forge_campaigns`, `get_forge_campaign`) are free once connected; `get_persona` is consent-scoped via `persona.read`.
|
|
77
|
+
|
|
78
|
+
### Verify any setup
|
|
79
|
+
|
|
80
|
+
Ask the agent:
|
|
81
|
+
|
|
82
|
+
> Use search_games to find: "economy sim like Lemonade Tycoon with a deep economy"
|
|
83
|
+
|
|
84
|
+
A working setup returns a short candidate list, each with `reasons`.
|
|
85
|
+
|
|
86
|
+
## Quick start (humans)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install -g snowbll-mcp
|
|
90
|
+
|
|
91
|
+
snowbll search "cozy farming game with deep crafting"
|
|
92
|
+
snowbll forge # campaigns on The Forge
|
|
93
|
+
snowbll login # mint + store an API key (email+password account)
|
|
94
|
+
snowbll whoami
|
|
95
|
+
snowbll persona # your gaming persona
|
|
96
|
+
snowbll setup # wizard + ready-to-paste agent configs
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API keys (developer preview)
|
|
100
|
+
|
|
101
|
+
Keys (`sb_…`) belong to Snowbll accounts and are hashed at rest — the plaintext is shown once.
|
|
102
|
+
|
|
103
|
+
- `snowbll login` — sign in with an email+password Snowbll account; mints and stores a key in `~/.snowbll/config.json`.
|
|
104
|
+
- Accounts created with Google/Discord have no password: ask the Snowbll team for a preview key, then `snowbll setup` to paste it.
|
|
105
|
+
- `snowbll keys list|create|revoke` manages keys; `SNOWBLL_API_KEY` env always wins over the stored key.
|
|
106
|
+
|
|
107
|
+
Keep keys out of browser code, public repos, and shared transcripts.
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
| Env var | Default | Purpose |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| `SNOWBLL_API_KEY` | – | Developer-preview API key |
|
|
114
|
+
| `SNOWBLL_API_BASE` | `https://www.snowbll.com` | API origin (point at a local dev server while developing) |
|
|
115
|
+
|
|
116
|
+
## REST instead?
|
|
117
|
+
|
|
118
|
+
Everything the tools do is plain HTTP — `POST https://www.snowbll.com/api/search` is live with no key. Full docs: <https://www.snowbll.com/docs>.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
package/dist/api.js
CHANGED
|
@@ -63,6 +63,12 @@ function requireKey() {
|
|
|
63
63
|
throw new SnowbllApiError(401, NEEDS_KEY);
|
|
64
64
|
return key;
|
|
65
65
|
}
|
|
66
|
+
// ─── Hardware ─────────────────────────────────────────────────────────────────
|
|
67
|
+
/** Save the gamer's detected rig to their account (keyed). Snowbll then flags
|
|
68
|
+
* which games run on it — on the site and via the MCP in Claude/ChatGPT. */
|
|
69
|
+
export async function saveHardware(profile) {
|
|
70
|
+
return request("/api/v1/hardware", { method: "POST", body: profile, key: requireKey() });
|
|
71
|
+
}
|
|
66
72
|
// ─── Catalog ────────────────────────────────────────────────────────────────
|
|
67
73
|
export async function searchGames(query, limit = 5) {
|
|
68
74
|
const key = getApiKey();
|
package/dist/bin/snowbll.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { createKey, getForgeCampaign, getGame, getPersona, listForgeCampaigns, listGames, listKeys, revokeKey, searchGames, SnowbllApiError, supabaseLogin, whoami, } from "../api.js";
|
|
5
|
-
import { clearConfig, getApiBase, writeConfig } from "../config.js";
|
|
4
|
+
import { createKey, getForgeCampaign, getGame, getPersona, listForgeCampaigns, listGames, listKeys, revokeKey, saveHardware, searchGames, SnowbllApiError, supabaseLogin, whoami, } from "../api.js";
|
|
5
|
+
import { clearConfig, getApiBase, getApiKey, writeConfig } from "../config.js";
|
|
6
6
|
import { runStdioServer } from "../server.js";
|
|
7
7
|
import { runSetup } from "../setup.js";
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
9
|
const { version } = require("../../package.json");
|
|
10
10
|
/** The `snowbll` CLI — the live Snowbll API from your terminal. */
|
|
11
|
-
const HELP = `snowbll v${version} — Snowbll from your terminal (live API: ${getApiBase()})
|
|
12
|
-
|
|
13
|
-
Usage:
|
|
14
|
-
snowbll search "<query>" [--limit N] [--json] Natural-language game search
|
|
15
|
-
snowbll game <id> [--json] One game's full metadata
|
|
16
|
-
snowbll games [--limit N] [--offset N] [--json] Plain catalog listing (key)
|
|
17
|
-
snowbll forge [id] [--status live] [--json] Forge campaigns / one campaign
|
|
18
|
-
snowbll persona [--json] Your gaming persona (key)
|
|
19
|
-
snowbll
|
|
20
|
-
snowbll
|
|
21
|
-
snowbll
|
|
22
|
-
snowbll
|
|
23
|
-
snowbll
|
|
24
|
-
snowbll
|
|
25
|
-
snowbll
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
const HELP = `snowbll v${version} — Snowbll from your terminal (live API: ${getApiBase()})
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
snowbll search "<query>" [--limit N] [--json] Natural-language game search
|
|
15
|
+
snowbll game <id> [--json] One game's full metadata
|
|
16
|
+
snowbll games [--limit N] [--offset N] [--json] Plain catalog listing (key)
|
|
17
|
+
snowbll forge [id] [--status live] [--json] Forge campaigns / one campaign
|
|
18
|
+
snowbll persona [--json] Your gaming persona (key)
|
|
19
|
+
snowbll hardware Detect your rig → "runs on your rig" on games (key)
|
|
20
|
+
snowbll whoami [--json] Who the configured key belongs to
|
|
21
|
+
snowbll login Sign in, mint + store an API key
|
|
22
|
+
snowbll logout Forget the stored key
|
|
23
|
+
snowbll keys list|create [name]|revoke <id> Manage your API keys (sign-in)
|
|
24
|
+
snowbll setup Setup wizard + agent MCP configs
|
|
25
|
+
snowbll mcp Run the MCP server on stdio
|
|
26
|
+
snowbll help | --version
|
|
27
|
+
|
|
28
|
+
Search, game, and forge work without a key; the rest use the developer-preview
|
|
28
29
|
keyed API (run \`snowbll login\` or set SNOWBLL_API_KEY). Docs: https://www.snowbll.com/docs`;
|
|
29
30
|
function parseFlags(args) {
|
|
30
31
|
const flags = { json: false, rest: [] };
|
|
@@ -70,6 +71,56 @@ async function promptCredentials() {
|
|
|
70
71
|
throw new SnowbllApiError(400, "Email and password are both required.");
|
|
71
72
|
return { email, password };
|
|
72
73
|
}
|
|
74
|
+
/** Detect this machine's rig and (when signed in) save it to the account, so
|
|
75
|
+
* Snowbll can flag which games actually run on it. GPU model needs the
|
|
76
|
+
* `systeminformation` package; CPU/RAM/OS come from node:os. The GPU performance
|
|
77
|
+
* bucket is classified server-side on read, so we just send raw specs. */
|
|
78
|
+
async function runHardware() {
|
|
79
|
+
const os = await import("node:os");
|
|
80
|
+
let gpuModel = null;
|
|
81
|
+
let gpuVramMb = null;
|
|
82
|
+
try {
|
|
83
|
+
const siMod = (await import("systeminformation"));
|
|
84
|
+
const si = siMod.default ?? siMod;
|
|
85
|
+
const g = await si.graphics();
|
|
86
|
+
const ctrls = (g.controllers ?? []).filter((c) => c.model);
|
|
87
|
+
const best = ctrls.sort((a, b) => (Number(b.vram) || 0) - (Number(a.vram) || 0))[0];
|
|
88
|
+
if (best) {
|
|
89
|
+
gpuModel = best.model ?? null;
|
|
90
|
+
gpuVramMb = Number(best.vram) || null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// systeminformation missing/blocked → GPU stays null; CPU/RAM/OS still reported.
|
|
95
|
+
}
|
|
96
|
+
const cpus = os.cpus() ?? [];
|
|
97
|
+
const osLabel = { win32: "Windows", darwin: "macOS", linux: "Linux" }[os.platform()] ||
|
|
98
|
+
os.platform();
|
|
99
|
+
const profile = {
|
|
100
|
+
gpu: { raw: gpuModel, model: gpuModel, bucket: null },
|
|
101
|
+
gpuVramMb,
|
|
102
|
+
cpuModel: cpus[0]?.model?.trim() || null,
|
|
103
|
+
cpuCores: cpus.length || null,
|
|
104
|
+
ramGb: Math.round(os.totalmem() / 1e9),
|
|
105
|
+
ramCapped: false,
|
|
106
|
+
os: osLabel,
|
|
107
|
+
source: "cli",
|
|
108
|
+
detectedAt: new Date().toISOString(),
|
|
109
|
+
};
|
|
110
|
+
console.log("Detected rig:");
|
|
111
|
+
console.log(` GPU: ${profile.gpu.model || "(unknown — driver/systeminformation needed)"}`);
|
|
112
|
+
console.log(` CPU: ${profile.cpuModel || "(unknown)"} · ${profile.cpuCores ?? "?"} cores`);
|
|
113
|
+
console.log(` Memory: ${profile.ramGb} GB`);
|
|
114
|
+
console.log(` OS: ${profile.os}`);
|
|
115
|
+
if (!getApiKey()) {
|
|
116
|
+
console.log("\nNot signed in — run `snowbll login` to save this to your account. " +
|
|
117
|
+
"Then Snowbll flags which games run on your rig, on the site and via the MCP.");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await saveHardware(profile);
|
|
121
|
+
console.log("\nSaved to your Snowbll account. Games now show whether they run on your rig — " +
|
|
122
|
+
"on the site and via the MCP in Claude / ChatGPT.");
|
|
123
|
+
}
|
|
73
124
|
async function main() {
|
|
74
125
|
const [command, ...args] = process.argv.slice(2);
|
|
75
126
|
const flags = parseFlags(args);
|
|
@@ -105,6 +156,10 @@ async function main() {
|
|
|
105
156
|
out(await getPersona(flags.rest[0] ?? "me"));
|
|
106
157
|
return;
|
|
107
158
|
}
|
|
159
|
+
case "hardware": {
|
|
160
|
+
await runHardware();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
108
163
|
case "whoami": {
|
|
109
164
|
out(await whoami());
|
|
110
165
|
return;
|
package/dist/shape.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP game-result shape — mirrors the hosted server's mapper
|
|
3
|
+
* (Snowbll lib/v1/service.ts `toMcpGame`/`toMcpSearchResult`/`toMcpGameDetail`)
|
|
4
|
+
* so the stdio package returns the same shape regardless of which API endpoint
|
|
5
|
+
* (public or keyed v1) served the raw data. Keep in sync with the hosted mapper.
|
|
6
|
+
*
|
|
7
|
+
* Every field is sourceable from the catalog — name, link, cover, aiFit (why it
|
|
8
|
+
* matched), technicalFit (objective dims), and real Steam screenshots. Never a
|
|
9
|
+
* quality verdict, never fabricated/in-game behavior.
|
|
10
|
+
*/
|
|
11
|
+
export function coverImageFor(g) {
|
|
12
|
+
if (g.headerImage)
|
|
13
|
+
return g.headerImage;
|
|
14
|
+
if (g.steamAppId)
|
|
15
|
+
return `https://cdn.cloudflare.steamstatic.com/steam/apps/${g.steamAppId}/header.jpg`;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
export function matchedTagsFor(g, c) {
|
|
19
|
+
if (!c)
|
|
20
|
+
return [];
|
|
21
|
+
const want = new Set([...(c.tags ?? []), ...(c.genres ?? []), ...(c.keywords ?? [])].map((t) => String(t).toLowerCase()));
|
|
22
|
+
return (g.gameplayTags ?? []).filter((t) => want.has(String(t).toLowerCase()));
|
|
23
|
+
}
|
|
24
|
+
export function toMcpGame(g, c) {
|
|
25
|
+
return {
|
|
26
|
+
id: g.id,
|
|
27
|
+
title: g.title,
|
|
28
|
+
url: g.storeUrl ?? g.itchUrl ?? g.url ?? null,
|
|
29
|
+
coverImage: coverImageFor(g),
|
|
30
|
+
technicalFit: {
|
|
31
|
+
genre: g.genre ?? null,
|
|
32
|
+
matchedTags: matchedTagsFor(g, c),
|
|
33
|
+
economyComplexity: g.economyComplexity ?? null,
|
|
34
|
+
graphicsFidelity: g.graphicsFidelity ?? null,
|
|
35
|
+
graphicsStyle: g.graphicsStyle ?? null,
|
|
36
|
+
similarTo: g.similarTo ?? [],
|
|
37
|
+
verification: g.verification ?? "unverified",
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Normalize a search response (public or keyed) into MCP search results. */
|
|
42
|
+
export function toMcpSearch(raw) {
|
|
43
|
+
const r = (raw ?? {});
|
|
44
|
+
const criteria = r.criteria;
|
|
45
|
+
const results = (r.results ?? []).map((row) => {
|
|
46
|
+
const item = (row ?? {});
|
|
47
|
+
const game = (item.game ?? item);
|
|
48
|
+
return { ...toMcpGame(game, criteria), aiFit: { score: item.score ?? 0, reasons: item.reasons ?? [] } };
|
|
49
|
+
});
|
|
50
|
+
return { query: r.query, parsedBy: r.parsedBy, criteria, results };
|
|
51
|
+
}
|
|
52
|
+
/** Real Steam screenshots via the store appdetails API. [] for non-Steam games,
|
|
53
|
+
* on any error/timeout, or when none — never fabricated. */
|
|
54
|
+
export async function fetchSteamScreenshots(appId) {
|
|
55
|
+
if (!appId)
|
|
56
|
+
return [];
|
|
57
|
+
try {
|
|
58
|
+
const ctrl = new AbortController();
|
|
59
|
+
const timer = setTimeout(() => ctrl.abort(), 5000);
|
|
60
|
+
const res = await fetch(`https://store.steampowered.com/api/appdetails?appids=${appId}&filters=screenshots`, { signal: ctrl.signal });
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
if (!res.ok)
|
|
63
|
+
return [];
|
|
64
|
+
const json = (await res.json());
|
|
65
|
+
const entry = json?.[String(appId)];
|
|
66
|
+
if (!entry?.success || !entry.data?.screenshots)
|
|
67
|
+
return [];
|
|
68
|
+
return entry.data.screenshots
|
|
69
|
+
.map((s) => s.path_full ?? s.path_thumbnail)
|
|
70
|
+
.filter((u) => typeof u === "string" && u.length > 0)
|
|
71
|
+
.slice(0, 8);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Normalize a get_game response into the MCP detail shape (+ screenshots). */
|
|
78
|
+
export async function toMcpDetail(raw, includeReviews) {
|
|
79
|
+
const wrapped = (raw ?? {});
|
|
80
|
+
const g = (wrapped.game ?? wrapped);
|
|
81
|
+
const detail = {
|
|
82
|
+
...toMcpGame(g),
|
|
83
|
+
developer: g.developer ?? null,
|
|
84
|
+
price: g.price ?? null,
|
|
85
|
+
description: g.description ?? null,
|
|
86
|
+
screenshots: await fetchSteamScreenshots(g.steamAppId),
|
|
87
|
+
};
|
|
88
|
+
if (includeReviews && g.behavioralSignals)
|
|
89
|
+
detail.behavioralSignals = g.behavioralSignals;
|
|
90
|
+
return detail;
|
|
91
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snowbll-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Snowbll MCP server + CLI — search the Snowbll game catalog, read consented personas, and follow Forge campaigns from any MCP-capable AI agent (Claude, ChatGPT, Cursor, Hermes) or your terminal. Live API, no mock data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
41
|
+
"systeminformation": "^5.23.0",
|
|
41
42
|
"zod": "^3.24.1"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|