snowbll-mcp 0.1.0 → 0.2.0
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 +61 -183
- package/dist/api.js +138 -0
- package/dist/bin/snowbll-mcp.js +30 -0
- package/dist/bin/snowbll.js +180 -0
- package/dist/config.js +51 -0
- package/dist/server.js +135 -0
- package/dist/setup.js +58 -0
- package/package.json +16 -13
- package/dist/client.js +0 -107
- package/dist/client.js.map +0 -1
- package/dist/communityClient.js +0 -96
- package/dist/communityClient.js.map +0 -1
- package/dist/crossCheckClient.js +0 -83
- package/dist/crossCheckClient.js.map +0 -1
- package/dist/http.js +0 -70
- package/dist/http.js.map +0 -1
- package/dist/index.js +0 -391
- package/dist/index.js.map +0 -1
- package/dist/mockData.js +0 -471
- package/dist/mockData.js.map +0 -1
- package/dist/prompts.js +0 -48
- package/dist/prompts.js.map +0 -1
- package/dist/resources.js +0 -54
- package/dist/resources.js.map +0 -1
- package/dist/schemas.js +0 -174
- package/dist/schemas.js.map +0 -1
package/dist/config.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Where the CLI keeps its state: ~/.snowbll/config.json
|
|
6
|
+
* Resolution order for every setting: environment variable, then config file,
|
|
7
|
+
* then built-in default — so CI/agent configs (env) always win over `snowbll
|
|
8
|
+
* setup` (file).
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_API_BASE = "https://www.snowbll.com";
|
|
11
|
+
// Supabase project behind snowbll.com. The anon key is public-safe by design
|
|
12
|
+
// (it ships in every browser bundle of the live site; RLS protects data) and
|
|
13
|
+
// is only used for the email+password login flow that mints an API key.
|
|
14
|
+
export const SUPABASE_URL = "https://jeuzlcpqaawcfiajkumh.supabase.co";
|
|
15
|
+
export const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImpldXpsY3BxYWF3Y2ZpYWprdW1oIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODA0NTA5MjcsImV4cCI6MjA5NjAyNjkyN30.XF3E1dYmpVPM_dBtkFgjFgk7RiJk-F_5vFEE5y4V8SM";
|
|
16
|
+
const configDir = () => join(homedir(), ".snowbll");
|
|
17
|
+
const configPath = () => join(configDir(), "config.json");
|
|
18
|
+
export function readConfig() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(configPath(), "utf8"));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function writeConfig(patch) {
|
|
27
|
+
const next = { ...readConfig(), ...patch };
|
|
28
|
+
for (const key of Object.keys(next)) {
|
|
29
|
+
if (next[key] === undefined)
|
|
30
|
+
delete next[key];
|
|
31
|
+
}
|
|
32
|
+
mkdirSync(configDir(), { recursive: true });
|
|
33
|
+
writeFileSync(configPath(), `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
34
|
+
}
|
|
35
|
+
export function clearConfig() {
|
|
36
|
+
if (existsSync(configPath()))
|
|
37
|
+
rmSync(configPath());
|
|
38
|
+
}
|
|
39
|
+
export function getApiKey() {
|
|
40
|
+
return process.env.SNOWBLL_API_KEY || readConfig().apiKey || undefined;
|
|
41
|
+
}
|
|
42
|
+
export function getApiBase() {
|
|
43
|
+
const base = process.env.SNOWBLL_API_BASE || readConfig().apiBase || DEFAULT_API_BASE;
|
|
44
|
+
return base.replace(/\/+$/, "");
|
|
45
|
+
}
|
|
46
|
+
export function getSupabaseUrl() {
|
|
47
|
+
return (process.env.SNOWBLL_SUPABASE_URL || SUPABASE_URL).replace(/\/+$/, "");
|
|
48
|
+
}
|
|
49
|
+
export function getSupabaseAnonKey() {
|
|
50
|
+
return process.env.SNOWBLL_SUPABASE_ANON_KEY || SUPABASE_ANON_KEY;
|
|
51
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { getForgeCampaign, getGame, getPersona, listForgeCampaigns, searchGames, SnowbllApiError, } from "./api.js";
|
|
5
|
+
import { getApiBase, getApiKey } from "./config.js";
|
|
6
|
+
/**
|
|
7
|
+
* The Snowbll MCP server over stdio — the local twin of the hosted server at
|
|
8
|
+
* https://www.snowbll.com/api/mcp. Same five read-only tools as the docs'
|
|
9
|
+
* tool catalog; every call hits the live Snowbll API (no mock data).
|
|
10
|
+
*
|
|
11
|
+
* Works keyless for public data (search, Forge). get_game and get_persona use
|
|
12
|
+
* the developer-preview keyed API, so they need SNOWBLL_API_KEY (or a key
|
|
13
|
+
* stored by `snowbll-mcp setup`).
|
|
14
|
+
*/
|
|
15
|
+
const json = (value) => ({
|
|
16
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
17
|
+
});
|
|
18
|
+
const toolError = (err) => ({
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
type: "text",
|
|
22
|
+
text: JSON.stringify({
|
|
23
|
+
error: err instanceof Error ? err.message : String(err),
|
|
24
|
+
...(err instanceof SnowbllApiError && err.status ? { status: err.status } : {}),
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
isError: true,
|
|
29
|
+
});
|
|
30
|
+
const READ_ONLY = { readOnlyHint: true, openWorldHint: false };
|
|
31
|
+
export function buildServer(version) {
|
|
32
|
+
const server = new McpServer({ name: "snowbll", version });
|
|
33
|
+
server.registerTool("search_games", {
|
|
34
|
+
title: "Search games",
|
|
35
|
+
description: "Natural-language search over the Snowbll catalog. Pass a player-style sentence " +
|
|
36
|
+
'(e.g. "economy sim like Lemonade Tycoon with a deep economy") and get ranked ' +
|
|
37
|
+
"candidates with reasons. Results are fit candidates, not quality verdicts: " +
|
|
38
|
+
"`verification` is 'verified' only after third-party human testing. Free, no key needed.",
|
|
39
|
+
inputSchema: {
|
|
40
|
+
query: z.string().min(1).describe("Player-style natural-language query."),
|
|
41
|
+
limit: z.number().int().min(1).max(10).optional().describe("Max candidates (default 5)."),
|
|
42
|
+
},
|
|
43
|
+
annotations: READ_ONLY,
|
|
44
|
+
}, async ({ query, limit }) => {
|
|
45
|
+
try {
|
|
46
|
+
return json(await searchGames(query, limit ?? 5));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return toolError(err);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
server.registerTool("get_game", {
|
|
53
|
+
title: "Get game",
|
|
54
|
+
description: "Full metadata for one catalog game by id (ids come from search_games), including " +
|
|
55
|
+
"its human-verification status. Quality fields can be null — no qualified human " +
|
|
56
|
+
"rated them yet, not that the game is bad. Free, no key needed.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
game_id: z.string().min(1).describe("Snowbll game id from search results."),
|
|
59
|
+
include_reviews: z
|
|
60
|
+
.boolean()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Include public review aggregates (behavioralSignals) when available. Default true."),
|
|
63
|
+
},
|
|
64
|
+
annotations: READ_ONLY,
|
|
65
|
+
}, async ({ game_id, include_reviews }) => {
|
|
66
|
+
try {
|
|
67
|
+
const data = (await getGame(game_id));
|
|
68
|
+
if (include_reviews === false && data?.game) {
|
|
69
|
+
const { behavioralSignals: _omit, ...rest } = data.game;
|
|
70
|
+
return json({ ...data, game: rest });
|
|
71
|
+
}
|
|
72
|
+
return json(data);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
return toolError(err);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
server.registerTool("get_persona", {
|
|
79
|
+
title: "Get persona",
|
|
80
|
+
description: "Read a player's gaming persona (playstyle summary + structured analysis). " +
|
|
81
|
+
"Consent-scoped: needs an API key and reads the persona of the key owner. " +
|
|
82
|
+
"Persona intelligence is a paid feature (Observer rank up); free accounts get " +
|
|
83
|
+
"a locked marker with an upgrade link. A shallow or null persona on a paid " +
|
|
84
|
+
"account means the player shared little — by design.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
player_id: z.string().optional().describe("Player id, or 'me' (default) for the key owner."),
|
|
87
|
+
},
|
|
88
|
+
annotations: READ_ONLY,
|
|
89
|
+
}, async ({ player_id }) => {
|
|
90
|
+
try {
|
|
91
|
+
return json(await getPersona(player_id ?? "me"));
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return toolError(err);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
server.registerTool("list_forge_campaigns", {
|
|
98
|
+
title: "List Forge campaigns",
|
|
99
|
+
description: "Campaigns on The Forge (Snowbll's pre-release backing surface) with live funding " +
|
|
100
|
+
"totals. Read-only: backing happens on snowbll.com, by the player.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
status: z.enum(["live", "funded", "past"]).optional().describe("Filter by status."),
|
|
103
|
+
},
|
|
104
|
+
annotations: READ_ONLY,
|
|
105
|
+
}, async ({ status }) => {
|
|
106
|
+
try {
|
|
107
|
+
return json(await listForgeCampaigns(status));
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
return toolError(err);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
server.registerTool("get_forge_campaign", {
|
|
114
|
+
title: "Get Forge campaign",
|
|
115
|
+
description: "One Forge campaign in detail: reward tiers, stretch goals, live progress.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
campaign_id: z.string().min(1).describe("Forge campaign id."),
|
|
118
|
+
},
|
|
119
|
+
annotations: READ_ONLY,
|
|
120
|
+
}, async ({ campaign_id }) => {
|
|
121
|
+
try {
|
|
122
|
+
return json(await getForgeCampaign(campaign_id));
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
return toolError(err);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return server;
|
|
129
|
+
}
|
|
130
|
+
export async function runStdioServer(version) {
|
|
131
|
+
const server = buildServer(version);
|
|
132
|
+
await server.connect(new StdioServerTransport());
|
|
133
|
+
// stdout is the MCP wire — status goes to stderr only.
|
|
134
|
+
console.error(`[snowbll-mcp] v${version} on stdio — live API: ${getApiBase()} — key: ${getApiKey() ? "configured" : "none (public tools only)"}`);
|
|
135
|
+
}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { whoami } from "./api.js";
|
|
3
|
+
import { getApiBase, getApiKey, writeConfig } from "./config.js";
|
|
4
|
+
/**
|
|
5
|
+
* `snowbll-mcp setup` / `snowbll setup` — the wizard the docs' agent prompt
|
|
6
|
+
* walks through: paste a developer-preview key (optional), validate it against
|
|
7
|
+
* the live API, store it, and print ready-to-paste MCP configs.
|
|
8
|
+
*/
|
|
9
|
+
export async function runSetup() {
|
|
10
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
11
|
+
console.log("Snowbll MCP setup");
|
|
12
|
+
console.log("─".repeat(60));
|
|
13
|
+
console.log(`API base: ${getApiBase()}`);
|
|
14
|
+
console.log("An API key unlocks get_game and get_persona. Search and Forge tools work without one.\n" +
|
|
15
|
+
"Get a key with `snowbll login` (email+password account) or from the Snowbll team.\n");
|
|
16
|
+
const existing = getApiKey();
|
|
17
|
+
const answer = await rl.question(existing
|
|
18
|
+
? `A key is already configured (${existing.slice(0, 11)}…). Paste a new one to replace it, or press Enter to keep it: `
|
|
19
|
+
: "Paste your API key (sb_...), or press Enter to skip: ");
|
|
20
|
+
rl.close();
|
|
21
|
+
const key = answer.trim();
|
|
22
|
+
if (key) {
|
|
23
|
+
if (!/^sb_[0-9a-f]{48}$/.test(key)) {
|
|
24
|
+
console.error("That does not look like a Snowbll API key (sb_ + 48 hex). Nothing saved.");
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
writeConfig({ apiKey: key });
|
|
29
|
+
console.log("Key saved to ~/.snowbll/config.json");
|
|
30
|
+
}
|
|
31
|
+
if (key || existing) {
|
|
32
|
+
try {
|
|
33
|
+
const me = (await whoami());
|
|
34
|
+
console.log(`Key verified — signed in as ${me.display_name ?? me.email ?? "unknown account"}.`);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error(`Key did NOT verify: ${err instanceof Error ? err.message : String(err)}`);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
console.log("\nConnect an agent — pick the line that matches yours:");
|
|
42
|
+
console.log("─".repeat(60));
|
|
43
|
+
console.log("Claude Code (local stdio):");
|
|
44
|
+
console.log(" claude mcp add snowbll -- npx -y snowbll-mcp\n");
|
|
45
|
+
console.log("Claude Desktop / Cursor / Hermes (claude_desktop_config.json or equivalent):");
|
|
46
|
+
console.log(JSON.stringify({
|
|
47
|
+
mcpServers: {
|
|
48
|
+
snowbll: {
|
|
49
|
+
command: "npx",
|
|
50
|
+
args: ["-y", "snowbll-mcp"],
|
|
51
|
+
env: { SNOWBLL_API_KEY: key || existing || "sb_YOUR_API_KEY" },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}, null, 2));
|
|
55
|
+
console.log("\nChatGPT / any remote-MCP client (no install):");
|
|
56
|
+
console.log(` ${getApiBase()}/api/mcp (Streamable HTTP, no auth needed for public tools)`);
|
|
57
|
+
console.log("\nVerify with: snowbll search \"economy sim like Lemonade Tycoon with a deep economy\"");
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snowbll-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Snowbll MCP
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
|
+
"homepage": "https://www.snowbll.com/docs",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/calvinasd567-boop/snowbll-mcp.git"
|
|
11
|
+
},
|
|
7
12
|
"bin": {
|
|
8
|
-
"snowbll-mcp": "dist/
|
|
13
|
+
"snowbll-mcp": "dist/bin/snowbll-mcp.js",
|
|
14
|
+
"snowbll": "dist/bin/snowbll.js"
|
|
9
15
|
},
|
|
10
16
|
"files": [
|
|
11
17
|
"dist",
|
|
@@ -15,30 +21,27 @@
|
|
|
15
21
|
"node": ">=18"
|
|
16
22
|
},
|
|
17
23
|
"scripts": {
|
|
18
|
-
"dev": "tsx src/index.ts",
|
|
19
24
|
"build": "tsc",
|
|
20
|
-
"
|
|
21
|
-
"test": "
|
|
22
|
-
"prepublishOnly": "npm run build
|
|
25
|
+
"dev": "tsx src/bin/snowbll-mcp.ts",
|
|
26
|
+
"test": "node test/smoke.mjs",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
23
28
|
},
|
|
24
29
|
"keywords": [
|
|
25
30
|
"mcp",
|
|
26
31
|
"model-context-protocol",
|
|
27
32
|
"snowbll",
|
|
28
33
|
"gaming",
|
|
29
|
-
"
|
|
34
|
+
"game-discovery",
|
|
30
35
|
"ai-agents",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
36
|
+
"persona",
|
|
37
|
+
"cli"
|
|
33
38
|
],
|
|
34
39
|
"dependencies": {
|
|
35
40
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
36
|
-
"zod": "^3.
|
|
41
|
+
"zod": "^3.24.1"
|
|
37
42
|
},
|
|
38
43
|
"devDependencies": {
|
|
39
44
|
"@types/node": "^20.14.0",
|
|
40
|
-
"snowbll-community-recommendation-engine": "file:../community-recommendation-engine",
|
|
41
|
-
"snowbll-recommendation-engine": "file:../recommendation-engine",
|
|
42
45
|
"tsx": "^4.16.0",
|
|
43
46
|
"typescript": "^5.5.0"
|
|
44
47
|
}
|
package/dist/client.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SnowbllClient — the data layer behind the MCP tools.
|
|
3
|
-
*
|
|
4
|
-
* Mock mode is the DEFAULT. The client only talks to the real Snowbll API when
|
|
5
|
-
* SNOWBLL_USE_MOCK is explicitly set to the string "false". This lets the MCP
|
|
6
|
-
* server run with zero configuration today, while keeping a single seam through
|
|
7
|
-
* which a real backend can be wired in later without touching index.ts.
|
|
8
|
-
*
|
|
9
|
-
* Environment variables:
|
|
10
|
-
* SNOWBLL_USE_MOCK "false" -> call the real API. Anything else (or unset) -> mock.
|
|
11
|
-
* SNOWBLL_API_URL Base URL of the Snowbll API (default https://api.snowbll.com/v1).
|
|
12
|
-
* SNOWBLL_API_KEY Required only in live mode; format snb_...
|
|
13
|
-
* SNOWBLL_API_TIMEOUT_MS Per-request timeout in ms (default 10000).
|
|
14
|
-
* SNOWBLL_API_RETRIES Retries after the first attempt for transient errors (default 2).
|
|
15
|
-
*/
|
|
16
|
-
import { requestJson } from "./http.js";
|
|
17
|
-
import * as mock from "./mockData.js";
|
|
18
|
-
const DEFAULT_API_URL = "https://api.snowbll.com/v1";
|
|
19
|
-
/** Parse a positive-number env var, falling back to a default. */
|
|
20
|
-
function numEnv(value, fallback, min = 0) {
|
|
21
|
-
const n = Number(value);
|
|
22
|
-
return Number.isFinite(n) && n >= min ? n : fallback;
|
|
23
|
-
}
|
|
24
|
-
export class SnowbllClient {
|
|
25
|
-
useMock;
|
|
26
|
-
apiUrl;
|
|
27
|
-
apiKey;
|
|
28
|
-
timeoutMs;
|
|
29
|
-
retries;
|
|
30
|
-
constructor() {
|
|
31
|
-
// Mock is the default. Only the literal "false" opts into the live API.
|
|
32
|
-
this.useMock = process.env.SNOWBLL_USE_MOCK !== "false";
|
|
33
|
-
this.apiUrl = (process.env.SNOWBLL_API_URL ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
34
|
-
this.apiKey = process.env.SNOWBLL_API_KEY;
|
|
35
|
-
this.timeoutMs = numEnv(process.env.SNOWBLL_API_TIMEOUT_MS, 10000, 1);
|
|
36
|
-
this.retries = numEnv(process.env.SNOWBLL_API_RETRIES, 2, 0);
|
|
37
|
-
}
|
|
38
|
-
/** "mock" when serving local data, "live" when calling the real Snowbll API. */
|
|
39
|
-
get mode() {
|
|
40
|
-
return this.useMock ? "mock" : "live";
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Route a tool call to mock data or the real API.
|
|
44
|
-
* In mock mode the network is never touched and no API key is required.
|
|
45
|
-
*/
|
|
46
|
-
async call(endpoint, payload, mockFn) {
|
|
47
|
-
if (this.useMock) {
|
|
48
|
-
return mockFn();
|
|
49
|
-
}
|
|
50
|
-
if (!this.apiKey) {
|
|
51
|
-
throw new Error("Snowbll live API mode is enabled (SNOWBLL_USE_MOCK=false) but SNOWBLL_API_KEY is not set.\n" +
|
|
52
|
-
"Fix one of the following:\n" +
|
|
53
|
-
" - Set SNOWBLL_API_KEY=snb_... to call the real Snowbll API, or\n" +
|
|
54
|
-
" - Set SNOWBLL_USE_MOCK=true (or unset it) to use local mock intelligence.");
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
return await requestJson(`${this.apiUrl}${endpoint}`, {
|
|
58
|
-
method: "POST",
|
|
59
|
-
headers: { authorization: `Bearer ${this.apiKey}` },
|
|
60
|
-
body: payload,
|
|
61
|
-
timeoutMs: this.timeoutMs,
|
|
62
|
-
retries: this.retries,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
67
|
-
throw new Error(`Snowbll API request to ${endpoint} failed: ${message}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
getPlayerOverview() {
|
|
71
|
-
return this.call("/player/overview", {}, () => mock.playerOverview);
|
|
72
|
-
}
|
|
73
|
-
getPlaystyleDna() {
|
|
74
|
-
return this.call("/player/playstyle-dna", {}, () => mock.playstyleDna);
|
|
75
|
-
}
|
|
76
|
-
getPersonaCards() {
|
|
77
|
-
return this.call("/player/persona-cards", {}, () => mock.personaCards);
|
|
78
|
-
}
|
|
79
|
-
getActivityPatterns() {
|
|
80
|
-
return this.call("/player/activity-patterns", {}, () => mock.activityPatterns);
|
|
81
|
-
}
|
|
82
|
-
getCompletionBehavior() {
|
|
83
|
-
return this.call("/player/completion-behavior", {}, () => mock.completionBehavior);
|
|
84
|
-
}
|
|
85
|
-
getPurchaseBehavior(includeSensitiveSignals) {
|
|
86
|
-
return this.call("/player/purchase-behavior", { includeSensitiveSignals }, () => mock.getPurchaseBehavior(includeSensitiveSignals));
|
|
87
|
-
}
|
|
88
|
-
detectGenreMismatch(includePurchaseSignals, includeCompletionSignals) {
|
|
89
|
-
return this.call("/player/genre-mismatch", { includePurchaseSignals, includeCompletionSignals }, () => mock.detectGenreMismatch(includePurchaseSignals, includeCompletionSignals));
|
|
90
|
-
}
|
|
91
|
-
analyzePlayerBehaviour(depth, includeEvidence) {
|
|
92
|
-
return this.call("/player/analyze", { depth, includeEvidence }, () => mock.analyzePlayerBehaviour(depth, includeEvidence));
|
|
93
|
-
}
|
|
94
|
-
explainTastePattern(topic) {
|
|
95
|
-
return this.call("/player/taste-pattern", { topic }, () => mock.explainTastePattern(topic));
|
|
96
|
-
}
|
|
97
|
-
predictGameFit(gameTitle, platform) {
|
|
98
|
-
return this.call("/predict/game-fit", { gameTitle, platform }, () => mock.predictGameFit(gameTitle, platform));
|
|
99
|
-
}
|
|
100
|
-
compareGamesForPlayer(games) {
|
|
101
|
-
return this.call("/predict/compare", { games }, () => mock.compareGames(games));
|
|
102
|
-
}
|
|
103
|
-
recommendGames(objective, limit) {
|
|
104
|
-
return this.call("/recommend", { objective, limit }, () => mock.recommendGames(objective, limit));
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/communityClient.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CommunityClient — the data layer behind the community MCP tools.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors {@link SnowbllClient}: mock mode is the DEFAULT. In mock mode the
|
|
5
|
-
* deterministic Community Recommendation Engine runs IN-PROCESS (full fidelity,
|
|
6
|
-
* zero config, no network). When SNOWBLL_USE_MOCK="false" it calls the live
|
|
7
|
-
* Community Recommendation REST API instead.
|
|
8
|
-
*
|
|
9
|
-
* The in-process engine (snowbll-community-recommendation-engine) is a PRIVATE
|
|
10
|
-
* package loaded LAZILY, only in mock mode, and declared as a devDependency — so
|
|
11
|
-
* the published MCP never ships or requires it (published builds use live mode /
|
|
12
|
-
* REST for community tools).
|
|
13
|
-
*
|
|
14
|
-
* Environment variables:
|
|
15
|
-
* SNOWBLL_USE_MOCK "false" -> call the live API. Anything else -> in-process engine.
|
|
16
|
-
* SNOWBLL_COMMUNITY_API_URL Base URL of the community API (default https://api.snowbll.com/v1/community).
|
|
17
|
-
* SNOWBLL_COMMUNITY_API_KEY Required only in live mode; sent as Authorization: Bearer <key>.
|
|
18
|
-
*/
|
|
19
|
-
const DEFAULT_API_URL = "https://api.snowbll.com/v1/community";
|
|
20
|
-
let enginePromise;
|
|
21
|
-
function loadEngine() {
|
|
22
|
-
if (!enginePromise) {
|
|
23
|
-
enginePromise = import("snowbll-community-recommendation-engine").catch(() => {
|
|
24
|
-
throw new Error("The in-process community engine is not available in this build.\n" +
|
|
25
|
-
"Set SNOWBLL_USE_MOCK=false and SNOWBLL_COMMUNITY_API_KEY to use the live community API instead.");
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
return enginePromise;
|
|
29
|
-
}
|
|
30
|
-
export class CommunityClient {
|
|
31
|
-
useMock;
|
|
32
|
-
apiUrl;
|
|
33
|
-
apiKey;
|
|
34
|
-
constructor() {
|
|
35
|
-
// Mock is the default. Only the literal "false" opts into the live API.
|
|
36
|
-
this.useMock = process.env.SNOWBLL_USE_MOCK !== "false";
|
|
37
|
-
this.apiUrl = (process.env.SNOWBLL_COMMUNITY_API_URL ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
38
|
-
this.apiKey = process.env.SNOWBLL_COMMUNITY_API_KEY;
|
|
39
|
-
}
|
|
40
|
-
/** "mock" when running the engine in-process, "live" when calling the REST API. */
|
|
41
|
-
get mode() {
|
|
42
|
-
return this.useMock ? "mock" : "live";
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Route a tool call to the in-process engine (mock) or the live REST API.
|
|
46
|
-
* In mock mode the network is never touched and no API key is required.
|
|
47
|
-
*/
|
|
48
|
-
async call(endpoint, payload, mockFn) {
|
|
49
|
-
if (this.useMock) {
|
|
50
|
-
return mockFn(await loadEngine());
|
|
51
|
-
}
|
|
52
|
-
if (!this.apiKey) {
|
|
53
|
-
throw new Error("Snowbll live API mode is enabled (SNOWBLL_USE_MOCK=false) but SNOWBLL_COMMUNITY_API_KEY is not set.\n" +
|
|
54
|
-
"Fix one of the following:\n" +
|
|
55
|
-
" - Set SNOWBLL_COMMUNITY_API_KEY=snb_comm_... to call the live community API, or\n" +
|
|
56
|
-
" - Set SNOWBLL_USE_MOCK=true (or unset it) to use the local deterministic engine.");
|
|
57
|
-
}
|
|
58
|
-
let res;
|
|
59
|
-
try {
|
|
60
|
-
res = await fetch(`${this.apiUrl}${endpoint}`, {
|
|
61
|
-
method: "POST",
|
|
62
|
-
headers: {
|
|
63
|
-
"content-type": "application/json",
|
|
64
|
-
authorization: `Bearer ${this.apiKey}`,
|
|
65
|
-
},
|
|
66
|
-
body: JSON.stringify(payload),
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
catch (cause) {
|
|
70
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
71
|
-
throw new Error(`Failed to reach the Snowbll community API at ${this.apiUrl}${endpoint}: ${message}`);
|
|
72
|
-
}
|
|
73
|
-
if (!res.ok) {
|
|
74
|
-
const detail = await res.text().catch(() => "");
|
|
75
|
-
throw new Error(`Snowbll community API error ${res.status} ${res.statusText} for ${endpoint}` +
|
|
76
|
-
(detail ? `: ${detail}` : ""));
|
|
77
|
-
}
|
|
78
|
-
return (await res.json());
|
|
79
|
-
}
|
|
80
|
-
recommendForumThreads(args) {
|
|
81
|
-
return this.call("/recommend/threads", { ...args }, (engine) => engine.recommendForumThreads({ ...args }));
|
|
82
|
-
}
|
|
83
|
-
predictThreadFit(threadTitle) {
|
|
84
|
-
return this.call("/predict/thread-fit", { threadTitle }, (engine) => engine.predictThreadFit({ threadTitle }));
|
|
85
|
-
}
|
|
86
|
-
compareThreadsForPlayer(threads) {
|
|
87
|
-
return this.call("/compare/threads", { threads }, (engine) => engine.compareThreadsForPlayer({ threads }));
|
|
88
|
-
}
|
|
89
|
-
explainThreadRecommendation(threadTitle) {
|
|
90
|
-
return this.call("/explain/thread", { threadTitle }, (engine) => engine.explainThreadRecommendation({ threadTitle }));
|
|
91
|
-
}
|
|
92
|
-
detectThreadsToAvoid(limit) {
|
|
93
|
-
return this.call("/detect-threads-to-avoid", { limit }, (engine) => engine.detectThreadsToAvoid({ limit }));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
//# sourceMappingURL=communityClient.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"communityClient.js","sourceRoot":"","sources":["../src/communityClient.ts"],"names":[],"mappings":"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"}
|
package/dist/crossCheckClient.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CrossCheckClient — the data layer behind the `crosscheck_recommendations` tool.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors {@link SnowbllClient} / {@link CommunityClient}: mock mode is the DEFAULT
|
|
5
|
-
* and runs the deterministic Recommendation Engine's cross-check IN-PROCESS (full
|
|
6
|
-
* fidelity, zero config, no network). When SNOWBLL_USE_MOCK="false" it calls the
|
|
7
|
-
* live Recommendation REST API (POST /recommend/consensus) instead.
|
|
8
|
-
*
|
|
9
|
-
* The cross-check reconciles TWO independent Snowbll recommenders (this engine and
|
|
10
|
-
* the Persona Engine's own recommender) into one validated answer.
|
|
11
|
-
*
|
|
12
|
-
* The in-process engine (snowbll-recommendation-engine) is a PRIVATE package loaded
|
|
13
|
-
* LAZILY, only in mock mode, and declared as a devDependency — so the published MCP
|
|
14
|
-
* never ships or requires it (published builds use live mode / REST).
|
|
15
|
-
*
|
|
16
|
-
* Environment variables:
|
|
17
|
-
* SNOWBLL_USE_MOCK "false" -> call the live API. Anything else -> in-process engine.
|
|
18
|
-
* SNOWBLL_RECO_API_URL Base URL of the recommendation API (default https://api.snowbll.com/v1).
|
|
19
|
-
* SNOWBLL_RECO_API_KEY OPTIONAL — sent as Authorization: Bearer <key> when set.
|
|
20
|
-
* The recommendation API is open by default, so no key is required.
|
|
21
|
-
*/
|
|
22
|
-
const DEFAULT_API_URL = "https://api.snowbll.com/v1";
|
|
23
|
-
let recoEnginePromise;
|
|
24
|
-
function loadRecoEngine() {
|
|
25
|
-
if (!recoEnginePromise) {
|
|
26
|
-
recoEnginePromise = import("snowbll-recommendation-engine").catch(() => {
|
|
27
|
-
throw new Error("The in-process recommendation engine is not available in this build.\n" +
|
|
28
|
-
"Set SNOWBLL_USE_MOCK=false (optionally with SNOWBLL_RECO_API_KEY) to use the live recommendation API instead.");
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
return recoEnginePromise;
|
|
32
|
-
}
|
|
33
|
-
export class CrossCheckClient {
|
|
34
|
-
useMock;
|
|
35
|
-
apiUrl;
|
|
36
|
-
apiKey;
|
|
37
|
-
constructor() {
|
|
38
|
-
// Mock is the default. Only the literal "false" opts into the live API.
|
|
39
|
-
this.useMock = process.env.SNOWBLL_USE_MOCK !== "false";
|
|
40
|
-
this.apiUrl = (process.env.SNOWBLL_RECO_API_URL ?? DEFAULT_API_URL).replace(/\/+$/, "");
|
|
41
|
-
this.apiKey = process.env.SNOWBLL_RECO_API_KEY;
|
|
42
|
-
}
|
|
43
|
-
/** "mock" when running the engine in-process, "live" when calling the REST API. */
|
|
44
|
-
get mode() {
|
|
45
|
-
return this.useMock ? "mock" : "live";
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Cross-check the two engines for an objective. In mock mode the reconciliation
|
|
49
|
-
* runs in-process; in live mode it POSTs to /recommend/consensus.
|
|
50
|
-
*/
|
|
51
|
-
async crossCheckRecommendations(args) {
|
|
52
|
-
if (this.useMock) {
|
|
53
|
-
const engine = await loadRecoEngine();
|
|
54
|
-
return engine.crossCheckRecommendations({
|
|
55
|
-
objective: args.objective,
|
|
56
|
-
limit: args.limit,
|
|
57
|
-
includeCommunity: args.includeCommunity,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
const headers = { "content-type": "application/json" };
|
|
61
|
-
if (this.apiKey)
|
|
62
|
-
headers.authorization = `Bearer ${this.apiKey}`;
|
|
63
|
-
let res;
|
|
64
|
-
try {
|
|
65
|
-
res = await fetch(`${this.apiUrl}/recommend/consensus`, {
|
|
66
|
-
method: "POST",
|
|
67
|
-
headers,
|
|
68
|
-
body: JSON.stringify(args),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
catch (cause) {
|
|
72
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
73
|
-
throw new Error(`Failed to reach the Snowbll recommendation API at ${this.apiUrl}/recommend/consensus: ${message}`);
|
|
74
|
-
}
|
|
75
|
-
if (!res.ok) {
|
|
76
|
-
const detail = await res.text().catch(() => "");
|
|
77
|
-
throw new Error(`Snowbll recommendation API error ${res.status} ${res.statusText} for /recommend/consensus` +
|
|
78
|
-
(detail ? `: ${detail}` : ""));
|
|
79
|
-
}
|
|
80
|
-
return (await res.json());
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
//# sourceMappingURL=crossCheckClient.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"crossCheckClient.js","sourceRoot":"","sources":["../src/crossCheckClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAIrD,IAAI,iBAAwD,CAAC;AAE7D,SAAS,cAAc;IACrB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,iBAAiB,GAAG,MAAM,CAAC,+BAA+B,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACrE,MAAM,IAAI,KAAK,CACb,wEAAwE;gBACtE,+GAA+G,CAClH,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAQD,MAAM,OAAO,gBAAgB;IACV,OAAO,CAAU;IACjB,MAAM,CAAS;IACf,MAAM,CAAqB;IAE5C;QACE,wEAAwE;QACxE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,OAAO,CAAC;QACxD,IAAI,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACxF,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IACjD,CAAC;IAED,mFAAmF;IACnF,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IACxC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,yBAAyB,CAAC,IAAoB;QAClD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAC;YACtC,OAAO,MAAM,CAAC,yBAAyB,CAAC;gBACtC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;aACxC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAA2B,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;QAC/E,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,aAAa,GAAG,UAAU,IAAI,CAAC,MAAM,EAAE,CAAC;QAEjE,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,sBAAsB,EAAE;gBACtD,MAAM,EAAE,MAAM;gBACd,OAAO;gBACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC3B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,MAAM,IAAI,KAAK,CACb,qDAAqD,IAAI,CAAC,MAAM,yBAAyB,OAAO,EAAE,CACnG,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAChD,MAAM,IAAI,KAAK,CACb,oCAAoC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,2BAA2B;gBACzF,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAChC,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoB,CAAC;IAC/C,CAAC;CACF"}
|