@semanticintent/semantic-chirp-intelligence-mcp 3.0.0 → 4.0.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/.chirp-data/opponent.json +24 -0
- package/.chirp-data/roster.json +34 -0
- package/.chirp-data/standings.json +17 -0
- package/.nhl-schedule-cache/20262027.json +1 -0
- package/.nhl-schedule-cache/players-20262027-20252026.json +1 -0
- package/README.md +129 -79
- package/build/analyses/BreakoutAnalysis.js +25 -36
- package/build/analyses/DraftPickAnalysis.js +431 -0
- package/build/analyses/GamesInHandAnalysis.js +52 -121
- package/build/analyses/IceAnalysis.js +66 -57
- package/build/analyses/LineupAnalysis.js +26 -46
- package/build/analyses/ScheduleValueAnalysis.js +279 -0
- package/build/analyses/StreamingAnalysis.js +40 -76
- package/build/analyses/WeekendStreamAnalysis.js +130 -66
- package/build/config/tool-metadata.js +68 -0
- package/build/domain/nhl-teams.js +67 -0
- package/build/index.js +619 -495
- package/build/services/LeagueDataService.js +130 -0
- package/build/services/NhlScheduleService.js +357 -0
- package/build/services/NhlStatsService.js +297 -0
- package/build/services/RosterStore.js +233 -0
- package/package.json +16 -17
- package/scripts/preflight.mjs +67 -0
- package/scripts/smoke.mjs +92 -0
- package/.env.example +0 -8
- package/authenticate.js +0 -207
- package/build/experimental/semantic-breakout-tool.js +0 -188
- package/build/experimental/semantic-intent-parser.js +0 -222
- package/build/experimental/semantic-tool-integration.js +0 -146
- package/build/experimental/test-parser.js +0 -61
- package/build/services/YahooApiClient.js +0 -309
package/build/index.js
CHANGED
|
@@ -7,41 +7,28 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
// Node.js
|
|
10
|
-
import * as fs from "fs";
|
|
11
10
|
import * as path from "path";
|
|
11
|
+
import { readFileSync } from "fs";
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
|
-
import * as dotenv from "dotenv";
|
|
14
|
-
import https from "https";
|
|
15
|
-
import { parseString } from "xml2js";
|
|
16
|
-
import { promisify } from "util";
|
|
17
13
|
import { GOVERNANCE_MONITOR, validateSemanticChirpContract, auditSemanticContract, checkGovernanceHealth } from './domain/governance.js';
|
|
18
14
|
// Config layer
|
|
19
15
|
import { CHIRP_STYLES } from './config/chirp-styles.js';
|
|
20
16
|
import { PERSONALITY_MODES } from './config/personality-modes.js';
|
|
21
17
|
import { TOOL_METADATA } from './config/tool-metadata.js';
|
|
22
|
-
// Services layer
|
|
23
|
-
import { YahooApiClient } from './services/YahooApiClient.js';
|
|
24
18
|
// Analysis layer
|
|
25
19
|
import { IceAnalysis } from './analyses/IceAnalysis.js';
|
|
26
20
|
import { GamesInHandAnalysis } from './analyses/GamesInHandAnalysis.js';
|
|
27
21
|
import { StreamingAnalysis } from './analyses/StreamingAnalysis.js';
|
|
28
22
|
import { LineupAnalysis } from './analyses/LineupAnalysis.js';
|
|
29
23
|
import { WeekendStreamAnalysis } from './analyses/WeekendStreamAnalysis.js';
|
|
30
|
-
// Experimental: Semantic Intent Parser
|
|
31
|
-
import { SEMANTIC_PLAYER_COMPARISON, getPlayerComparisonInputSchema, executePlayerComparison } from './experimental/semantic-tool-integration.js';
|
|
32
|
-
import { SEMANTIC_BREAKOUT_ANALYSIS, getBreakoutAnalysisInputSchema, executeBreakoutAnalysis } from './experimental/semantic-breakout-tool.js';
|
|
33
24
|
import { BreakoutAnalysis } from './analyses/BreakoutAnalysis.js';
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
import { ScheduleValueAnalysis } from './analyses/ScheduleValueAnalysis.js';
|
|
26
|
+
import { DraftPickAnalysis } from './analyses/DraftPickAnalysis.js';
|
|
27
|
+
import { NHL_STATS } from './services/NhlStatsService.js';
|
|
28
|
+
import { ROSTER_STORE } from './services/RosterStore.js';
|
|
29
|
+
import { LEAGUE_DATA, NO_ROSTER_MESSAGE, NO_OPPONENT_MESSAGE } from './services/LeagueDataService.js';
|
|
30
|
+
import { NHL_SCHEDULE, NhlScheduleService } from './services/NhlScheduleService.js';
|
|
36
31
|
// Configuration
|
|
37
|
-
const YAHOO_CLIENT_ID = process.env.YAHOO_CLIENT_ID;
|
|
38
|
-
const YAHOO_CLIENT_SECRET = process.env.YAHOO_CLIENT_SECRET;
|
|
39
|
-
const LEAGUE_ID = process.env.YAHOO_LEAGUE_ID;
|
|
40
|
-
const TEAM_ID = process.env.YAHOO_TEAM_ID;
|
|
41
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
42
|
-
const __dirname = path.dirname(__filename);
|
|
43
|
-
const TOKEN_FILE = path.join(__dirname, "..", ".yahoo-oauth.json");
|
|
44
|
-
const YAHOO_API_BASE = "https://fantasysports.yahooapis.com/fantasy/v2";
|
|
45
32
|
// ==========================================
|
|
46
33
|
// 🔧 MCP Tool Schema Base
|
|
47
34
|
// ==========================================
|
|
@@ -65,101 +52,15 @@ const baseChirpSchema = {
|
|
|
65
52
|
// ==========================================
|
|
66
53
|
// 🏗️ Service Initialization
|
|
67
54
|
// ==========================================
|
|
68
|
-
// Initialize Yahoo API client
|
|
69
|
-
const yahooClient = new YahooApiClient(YAHOO_CLIENT_ID, YAHOO_CLIENT_SECRET, YAHOO_API_BASE);
|
|
70
55
|
// Initialize analysis instances
|
|
71
|
-
const iceAnalysis = new IceAnalysis(
|
|
72
|
-
const gamesInHandAnalysis = new GamesInHandAnalysis(
|
|
73
|
-
const streamingAnalysis = new StreamingAnalysis(
|
|
74
|
-
const lineupAnalysis = new LineupAnalysis(
|
|
75
|
-
const breakoutAnalysis = new BreakoutAnalysis(
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
console.error(`[DEBUG] Looking for token at: ${TOKEN_FILE}`);
|
|
81
|
-
if (fs.existsSync(TOKEN_FILE)) {
|
|
82
|
-
const tokenData = fs.readFileSync(TOKEN_FILE, "utf8");
|
|
83
|
-
const token = JSON.parse(tokenData);
|
|
84
|
-
console.error(`[DEBUG] Token loaded successfully`);
|
|
85
|
-
cachedToken = token;
|
|
86
|
-
return token;
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
console.error(`[DEBUG] Token file not found`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
console.error("[ERROR] Error loading token:", error);
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
function saveToken(token) {
|
|
98
|
-
token.expires_at = Date.now() + (token.expires_in * 1000);
|
|
99
|
-
fs.writeFileSync(TOKEN_FILE, JSON.stringify(token, null, 2));
|
|
100
|
-
cachedToken = token;
|
|
101
|
-
console.error("[DEBUG] Token saved successfully");
|
|
102
|
-
}
|
|
103
|
-
async function refreshAccessToken() {
|
|
104
|
-
const token = cachedToken || loadToken();
|
|
105
|
-
if (!token) {
|
|
106
|
-
throw new Error("No refresh token available. Please re-authenticate.");
|
|
107
|
-
}
|
|
108
|
-
console.error("[DEBUG] Refreshing access token...");
|
|
109
|
-
const tokenData = new URLSearchParams({
|
|
110
|
-
client_id: YAHOO_CLIENT_ID,
|
|
111
|
-
client_secret: YAHOO_CLIENT_SECRET,
|
|
112
|
-
redirect_uri: "oob",
|
|
113
|
-
refresh_token: token.refresh_token,
|
|
114
|
-
grant_type: "refresh_token",
|
|
115
|
-
});
|
|
116
|
-
const options = {
|
|
117
|
-
hostname: "api.login.yahoo.com",
|
|
118
|
-
port: 443,
|
|
119
|
-
path: "/oauth2/get_token",
|
|
120
|
-
method: "POST",
|
|
121
|
-
headers: {
|
|
122
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
123
|
-
"Content-Length": tokenData.toString().length,
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
return new Promise((resolve, reject) => {
|
|
127
|
-
const req = https.request(options, (res) => {
|
|
128
|
-
let data = "";
|
|
129
|
-
res.on("data", (chunk) => {
|
|
130
|
-
data += chunk;
|
|
131
|
-
});
|
|
132
|
-
res.on("end", () => {
|
|
133
|
-
try {
|
|
134
|
-
const newToken = JSON.parse(data);
|
|
135
|
-
saveToken(newToken);
|
|
136
|
-
console.error("[DEBUG] Token refreshed successfully");
|
|
137
|
-
resolve(newToken.access_token);
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
reject(new Error(`Failed to parse token response: ${error}`));
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
req.on("error", (error) => {
|
|
145
|
-
reject(error);
|
|
146
|
-
});
|
|
147
|
-
req.write(tokenData.toString());
|
|
148
|
-
req.end();
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
async function getValidAccessToken() {
|
|
152
|
-
const token = cachedToken || loadToken();
|
|
153
|
-
if (!token) {
|
|
154
|
-
throw new Error("No authentication token found! Run: node authenticate.js");
|
|
155
|
-
}
|
|
156
|
-
// Check if token is expired (with 5 minute buffer)
|
|
157
|
-
if (token.expires_at && token.expires_at < Date.now() + 300000) {
|
|
158
|
-
console.error("[DEBUG] Token expired or expiring soon, refreshing...");
|
|
159
|
-
return await refreshAccessToken();
|
|
160
|
-
}
|
|
161
|
-
return token.access_token;
|
|
162
|
-
}
|
|
56
|
+
const iceAnalysis = new IceAnalysis();
|
|
57
|
+
const gamesInHandAnalysis = new GamesInHandAnalysis();
|
|
58
|
+
const streamingAnalysis = new StreamingAnalysis();
|
|
59
|
+
const lineupAnalysis = new LineupAnalysis();
|
|
60
|
+
const breakoutAnalysis = new BreakoutAnalysis();
|
|
61
|
+
const scheduleValueAnalysis = new ScheduleValueAnalysis();
|
|
62
|
+
const draftPickAnalysis = new DraftPickAnalysis();
|
|
63
|
+
const weekendStreamAnalysis = new WeekendStreamAnalysis();
|
|
163
64
|
// Helper function to find current matchup by status
|
|
164
65
|
function findCurrentMatchup(matchups) {
|
|
165
66
|
console.error('[DEBUG] findCurrentMatchup called');
|
|
@@ -196,282 +97,167 @@ function findCurrentMatchup(matchups) {
|
|
|
196
97
|
return null;
|
|
197
98
|
return matchups[lastKey].matchup;
|
|
198
99
|
}
|
|
199
|
-
// Yahoo API helper function
|
|
200
|
-
async function yahooApiRequest(endpoint, format = "json") {
|
|
201
|
-
const accessToken = await getValidAccessToken();
|
|
202
|
-
const url = `${YAHOO_API_BASE}${endpoint}${endpoint.includes('?') ? '&' : '?'}format=${format}`;
|
|
203
|
-
console.error(`[DEBUG] API Request: ${endpoint}`);
|
|
204
|
-
return new Promise((resolve, reject) => {
|
|
205
|
-
const urlObj = new URL(url);
|
|
206
|
-
const options = {
|
|
207
|
-
hostname: urlObj.hostname,
|
|
208
|
-
port: 443,
|
|
209
|
-
path: urlObj.pathname + urlObj.search,
|
|
210
|
-
method: "GET",
|
|
211
|
-
headers: {
|
|
212
|
-
Authorization: `Bearer ${accessToken}`,
|
|
213
|
-
Accept: "application/json",
|
|
214
|
-
},
|
|
215
|
-
};
|
|
216
|
-
const req = https.request(options, (res) => {
|
|
217
|
-
let data = "";
|
|
218
|
-
res.on("data", (chunk) => {
|
|
219
|
-
data += chunk;
|
|
220
|
-
});
|
|
221
|
-
res.on("end", () => {
|
|
222
|
-
if (res.statusCode === 401) {
|
|
223
|
-
// Token expired, try to refresh and retry
|
|
224
|
-
refreshAccessToken()
|
|
225
|
-
.then(() => yahooApiRequest(endpoint, format))
|
|
226
|
-
.then(resolve)
|
|
227
|
-
.catch(reject);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
if (res.statusCode !== 200) {
|
|
231
|
-
reject(new Error(`API returned status ${res.statusCode}: ${data}`));
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
try {
|
|
235
|
-
const parsed = JSON.parse(data);
|
|
236
|
-
resolve(parsed);
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
reject(new Error(`Failed to parse API response: ${error}`));
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
req.on("error", (error) => {
|
|
244
|
-
reject(error);
|
|
245
|
-
});
|
|
246
|
-
req.end();
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
100
|
// Tool: Get Team Roster
|
|
250
101
|
async function getTeamRoster() {
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
// Parse players
|
|
258
|
-
const playerKeys = Object.keys(rosterData).filter(key => key !== 'count');
|
|
259
|
-
const players = playerKeys.map(key => {
|
|
260
|
-
const playerData = rosterData[key].player[0];
|
|
261
|
-
const positionData = rosterData[key].player[1];
|
|
262
|
-
// Find attributes in the array
|
|
263
|
-
const playerId = playerData.find((item) => item.player_id)?.player_id;
|
|
264
|
-
const name = playerData.find((item) => item.name)?.name?.full;
|
|
265
|
-
const displayPosition = playerData.find((item) => item.display_position)?.display_position;
|
|
266
|
-
const team = playerData.find((item) => item.editorial_team_abbr)?.editorial_team_abbr;
|
|
267
|
-
const status = playerData.find((item) => item.status)?.status || "";
|
|
268
|
-
const selectedPos = positionData?.selected_position?.find((item) => item.position)?.position || "BN";
|
|
269
|
-
return {
|
|
270
|
-
player_id: playerId,
|
|
271
|
-
name: name,
|
|
272
|
-
position: displayPosition,
|
|
273
|
-
team: team,
|
|
274
|
-
status: status,
|
|
275
|
-
selected_position: selectedPos,
|
|
276
|
-
};
|
|
277
|
-
});
|
|
102
|
+
await Promise.all([NHL_STATS.load(), NHL_SCHEDULE.load()]);
|
|
103
|
+
const roster = LEAGUE_DATA.getRoster();
|
|
104
|
+
if (!roster)
|
|
105
|
+
return { error: NO_ROSTER_MESSAGE };
|
|
106
|
+
const today = NhlScheduleService.today();
|
|
107
|
+
const weekEnd = NhlScheduleService.addDays(today, 6);
|
|
278
108
|
return {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
109
|
+
team_name: roster.team_name,
|
|
110
|
+
player_count: roster.players.length,
|
|
111
|
+
players: roster.players.map((p) => ({
|
|
112
|
+
...p,
|
|
113
|
+
games_next_7_days: NHL_SCHEDULE.isAvailable()
|
|
114
|
+
? NHL_SCHEDULE.countGamesInRange(p.team, today, weekEnd)
|
|
115
|
+
: null,
|
|
116
|
+
season_stats: p.stats ?? null
|
|
117
|
+
})),
|
|
118
|
+
data_source: `NHL public API (rosters ${NHL_STATS.getSeasons().roster}, stats ${NHL_STATS.getSeasons().stats})`
|
|
282
119
|
};
|
|
283
120
|
}
|
|
284
121
|
// Tool: Get League Standings
|
|
285
122
|
async function getLeagueStandings() {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
const standings = Object.keys(teams)
|
|
289
|
-
.filter(key => key !== 'count')
|
|
290
|
-
.map(key => {
|
|
291
|
-
const team = teams[key].team[0];
|
|
292
|
-
const standings = teams[key].team[1].team_standings;
|
|
123
|
+
const standings = LEAGUE_DATA.getStandings();
|
|
124
|
+
if (!standings) {
|
|
293
125
|
return {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
wins: standings.outcome_totals?.wins || 0,
|
|
297
|
-
losses: standings.outcome_totals?.losses || 0,
|
|
298
|
-
ties: standings.outcome_totals?.ties || 0,
|
|
299
|
-
points: standings.points_for,
|
|
126
|
+
error: 'No standings stored. Paste them with `set_standings` — copy the standings ' +
|
|
127
|
+
'table from your league, one team per line.'
|
|
300
128
|
};
|
|
301
|
-
});
|
|
302
|
-
return { standings };
|
|
303
|
-
}
|
|
304
|
-
// Tool: Get Current Matchup
|
|
305
|
-
async function getCurrentMatchup() {
|
|
306
|
-
console.error('[DEBUG] getCurrentMatchup called');
|
|
307
|
-
const data = await yahooApiRequest(`/team/nhl.l.${LEAGUE_ID}.t.${TEAM_ID}/matchups`);
|
|
308
|
-
console.error('[DEBUG] API response received');
|
|
309
|
-
const matchups = data.fantasy_content.team[1].matchups;
|
|
310
|
-
console.error('[DEBUG] Matchups count:', matchups?.count);
|
|
311
|
-
// Debug: log first 3 matchup keys and their structure
|
|
312
|
-
if (matchups) {
|
|
313
|
-
const keys = Object.keys(matchups).filter(k => k !== 'count').slice(0, 3);
|
|
314
|
-
keys.forEach(key => {
|
|
315
|
-
const m = matchups[key];
|
|
316
|
-
console.error(`[DEBUG] matchups["${key}"] structure:`, JSON.stringify({
|
|
317
|
-
hasMatchup: !!m?.matchup,
|
|
318
|
-
isArray: Array.isArray(m?.matchup),
|
|
319
|
-
week: m?.matchup?.[0]?.week || m?.matchup?.week,
|
|
320
|
-
status: m?.matchup?.[0]?.status || m?.matchup?.status
|
|
321
|
-
}));
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
const currentMatchup = findCurrentMatchup(matchups);
|
|
325
|
-
if (!currentMatchup) {
|
|
326
|
-
console.error('[DEBUG] No current matchup found');
|
|
327
|
-
return { message: "No current matchup (bye week or season not started)" };
|
|
328
|
-
}
|
|
329
|
-
// currentMatchup is the matchup object with structure:
|
|
330
|
-
// matchup = { "0": { teams: {...} }, week: "1", status: "postevent", ... }
|
|
331
|
-
const matchupData = currentMatchup[0];
|
|
332
|
-
const week = currentMatchup.week;
|
|
333
|
-
const status = currentMatchup.status;
|
|
334
|
-
const teams = matchupData?.teams;
|
|
335
|
-
if (!teams) {
|
|
336
|
-
console.error('[DEBUG] No teams found in matchup');
|
|
337
|
-
return { message: "No teams data in current matchup" };
|
|
338
129
|
}
|
|
339
|
-
console.error('[DEBUG] Final result:', {
|
|
340
|
-
week,
|
|
341
|
-
status,
|
|
342
|
-
opponent: teams['1']?.team?.[0]?.[2]?.name
|
|
343
|
-
});
|
|
344
130
|
return {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
opponent: teams['1']?.team?.[0]?.[2]?.name || 'Unknown',
|
|
131
|
+
teams: standings.rows.length,
|
|
132
|
+
standings: standings.rows,
|
|
133
|
+
updated_at: standings.updated_at
|
|
349
134
|
};
|
|
350
135
|
}
|
|
136
|
+
// Tool: Get Current Matchup
|
|
137
|
+
// Removed in v4: getCurrentMatchup required live fantasy-platform data
|
|
138
|
+
// (live matchup scoring, weekly results, or league ownership) that no
|
|
139
|
+
// public source exposes. See CHANGELOG for the full list.
|
|
351
140
|
// Tool: Search Players
|
|
352
141
|
async function searchPlayers(position, count = 25) {
|
|
353
|
-
|
|
354
|
-
if (
|
|
355
|
-
|
|
142
|
+
await NHL_STATS.load();
|
|
143
|
+
if (!NHL_STATS.isAvailable()) {
|
|
144
|
+
return { error: 'NHL player data unavailable', reason: NHL_STATS.getUnavailableReason() };
|
|
356
145
|
}
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
};
|
|
376
|
-
});
|
|
377
|
-
return { players };
|
|
378
|
-
}
|
|
379
|
-
// Tool: Get Player Stats
|
|
380
|
-
async function getPlayerStats(playerId) {
|
|
381
|
-
const data = await yahooApiRequest(`/player/nhl.p.${playerId}/stats`);
|
|
382
|
-
const playerData = data.fantasy_content.player[0];
|
|
383
|
-
const stats = data.fantasy_content.player[1]?.player_stats?.stats || [];
|
|
384
|
-
// Find attributes in the array using .find() to handle dynamic structure
|
|
385
|
-
const playerIdResult = playerData.find((item) => item.player_id)?.player_id;
|
|
386
|
-
const name = playerData.find((item) => item.name)?.name?.full;
|
|
387
|
-
const displayPosition = playerData.find((item) => item.display_position)?.display_position;
|
|
388
|
-
const team = playerData.find((item) => item.editorial_team_abbr)?.editorial_team_abbr;
|
|
146
|
+
// Fantasy platforms say LW/RW; the NHL says L/R.
|
|
147
|
+
const wanted = String(position ?? '').toUpperCase()
|
|
148
|
+
.replace(/^LW$/, 'L').replace(/^RW$/, 'R');
|
|
149
|
+
const owned = new Set((LEAGUE_DATA.getRoster()?.players ?? []).map((p) => p.player_id));
|
|
150
|
+
const players = NHL_STATS.getAll()
|
|
151
|
+
.filter(p => !wanted || p.position === wanted)
|
|
152
|
+
.map(p => ({
|
|
153
|
+
player_id: p.player_id,
|
|
154
|
+
name: p.name,
|
|
155
|
+
team: p.team,
|
|
156
|
+
position: p.position,
|
|
157
|
+
on_your_roster: owned.has(p.player_id),
|
|
158
|
+
season_stats: p.stats ?? null,
|
|
159
|
+
// Rank skaters by production, goalies by wins.
|
|
160
|
+
rank_value: p.position === 'G' ? (p.stats?.wins ?? 0) : (p.stats?.points ?? 0)
|
|
161
|
+
}))
|
|
162
|
+
.sort((a, b) => b.rank_value - a.rank_value)
|
|
163
|
+
.slice(0, Math.max(1, count));
|
|
389
164
|
return {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
165
|
+
position: position ?? 'all',
|
|
166
|
+
returned: players.length,
|
|
167
|
+
players,
|
|
168
|
+
note: 'Ranked by last season production across all NHL players. Whether a player ' +
|
|
169
|
+
'is available in your league is league-private and not knowable here — ' +
|
|
170
|
+
'on_your_roster reflects only the roster you pasted.',
|
|
171
|
+
data_source: `NHL public API (stats ${NHL_STATS.getSeasons().stats})`
|
|
395
172
|
};
|
|
396
173
|
}
|
|
397
|
-
// Tool: Get
|
|
398
|
-
async function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return { message: "No teams data in current matchup" };
|
|
174
|
+
// Tool: Get Player Stats
|
|
175
|
+
async function getPlayerStats(playerId) {
|
|
176
|
+
await Promise.all([NHL_STATS.load(), NHL_SCHEDULE.load()]);
|
|
177
|
+
// Accept an NHL id or, far more usefully, a name a human would type.
|
|
178
|
+
const direct = NHL_STATS.getById(playerId);
|
|
179
|
+
const resolution = direct ? { player: direct } : NHL_STATS.resolve(playerId);
|
|
180
|
+
if (!resolution.player) {
|
|
181
|
+
const r = resolution;
|
|
182
|
+
return {
|
|
183
|
+
error: `Could not resolve "${playerId}" to a single NHL player`,
|
|
184
|
+
reason: r.reason,
|
|
185
|
+
...(r.ambiguous ? { candidates: r.ambiguous.map((p) => `${p.name} (${p.team} ${p.position})`) } : {})
|
|
186
|
+
};
|
|
411
187
|
}
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
const oppGames = teams['1']?.team?.[1]?.team_remaining_games?.total;
|
|
188
|
+
const p = resolution.player;
|
|
189
|
+
const today = NhlScheduleService.today();
|
|
415
190
|
return {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
games_remaining: oppGames?.remaining_games || 0,
|
|
429
|
-
games_completed: oppGames?.completed_games || 0,
|
|
430
|
-
live_games: oppGames?.live_games || 0,
|
|
431
|
-
},
|
|
432
|
-
games_in_hand: {
|
|
433
|
-
your_remaining: yourGames?.remaining_games || 0,
|
|
434
|
-
opponent_remaining: oppGames?.remaining_games || 0,
|
|
435
|
-
difference: (yourGames?.remaining_games || 0) - (oppGames?.remaining_games || 0),
|
|
436
|
-
advantage: (yourGames?.remaining_games || 0) > (oppGames?.remaining_games || 0) ? "You" : "Opponent"
|
|
437
|
-
}
|
|
191
|
+
player_id: p.player_id,
|
|
192
|
+
name: p.name,
|
|
193
|
+
team: p.team,
|
|
194
|
+
position: p.position,
|
|
195
|
+
season_stats: p.stats ?? null,
|
|
196
|
+
stats_season: NHL_STATS.getSeasons().stats,
|
|
197
|
+
games_next_7_days: NHL_SCHEDULE.isAvailable()
|
|
198
|
+
? NHL_SCHEDULE.countGamesInRange(p.team, today, NhlScheduleService.addDays(today, 6))
|
|
199
|
+
: null,
|
|
200
|
+
upcoming: NHL_SCHEDULE.isAvailable()
|
|
201
|
+
? NHL_SCHEDULE.getGamesInRange(p.team, today, NhlScheduleService.addDays(today, 13)).slice(0, 6)
|
|
202
|
+
: []
|
|
438
203
|
};
|
|
439
204
|
}
|
|
205
|
+
// Tool: Get Weekly Stats
|
|
206
|
+
// Removed in v4: getWeeklyStats required live fantasy-platform data
|
|
207
|
+
// (live matchup scoring, weekly results, or league ownership) that no
|
|
208
|
+
// public source exposes. See CHANGELOG for the full list.
|
|
440
209
|
// Tool: Compare Matchup
|
|
441
210
|
async function compareMatchup() {
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
const
|
|
445
|
-
if (!
|
|
446
|
-
return {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
211
|
+
await Promise.all([NHL_STATS.load(), NHL_SCHEDULE.load()]);
|
|
212
|
+
const mine = LEAGUE_DATA.getRoster();
|
|
213
|
+
const theirs = LEAGUE_DATA.getOpponentRoster();
|
|
214
|
+
if (!mine)
|
|
215
|
+
return { error: NO_ROSTER_MESSAGE };
|
|
216
|
+
if (!theirs)
|
|
217
|
+
return { error: NO_OPPONENT_MESSAGE };
|
|
218
|
+
const today = NhlScheduleService.today();
|
|
219
|
+
const weekEnd = NhlScheduleService.addDays(today, 6);
|
|
220
|
+
const summarize = (roster) => {
|
|
221
|
+
const totals = { G: 0, A: 0, P: 0, '+/-': 0, PIM: 0, SOG: 0, PPG: 0, W: 0 };
|
|
222
|
+
let games = 0;
|
|
223
|
+
for (const p of roster.players) {
|
|
224
|
+
if (p.selected_position === 'IR')
|
|
225
|
+
continue;
|
|
226
|
+
const s = p.stats ?? {};
|
|
227
|
+
totals.G += s.goals ?? 0;
|
|
228
|
+
totals.A += s.assists ?? 0;
|
|
229
|
+
totals.P += s.points ?? 0;
|
|
230
|
+
totals['+/-'] += s.plus_minus ?? 0;
|
|
231
|
+
totals.PIM += s.penalty_minutes ?? 0;
|
|
232
|
+
totals.SOG += s.shots ?? 0;
|
|
233
|
+
totals.PPG += s.power_play_goals ?? 0;
|
|
234
|
+
totals.W += s.wins ?? 0;
|
|
235
|
+
games += NHL_SCHEDULE.isAvailable() ? NHL_SCHEDULE.countGamesInRange(p.team, today, weekEnd) : 0;
|
|
236
|
+
}
|
|
237
|
+
return { team_name: roster.team_name, totals, games_this_week: games };
|
|
238
|
+
};
|
|
239
|
+
const you = summarize(mine);
|
|
240
|
+
const them = summarize(theirs);
|
|
241
|
+
const categories = Object.keys(you.totals).map(cat => {
|
|
242
|
+
const a = you.totals[cat];
|
|
243
|
+
const b = them.totals[cat];
|
|
244
|
+
return { category: cat, you: Math.round(a * 10) / 10, opponent: Math.round(b * 10) / 10,
|
|
245
|
+
edge: a === b ? 'EVEN' : a > b ? 'YOU' : 'OPPONENT' };
|
|
466
246
|
});
|
|
467
|
-
const
|
|
247
|
+
const won = categories.filter(c => c.edge === 'YOU').length;
|
|
468
248
|
return {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
249
|
+
your_team: you.team_name,
|
|
250
|
+
opponent: them.team_name,
|
|
251
|
+
categories,
|
|
252
|
+
category_edge: `${won}-${categories.filter(c => c.edge === 'OPPONENT').length}`,
|
|
253
|
+
schedule: {
|
|
254
|
+
your_games_this_week: you.games_this_week,
|
|
255
|
+
opponent_games_this_week: them.games_this_week,
|
|
256
|
+
advantage: you.games_this_week - them.games_this_week
|
|
257
|
+
},
|
|
258
|
+
basis: `Last season totals (${NHL_STATS.getSeasons().stats}) as a proxy for current strength, ` +
|
|
259
|
+
'plus real games scheduled this week. Not live scoring — this compares roster ' +
|
|
260
|
+
'quality and volume, not what has actually happened in your matchup.'
|
|
475
261
|
};
|
|
476
262
|
}
|
|
477
263
|
async function getRosterTransactionRecommendations(lookAheadDays = 7, targetPositions) {
|
|
@@ -488,7 +274,7 @@ async function getRosterTransactionRecommendations(lookAheadDays = 7, targetPosi
|
|
|
488
274
|
// Find transaction opportunities
|
|
489
275
|
const recommendations = [];
|
|
490
276
|
// 1. IMMEDIATE FIXES (injured players)
|
|
491
|
-
const injuredActive = roster.
|
|
277
|
+
const injuredActive = (roster.players ?? []).filter((p) => p.status && p.status !== "" && !p.selected_position.includes("IR"));
|
|
492
278
|
for (const player of injuredActive) {
|
|
493
279
|
recommendations.push({
|
|
494
280
|
priority: "CRITICAL",
|
|
@@ -910,35 +696,159 @@ function findBenchUpgrades(roster, streaming) {
|
|
|
910
696
|
// - getStreamingRecommendations() → StreamingAnalysis
|
|
911
697
|
// ==========================================
|
|
912
698
|
// Tool: Get Trending Players
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
699
|
+
// Removed in v4: getTrendingPlayers required live fantasy-platform data
|
|
700
|
+
// (live matchup scoring, weekly results, or league ownership) that no
|
|
701
|
+
// public source exposes. See CHANGELOG for the full list.
|
|
702
|
+
// Tool: Chirp Opponent
|
|
703
|
+
async function chirpOpponent(chirpIntensity = 'savage', personalityMode = 'roast_master') {
|
|
704
|
+
await Promise.all([NHL_STATS.load(), NHL_SCHEDULE.load()]);
|
|
705
|
+
const theirs = LEAGUE_DATA.getOpponentRoster();
|
|
706
|
+
if (!theirs)
|
|
707
|
+
return { error: NO_OPPONENT_MESSAGE };
|
|
708
|
+
const today = NhlScheduleService.today();
|
|
709
|
+
const weekEnd = NhlScheduleService.addDays(today, 6);
|
|
710
|
+
const players = theirs.players.map((p) => ({
|
|
711
|
+
name: p.name,
|
|
712
|
+
team: p.team,
|
|
713
|
+
position: p.position,
|
|
714
|
+
slot: p.selected_position,
|
|
715
|
+
points_last_season: p.stats?.points ?? null,
|
|
716
|
+
games_this_week: NHL_SCHEDULE.isAvailable()
|
|
717
|
+
? NHL_SCHEDULE.countGamesInRange(p.team, today, weekEnd)
|
|
718
|
+
: null
|
|
719
|
+
}));
|
|
720
|
+
const onIr = players.filter(p => p.slot === 'IR');
|
|
721
|
+
const idle = players.filter(p => p.games_this_week === 0);
|
|
722
|
+
const light = players.filter(p => (p.games_this_week ?? 9) <= 2 && p.slot !== 'IR');
|
|
723
|
+
const totalGames = players.reduce((n, p) => n + (p.games_this_week ?? 0), 0);
|
|
724
|
+
const style = CHIRP_STYLES[chirpIntensity] ?? CHIRP_STYLES.standard;
|
|
725
|
+
const persona = PERSONALITY_MODES[personalityMode] ?? PERSONALITY_MODES.analytical;
|
|
726
|
+
const lines = [];
|
|
727
|
+
if (idle.length)
|
|
728
|
+
lines.push(`${idle.length} of their players don't play at all this week. Free real estate.`);
|
|
729
|
+
if (light.length)
|
|
730
|
+
lines.push(`${light.length} more are stuck on two games or fewer.`);
|
|
731
|
+
if (onIr.length)
|
|
732
|
+
lines.push(`${onIr.length} parked on IR — that roster is holding a hospital ward.`);
|
|
733
|
+
if (!lines.length)
|
|
734
|
+
lines.push(`${theirs.team_name} is actually well set up this week. Annoying, but true.`);
|
|
735
|
+
return {
|
|
736
|
+
opponent: theirs.team_name,
|
|
737
|
+
roster_size: players.length,
|
|
738
|
+
total_games_this_week: totalGames,
|
|
739
|
+
weaknesses: { idle_players: idle.map(p => p.name), light_schedule: light.map(p => p.name), on_ir: onIr.map(p => p.name) },
|
|
740
|
+
players,
|
|
741
|
+
chirp: lines.join(' '),
|
|
742
|
+
chirp_style: style?.tone,
|
|
743
|
+
personality: persona?.voice,
|
|
744
|
+
basis: 'Real NHL schedule for this week, plus the opponent roster you pasted.'
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
// Tool: Analyze Trade
|
|
748
|
+
async function analyzeTradeImpact(giving, receiving, chirpIntensity = 'standard') {
|
|
749
|
+
await Promise.all([NHL_STATS.load(), NHL_SCHEDULE.load()]);
|
|
750
|
+
if (!NHL_STATS.isAvailable()) {
|
|
751
|
+
return { error: 'NHL player data unavailable', reason: NHL_STATS.getUnavailableReason() };
|
|
752
|
+
}
|
|
753
|
+
const categories = ['G', 'A', 'P', '+/-', 'PIM', 'SOG', 'PPG', 'W', 'GAA', 'SV%'];
|
|
754
|
+
const lowerIsBetter = new Set(['GAA']);
|
|
755
|
+
const resolveSide = (names) => names.map(n => {
|
|
756
|
+
const r = NHL_STATS.resolve(n);
|
|
928
757
|
return {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
team: team,
|
|
933
|
-
|
|
758
|
+
input: n,
|
|
759
|
+
found: Boolean(r.player),
|
|
760
|
+
name: r.player?.name ?? n,
|
|
761
|
+
team: r.player?.team ?? '?',
|
|
762
|
+
position: r.player?.position ?? '?',
|
|
763
|
+
stats: r.player?.stats ?? null,
|
|
764
|
+
...(r.ambiguous ? { candidates: r.ambiguous.map(p => `${p.name} (${p.team} ${p.position})`) } : {}),
|
|
765
|
+
...(r.player ? {} : { reason: r.reason })
|
|
934
766
|
};
|
|
935
767
|
});
|
|
936
|
-
|
|
768
|
+
const sum = (side) => {
|
|
769
|
+
const totals = {};
|
|
770
|
+
const rates = {};
|
|
771
|
+
for (const p of side.filter(x => x.found && x.stats)) {
|
|
772
|
+
const s = p.stats;
|
|
773
|
+
totals.G = (totals.G ?? 0) + (s.goals ?? 0);
|
|
774
|
+
totals.A = (totals.A ?? 0) + (s.assists ?? 0);
|
|
775
|
+
totals.P = (totals.P ?? 0) + (s.points ?? 0);
|
|
776
|
+
totals['+/-'] = (totals['+/-'] ?? 0) + (s.plus_minus ?? 0);
|
|
777
|
+
totals.PIM = (totals.PIM ?? 0) + (s.penalty_minutes ?? 0);
|
|
778
|
+
totals.SOG = (totals.SOG ?? 0) + (s.shots ?? 0);
|
|
779
|
+
totals.PPG = (totals.PPG ?? 0) + (s.power_play_goals ?? 0);
|
|
780
|
+
totals.W = (totals.W ?? 0) + (s.wins ?? 0);
|
|
781
|
+
// Rate stats average rather than sum, and only across goalies who have them.
|
|
782
|
+
if (s.goals_against_average !== undefined)
|
|
783
|
+
(rates.GAA ??= []).push(s.goals_against_average);
|
|
784
|
+
if (s.save_percentage !== undefined)
|
|
785
|
+
(rates['SV%'] ??= []).push(s.save_percentage);
|
|
786
|
+
}
|
|
787
|
+
for (const [cat, values] of Object.entries(rates)) {
|
|
788
|
+
if (values.length)
|
|
789
|
+
totals[cat] = values.reduce((a, b) => a + b, 0) / values.length;
|
|
790
|
+
}
|
|
791
|
+
return totals;
|
|
792
|
+
};
|
|
793
|
+
const givingPlayers = resolveSide(giving);
|
|
794
|
+
const receivingPlayers = resolveSide(receiving);
|
|
795
|
+
const out = sum(givingPlayers);
|
|
796
|
+
const inn = sum(receivingPlayers);
|
|
797
|
+
let wins = 0, losses = 0;
|
|
798
|
+
const breakdown = categories.map(cat => {
|
|
799
|
+
const g = out[cat], r = inn[cat];
|
|
800
|
+
if (g === undefined && r === undefined)
|
|
801
|
+
return null;
|
|
802
|
+
const gv = g ?? 0, rv = r ?? 0;
|
|
803
|
+
const better = lowerIsBetter.has(cat) ? rv < gv : rv > gv;
|
|
804
|
+
const same = Math.abs(rv - gv) < 1e-9;
|
|
805
|
+
if (!same)
|
|
806
|
+
better ? wins++ : losses++;
|
|
807
|
+
const round = (n) => Math.round(n * 1000) / 1000;
|
|
808
|
+
return { category: cat, giving_up: round(gv), receiving: round(rv),
|
|
809
|
+
net: round(rv - gv), verdict: same ? 'PUSH' : better ? 'GAIN' : 'LOSS' };
|
|
810
|
+
}).filter(Boolean);
|
|
811
|
+
const verdict = wins > losses ? 'ACCEPT' : wins < losses ? 'DECLINE' : 'PUSH';
|
|
812
|
+
const unresolved = [...givingPlayers, ...receivingPlayers].filter(p => !p.found);
|
|
813
|
+
return {
|
|
814
|
+
giving: givingPlayers,
|
|
815
|
+
receiving: receivingPlayers,
|
|
816
|
+
category_breakdown: breakdown,
|
|
817
|
+
categories_gained: wins,
|
|
818
|
+
categories_lost: losses,
|
|
819
|
+
verdict,
|
|
820
|
+
chirp: verdict === 'ACCEPT'
|
|
821
|
+
? `You win ${wins} categories to ${losses}. Take it before they think twice.`
|
|
822
|
+
: verdict === 'DECLINE'
|
|
823
|
+
? `You lose ${losses} categories to ${wins}. That's not a trade, that's a donation.`
|
|
824
|
+
: `Dead even at ${wins}-${losses}. Decide on need, not numbers.`,
|
|
825
|
+
...(unresolved.length ? { unresolved } : {}),
|
|
826
|
+
basis: `Last full season totals (${NHL_STATS.getSeasons().stats}) from the NHL public API. ` +
|
|
827
|
+
'GAA and SV% are averaged across goalies; counting stats are summed. ' +
|
|
828
|
+
'This measures past production, not your league\'s scoring settings.',
|
|
829
|
+
chirp_intensity: chirpIntensity
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Version reported in the MCP handshake.
|
|
834
|
+
*
|
|
835
|
+
* Read from package.json rather than hardcoded — this string had drifted to
|
|
836
|
+
* 3.0.0 while the package was on 3.2.0, so clients were told the wrong version.
|
|
837
|
+
*/
|
|
838
|
+
function readPackageVersion() {
|
|
839
|
+
try {
|
|
840
|
+
const packageRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
841
|
+
const pkg = JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
842
|
+
return pkg.version ?? "0.0.0";
|
|
843
|
+
}
|
|
844
|
+
catch {
|
|
845
|
+
return "0.0.0";
|
|
846
|
+
}
|
|
937
847
|
}
|
|
938
848
|
// Initialize MCP Server
|
|
939
849
|
const server = new Server({
|
|
940
850
|
name: "semantic-chirp-intelligence-mcp",
|
|
941
|
-
version:
|
|
851
|
+
version: readPackageVersion(),
|
|
942
852
|
}, {
|
|
943
853
|
capabilities: {
|
|
944
854
|
tools: {},
|
|
@@ -964,14 +874,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
964
874
|
properties: {},
|
|
965
875
|
},
|
|
966
876
|
},
|
|
967
|
-
{
|
|
968
|
-
name: "get_current_matchup",
|
|
969
|
-
description: "Get information about your current week's matchup",
|
|
970
|
-
inputSchema: {
|
|
971
|
-
type: "object",
|
|
972
|
-
properties: {},
|
|
973
|
-
},
|
|
974
|
-
},
|
|
975
877
|
{
|
|
976
878
|
name: "search_players",
|
|
977
879
|
description: "Search for available players (free agents) by position. Returns top available players.",
|
|
@@ -1004,14 +906,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1004
906
|
required: ["player_id"],
|
|
1005
907
|
},
|
|
1006
908
|
},
|
|
1007
|
-
{
|
|
1008
|
-
name: "get_weekly_stats",
|
|
1009
|
-
description: "Get your team's current week statistics and compare with opponent",
|
|
1010
|
-
inputSchema: {
|
|
1011
|
-
type: "object",
|
|
1012
|
-
properties: {},
|
|
1013
|
-
},
|
|
1014
|
-
},
|
|
1015
909
|
{
|
|
1016
910
|
name: "compare_matchup",
|
|
1017
911
|
description: "Get detailed category-by-category comparison with your current opponent",
|
|
@@ -1028,40 +922,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1028
922
|
properties: {},
|
|
1029
923
|
},
|
|
1030
924
|
},
|
|
1031
|
-
{
|
|
1032
|
-
name: "get_trending_players",
|
|
1033
|
-
description: "Get trending players (most added or most owned) to identify hot pickups",
|
|
1034
|
-
inputSchema: {
|
|
1035
|
-
type: "object",
|
|
1036
|
-
properties: {
|
|
1037
|
-
trend_type: {
|
|
1038
|
-
type: "string",
|
|
1039
|
-
description: "Type of trending: 'add' for most added, 'own' for most owned",
|
|
1040
|
-
enum: ["add", "own"],
|
|
1041
|
-
default: "add",
|
|
1042
|
-
},
|
|
1043
|
-
count: {
|
|
1044
|
-
type: "number",
|
|
1045
|
-
description: "Number of players to return (default 25)",
|
|
1046
|
-
default: 25,
|
|
1047
|
-
},
|
|
1048
|
-
},
|
|
1049
|
-
},
|
|
1050
|
-
},
|
|
1051
|
-
{
|
|
1052
|
-
name: "debug_api_call",
|
|
1053
|
-
description: "Debug tool to see raw Yahoo API responses for troubleshooting",
|
|
1054
|
-
inputSchema: {
|
|
1055
|
-
type: "object",
|
|
1056
|
-
properties: {
|
|
1057
|
-
endpoint: {
|
|
1058
|
-
type: "string",
|
|
1059
|
-
description: "API endpoint to call (e.g., '/team/nhl.l.{LEAGUE_ID}.t.{TEAM_ID}/roster')",
|
|
1060
|
-
},
|
|
1061
|
-
},
|
|
1062
|
-
required: ["endpoint"],
|
|
1063
|
-
},
|
|
1064
|
-
},
|
|
1065
925
|
{
|
|
1066
926
|
name: "get_streaming_recommendations",
|
|
1067
927
|
description: "Get AI-powered streaming recommendations based on team schedules, player trends, and ownership. Identifies players on teams with favorable schedules (more games remaining this week) for optimal waiver pickups.",
|
|
@@ -1153,14 +1013,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1153
1013
|
}
|
|
1154
1014
|
},
|
|
1155
1015
|
{
|
|
1156
|
-
name:
|
|
1157
|
-
description:
|
|
1158
|
-
inputSchema:
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1016
|
+
name: "analyze_breakout_players",
|
|
1017
|
+
description: "📈 Find breakout candidates among NHL players not on the rosters you have provided, scored on real season production, opportunity and risk. Availability in your league is league-private and cannot be determined here — treat these as candidates to check.",
|
|
1018
|
+
inputSchema: {
|
|
1019
|
+
type: "object",
|
|
1020
|
+
properties: {
|
|
1021
|
+
position_filter: { type: "array", items: { type: "string" }, description: "Limit to positions, e.g. [\"C\", \"D\"]" },
|
|
1022
|
+
breakout_age_max: { type: "number", description: "Maximum age for a breakout candidate (default 26)" },
|
|
1023
|
+
min_score: { type: "number", description: "Minimum breakout score to include" },
|
|
1024
|
+
max_results: { type: "number", description: "How many candidates to return (default 10)" },
|
|
1025
|
+
...baseChirpSchema
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1164
1028
|
},
|
|
1165
1029
|
{
|
|
1166
1030
|
name: "analyze_weekend_streams",
|
|
@@ -1212,7 +1076,155 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1212
1076
|
},
|
|
1213
1077
|
required: ["date_range"]
|
|
1214
1078
|
}
|
|
1215
|
-
}
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
name: "chirp_opponent",
|
|
1082
|
+
description: "Scout your current matchup opponent's roster and generate savage trash talk based on their weaknesses — injuries, IR mismanagement, bench-heavy lineups. Pure ChirpIQX energy.",
|
|
1083
|
+
inputSchema: {
|
|
1084
|
+
type: "object",
|
|
1085
|
+
properties: {
|
|
1086
|
+
...baseChirpSchema,
|
|
1087
|
+
},
|
|
1088
|
+
},
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
name: "analyze_trade",
|
|
1092
|
+
description: "Evaluate a trade offer by comparing the net category impact of players you're giving vs receiving. Returns a category-by-category breakdown and an ACCEPT / DECLINE / PUSH verdict with chirp commentary.",
|
|
1093
|
+
inputSchema: {
|
|
1094
|
+
type: "object",
|
|
1095
|
+
properties: {
|
|
1096
|
+
giving: {
|
|
1097
|
+
type: "array",
|
|
1098
|
+
items: { type: "string" },
|
|
1099
|
+
description: "Player names you are giving away (e.g. [\"Nathan MacKinnon\", \"Mitch Marner\"])",
|
|
1100
|
+
},
|
|
1101
|
+
receiving: {
|
|
1102
|
+
type: "array",
|
|
1103
|
+
items: { type: "string" },
|
|
1104
|
+
description: "Player names you are receiving (e.g. [\"Auston Matthews\"])",
|
|
1105
|
+
},
|
|
1106
|
+
...baseChirpSchema,
|
|
1107
|
+
},
|
|
1108
|
+
required: ["giving", "receiving"],
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
name: "set_roster",
|
|
1113
|
+
description: "📋 Paste your team's roster to teach CHIRP who you own. Works with text copied from any fantasy platform — Yahoo, ESPN, Sleeper, a spreadsheet, or just a list of names. Player names are resolved against live NHL rosters, so team and position fill themselves in. Anything that can't be resolved to exactly one player is reported back rather than guessed. No account or API key needed.",
|
|
1114
|
+
inputSchema: {
|
|
1115
|
+
type: "object",
|
|
1116
|
+
properties: {
|
|
1117
|
+
roster_text: {
|
|
1118
|
+
type: "string",
|
|
1119
|
+
description: "The pasted roster. One player per line, in whatever shape you copied it — \"Auston Matthews\", \"MATTHEWS, Auston\", or a full row like \"C Auston Matthews TOR - C Q\". BN and IR slots are preserved if present."
|
|
1120
|
+
},
|
|
1121
|
+
team_name: {
|
|
1122
|
+
type: "string",
|
|
1123
|
+
description: "What to call this team (default: \"My Team\")"
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
required: ["roster_text"]
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
name: "set_opponent_roster",
|
|
1131
|
+
description: "📋 Paste your weekly opponent's roster, so head-to-head tools (games-in-hand, matchup comparison, opponent scouting) can work without a league account. Same forgiving format as set_roster.",
|
|
1132
|
+
inputSchema: {
|
|
1133
|
+
type: "object",
|
|
1134
|
+
properties: {
|
|
1135
|
+
roster_text: { type: "string", description: "The pasted opponent roster, one player per line" },
|
|
1136
|
+
team_name: { type: "string", description: "Opponent's team name (default: \"Opponent\")" }
|
|
1137
|
+
},
|
|
1138
|
+
required: ["roster_text"]
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
name: "set_standings",
|
|
1143
|
+
description: "📊 Paste your league standings to give CHIRP league context. Extracts rank, team name, record and points from rows like \"1. TeamDestroyersz 8-2-1 142 pts\".",
|
|
1144
|
+
inputSchema: {
|
|
1145
|
+
type: "object",
|
|
1146
|
+
properties: {
|
|
1147
|
+
standings_text: { type: "string", description: "The pasted standings, one team per line" }
|
|
1148
|
+
},
|
|
1149
|
+
required: ["standings_text"]
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
name: "show_stored_data",
|
|
1154
|
+
description: "🗂️ Show what CHIRP currently knows about your league — stored roster, opponent roster and standings, with when each was last updated. Use `clear` to forget one of them.",
|
|
1155
|
+
inputSchema: {
|
|
1156
|
+
type: "object",
|
|
1157
|
+
properties: {
|
|
1158
|
+
clear: {
|
|
1159
|
+
type: "string",
|
|
1160
|
+
enum: ["roster", "opponent", "standings"],
|
|
1161
|
+
description: "Optionally forget one stored item instead of showing everything"
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
name: "schedule_value",
|
|
1168
|
+
description: "🗓️ Rate all 32 NHL clubs on what their schedule is worth to a fantasy roster — total games, four-game weeks, light weeks, back-to-backs, and games played during YOUR league's playoff weeks (read from your Yahoo league settings, not guessed). The draft tiebreaker when two players are close.",
|
|
1169
|
+
inputSchema: {
|
|
1170
|
+
type: "object",
|
|
1171
|
+
properties: {
|
|
1172
|
+
teams: {
|
|
1173
|
+
type: "array",
|
|
1174
|
+
items: { type: "string" },
|
|
1175
|
+
description: "Limit to specific clubs (NHL or Yahoo abbreviations, e.g. [\"TOR\", \"SJ\"]). Omit to rate all 32."
|
|
1176
|
+
},
|
|
1177
|
+
playoff_start_week: {
|
|
1178
|
+
type: "number",
|
|
1179
|
+
description: "Override the fantasy playoff start week. Defaults to playoff_start_week from your Yahoo league settings."
|
|
1180
|
+
},
|
|
1181
|
+
playoff_end_week: {
|
|
1182
|
+
type: "number",
|
|
1183
|
+
description: "Override the final fantasy week. Defaults to your league's end_week."
|
|
1184
|
+
},
|
|
1185
|
+
top_n: {
|
|
1186
|
+
type: "number",
|
|
1187
|
+
description: "How many clubs to highlight (default 8)",
|
|
1188
|
+
default: 8
|
|
1189
|
+
},
|
|
1190
|
+
...baseChirpSchema
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
name: "chirp_draft_pick",
|
|
1196
|
+
description: "❄️ ICE at the draft table — with a pick on the clock, ranks who to take against YOUR draft: who is already gone, what your roster still needs, Yahoo's ADP (so 'value' means the market is wrong here), and each club's schedule during your league's playoff weeks. Pass already_drafted if Yahoo's draft results lag your live draft.",
|
|
1197
|
+
inputSchema: {
|
|
1198
|
+
type: "object",
|
|
1199
|
+
properties: {
|
|
1200
|
+
pick_number: {
|
|
1201
|
+
type: "number",
|
|
1202
|
+
description: "The pick currently on the clock. Inferred from Yahoo draft results if omitted."
|
|
1203
|
+
},
|
|
1204
|
+
already_drafted: {
|
|
1205
|
+
type: "array",
|
|
1206
|
+
items: { type: "string" },
|
|
1207
|
+
description: "Player names already off the board. Merged with Yahoo's draft results — use this when Yahoo's API lags a fast live draft."
|
|
1208
|
+
},
|
|
1209
|
+
roster_needs: {
|
|
1210
|
+
type: "array",
|
|
1211
|
+
items: { type: "string" },
|
|
1212
|
+
description: "Positions you still need, e.g. [\"RW\", \"G\"]. Inferred from your roster if omitted."
|
|
1213
|
+
},
|
|
1214
|
+
max_results: {
|
|
1215
|
+
type: "number",
|
|
1216
|
+
description: "How many candidates to return (default 8)",
|
|
1217
|
+
default: 8
|
|
1218
|
+
},
|
|
1219
|
+
pool_size: {
|
|
1220
|
+
type: "number",
|
|
1221
|
+
description: "How deep to pull the player pool (default 150, max 300)",
|
|
1222
|
+
default: 150
|
|
1223
|
+
},
|
|
1224
|
+
...baseChirpSchema
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
},
|
|
1216
1228
|
],
|
|
1217
1229
|
};
|
|
1218
1230
|
});
|
|
@@ -1233,12 +1245,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1233
1245
|
content: [{ type: "text", text: JSON.stringify(standings, null, 2) }],
|
|
1234
1246
|
};
|
|
1235
1247
|
}
|
|
1236
|
-
case "get_current_matchup": {
|
|
1237
|
-
const matchup = await getCurrentMatchup();
|
|
1238
|
-
return {
|
|
1239
|
-
content: [{ type: "text", text: JSON.stringify(matchup, null, 2) }],
|
|
1240
|
-
};
|
|
1241
|
-
}
|
|
1242
1248
|
case "search_players": {
|
|
1243
1249
|
const position = args?.position;
|
|
1244
1250
|
const count = args?.count || 25;
|
|
@@ -1257,12 +1263,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1257
1263
|
content: [{ type: "text", text: JSON.stringify(stats, null, 2) }],
|
|
1258
1264
|
};
|
|
1259
1265
|
}
|
|
1260
|
-
case "get_weekly_stats": {
|
|
1261
|
-
const stats = await getWeeklyStats();
|
|
1262
|
-
return {
|
|
1263
|
-
content: [{ type: "text", text: JSON.stringify(stats, null, 2) }],
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
1266
|
case "compare_matchup": {
|
|
1267
1267
|
const comparison = await compareMatchup();
|
|
1268
1268
|
return {
|
|
@@ -1283,24 +1283,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1283
1283
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1284
1284
|
};
|
|
1285
1285
|
}
|
|
1286
|
-
case "get_trending_players": {
|
|
1287
|
-
const trendType = args?.trend_type || "add";
|
|
1288
|
-
const count = args?.count || 25;
|
|
1289
|
-
const trending = await getTrendingPlayers(trendType, count);
|
|
1290
|
-
return {
|
|
1291
|
-
content: [{ type: "text", text: JSON.stringify(trending, null, 2) }],
|
|
1292
|
-
};
|
|
1293
|
-
}
|
|
1294
|
-
case "debug_api_call": {
|
|
1295
|
-
const endpoint = args?.endpoint;
|
|
1296
|
-
if (!endpoint) {
|
|
1297
|
-
throw new Error("endpoint is required");
|
|
1298
|
-
}
|
|
1299
|
-
const rawData = await yahooApiRequest(endpoint);
|
|
1300
|
-
return {
|
|
1301
|
-
content: [{ type: "text", text: JSON.stringify(rawData, null, 2) }],
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
1286
|
case "get_streaming_recommendations": {
|
|
1305
1287
|
// 🎯 Template Method Pattern: Use StreamingAnalysis class
|
|
1306
1288
|
const semanticContract = {
|
|
@@ -1459,27 +1441,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1459
1441
|
content: [{ type: "text", text: JSON.stringify(reportData, null, 2) }]
|
|
1460
1442
|
};
|
|
1461
1443
|
}
|
|
1462
|
-
case "semantic_player_comparison": {
|
|
1463
|
-
try {
|
|
1464
|
-
const result = await executePlayerComparison(args, getPlayerStats, searchPlayers);
|
|
1465
|
-
return {
|
|
1466
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1467
|
-
};
|
|
1468
|
-
}
|
|
1469
|
-
catch (error) {
|
|
1470
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1471
|
-
return {
|
|
1472
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
1473
|
-
error: errorMessage,
|
|
1474
|
-
note: "This is an experimental semantic intent-driven tool"
|
|
1475
|
-
}, null, 2) }],
|
|
1476
|
-
isError: true
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
1444
|
case "analyze_breakout_players": {
|
|
1481
1445
|
try {
|
|
1482
|
-
const result = await
|
|
1446
|
+
const result = await breakoutAnalysis.executeAnalysis({
|
|
1447
|
+
position_filter: args?.position_filter,
|
|
1448
|
+
breakout_age_max: args?.breakout_age_max,
|
|
1449
|
+
min_score: args?.min_score,
|
|
1450
|
+
max_results: args?.max_results
|
|
1451
|
+
}, {
|
|
1452
|
+
chirp_intensity: args?.chirp_intensity || 'standard',
|
|
1453
|
+
personality_mode: args?.personality_mode || 'analytical',
|
|
1454
|
+
enable_chirp: args?.enable_chirp !== false,
|
|
1455
|
+
semantic_intent: 'user_requested'
|
|
1456
|
+
});
|
|
1483
1457
|
return {
|
|
1484
1458
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1485
1459
|
};
|
|
@@ -1529,6 +1503,156 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1529
1503
|
};
|
|
1530
1504
|
}
|
|
1531
1505
|
}
|
|
1506
|
+
case "chirp_opponent": {
|
|
1507
|
+
const intensity = args?.chirp_intensity || 'savage';
|
|
1508
|
+
const mode = args?.personality_mode || 'roast_master';
|
|
1509
|
+
const result = await chirpOpponent(intensity, mode);
|
|
1510
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1511
|
+
}
|
|
1512
|
+
case "analyze_trade": {
|
|
1513
|
+
const giving = args?.giving;
|
|
1514
|
+
const receiving = args?.receiving;
|
|
1515
|
+
if (!giving || !receiving || giving.length === 0 || receiving.length === 0) {
|
|
1516
|
+
throw new Error("Both 'giving' and 'receiving' arrays are required and must not be empty.");
|
|
1517
|
+
}
|
|
1518
|
+
const intensity = args?.chirp_intensity || 'standard';
|
|
1519
|
+
const result = await analyzeTradeImpact(giving, receiving, intensity);
|
|
1520
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1521
|
+
}
|
|
1522
|
+
case "set_roster":
|
|
1523
|
+
case "set_opponent_roster": {
|
|
1524
|
+
const key = name === "set_roster" ? "roster" : "opponent";
|
|
1525
|
+
const text = args?.roster_text;
|
|
1526
|
+
if (!text || !text.trim()) {
|
|
1527
|
+
throw new Error("roster_text is required — paste your roster, one player per line.");
|
|
1528
|
+
}
|
|
1529
|
+
await NHL_STATS.load();
|
|
1530
|
+
if (!NHL_STATS.isAvailable()) {
|
|
1531
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
1532
|
+
error: "NHL player data unavailable",
|
|
1533
|
+
reason: NHL_STATS.getUnavailableReason(),
|
|
1534
|
+
note: "Names are resolved against live NHL rosters; retry shortly."
|
|
1535
|
+
}, null, 2) }], isError: true };
|
|
1536
|
+
}
|
|
1537
|
+
const report = ROSTER_STORE.parseRoster(text);
|
|
1538
|
+
const label = args?.team_name || (key === "roster" ? "My Team" : "Opponent");
|
|
1539
|
+
if (report.resolved.length === 0) {
|
|
1540
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
1541
|
+
saved: false,
|
|
1542
|
+
reason: "No player names could be resolved from that text.",
|
|
1543
|
+
lines_read: report.lines_read,
|
|
1544
|
+
unresolved: report.unresolved,
|
|
1545
|
+
ambiguous: report.ambiguous
|
|
1546
|
+
}, null, 2) }], isError: true };
|
|
1547
|
+
}
|
|
1548
|
+
const stored = ROSTER_STORE.saveRoster(key, report.resolved, label);
|
|
1549
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
1550
|
+
saved: true,
|
|
1551
|
+
team: label,
|
|
1552
|
+
players_resolved: report.resolved.length,
|
|
1553
|
+
lines_read: report.lines_read,
|
|
1554
|
+
roster: stored.players,
|
|
1555
|
+
// Surfaced, never silently dropped - the user decides what to do.
|
|
1556
|
+
needs_attention: {
|
|
1557
|
+
unresolved: report.unresolved,
|
|
1558
|
+
ambiguous: report.ambiguous
|
|
1559
|
+
},
|
|
1560
|
+
note: report.unresolved.length || report.ambiguous.length
|
|
1561
|
+
? "Some lines could not be matched to exactly one NHL player. Re-run set_roster with corrected names to include them."
|
|
1562
|
+
: "All lines resolved.",
|
|
1563
|
+
data_source: `NHL public API (rosters ${NHL_STATS.getSeasons().roster}, stats ${NHL_STATS.getSeasons().stats})`
|
|
1564
|
+
}, null, 2) }] };
|
|
1565
|
+
}
|
|
1566
|
+
case "set_standings": {
|
|
1567
|
+
const text = args?.standings_text;
|
|
1568
|
+
if (!text || !text.trim()) {
|
|
1569
|
+
throw new Error("standings_text is required — paste your league standings, one team per line.");
|
|
1570
|
+
}
|
|
1571
|
+
const rows = ROSTER_STORE.parseStandings(text);
|
|
1572
|
+
if (rows.length === 0) {
|
|
1573
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
1574
|
+
saved: false,
|
|
1575
|
+
reason: "No standings rows could be read from that text."
|
|
1576
|
+
}, null, 2) }], isError: true };
|
|
1577
|
+
}
|
|
1578
|
+
ROSTER_STORE.saveStandings(rows);
|
|
1579
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
1580
|
+
saved: true, teams: rows.length, standings: rows
|
|
1581
|
+
}, null, 2) }] };
|
|
1582
|
+
}
|
|
1583
|
+
case "show_stored_data": {
|
|
1584
|
+
const clearKey = args?.clear;
|
|
1585
|
+
if (clearKey) {
|
|
1586
|
+
const removed = ROSTER_STORE.clear(clearKey);
|
|
1587
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
1588
|
+
cleared: clearKey, existed: removed
|
|
1589
|
+
}, null, 2) }] };
|
|
1590
|
+
}
|
|
1591
|
+
const roster = ROSTER_STORE.getRoster('roster');
|
|
1592
|
+
const opponent = ROSTER_STORE.getRoster('opponent');
|
|
1593
|
+
const standings = ROSTER_STORE.getStandings();
|
|
1594
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
1595
|
+
roster: roster ?? "not set — paste yours with set_roster",
|
|
1596
|
+
opponent: opponent ?? "not set — paste one with set_opponent_roster",
|
|
1597
|
+
standings: standings ?? "not set — paste them with set_standings",
|
|
1598
|
+
storage: ROSTER_STORE.getDataDir()
|
|
1599
|
+
}, null, 2) }] };
|
|
1600
|
+
}
|
|
1601
|
+
case "schedule_value": {
|
|
1602
|
+
try {
|
|
1603
|
+
const semanticContract = {
|
|
1604
|
+
chirp_intensity: args?.chirp_intensity || 'standard',
|
|
1605
|
+
personality_mode: args?.personality_mode || 'analytical',
|
|
1606
|
+
enable_chirp: args?.enable_chirp !== false,
|
|
1607
|
+
semantic_intent: 'user_requested'
|
|
1608
|
+
};
|
|
1609
|
+
const result = await scheduleValueAnalysis.executeAnalysis({
|
|
1610
|
+
teams: args?.teams,
|
|
1611
|
+
playoff_start_week: args?.playoff_start_week,
|
|
1612
|
+
playoff_end_week: args?.playoff_end_week,
|
|
1613
|
+
top_n: args?.top_n
|
|
1614
|
+
}, semanticContract);
|
|
1615
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1616
|
+
}
|
|
1617
|
+
catch (error) {
|
|
1618
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1619
|
+
return {
|
|
1620
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1621
|
+
error: errorMessage,
|
|
1622
|
+
note: "Schedule value analysis failed - rates NHL club schedules against your league's playoff weeks"
|
|
1623
|
+
}, null, 2) }],
|
|
1624
|
+
isError: true
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
case "chirp_draft_pick": {
|
|
1629
|
+
try {
|
|
1630
|
+
const semanticContract = {
|
|
1631
|
+
chirp_intensity: args?.chirp_intensity || 'ice_cold',
|
|
1632
|
+
personality_mode: args?.personality_mode || 'championship_coach',
|
|
1633
|
+
enable_chirp: args?.enable_chirp !== false,
|
|
1634
|
+
semantic_intent: 'user_requested'
|
|
1635
|
+
};
|
|
1636
|
+
const result = await draftPickAnalysis.executeAnalysis({
|
|
1637
|
+
pick_number: args?.pick_number,
|
|
1638
|
+
already_drafted: args?.already_drafted,
|
|
1639
|
+
roster_needs: args?.roster_needs,
|
|
1640
|
+
max_results: args?.max_results,
|
|
1641
|
+
pool_size: args?.pool_size
|
|
1642
|
+
}, semanticContract);
|
|
1643
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1644
|
+
}
|
|
1645
|
+
catch (error) {
|
|
1646
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1647
|
+
return {
|
|
1648
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1649
|
+
error: errorMessage,
|
|
1650
|
+
note: "Draft pick analysis failed - pass already_drafted explicitly if Yahoo draft results are unavailable"
|
|
1651
|
+
}, null, 2) }],
|
|
1652
|
+
isError: true
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1532
1656
|
default:
|
|
1533
1657
|
throw new Error(`Unknown tool: ${name}`);
|
|
1534
1658
|
}
|
|
@@ -1544,6 +1668,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1544
1668
|
async function main() {
|
|
1545
1669
|
const transport = new StdioServerTransport();
|
|
1546
1670
|
await server.connect(transport);
|
|
1547
|
-
console.error(
|
|
1671
|
+
console.error(`🏒❄️ Semantic Chirp Intelligence MCP v${readPackageVersion()} - ICE is ON! (Real schedule, real stats)`);
|
|
1548
1672
|
}
|
|
1549
1673
|
main().catch(console.error);
|