yahoo-fantasy-baseball-mcp 0.4.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +180 -0
  3. package/dist/auth.d.ts +12 -0
  4. package/dist/auth.js +71 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/callbackServer.d.ts +10 -0
  7. package/dist/callbackServer.js +108 -0
  8. package/dist/callbackServer.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +93 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/config.d.ts +54 -0
  13. package/dist/config.js +72 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/server.d.ts +8 -0
  16. package/dist/server.js +88 -0
  17. package/dist/server.js.map +1 -0
  18. package/dist/session.d.ts +61 -0
  19. package/dist/session.js +207 -0
  20. package/dist/session.js.map +1 -0
  21. package/dist/statsClient.d.ts +26 -0
  22. package/dist/statsClient.js +158 -0
  23. package/dist/statsClient.js.map +1 -0
  24. package/dist/tokenManager.d.ts +35 -0
  25. package/dist/tokenManager.js +91 -0
  26. package/dist/tokenManager.js.map +1 -0
  27. package/dist/tools/analysis.d.ts +3 -0
  28. package/dist/tools/analysis.js +298 -0
  29. package/dist/tools/analysis.js.map +1 -0
  30. package/dist/tools/context.d.ts +37 -0
  31. package/dist/tools/context.js +46 -0
  32. package/dist/tools/context.js.map +1 -0
  33. package/dist/tools/mappers.d.ts +144 -0
  34. package/dist/tools/mappers.js +411 -0
  35. package/dist/tools/mappers.js.map +1 -0
  36. package/dist/tools/onboarding.d.ts +7 -0
  37. package/dist/tools/onboarding.js +149 -0
  38. package/dist/tools/onboarding.js.map +1 -0
  39. package/dist/tools/read.d.ts +9 -0
  40. package/dist/tools/read.js +350 -0
  41. package/dist/tools/read.js.map +1 -0
  42. package/dist/tools/write.d.ts +7 -0
  43. package/dist/tools/write.js +174 -0
  44. package/dist/tools/write.js.map +1 -0
  45. package/dist/util.d.ts +9 -0
  46. package/dist/util.js +21 -0
  47. package/dist/util.js.map +1 -0
  48. package/dist/yahooClient.d.ts +29 -0
  49. package/dist/yahooClient.js +103 -0
  50. package/dist/yahooClient.js.map +1 -0
  51. package/package.json +49 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dingyiyi0226
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 ADDED
@@ -0,0 +1,180 @@
1
+ # Fantasy Baseball for Claude
2
+
3
+ A **Claude Desktop Extension** — drag the `.mcpb` file into Claude and you're done. No npm, no terminal, no config files.
4
+
5
+ Let **Claude** look after your **Yahoo Fantasy Baseball** team: check your roster, scout free agents, review your matchup, and more. Everything runs on your own computer with your own Yahoo access.
6
+
7
+ > **Note:** Adding/dropping players and setting lineups are not supported — Yahoo has deprecated the write-scope Fantasy Sports API ([yfpy#79](https://github.com/uberfastman/yfpy/issues/79)).
8
+
9
+ ---
10
+
11
+ ## Part 1 · Install
12
+
13
+ 1. **Download** `yahoo-fantasy-baseball-vX.X.X.mcpb` from the **[Releases page](../../releases/latest)**.
14
+ 2. **Open Claude Desktop** — get it at [claude.ai/download](https://claude.ai/download) if needed.
15
+ 3. Go to **Settings → Extensions**.
16
+ 4. **Drag the `.mcpb` file** into the Extensions window and click **Install**.
17
+
18
+ > Leave the **Client ID / Client Secret** boxes empty for now — you'll fill them in during setup.
19
+
20
+ ## Part 2 · Connect your Yahoo team
21
+
22
+ You only do this once. In a chat with Claude, type `fantasy start` and follow the prompts.
23
+
24
+ ### a) Create your free Yahoo app
25
+
26
+ Go to **[developer.yahoo.com/apps/create](https://developer.yahoo.com/apps/create/)** and fill in:
27
+
28
+ - **Application Name:** anything (e.g. "My Fantasy Helper")
29
+ - **Homepage URL:** `https://localhost:8488`
30
+ - **Redirect URI(s):** `https://localhost:8488/callback`
31
+ - **OAuth Client Type:** Confidential Client
32
+ - **API Permissions:** Fantasy Sports → Read
33
+ - Click **Create App**
34
+
35
+ Yahoo gives you a **Client ID** and **Client Secret**.
36
+
37
+ ### b) Give those values to Claude
38
+
39
+ Paste them into the chat, or enter them in **Settings → Extensions → Yahoo Fantasy Baseball**, then say `fantasy start` again.
40
+
41
+ ### c) Authorize and finish
42
+
43
+ Click the link Claude gives you, then click **Agree**. If your browser warns about a self-signed certificate, click **Advanced → Proceed to localhost**. Once you see **"Authorization complete!"**, say `fantasy authorize`. Claude will find your leagues, set your team as default, and you're ready. 🎉
44
+
45
+ > Stuck? Type `fantasy status` and Claude will tell you what's left.
46
+
47
+ ---
48
+
49
+ ## Part 3 · Talk to your team
50
+
51
+ | Say this… | …and Claude will |
52
+ | --- | --- |
53
+ | `fantasy show roster` | Show your current roster |
54
+ | `fantasy my matchup` | Summarize this week's matchup |
55
+ | `fantasy standings` | Show league standings |
56
+ | `fantasy who should I add` | Find the best free agents |
57
+ | `fantasy my stats this week` | Show your team's weekly totals |
58
+ | `fantasy recent moves` | List recent adds, drops, and trades |
59
+ | `analyze Shohei Ohtani` | Pull Statcast, xStats, FanGraphs WAR/wRC+, and recent splits |
60
+ | `analyze my roster` | Run the above for every player on your roster |
61
+
62
+ You can also ask naturally: *"Who on my bench should I start tonight?"* or *"Is there a better closer available?"*
63
+
64
+ ### What "analyze" pulls
65
+
66
+ | Source | Stats |
67
+ | --- | --- |
68
+ | **MLB Stats API** | Season totals + 14-day & 30-day splits |
69
+ | **Baseball Savant** | xBA, xSLG, xwOBA vs. actual; exit velocity, barrel %, hard-hit % |
70
+ | **FanGraphs** | WAR, wRC+/FIP/xFIP, K%, BB%, SwStr%, GB/FB% |
71
+
72
+ Analysis automatically targets your **league's scoring categories**. Results include links to each player's page on Baseball Savant, MLB.com, Baseball Reference, and FanGraphs. Leaderboard data is cached for one hour.
73
+
74
+ ---
75
+
76
+ ## FAQ
77
+
78
+ **Is my data safe?** Your Yahoo keys are stored only on your computer (`~/.yahoo-fantasy-mcp/config.json`) and in your OS keychain. Nothing is sent anywhere but Yahoo's API.
79
+
80
+ **Why do I need my own Yahoo app?** Yahoo requires each person to use their own keys — no shared secrets.
81
+
82
+ **Rate limit errors?** Yahoo limits heavy use. Wait an hour and try again.
83
+
84
+ **Does this cost anything?** No. Both the Yahoo app and this extension are free.
85
+
86
+ **`analyze` commands fail with a connection error?** Claude may need explicit permission to reach the stats APIs. Go to **Settings → Capabilities** (Team/Enterprise: **Organization settings → Capabilities**) and add these under **Additional allowed domains**:
87
+
88
+ | Domain | Used for |
89
+ | --- | --- |
90
+ | `statsapi.mlb.com` | MLB Stats API |
91
+ | `baseballsavant.mlb.com` | Statcast / expected stats |
92
+ | `www.fangraphs.com` | FanGraphs WAR/wRC+ |
93
+
94
+ ---
95
+
96
+ ## For developers
97
+
98
+ Local MCP server (Node.js + TypeScript, stdio transport) for the Yahoo Fantasy Sports v2 API.
99
+
100
+ ```bash
101
+ npm install
102
+ npm run build # compile TypeScript → dist/
103
+ node dist/cli.js auth # optional terminal auth
104
+ node dist/cli.js serve # run MCP server over stdio
105
+ npm run pack # build .mcpb bundle
106
+ ```
107
+
108
+ ### Use with Claude Code or any MCP client
109
+
110
+ ```bash
111
+ claude mcp add yahoo-fantasy-baseball \
112
+ -e YF_CLIENT_ID=your_id -e YF_CLIENT_SECRET=your_secret \
113
+ -- node /absolute/path/to/dist/cli.js serve
114
+ ```
115
+
116
+ Or add to `claude_desktop_config.json`:
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "yahoo-fantasy-baseball": {
122
+ "command": "node",
123
+ "args": ["/absolute/path/to/dist/cli.js", "serve"],
124
+ "env": { "YF_CLIENT_ID": "your_id", "YF_CLIENT_SECRET": "your_secret" }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Use with OpenAI Codex
131
+
132
+ Codex has no one-click bundle like Claude's `.mcpb`. The server is the same; only how you register it differs. No clone or build needed — `npx` fetches the published package. After registering, restart Codex and run `fantasy start` to connect your Yahoo team.
133
+
134
+ > **Requires Node.js 18+** (it provides `npx`). Install it from [nodejs.org/download](https://nodejs.org/en/download) — or on macOS just run `brew install node`. Unlike Claude Desktop, which bundles a Node runtime, Codex launches the command in your own environment, so Node must be installed.
135
+
136
+ **Desktop app** — Settings → MCP → **Connect to a custom MCP**, choose **STDIO**, and fill in:
137
+
138
+ | Field | Value |
139
+ | --- | --- |
140
+ | **Name** | `yahoo-fantasy-baseball` |
141
+ | **Command to launch** | `npx` |
142
+ | **Arguments** | `-y`, then `yahoo-fantasy-baseball-mcp`, then `serve` — one per "Add argument" |
143
+ | **Environment variables** | `YF_CLIENT_ID` / `YF_CLIENT_SECRET` (optional — or authorize in-chat) |
144
+ | **Working directory** | leave blank |
145
+
146
+ > **"Command not found"?** GUI apps don't always inherit your shell `PATH`. Run `which npx` in a terminal and paste that absolute path into "Command to launch."
147
+
148
+ **CLI / IDE extension** — add to `~/.codex/config.toml` (`/mcp` confirms the connection):
149
+
150
+ ```toml
151
+ [mcp_servers.yahoo-fantasy-baseball]
152
+ command = "npx"
153
+ args = ["-y", "yahoo-fantasy-baseball-mcp", "serve"]
154
+
155
+ [mcp_servers.yahoo-fantasy-baseball.env]
156
+ YF_CLIENT_ID = "your_id"
157
+ YF_CLIENT_SECRET = "your_secret"
158
+ ```
159
+
160
+ > **From a local checkout** instead of npm: run `npm install && npm run build`, then use `node` as the command with `/absolute/path/to/dist/cli.js serve` as the arguments.
161
+
162
+ A copy-paste version lives in [`codex.config.example.toml`](codex.config.example.toml).
163
+
164
+ ### Tools
165
+
166
+ **Onboarding:** `fantasy_status`, `fantasy_login`, `fantasy_authorize`, `fantasy_logout`, `fantasy_select_team`
167
+
168
+ **Read:** `list_leagues`, `get_league`, `get_standings`, `get_teams`, `get_roster`, `get_roster_stats`, `get_team_stats_week`, `get_team_stats_season`, `get_matchups`, `get_team_matchups`, `get_player_stats`, `list_players`, `rank_players`, `search_players`, `get_league_scoring_categories`, `get_transactions`
169
+
170
+ Several tools come in a light/detailed pair — prefer the lighter one unless stats are needed: `get_standings` vs `get_teams` (+matchups), `get_roster` vs `get_roster_stats` (+stats), `list_players` vs `rank_players` (+stats).
171
+
172
+ **Analysis** (no Yahoo auth — public APIs): `analyze_player_stats`, `analyze_roster_stats` (accepts optional `playerKeys` array, max 10 per call)
173
+
174
+ **Write** (deprecated by Yahoo — require `force: true`): `add_drop_player`, `set_lineup`
175
+
176
+ Credentials resolve from the saved config or `YF_CLIENT_ID` / `YF_CLIENT_SECRET` env vars. Releases are published automatically by GitHub Actions on a `v*` tag.
177
+
178
+ ## License
179
+
180
+ MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export interface AuthFlags {
2
+ clientId?: string;
3
+ clientSecret?: string;
4
+ }
5
+ /**
6
+ * One-time interactive terminal setup. The same flow is also available entirely
7
+ * inside Claude via the `fantasy_*` tools; this command exists for users who
8
+ * prefer the shell (e.g. Claude Code). Opens a local callback server on
9
+ * localhost:8488, prints the Yahoo authorization URL, then waits for the browser
10
+ * redirect to capture the code automatically.
11
+ */
12
+ export declare function runAuth(flags: AuthFlags): Promise<void>;
package/dist/auth.js ADDED
@@ -0,0 +1,71 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin, stdout } from "node:process";
3
+ import { CONFIG_PATH } from "./config.js";
4
+ import { Session } from "./session.js";
5
+ /**
6
+ * One-time interactive terminal setup. The same flow is also available entirely
7
+ * inside Claude via the `fantasy_*` tools; this command exists for users who
8
+ * prefer the shell (e.g. Claude Code). Opens a local callback server on
9
+ * localhost:8488, prints the Yahoo authorization URL, then waits for the browser
10
+ * redirect to capture the code automatically.
11
+ */
12
+ export async function runAuth(flags) {
13
+ const rl = readline.createInterface({ input: stdin, output: stdout });
14
+ try {
15
+ const session = new Session();
16
+ await session.init();
17
+ const clientId = flags.clientId ||
18
+ process.env.YF_CLIENT_ID ||
19
+ (await rl.question("Yahoo Client ID (Consumer Key): ")).trim();
20
+ const clientSecret = flags.clientSecret ||
21
+ process.env.YF_CLIENT_SECRET ||
22
+ (await rl.question("Yahoo Client Secret (Consumer Secret): ")).trim();
23
+ if (!clientId || !clientSecret) {
24
+ throw new Error("Both Client ID and Client Secret are required.");
25
+ }
26
+ await session.setCredentials(clientId, clientSecret);
27
+ console.log("\n── Step 1: Authorize ──────────────────────────────────────");
28
+ console.log("Open this URL in your browser, sign in, and allow access:\n");
29
+ console.log(` ${session.authorizeUrl()}\n`);
30
+ console.log("Waiting for Yahoo to redirect to localhost… (up to 5 minutes)");
31
+ const choices = await session.completeAuthorization();
32
+ if (choices.length === 0) {
33
+ console.log("\nNo baseball leagues found for this account. Saved your credentials " +
34
+ `to ${CONFIG_PATH}; you can set a default league/team later.`);
35
+ return;
36
+ }
37
+ console.log("\n── Step 2: Pick your default league ──────────────────────");
38
+ choices.forEach((c, i) => printChoice(c, i));
39
+ const pick = await promptIndex(rl, "\nSelect your default league", choices.length);
40
+ const chosen = choices[pick];
41
+ await session.setDefaults(chosen.leagueKey, chosen.teamKey);
42
+ console.log("\n── Done ──────────────────────────────────────────────────");
43
+ console.log(`Saved configuration to ${CONFIG_PATH}`);
44
+ console.log(` default league: ${chosen.leagueName} (${chosen.leagueKey})`);
45
+ if (chosen.teamKey) {
46
+ console.log(` default team: ${chosen.teamName} (${chosen.teamKey})`);
47
+ }
48
+ else {
49
+ console.log(" (no owned team detected in this league)");
50
+ }
51
+ console.log("\nYou can now run the MCP server (`serve`) and connect it to Claude.");
52
+ }
53
+ finally {
54
+ rl.close();
55
+ }
56
+ }
57
+ function printChoice(c, i) {
58
+ const team = c.teamName ? ` — your team: ${c.teamName}` : "";
59
+ console.log(` [${i + 1}] ${c.leagueName} (${c.season})${team}`);
60
+ console.log(` league_key=${c.leagueKey}${c.teamKey ? ` team_key=${c.teamKey}` : ""}`);
61
+ }
62
+ async function promptIndex(rl, label, count) {
63
+ while (true) {
64
+ const answer = (await rl.question(`${label} [1-${count}]: `)).trim();
65
+ const n = Number(answer);
66
+ if (Number.isInteger(n) && n >= 1 && n <= count)
67
+ return n - 1;
68
+ console.log(`Please enter a number between 1 and ${count}.`);
69
+ }
70
+ }
71
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare const REDIRECT_URI = "https://localhost:8488/callback";
2
+ /**
3
+ * Starts a temporary HTTPS server on localhost that captures Yahoo's OAuth redirect.
4
+ * Resolves with the authorization code, or rejects on error or 5-minute timeout.
5
+ * The server shuts itself down as soon as it receives the callback.
6
+ *
7
+ * Uses a self-signed TLS certificate. The browser will show a security warning —
8
+ * the user must click "Advanced" → "Proceed to localhost" to continue.
9
+ */
10
+ export declare function startCallbackServer(): Promise<string>;
@@ -0,0 +1,108 @@
1
+ import * as https from "node:https";
2
+ import { generate as generateCert } from "selfsigned";
3
+ const PORT = 8488;
4
+ export const REDIRECT_URI = `https://localhost:${PORT}/callback`;
5
+ const TIMEOUT_MS = 5 * 60 * 1000;
6
+ // Generated once per process; kept in memory only.
7
+ let tlsCreds = null;
8
+ async function getTlsCreds() {
9
+ if (!tlsCreds) {
10
+ const result = await generateCert([{ name: "commonName", value: "localhost" }], {
11
+ algorithm: "sha256",
12
+ notAfterDate: new Date(Date.now() + 24 * 60 * 60 * 1000),
13
+ });
14
+ tlsCreds = { key: result.private, cert: result.cert };
15
+ }
16
+ return tlsCreds;
17
+ }
18
+ const SUCCESS_HTML = `<!DOCTYPE html>
19
+ <html>
20
+ <head><meta charset="utf-8"><title>Authorization complete</title>
21
+ <style>body{font-family:sans-serif;max-width:480px;margin:80px auto;text-align:center}
22
+ h2{color:#1a7f37}p{color:#444;line-height:1.6}code{background:#f4f4f4;padding:2px 6px;border-radius:3px}</style>
23
+ </head>
24
+ <body>
25
+ <h2>Authorization complete!</h2>
26
+ <p>Yahoo has connected your account.</p>
27
+ <p>Go back to Claude and say <code>fantasy authorize</code> to finish setup.</p>
28
+ </body>
29
+ </html>`;
30
+ function errorHtml(detail) {
31
+ return `<!DOCTYPE html>
32
+ <html>
33
+ <head><meta charset="utf-8"><title>Authorization failed</title>
34
+ <style>body{font-family:sans-serif;max-width:480px;margin:80px auto;text-align:center}
35
+ h2{color:#d1242f}p{color:#444;line-height:1.6}code{background:#f4f4f4;padding:2px 6px;border-radius:3px}</style>
36
+ </head>
37
+ <body>
38
+ <h2>Authorization failed</h2>
39
+ <p>${detail}</p>
40
+ <p>Go back to Claude and say <code>fantasy start</code> to try again.</p>
41
+ </body>
42
+ </html>`;
43
+ }
44
+ /**
45
+ * Starts a temporary HTTPS server on localhost that captures Yahoo's OAuth redirect.
46
+ * Resolves with the authorization code, or rejects on error or 5-minute timeout.
47
+ * The server shuts itself down as soon as it receives the callback.
48
+ *
49
+ * Uses a self-signed TLS certificate. The browser will show a security warning —
50
+ * the user must click "Advanced" → "Proceed to localhost" to continue.
51
+ */
52
+ export async function startCallbackServer() {
53
+ const tls = await getTlsCreds();
54
+ return new Promise((resolve, reject) => {
55
+ const server = https.createServer(tls, (req, res) => {
56
+ let url;
57
+ try {
58
+ url = new URL(req.url ?? "/", `https://localhost:${PORT}`);
59
+ }
60
+ catch {
61
+ res.writeHead(400);
62
+ res.end("Bad request");
63
+ return;
64
+ }
65
+ if (url.pathname !== "/callback") {
66
+ res.writeHead(404);
67
+ res.end("Not found");
68
+ return;
69
+ }
70
+ const code = url.searchParams.get("code");
71
+ const error = url.searchParams.get("error");
72
+ if (error) {
73
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
74
+ res.end(errorHtml(`Yahoo returned an error: ${error}`));
75
+ server.close();
76
+ reject(new Error(`Yahoo authorization error: ${error}`));
77
+ return;
78
+ }
79
+ if (!code) {
80
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
81
+ res.end(errorHtml("No authorization code was received."));
82
+ server.close();
83
+ reject(new Error("No authorization code in callback URL"));
84
+ return;
85
+ }
86
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
87
+ res.end(SUCCESS_HTML);
88
+ server.close();
89
+ clearTimeout(timeout);
90
+ resolve(code);
91
+ });
92
+ server.listen(PORT, "127.0.0.1");
93
+ server.on("error", (err) => {
94
+ if (err.code === "EADDRINUSE") {
95
+ reject(new Error(`Port ${PORT} is already in use. Close whatever is using it and try again.`));
96
+ }
97
+ else {
98
+ reject(new Error(`Callback server error: ${err.message}`));
99
+ }
100
+ });
101
+ const timeout = setTimeout(() => {
102
+ server.close();
103
+ reject(new Error("Authorization timed out after 5 minutes. Say `fantasy start` to try again."));
104
+ }, TIMEOUT_MS);
105
+ timeout.unref();
106
+ });
107
+ }
108
+ //# sourceMappingURL=callbackServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callbackServer.js","sourceRoot":"","sources":["../src/callbackServer.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ import { writeSync } from "node:fs";
3
+ import { runAuth } from "./auth.js";
4
+ import { runServer } from "./server.js";
5
+ /**
6
+ * Write to stderr synchronously. On Windows, writes to a pipe (which is how the
7
+ * MCP host captures our stderr) are asynchronous, so the usual
8
+ * `console.error(msg); process.exit(1)` loses the message when the process exits
9
+ * before the write flushes — leaving startup crashes invisible in the host log.
10
+ * fs.writeSync to fd 2 is synchronous on every platform.
11
+ */
12
+ function logError(prefix, err) {
13
+ const detail = err instanceof Error ? (err.stack ?? err.message) : String(err);
14
+ try {
15
+ writeSync(2, `[yahoo-fantasy-mcp] ${prefix}: ${detail}\n`);
16
+ }
17
+ catch {
18
+ // Nothing more we can do if even stderr is gone.
19
+ }
20
+ }
21
+ // Surface otherwise-silent startup failures (e.g. a dependency that fails to
22
+ // load, or a rejected promise during boot) instead of exiting with no output.
23
+ process.on("uncaughtException", (err) => {
24
+ logError("uncaughtException", err);
25
+ process.exit(1);
26
+ });
27
+ process.on("unhandledRejection", (err) => {
28
+ logError("unhandledRejection", err);
29
+ process.exit(1);
30
+ });
31
+ const HELP = `yahoo-fantasy-baseball-mcp — local MCP server for Yahoo Fantasy Baseball
32
+
33
+ Usage:
34
+ yahoo-fantasy-mcp auth [--client-id <id>] [--client-secret <secret>]
35
+ One-time interactive setup: authorize with Yahoo and save your config.
36
+ Credentials may also come from YF_CLIENT_ID / YF_CLIENT_SECRET env vars.
37
+
38
+ yahoo-fantasy-mcp serve Run the MCP server over stdio (default).
39
+ yahoo-fantasy-mcp --help Show this help.
40
+ `;
41
+ /** Minimal flag parser: supports "--key value" and "--key=value". */
42
+ function parseFlags(args) {
43
+ const flags = {};
44
+ for (let i = 0; i < args.length; i++) {
45
+ const arg = args[i];
46
+ if (!arg.startsWith("--"))
47
+ continue;
48
+ const eq = arg.indexOf("=");
49
+ if (eq !== -1) {
50
+ flags[arg.slice(2, eq)] = arg.slice(eq + 1);
51
+ }
52
+ else {
53
+ const next = args[i + 1];
54
+ if (next && !next.startsWith("--")) {
55
+ flags[arg.slice(2)] = next;
56
+ i++;
57
+ }
58
+ else {
59
+ flags[arg.slice(2)] = "true";
60
+ }
61
+ }
62
+ }
63
+ return flags;
64
+ }
65
+ async function main() {
66
+ const [, , rawCommand, ...rest] = process.argv;
67
+ const command = rawCommand ?? "serve";
68
+ if (command === "-h" || command === "--help" || command === "help") {
69
+ process.stdout.write(HELP);
70
+ return;
71
+ }
72
+ switch (command) {
73
+ case "auth": {
74
+ const flags = parseFlags(rest);
75
+ await runAuth({
76
+ clientId: flags["client-id"],
77
+ clientSecret: flags["client-secret"],
78
+ });
79
+ break;
80
+ }
81
+ case "serve":
82
+ await runServer();
83
+ break;
84
+ default:
85
+ process.stderr.write(`Unknown command: ${command}\n\n${HELP}`);
86
+ process.exit(1);
87
+ }
88
+ }
89
+ main().catch((err) => {
90
+ logError("fatal", err);
91
+ process.exit(1);
92
+ });
93
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /** A single scored stat category from a Yahoo Fantasy league. */
2
+ export interface ScoringCategory {
3
+ statId: string;
4
+ displayName: string;
5
+ /** "B" = batting, "P" = pitching, "" = unknown */
6
+ positionType: string;
7
+ }
8
+ /**
9
+ * Persistent, per-user configuration. Stored at ~/.yahoo-fantasy-mcp/config.json
10
+ * with 0600 permissions. Holds the user's own Yahoo app credentials, a long-lived
11
+ * refresh token, and the default league/team to operate on.
12
+ *
13
+ * Every field is optional because setup happens incrementally (and credentials
14
+ * may instead arrive via environment variables, e.g. from the desktop extension
15
+ * settings form). Nothing here is ever shared or hosted: each user runs their
16
+ * own server locally.
17
+ */
18
+ export interface Config {
19
+ clientId?: string;
20
+ clientSecret?: string;
21
+ refreshToken?: string;
22
+ defaultLeagueKey?: string;
23
+ defaultTeamKey?: string;
24
+ /** Scoring categories per league, keyed by leagueKey. Cached because they rarely change. */
25
+ scoringCategories?: Record<string, ScoringCategory[]>;
26
+ }
27
+ /** A resolved Yahoo app credential pair (from config file or environment). */
28
+ export interface Credentials {
29
+ clientId: string;
30
+ clientSecret: string;
31
+ }
32
+ export declare const CONFIG_DIR: string;
33
+ export declare const CONFIG_PATH: string;
34
+ /**
35
+ * Load the saved config, or null when no file exists yet. Does not throw on
36
+ * missing fields — callers decide what is "configured enough" to operate.
37
+ */
38
+ export declare function loadConfig(): Promise<Config | null>;
39
+ /**
40
+ * Resolve the Yahoo app credentials, preferring the saved config and falling
41
+ * back to YF_CLIENT_ID / YF_CLIENT_SECRET (set by the desktop extension form or
42
+ * the shell). Returns null when either half is missing.
43
+ */
44
+ export declare function resolveCredentials(config: Config | null): Credentials | null;
45
+ /**
46
+ * Write the config, creating the directory and locking the file to 0600 so the
47
+ * stored credentials are only readable by the current user.
48
+ */
49
+ export declare function saveConfig(config: Config): Promise<void>;
50
+ /**
51
+ * Merge a partial update into the existing config (or an empty one) and persist
52
+ * it. Used during incremental setup and to store a rotated refresh token.
53
+ */
54
+ export declare function updateConfig(patch: Partial<Config>): Promise<Config>;
package/dist/config.js ADDED
@@ -0,0 +1,72 @@
1
+ import { promises as fs } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ export const CONFIG_DIR = path.join(os.homedir(), ".yahoo-fantasy-mcp");
5
+ export const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
6
+ /**
7
+ * Load the saved config, or null when no file exists yet. Does not throw on
8
+ * missing fields — callers decide what is "configured enough" to operate.
9
+ */
10
+ export async function loadConfig() {
11
+ let raw;
12
+ try {
13
+ raw = await fs.readFile(CONFIG_PATH, "utf8");
14
+ }
15
+ catch (err) {
16
+ if (err.code === "ENOENT")
17
+ return null;
18
+ throw err;
19
+ }
20
+ try {
21
+ return JSON.parse(raw);
22
+ }
23
+ catch {
24
+ throw new Error(`Config at ${CONFIG_PATH} is not valid JSON. Delete it and run setup again.`);
25
+ }
26
+ }
27
+ /**
28
+ * Resolve the Yahoo app credentials, preferring the saved config and falling
29
+ * back to YF_CLIENT_ID / YF_CLIENT_SECRET (set by the desktop extension form or
30
+ * the shell). Returns null when either half is missing.
31
+ */
32
+ export function resolveCredentials(config) {
33
+ const clientId = clean(config?.clientId) ?? clean(process.env.YF_CLIENT_ID);
34
+ const clientSecret = clean(config?.clientSecret) ?? clean(process.env.YF_CLIENT_SECRET);
35
+ if (clientId && clientSecret)
36
+ return { clientId, clientSecret };
37
+ return null;
38
+ }
39
+ /**
40
+ * Treat blank or unsubstituted-template values as missing. When the desktop
41
+ * extension is installed without filling the settings form, the client may pass
42
+ * the literal "${user_config.client_id}" as the env value.
43
+ */
44
+ function clean(value) {
45
+ if (!value)
46
+ return undefined;
47
+ const trimmed = value.trim();
48
+ if (!trimmed || trimmed.includes("${"))
49
+ return undefined;
50
+ return trimmed;
51
+ }
52
+ /**
53
+ * Write the config, creating the directory and locking the file to 0600 so the
54
+ * stored credentials are only readable by the current user.
55
+ */
56
+ export async function saveConfig(config) {
57
+ await fs.mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
58
+ await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
59
+ // writeFile only applies mode when creating; enforce it on existing files too.
60
+ await fs.chmod(CONFIG_PATH, 0o600);
61
+ }
62
+ /**
63
+ * Merge a partial update into the existing config (or an empty one) and persist
64
+ * it. Used during incremental setup and to store a rotated refresh token.
65
+ */
66
+ export async function updateConfig(patch) {
67
+ const current = (await loadConfig()) ?? {};
68
+ const next = { ...current, ...patch };
69
+ await saveConfig(next);
70
+ return next;
71
+ }
72
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAoC7B,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,oBAAoB,CAAC,CAAC;AACxE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEhE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAW,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,aAAa,WAAW,oDAAoD,CAC7E,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAqB;IACtD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5E,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACxF,IAAI,QAAQ,IAAI,YAAY;QAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,KAAK,CAAC,KAAyB;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACzD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc;IAC7C,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClF,+EAA+E;IAC/E,MAAM,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAsB;IACvD,MAAM,OAAO,GAAG,CAAC,MAAM,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC;IAC3C,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;IACtC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC"}