@semanticintent/semantic-chirp-intelligence-mcp 3.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/build/index.js ADDED
@@ -0,0 +1,1549 @@
1
+ #!/usr/bin/env node
2
+ // ==========================================
3
+ // 📦 Imports - Organized by Domain
4
+ // ==========================================
5
+ // Core MCP
6
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
9
+ // Node.js
10
+ import * as fs from "fs";
11
+ import * as path from "path";
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
+ import { GOVERNANCE_MONITOR, validateSemanticChirpContract, auditSemanticContract, checkGovernanceHealth } from './domain/governance.js';
18
+ // Config layer
19
+ import { CHIRP_STYLES } from './config/chirp-styles.js';
20
+ import { PERSONALITY_MODES } from './config/personality-modes.js';
21
+ import { TOOL_METADATA } from './config/tool-metadata.js';
22
+ // Services layer
23
+ import { YahooApiClient } from './services/YahooApiClient.js';
24
+ // Analysis layer
25
+ import { IceAnalysis } from './analyses/IceAnalysis.js';
26
+ import { GamesInHandAnalysis } from './analyses/GamesInHandAnalysis.js';
27
+ import { StreamingAnalysis } from './analyses/StreamingAnalysis.js';
28
+ import { LineupAnalysis } from './analyses/LineupAnalysis.js';
29
+ 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
+ import { BreakoutAnalysis } from './analyses/BreakoutAnalysis.js';
34
+ dotenv.config();
35
+ const parseXML = promisify(parseString);
36
+ // 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
+ // ==========================================
46
+ // 🔧 MCP Tool Schema Base
47
+ // ==========================================
48
+ // Base chirp schema for MCP tool definitions
49
+ const baseChirpSchema = {
50
+ chirp_intensity: {
51
+ type: "string",
52
+ enum: ["gentle", "standard", "savage", "ice_cold"],
53
+ description: "Level of chirp intensity in responses (default: standard)"
54
+ },
55
+ personality_mode: {
56
+ type: "string",
57
+ enum: ["analytical", "motivational", "roast_master", "championship_coach"],
58
+ description: "Chirp personality style for responses (default: analytical)"
59
+ },
60
+ enable_chirp: {
61
+ type: "boolean",
62
+ description: "Enable chirp intelligence in responses (default: true)"
63
+ }
64
+ };
65
+ // ==========================================
66
+ // 🏗️ Service Initialization
67
+ // ==========================================
68
+ // Initialize Yahoo API client
69
+ const yahooClient = new YahooApiClient(YAHOO_CLIENT_ID, YAHOO_CLIENT_SECRET, YAHOO_API_BASE);
70
+ // Initialize analysis instances
71
+ const iceAnalysis = new IceAnalysis(yahooClient, LEAGUE_ID, TEAM_ID);
72
+ const gamesInHandAnalysis = new GamesInHandAnalysis(yahooClient, LEAGUE_ID, TEAM_ID);
73
+ const streamingAnalysis = new StreamingAnalysis(yahooClient, LEAGUE_ID, TEAM_ID);
74
+ const lineupAnalysis = new LineupAnalysis(yahooClient, LEAGUE_ID, TEAM_ID);
75
+ const breakoutAnalysis = new BreakoutAnalysis(yahooClient, LEAGUE_ID, TEAM_ID);
76
+ const weekendStreamAnalysis = new WeekendStreamAnalysis(yahooClient, LEAGUE_ID, TEAM_ID);
77
+ let cachedToken = null;
78
+ function loadToken() {
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
+ }
163
+ // Helper function to find current matchup by status
164
+ function findCurrentMatchup(matchups) {
165
+ console.error('[DEBUG] findCurrentMatchup called');
166
+ if (!matchups || matchups.count === '0') {
167
+ console.error('[DEBUG] No matchups or count is 0');
168
+ return null;
169
+ }
170
+ // Find matchup with status === "midevent" (current week)
171
+ const matchupKeys = Object.keys(matchups).filter(key => key !== 'count');
172
+ console.error(`[DEBUG] Total matchup keys: ${matchupKeys.length}`, matchupKeys);
173
+ const currentMatchup = matchupKeys.find(key => {
174
+ const matchupData = matchups[key]?.matchup;
175
+ // Matchup can be either an object or an array
176
+ const matchup = Array.isArray(matchupData) ? matchupData[0] : matchupData;
177
+ const week = matchup?.week;
178
+ const status = matchup?.status;
179
+ console.error(`[DEBUG] Checking key "${key}": week=${week}, status="${status}"`);
180
+ return matchup?.status === 'midevent';
181
+ });
182
+ console.error(`[DEBUG] Found currentMatchup key: "${currentMatchup}"`);
183
+ // If found, return it; otherwise fallback to last matchup
184
+ if (currentMatchup) {
185
+ const matchupData = matchups[currentMatchup].matchup;
186
+ console.error(`[DEBUG] Returning matchup for key "${currentMatchup}":`, {
187
+ week: matchupData?.week,
188
+ status: matchupData?.status
189
+ });
190
+ return matchupData;
191
+ }
192
+ // Fallback: return the last matchup in the list
193
+ const lastKey = matchupKeys[matchupKeys.length - 1];
194
+ console.error(`[DEBUG] FALLBACK - Using last key: "${lastKey}"`);
195
+ if (!lastKey)
196
+ return null;
197
+ return matchups[lastKey].matchup;
198
+ }
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
+ // Tool: Get Team Roster
250
+ async function getTeamRoster() {
251
+ const data = await yahooApiRequest(`/team/nhl.l.${LEAGUE_ID}.t.${TEAM_ID}/roster`);
252
+ const teamArray = data.fantasy_content.team[0];
253
+ const rosterData = data.fantasy_content.team[1].roster["0"].players;
254
+ // Extract team info
255
+ const teamKey = teamArray.find((item) => item.team_key)?.team_key;
256
+ const teamName = teamArray.find((item) => item.name)?.name;
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
+ });
278
+ return {
279
+ team_key: teamKey,
280
+ team_name: teamName,
281
+ roster: players,
282
+ };
283
+ }
284
+ // Tool: Get League Standings
285
+ async function getLeagueStandings() {
286
+ const data = await yahooApiRequest(`/league/nhl.l.${LEAGUE_ID}/standings`);
287
+ const teams = data.fantasy_content.league[1].standings[0].teams;
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;
293
+ return {
294
+ team_name: team[2].name,
295
+ rank: standings.rank,
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,
300
+ };
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
+ }
339
+ console.error('[DEBUG] Final result:', {
340
+ week,
341
+ status,
342
+ opponent: teams['1']?.team?.[0]?.[2]?.name
343
+ });
344
+ return {
345
+ week,
346
+ status,
347
+ your_team: teams['0']?.team?.[0]?.[2]?.name || 'Unknown',
348
+ opponent: teams['1']?.team?.[0]?.[2]?.name || 'Unknown',
349
+ };
350
+ }
351
+ // Tool: Search Players
352
+ async function searchPlayers(position, count = 25) {
353
+ let endpoint = `/league/nhl.l.${LEAGUE_ID}/players;status=A;count=${count}`;
354
+ if (position) {
355
+ endpoint += `;position=${position}`;
356
+ }
357
+ const data = await yahooApiRequest(endpoint);
358
+ const playersData = data.fantasy_content.league[1].players;
359
+ const players = Object.keys(playersData)
360
+ .filter(key => key !== 'count')
361
+ .map(key => {
362
+ const playerData = playersData[key].player[0];
363
+ // Find attributes in the array using .find() to handle dynamic structure
364
+ const playerId = playerData.find((item) => item.player_id)?.player_id;
365
+ const name = playerData.find((item) => item.name)?.name?.full;
366
+ const displayPosition = playerData.find((item) => item.display_position)?.display_position;
367
+ const team = playerData.find((item) => item.editorial_team_abbr)?.editorial_team_abbr;
368
+ const percentOwned = playerData.find((item) => item.percent_owned)?.percent_owned?.value || "0";
369
+ return {
370
+ player_id: playerId,
371
+ name: name,
372
+ position: displayPosition,
373
+ team: team,
374
+ percent_owned: percentOwned,
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;
389
+ return {
390
+ player_id: playerIdResult,
391
+ name: name,
392
+ position: displayPosition,
393
+ team: team,
394
+ stats: stats,
395
+ };
396
+ }
397
+ // Tool: Get Weekly Stats
398
+ async function getWeeklyStats() {
399
+ const data = await yahooApiRequest(`/team/nhl.l.${LEAGUE_ID}.t.${TEAM_ID}/matchups`);
400
+ const matchups = data.fantasy_content.team[1].matchups;
401
+ const currentMatchup = findCurrentMatchup(matchups);
402
+ if (!currentMatchup) {
403
+ return { message: "No current matchup" };
404
+ }
405
+ // currentMatchup structure: { "0": { teams: {...} }, week: 5, status: "midevent", ... }
406
+ const teams = currentMatchup["0"]?.teams;
407
+ const week = currentMatchup.week;
408
+ const status = currentMatchup.status;
409
+ if (!teams) {
410
+ return { message: "No teams data in current matchup" };
411
+ }
412
+ // Extract games remaining data
413
+ const yourGames = teams['0']?.team?.[1]?.team_remaining_games?.total;
414
+ const oppGames = teams['1']?.team?.[1]?.team_remaining_games?.total;
415
+ return {
416
+ week,
417
+ status,
418
+ your_team: {
419
+ name: teams['0']?.team?.[0]?.[2]?.name || 'Unknown',
420
+ stats: teams['0']?.team?.[1]?.team_stats?.stats || [],
421
+ games_remaining: yourGames?.remaining_games || 0,
422
+ games_completed: yourGames?.completed_games || 0,
423
+ live_games: yourGames?.live_games || 0,
424
+ },
425
+ opponent: {
426
+ name: teams['1']?.team?.[0]?.[2]?.name || 'Unknown',
427
+ stats: teams['1']?.team?.[1]?.team_stats?.stats || [],
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
+ }
438
+ };
439
+ }
440
+ // Tool: Compare Matchup
441
+ async function compareMatchup() {
442
+ const data = await yahooApiRequest(`/team/nhl.l.${LEAGUE_ID}.t.${TEAM_ID}/matchups`);
443
+ const matchups = data.fantasy_content.team[1].matchups;
444
+ const currentMatchup = findCurrentMatchup(matchups);
445
+ if (!currentMatchup) {
446
+ return { message: "No current matchup" };
447
+ }
448
+ // currentMatchup structure: { "0": { teams: {...} }, week: 5, status: "midevent", ... }
449
+ const teams = currentMatchup["0"]?.teams;
450
+ const week = currentMatchup.week;
451
+ if (!teams) {
452
+ return { message: "No teams data in current matchup" };
453
+ }
454
+ const yourStats = teams['0']?.team?.[1]?.team_stats?.stats || [];
455
+ const oppStats = teams['1']?.team?.[1]?.team_stats?.stats || [];
456
+ const comparison = yourStats.map((stat, idx) => {
457
+ const yourVal = parseFloat(stat.stat.value) || 0;
458
+ const oppVal = parseFloat(oppStats[idx]?.stat?.value || 0) || 0;
459
+ return {
460
+ category: stat.stat.display_name,
461
+ stat_id: stat.stat.stat_id,
462
+ your_value: yourVal,
463
+ opp_value: oppVal,
464
+ winning: yourVal > oppVal,
465
+ };
466
+ });
467
+ const categoriesWinning = comparison.filter((c) => c.winning).length;
468
+ return {
469
+ week,
470
+ your_team: teams['0']?.team?.[0]?.[2]?.name || 'Unknown',
471
+ opponent: teams['1']?.team?.[0]?.[2]?.name || 'Unknown',
472
+ categories_winning: categoriesWinning,
473
+ categories_total: comparison.length,
474
+ category_breakdown: comparison,
475
+ };
476
+ }
477
+ async function getRosterTransactionRecommendations(lookAheadDays = 7, targetPositions) {
478
+ try {
479
+ // Get all the data we need
480
+ const roster = await getTeamRoster();
481
+ // @ts-ignore - Legacy functions removed, but still referenced
482
+ const gamesInHand = null;
483
+ // @ts-ignore - Legacy functions removed, but still referenced
484
+ const streaming = null;
485
+ // Analyze current roster
486
+ // @ts-ignore - Legacy function still used here
487
+ const analysis = analyzeRosterStrengths(roster);
488
+ // Find transaction opportunities
489
+ const recommendations = [];
490
+ // 1. IMMEDIATE FIXES (injured players)
491
+ const injuredActive = roster.roster.filter(p => p.status && p.status !== "" && !p.selected_position.includes("IR"));
492
+ for (const player of injuredActive) {
493
+ recommendations.push({
494
+ priority: "CRITICAL",
495
+ action: "move_to_ir",
496
+ player: player.name,
497
+ player_id: player.player_id,
498
+ current_position: player.selected_position,
499
+ status: player.status,
500
+ reason: `${player.name} is ${player.status} but still in active lineup`,
501
+ suggested_action: player.status === "O" ? "Move to IR+" : "Move to IR or bench"
502
+ });
503
+ }
504
+ // 2. POSITION WEAKNESS ANALYSIS
505
+ // @ts-ignore - Legacy function, streaming is now null
506
+ const weakPositions = identifyWeakPositions(roster, analysis);
507
+ // @ts-ignore - Legacy streaming data
508
+ for (const position of weakPositions) {
509
+ // @ts-ignore
510
+ const bestAvailable = (streaming?.streaming_targets || [])
511
+ .filter((p) => targetPositions ? targetPositions.includes(position.position) : true)
512
+ .filter((p) => p.position.includes(position.position))
513
+ .slice(0, 3);
514
+ if (bestAvailable.length > 0) {
515
+ const dropCandidate = findBestDropCandidate(roster, position.position);
516
+ recommendations.push({
517
+ priority: "HIGH",
518
+ action: "pickup_drop",
519
+ pickup: {
520
+ name: bestAvailable[0].name,
521
+ player_id: bestAvailable[0].player_id,
522
+ position: bestAvailable[0].position,
523
+ team: bestAvailable[0].team,
524
+ reason: bestAvailable[0].reason,
525
+ streaming_score: bestAvailable[0].streaming_score,
526
+ percent_owned: bestAvailable[0].percent_owned
527
+ },
528
+ drop: dropCandidate,
529
+ position_need: position.position,
530
+ reasoning: `Strengthen ${position.position} - ${position.weakness_reason}`
531
+ });
532
+ }
533
+ }
534
+ // 3. SCHEDULE OPTIMIZATION
535
+ // @ts-ignore - Legacy null handling
536
+ const gamesDiff = gamesInHand?.games_in_hand_difference || 0;
537
+ if (gamesDiff < 0) {
538
+ // Opponent has more games - prioritize volume players
539
+ // @ts-ignore
540
+ const volumePickups = (streaming?.streaming_targets || [])
541
+ .filter((t) => t.team_trending_count >= 3)
542
+ .slice(0, 2);
543
+ for (const pickup of volumePickups) {
544
+ recommendations.push({
545
+ priority: "MEDIUM",
546
+ action: "volume_play",
547
+ pickup: pickup,
548
+ reasoning: `Opponent has ${Math.abs(gamesDiff)} more games - need volume players from teams with favorable schedules`
549
+ });
550
+ }
551
+ }
552
+ // 4. BENCH OPTIMIZATION
553
+ const benchUpgrades = findBenchUpgrades(roster, streaming);
554
+ recommendations.push(...benchUpgrades);
555
+ return {
556
+ roster_analysis: analysis,
557
+ immediate_issues: injuredActive.length,
558
+ // @ts-ignore - Legacy null handling
559
+ games_disadvantage: gamesInHand.games_in_hand_difference,
560
+ weak_positions: weakPositions,
561
+ recommendations: recommendations
562
+ .sort((a, b) => {
563
+ const priorityOrder = { "CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3 };
564
+ return (priorityOrder[a.priority] || 99) - (priorityOrder[b.priority] || 99);
565
+ })
566
+ .slice(0, 8), // Top 8 recommendations
567
+ // @ts-ignore - Legacy null handling
568
+ optimal_timing: streaming.optimal_timing,
569
+ // @ts-ignore - Legacy null handling
570
+ market_intelligence: streaming.market_intelligence
571
+ };
572
+ }
573
+ catch (error) {
574
+ return {
575
+ error: `Failed to get roster recommendations: ${error.message}`,
576
+ recommendations: []
577
+ };
578
+ }
579
+ }
580
+ // ==========================================
581
+ // 🏒 Chirp Intelligence Engine
582
+ // ==========================================
583
+ function generateContextualChirp(toolName, data, chirpStyle, personality) {
584
+ const metadata = TOOL_METADATA[toolName];
585
+ if (!metadata)
586
+ return "";
587
+ switch (metadata.chirp_potential) {
588
+ case "roster_weaknesses":
589
+ return generateRosterChirp(data, chirpStyle, personality);
590
+ case "schedule_domination":
591
+ return generateScheduleChirp(data, chirpStyle, personality);
592
+ case "brutal_optimization":
593
+ return generateOptimizationChirp(data, chirpStyle, personality);
594
+ case "weekly_performance":
595
+ return generateWeeklyPerformanceChirp(data, chirpStyle, personality);
596
+ case "pickup_strategy":
597
+ return generatePickupStrategyChirp(data, chirpStyle, personality);
598
+ default:
599
+ return generateGenericChirp(data, chirpStyle, personality);
600
+ }
601
+ }
602
+ function generateRosterChirp(data, chirpStyle, personality) {
603
+ const injured = data.roster?.filter((p) => p.status && p.status !== "").length || 0;
604
+ if (injured > 0 && chirpStyle.tone === "brutal_truth") {
605
+ return `${chirpStyle.prefix} you've got ${injured} injured players mucking up your lineup. That's not championship material! ${chirpStyle.suffix}`;
606
+ }
607
+ if (injured > 0 && chirpStyle.tone === "encouraging") {
608
+ return `${chirpStyle.prefix} moving those ${injured} injured players to IR to optimize your roster. ${chirpStyle.suffix}`;
609
+ }
610
+ if (injured > 0 && chirpStyle.tone === "championship_enforcer") {
611
+ return `${chirpStyle.prefix} ${injured} injured players dragging down your roster. Champions handle their IR like pros. ${chirpStyle.suffix}`;
612
+ }
613
+ return `${personality.phrases[0]} your team composition looks solid.`;
614
+ }
615
+ function generateScheduleChirp(data, chirpStyle, personality) {
616
+ const advantage = data.advantage;
617
+ const diff = Math.abs(data.games_in_hand_difference || 0);
618
+ if (advantage === "opponent" && chirpStyle.tone === "brutal_truth") {
619
+ return `${chirpStyle.prefix} your opponent has ${diff} more games than you and you're just sitting there? Time to drop the mittens and get aggressive! ${chirpStyle.suffix}`;
620
+ }
621
+ if (advantage === "you" && chirpStyle.tone === "championship_enforcer") {
622
+ return `${chirpStyle.prefix} You've got ${diff} more games. This is where champions separate from the pretenders. ${chirpStyle.suffix}`;
623
+ }
624
+ if (advantage === "you" && chirpStyle.tone === "direct_honest") {
625
+ return `${chirpStyle.prefix} capitalize on your ${diff}-game advantage ${chirpStyle.suffix}`;
626
+ }
627
+ return `${personality.phrases[0]} the schedule advantage situation.`;
628
+ }
629
+ function generateOptimizationChirp(data, chirpStyle, personality) {
630
+ const criticalIssues = data.immediate_issues || 0;
631
+ const recommendations = data.recommendations?.length || 0;
632
+ if (criticalIssues > 0 && chirpStyle.tone === "brutal_truth") {
633
+ return `${chirpStyle.prefix} you've got ${criticalIssues} critical lineup issues and ${recommendations} ways to fix them. Stop window shopping and start dominating! ${chirpStyle.suffix}`;
634
+ }
635
+ if (criticalIssues === 0 && chirpStyle.tone === "championship_enforcer") {
636
+ return `${chirpStyle.prefix} Your lineup is solid but ICE found ${recommendations} ways to push you over the top. ${chirpStyle.suffix}`;
637
+ }
638
+ if (recommendations > 5 && chirpStyle.tone === "direct_honest") {
639
+ return `${chirpStyle.prefix} execute these ${recommendations} optimizations ${chirpStyle.suffix}`;
640
+ }
641
+ return `${personality.phrases[0]} ${recommendations} optimization opportunities to consider.`;
642
+ }
643
+ function generateWeeklyPerformanceChirp(data, chirpStyle, personality) {
644
+ const yourGames = data.games_in_hand?.your_remaining || 0;
645
+ const oppGames = data.games_in_hand?.opponent_remaining || 0;
646
+ if (yourGames > oppGames && chirpStyle.tone === "championship_enforcer") {
647
+ return `${chirpStyle.prefix} You've got more games left - time to bury them. ${chirpStyle.suffix}`;
648
+ }
649
+ if (yourGames < oppGames && chirpStyle.tone === "brutal_truth") {
650
+ return `${chirpStyle.prefix} they've got more games - every stat matters now! ${chirpStyle.suffix}`;
651
+ }
652
+ return `${personality.phrases[0]} your weekly matchup positioning.`;
653
+ }
654
+ function generatePickupStrategyChirp(data, chirpStyle, personality) {
655
+ const targets = data.streaming_targets?.length || 0;
656
+ const hotTeam = data.market_intelligence?.top_trending_team || "unknown";
657
+ if (targets > 10 && chirpStyle.tone === "championship_enforcer") {
658
+ return `${chirpStyle.prefix} ${targets} targets identified. Focus on ${hotTeam} players for maximum impact. ${chirpStyle.suffix}`;
659
+ }
660
+ if (targets > 10 && chirpStyle.tone === "brutal_truth") {
661
+ return `${chirpStyle.prefix} ${targets} players better than what you've got - are you here to compete or participate? ${chirpStyle.suffix}`;
662
+ }
663
+ return `${personality.phrases[0]} ${targets} streaming opportunities on the wire.`;
664
+ }
665
+ function generateGenericChirp(data, chirpStyle, personality) {
666
+ return `${personality.phrases[0]} the data patterns. ${chirpStyle.prefix} taking action based on these insights. ${chirpStyle.suffix}`;
667
+ }
668
+ function generateIntentSummary(data, personality) {
669
+ switch (personality.focus) {
670
+ case "championship_mindset":
671
+ return "Championship strategy: Execute these moves for league domination";
672
+ case "data_driven":
673
+ return "Statistical analysis: Data-driven recommendations for optimal performance";
674
+ case "entertainment_value":
675
+ return "Bottom line: Time to separate the contenders from the pretenders";
676
+ case "winning_strategy":
677
+ return "Elite strategy: Next-level moves for next-level results";
678
+ default:
679
+ return "Action required: Strategic improvements identified";
680
+ }
681
+ }
682
+ function generateICETruth(data, chirpStyle) {
683
+ if (chirpStyle.tone === "championship_enforcer") {
684
+ return "❄️ ICE Cold Truth: Champions make moves, pretenders make excuses.";
685
+ }
686
+ if (chirpStyle.tone === "brutal_truth") {
687
+ return "🔥 Savage Reality: Your competition isn't waiting - neither should you.";
688
+ }
689
+ if (chirpStyle.tone === "direct_honest") {
690
+ return "💪 Real Talk: Smart players act on good intel.";
691
+ }
692
+ return "🧠 Smart Play: Optimal decisions lead to optimal results.";
693
+ }
694
+ function enhanceWithChirpIntelligence(toolName, originalData, chirpOptions = {}) {
695
+ // 🏛️ Semantic Anchoring (Rule 2): Validate semantic contract before processing
696
+ const semanticContract = {
697
+ ...chirpOptions,
698
+ semantic_intent: chirpOptions.enable_chirp === false ? "user_requested" : "system_default",
699
+ tool_context: toolName
700
+ };
701
+ validateSemanticChirpContract(semanticContract, toolName);
702
+ // 🛡️ Semantic Anchoring (Rule 4): Freeze semantic contract to prevent violations
703
+ const frozenChirpOptions = Object.freeze({ ...chirpOptions });
704
+ // 🔍 Phase 5: Audit immutability enforcement
705
+ auditSemanticContract(semanticContract, toolName, "enforcement");
706
+ // 🛡️ Protected semantic contract with Proxy for runtime enforcement
707
+ const protectedChirpOptions = new Proxy(frozenChirpOptions, {
708
+ set() {
709
+ // 📊 Phase 5: Track immutability violation
710
+ GOVERNANCE_MONITOR.trackViolation({
711
+ rule: "Rule 4 - Immutability Protection",
712
+ severity: "error",
713
+ tool_name: toolName,
714
+ violation_type: "attempted_mutation",
715
+ details: "Attempted to set property on immutable ChirpParameters"
716
+ });
717
+ throw new Error('🚨 Semantic contract violation: ChirpParameters are immutable after creation');
718
+ },
719
+ deleteProperty() {
720
+ // 📊 Phase 5: Track immutability violation
721
+ GOVERNANCE_MONITOR.trackViolation({
722
+ rule: "Rule 4 - Immutability Protection",
723
+ severity: "error",
724
+ tool_name: toolName,
725
+ violation_type: "attempted_property_deletion",
726
+ details: "Attempted to delete property from immutable ChirpParameters"
727
+ });
728
+ throw new Error('🚨 Semantic contract violation: Cannot delete ChirpParameters properties');
729
+ }
730
+ });
731
+ if (protectedChirpOptions.enable_chirp === false) {
732
+ return originalData;
733
+ }
734
+ const metadata = TOOL_METADATA[toolName];
735
+ if (!metadata) {
736
+ return originalData;
737
+ }
738
+ const chirpStyle = CHIRP_STYLES[protectedChirpOptions.chirp_intensity || 'standard'];
739
+ const personality = PERSONALITY_MODES[protectedChirpOptions.personality_mode || 'analytical'];
740
+ return {
741
+ // Original data preserved
742
+ ...originalData,
743
+ // NEW: Chirp Intelligence Layer
744
+ chirp_intelligence: {
745
+ // 🎯 Semantic Anchoring (Rule 1): Use observable semantic property instead of string comparison
746
+ tool_identity: metadata.is_ice_engine
747
+ ? metadata.tool_semantic_identity
748
+ : `${toolName} with chirp intelligence`,
749
+ style: chirpStyle.tone,
750
+ personality: personality.voice,
751
+ intensity: protectedChirpOptions.chirp_intensity || 'standard',
752
+ semantic_context: metadata.hockey_context,
753
+ // Dynamic chirp based on data
754
+ analysis_chirp: generateContextualChirp(toolName, originalData, chirpStyle, personality),
755
+ // Intent-driven one-liner
756
+ intent_summary: generateIntentSummary(originalData, personality),
757
+ // Hockey wisdom
758
+ ice_cold_truth: generateICETruth(originalData, chirpStyle)
759
+ },
760
+ // Discovery metadata
761
+ metadata: {
762
+ tool_tags: metadata.discovery_tags,
763
+ intent_category: metadata.intent_category,
764
+ chirp_energy: chirpStyle.energy,
765
+ hockey_wisdom_level: "ICE_tier",
766
+ semantic_depth: "enhanced"
767
+ }
768
+ };
769
+ }
770
+ // Helper functions
771
+ function analyzeRosterStrengths(roster) {
772
+ const positions = {
773
+ C: [], LW: [], RW: [], D: [], G: [],
774
+ bench: [], ir: [], active: []
775
+ };
776
+ roster.roster.forEach((player) => {
777
+ if (player.selected_position === "BN") {
778
+ positions.bench.push(player);
779
+ }
780
+ else if (player.selected_position.includes("IR")) {
781
+ positions.ir.push(player);
782
+ }
783
+ else {
784
+ positions.active.push(player);
785
+ // Analyze by primary position
786
+ if (player.position.includes("C"))
787
+ positions.C.push(player);
788
+ if (player.position.includes("LW"))
789
+ positions.LW.push(player);
790
+ if (player.position.includes("RW"))
791
+ positions.RW.push(player);
792
+ if (player.position.includes("D"))
793
+ positions.D.push(player);
794
+ if (player.position.includes("G"))
795
+ positions.G.push(player);
796
+ }
797
+ });
798
+ return {
799
+ ...positions,
800
+ position_counts: {
801
+ C: positions.C.length,
802
+ LW: positions.LW.length,
803
+ RW: positions.RW.length,
804
+ D: positions.D.length,
805
+ G: positions.G.length,
806
+ bench: positions.bench.length,
807
+ ir: positions.ir.length
808
+ }
809
+ };
810
+ }
811
+ function identifyWeakPositions(roster, analysis) {
812
+ const weaknesses = [];
813
+ // Check goalies first (most critical)
814
+ const healthyGoalies = analysis.G.filter((p) => !p.status || p.status === "");
815
+ if (healthyGoalies.length < 2) {
816
+ weaknesses.push({
817
+ position: "G",
818
+ weakness_reason: `Only ${healthyGoalies.length} healthy goalie(s) - need backup`,
819
+ severity: "HIGH"
820
+ });
821
+ }
822
+ // Check defense depth
823
+ const healthyDefense = analysis.D.filter((p) => !p.status || p.status === "");
824
+ if (healthyDefense.length < 4) {
825
+ weaknesses.push({
826
+ position: "D",
827
+ weakness_reason: `Only ${healthyDefense.length} healthy defensemen - need depth`,
828
+ severity: "MEDIUM"
829
+ });
830
+ }
831
+ // Check forward positions
832
+ const healthyC = analysis.C.filter((p) => !p.status || p.status === "");
833
+ if (healthyC.length < 2) {
834
+ weaknesses.push({
835
+ position: "C",
836
+ weakness_reason: `Only ${healthyC.length} healthy center(s) - need depth`,
837
+ severity: "MEDIUM"
838
+ });
839
+ }
840
+ return weaknesses;
841
+ }
842
+ function findBestDropCandidate(roster, positionNeed) {
843
+ // Priority order for drops: bench players > injured players > worst performers
844
+ const benchPlayers = roster.roster.filter((p) => p.selected_position === "BN");
845
+ if (benchPlayers.length > 0) {
846
+ // Find bench player with lowest priority (could be enhanced with stats)
847
+ const dropCandidate = benchPlayers[benchPlayers.length - 1]; // Last bench player
848
+ return {
849
+ name: dropCandidate.name,
850
+ player_id: dropCandidate.player_id,
851
+ position: dropCandidate.position,
852
+ team: dropCandidate.team,
853
+ current_position: dropCandidate.selected_position,
854
+ reason: "Lowest priority bench player for position upgrade"
855
+ };
856
+ }
857
+ // If no bench players, suggest dropping injured player not on IR
858
+ const injuredNotOnIR = roster.roster.filter((p) => p.status && p.status !== "" && !p.selected_position.includes("IR"));
859
+ if (injuredNotOnIR.length > 0) {
860
+ const dropCandidate = injuredNotOnIR[0];
861
+ return {
862
+ name: dropCandidate.name,
863
+ player_id: dropCandidate.player_id,
864
+ position: dropCandidate.position,
865
+ team: dropCandidate.team,
866
+ current_position: dropCandidate.selected_position,
867
+ reason: `Injured player (${dropCandidate.status}) - consider dropping if no IR space`
868
+ };
869
+ }
870
+ return {
871
+ name: "Manual Review Needed",
872
+ reason: "No obvious drop candidates - review roster manually"
873
+ };
874
+ }
875
+ function findBenchUpgrades(roster, streaming) {
876
+ const recommendations = [];
877
+ const benchPlayers = roster.roster.filter((p) => p.selected_position === "BN");
878
+ // Look for significantly better available players
879
+ for (const benchPlayer of benchPlayers) {
880
+ const betterOptions = streaming.streaming_targets
881
+ .filter((available) => {
882
+ // Same position and significantly higher score
883
+ return available.position.includes(benchPlayer.position.split(',')[0]) &&
884
+ available.streaming_score > 75; // High threshold for bench upgrades
885
+ })
886
+ .slice(0, 1);
887
+ if (betterOptions.length > 0) {
888
+ recommendations.push({
889
+ priority: "LOW",
890
+ action: "bench_upgrade",
891
+ pickup: betterOptions[0],
892
+ drop: {
893
+ name: benchPlayer.name,
894
+ player_id: benchPlayer.player_id,
895
+ reason: "Upgrade bench depth"
896
+ },
897
+ reasoning: `${betterOptions[0].name} (score: ${betterOptions[0].streaming_score}) could upgrade over ${benchPlayer.name}`
898
+ });
899
+ }
900
+ }
901
+ return recommendations;
902
+ }
903
+ // Tool: Get Games In Hand
904
+ // ==========================================
905
+ // 🗑️ Legacy Functions Removed - Phase 4
906
+ // ==========================================
907
+ // The following functions have been migrated to Template Method Pattern classes:
908
+ // - getGamesInHand() → GamesInHandAnalysis
909
+ // - optimizeLineup() → LineupAnalysis
910
+ // - getStreamingRecommendations() → StreamingAnalysis
911
+ // ==========================================
912
+ // Tool: Get Trending Players
913
+ async function getTrendingPlayers(trendType = "add", count = 25) {
914
+ const sortParam = trendType === "add" ? "AR" : "OR";
915
+ const endpoint = `/league/nhl.l.${LEAGUE_ID}/players;status=A;sort=${sortParam};count=${count}`;
916
+ const data = await yahooApiRequest(endpoint);
917
+ const playersData = data.fantasy_content.league[1].players;
918
+ const players = Object.keys(playersData)
919
+ .filter(key => key !== 'count')
920
+ .map(key => {
921
+ const playerData = playersData[key].player[0];
922
+ // Find attributes in the array using .find() to handle dynamic structure
923
+ const playerId = playerData.find((item) => item.player_id)?.player_id;
924
+ const name = playerData.find((item) => item.name)?.name?.full;
925
+ const displayPosition = playerData.find((item) => item.display_position)?.display_position;
926
+ const team = playerData.find((item) => item.editorial_team_abbr)?.editorial_team_abbr;
927
+ const percentOwned = playerData.find((item) => item.percent_owned)?.percent_owned?.value || "0";
928
+ return {
929
+ player_id: playerId,
930
+ name: name,
931
+ position: displayPosition,
932
+ team: team,
933
+ percent_owned: percentOwned,
934
+ };
935
+ });
936
+ return { trend_type: trendType, players };
937
+ }
938
+ // Initialize MCP Server
939
+ const server = new Server({
940
+ name: "semantic-chirp-intelligence-mcp",
941
+ version: "3.0.0",
942
+ }, {
943
+ capabilities: {
944
+ tools: {},
945
+ },
946
+ });
947
+ // Register Tools
948
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
949
+ return {
950
+ tools: [
951
+ {
952
+ name: "get_team_roster",
953
+ description: "Get your current fantasy hockey roster with all players and their positions",
954
+ inputSchema: {
955
+ type: "object",
956
+ properties: {},
957
+ },
958
+ },
959
+ {
960
+ name: "get_league_standings",
961
+ description: "Get current league standings showing all teams and their records",
962
+ inputSchema: {
963
+ type: "object",
964
+ properties: {},
965
+ },
966
+ },
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
+ {
976
+ name: "search_players",
977
+ description: "Search for available players (free agents) by position. Returns top available players.",
978
+ inputSchema: {
979
+ type: "object",
980
+ properties: {
981
+ position: {
982
+ type: "string",
983
+ description: "Player position: C, LW, RW, D, G, or leave empty for all",
984
+ },
985
+ count: {
986
+ type: "number",
987
+ description: "Number of players to return (default 25, max 100)",
988
+ default: 25,
989
+ },
990
+ },
991
+ },
992
+ },
993
+ {
994
+ name: "get_player_stats",
995
+ description: "Get detailed statistics for a specific player using their player ID",
996
+ inputSchema: {
997
+ type: "object",
998
+ properties: {
999
+ player_id: {
1000
+ type: "string",
1001
+ description: "Player ID (just the number, e.g., '6381')",
1002
+ },
1003
+ },
1004
+ required: ["player_id"],
1005
+ },
1006
+ },
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
+ {
1016
+ name: "compare_matchup",
1017
+ description: "Get detailed category-by-category comparison with your current opponent",
1018
+ inputSchema: {
1019
+ type: "object",
1020
+ properties: {},
1021
+ },
1022
+ },
1023
+ {
1024
+ name: "optimize_lineup",
1025
+ description: "Get AI-powered recommendations for optimal lineup based on player health and positions",
1026
+ inputSchema: {
1027
+ type: "object",
1028
+ properties: {},
1029
+ },
1030
+ },
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
+ {
1066
+ name: "get_streaming_recommendations",
1067
+ 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.",
1068
+ inputSchema: {
1069
+ type: "object",
1070
+ properties: {
1071
+ days_ahead: {
1072
+ type: "number",
1073
+ description: "Look ahead window in days (default 7)",
1074
+ default: 7,
1075
+ },
1076
+ position_filter: {
1077
+ type: "string",
1078
+ description: "Filter by position: C, LW, RW, D, G, or leave empty for all",
1079
+ },
1080
+ strategy_type: {
1081
+ type: "string",
1082
+ description: "Streaming strategy: 'weekly' (full week holds), 'weekend' (Fri-Sun pickups), or 'daily' (day-to-day streaming)",
1083
+ enum: ["weekly", "weekend", "daily"],
1084
+ default: "weekly",
1085
+ },
1086
+ ...baseChirpSchema
1087
+ },
1088
+ },
1089
+ },
1090
+ {
1091
+ name: "get_games_in_hand",
1092
+ description: "Get games in hand analysis with optional chirp intelligence - shows remaining games for you vs opponent to identify schedule advantages",
1093
+ inputSchema: {
1094
+ type: "object",
1095
+ properties: {
1096
+ ...baseChirpSchema
1097
+ },
1098
+ },
1099
+ },
1100
+ {
1101
+ name: "get_roster_transaction_recommendations",
1102
+ description: "🏒 ICE - Intent Chirp Engine: Get championship-level roster optimization with savage analysis and brutal honesty about your lineup decisions",
1103
+ inputSchema: {
1104
+ type: "object",
1105
+ properties: {
1106
+ look_ahead_days: {
1107
+ type: "number",
1108
+ description: "Days to look ahead for schedule analysis (default 7)",
1109
+ default: 7,
1110
+ },
1111
+ target_positions: {
1112
+ type: "array",
1113
+ items: { type: "string" },
1114
+ description: "Specific positions to focus on (C, LW, RW, D, G)",
1115
+ },
1116
+ ...baseChirpSchema
1117
+ },
1118
+ },
1119
+ },
1120
+ {
1121
+ name: "ice",
1122
+ description: "❄️ ICE - Intent Chirp Engine: The ultimate fantasy hockey advisor with ice-cold analysis and championship-level chirp intelligence. Multi-mode analysis tool that combines all insights.",
1123
+ inputSchema: {
1124
+ type: "object",
1125
+ properties: {
1126
+ analysis_type: {
1127
+ type: "string",
1128
+ enum: ["full_roster", "weekly_matchup", "pickup_strategy", "lineup_optimization"],
1129
+ description: "Type of ICE analysis to perform (default: full_roster)"
1130
+ },
1131
+ ...baseChirpSchema,
1132
+ look_ahead_days: {
1133
+ type: "number",
1134
+ default: 7,
1135
+ description: "Days ahead for schedule analysis"
1136
+ }
1137
+ },
1138
+ },
1139
+ },
1140
+ {
1141
+ name: "governance_dashboard",
1142
+ description: "🏛️ View Semantic Anchoring Governance health metrics and analysis performance statistics. Monitor violations, contract validations, and template pattern execution metrics.",
1143
+ inputSchema: {
1144
+ type: "object",
1145
+ properties: {
1146
+ report_type: {
1147
+ type: "string",
1148
+ enum: ["health", "analyses", "violations", "full"],
1149
+ description: "Type of report: 'health' (governance status), 'analyses' (performance metrics), 'violations' (error logs), 'full' (complete report)",
1150
+ default: "full"
1151
+ }
1152
+ }
1153
+ }
1154
+ },
1155
+ {
1156
+ name: SEMANTIC_PLAYER_COMPARISON.name,
1157
+ description: `${SEMANTIC_PLAYER_COMPARISON.description} - Auto-configured from semantic intent!`,
1158
+ inputSchema: getPlayerComparisonInputSchema()
1159
+ },
1160
+ {
1161
+ name: SEMANTIC_BREAKOUT_ANALYSIS.name,
1162
+ description: `${SEMANTIC_BREAKOUT_ANALYSIS.description} - 🏒 Comprehensive breakout analysis with data-driven scoring (40% recent, 30% projections, 20% opportunity, 10% risk)`,
1163
+ inputSchema: getBreakoutAnalysisInputSchema()
1164
+ },
1165
+ {
1166
+ name: "analyze_weekend_streams",
1167
+ description: "🏒❄️ Weekend Stream Classifier - Distinguish desperation streams (bye-week fillers, <1 week value) from genuine opportunities (sustainable roles, >2 week upside). Uses binary decision tree and upside scoring (0-100). Chirp style: desperate_or_legit",
1168
+ inputSchema: {
1169
+ type: "object",
1170
+ properties: {
1171
+ date_range: {
1172
+ type: "object",
1173
+ properties: {
1174
+ start: {
1175
+ type: "string",
1176
+ description: "Weekend start date (YYYY-MM-DD, e.g., '2025-10-11')"
1177
+ },
1178
+ end: {
1179
+ type: "string",
1180
+ description: "Weekend end date (YYYY-MM-DD, e.g., '2025-10-13')"
1181
+ }
1182
+ },
1183
+ required: ["start", "end"],
1184
+ description: "Weekend date range to analyze (typically Fri-Sun or Sat-Sun)"
1185
+ },
1186
+ position_filter: {
1187
+ type: "array",
1188
+ items: { type: "string" },
1189
+ description: "Filter by positions: ['C', 'LW', 'RW', 'D', 'G']. Leave empty for all positions"
1190
+ },
1191
+ ownership_max: {
1192
+ type: "number",
1193
+ description: "Maximum ownership percentage (default 50). Players above this are excluded",
1194
+ default: 50
1195
+ },
1196
+ team_needs: {
1197
+ type: "array",
1198
+ items: { type: "string" },
1199
+ description: "Your roster needs: ['bye_fill', 'injury_cover', 'G_volume', 'C_depth']. Helps classification"
1200
+ },
1201
+ min_upside_score: {
1202
+ type: "number",
1203
+ description: "Minimum upside score (0-100) to include. Higher = more genuine opportunities",
1204
+ default: 0
1205
+ },
1206
+ max_results: {
1207
+ type: "number",
1208
+ description: "Maximum results per classification (default 10)",
1209
+ default: 10
1210
+ },
1211
+ ...baseChirpSchema
1212
+ },
1213
+ required: ["date_range"]
1214
+ }
1215
+ }
1216
+ ],
1217
+ };
1218
+ });
1219
+ // Handle Tool Calls
1220
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1221
+ try {
1222
+ const { name, arguments: args } = request.params;
1223
+ switch (name) {
1224
+ case "get_team_roster": {
1225
+ const roster = await getTeamRoster();
1226
+ return {
1227
+ content: [{ type: "text", text: JSON.stringify(roster, null, 2) }],
1228
+ };
1229
+ }
1230
+ case "get_league_standings": {
1231
+ const standings = await getLeagueStandings();
1232
+ return {
1233
+ content: [{ type: "text", text: JSON.stringify(standings, null, 2) }],
1234
+ };
1235
+ }
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
+ case "search_players": {
1243
+ const position = args?.position;
1244
+ const count = args?.count || 25;
1245
+ const players = await searchPlayers(position, count);
1246
+ return {
1247
+ content: [{ type: "text", text: JSON.stringify(players, null, 2) }],
1248
+ };
1249
+ }
1250
+ case "get_player_stats": {
1251
+ const playerId = args?.player_id;
1252
+ if (!playerId) {
1253
+ throw new Error("player_id is required");
1254
+ }
1255
+ const stats = await getPlayerStats(playerId);
1256
+ return {
1257
+ content: [{ type: "text", text: JSON.stringify(stats, null, 2) }],
1258
+ };
1259
+ }
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
+ case "compare_matchup": {
1267
+ const comparison = await compareMatchup();
1268
+ return {
1269
+ content: [{ type: "text", text: JSON.stringify(comparison, null, 2) }],
1270
+ };
1271
+ }
1272
+ case "optimize_lineup": {
1273
+ // 🎯 Template Method Pattern: Use LineupAnalysis class
1274
+ const semanticContract = {
1275
+ chirp_intensity: args?.chirp_intensity,
1276
+ personality_mode: args?.personality_mode,
1277
+ enable_chirp: args?.enable_chirp,
1278
+ semantic_intent: "user_requested",
1279
+ tool_context: "optimize_lineup"
1280
+ };
1281
+ const result = await lineupAnalysis.executeAnalysis({}, semanticContract);
1282
+ return {
1283
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1284
+ };
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
+ case "get_streaming_recommendations": {
1305
+ // 🎯 Template Method Pattern: Use StreamingAnalysis class
1306
+ const semanticContract = {
1307
+ chirp_intensity: args?.chirp_intensity,
1308
+ personality_mode: args?.personality_mode,
1309
+ enable_chirp: args?.enable_chirp,
1310
+ semantic_intent: "user_requested",
1311
+ tool_context: "get_streaming_recommendations"
1312
+ };
1313
+ const analysisArgs = {
1314
+ look_ahead_days: args?.days_ahead || 7,
1315
+ position_filter: args?.position_filter,
1316
+ max_recommendations: args?.max_recommendations || 5
1317
+ };
1318
+ const result = await streamingAnalysis.executeAnalysis(analysisArgs, semanticContract);
1319
+ return {
1320
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1321
+ };
1322
+ }
1323
+ case "get_games_in_hand": {
1324
+ // 🎯 Template Method Pattern: Use GamesInHandAnalysis class
1325
+ const semanticContract = {
1326
+ chirp_intensity: args?.chirp_intensity,
1327
+ personality_mode: args?.personality_mode,
1328
+ enable_chirp: args?.enable_chirp,
1329
+ semantic_intent: "user_requested",
1330
+ tool_context: "get_games_in_hand"
1331
+ };
1332
+ const analysisArgs = {
1333
+ look_ahead_days: args?.look_ahead_days || 7
1334
+ };
1335
+ const result = await gamesInHandAnalysis.executeAnalysis(analysisArgs, semanticContract);
1336
+ return {
1337
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1338
+ };
1339
+ }
1340
+ case "get_roster_transaction_recommendations": {
1341
+ // 🎯 Template Method Pattern: Use IceAnalysis class
1342
+ const semanticContract = {
1343
+ chirp_intensity: args?.chirp_intensity,
1344
+ personality_mode: args?.personality_mode,
1345
+ enable_chirp: args?.enable_chirp,
1346
+ semantic_intent: "user_requested",
1347
+ tool_context: "get_roster_transaction_recommendations"
1348
+ };
1349
+ const analysisArgs = {
1350
+ look_ahead_days: args?.look_ahead_days || 7,
1351
+ target_positions: args?.target_positions
1352
+ };
1353
+ const result = await iceAnalysis.executeAnalysis(analysisArgs, semanticContract);
1354
+ return {
1355
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1356
+ };
1357
+ }
1358
+ case "ice": {
1359
+ // 🎯 Template Method Pattern: All analysis modes use dedicated classes
1360
+ const analysisType = args?.analysis_type || "full_roster";
1361
+ const lookAheadDays = args?.look_ahead_days || 7;
1362
+ const semanticContract = {
1363
+ chirp_intensity: args?.chirp_intensity || "ice_cold",
1364
+ personality_mode: args?.personality_mode || "championship_coach",
1365
+ enable_chirp: true,
1366
+ semantic_intent: "tool_override",
1367
+ tool_context: "ice"
1368
+ };
1369
+ let result;
1370
+ switch (analysisType) {
1371
+ case "full_roster":
1372
+ // ✅ IceAnalysis class
1373
+ result = await iceAnalysis.executeAnalysis({ look_ahead_days: lookAheadDays }, semanticContract);
1374
+ break;
1375
+ case "weekly_matchup":
1376
+ // ✅ GamesInHandAnalysis class
1377
+ result = await gamesInHandAnalysis.executeAnalysis({ look_ahead_days: lookAheadDays }, semanticContract);
1378
+ break;
1379
+ case "pickup_strategy":
1380
+ // ✅ StreamingAnalysis class
1381
+ result = await streamingAnalysis.executeAnalysis({ look_ahead_days: lookAheadDays }, semanticContract);
1382
+ break;
1383
+ case "lineup_optimization":
1384
+ // ✅ LineupAnalysis class
1385
+ result = await lineupAnalysis.executeAnalysis({}, semanticContract);
1386
+ break;
1387
+ default:
1388
+ // Default to full roster analysis
1389
+ result = await iceAnalysis.executeAnalysis({ look_ahead_days: lookAheadDays }, semanticContract);
1390
+ }
1391
+ return {
1392
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1393
+ };
1394
+ }
1395
+ case "governance_dashboard": {
1396
+ // 🏛️ Governance Dashboard: View health metrics and analysis performance
1397
+ const reportType = args?.report_type || "full";
1398
+ const health = checkGovernanceHealth();
1399
+ let reportData;
1400
+ switch (reportType) {
1401
+ case "health":
1402
+ reportData = {
1403
+ status: health.status,
1404
+ total_violations: health.report.total_violations,
1405
+ warnings: health.report.warnings,
1406
+ errors: health.report.errors,
1407
+ contracts_validated: health.report.contracts_validated,
1408
+ recommendations: health.recommendations
1409
+ };
1410
+ break;
1411
+ case "analyses":
1412
+ reportData = {
1413
+ total_analyses: health.report.analyses_executed,
1414
+ by_type: health.report.analysis_by_type,
1415
+ performance: {
1416
+ avg_duration_ms: health.report.avg_duration_ms,
1417
+ slowest_analysis: health.report.slowest_analysis
1418
+ }
1419
+ };
1420
+ break;
1421
+ case "violations":
1422
+ reportData = {
1423
+ total_violations: health.report.total_violations,
1424
+ warnings: health.report.warnings,
1425
+ errors: health.report.errors,
1426
+ recent_violations: health.report.recent_violations
1427
+ };
1428
+ break;
1429
+ case "full":
1430
+ default:
1431
+ reportData = {
1432
+ governance_health: {
1433
+ status: health.status,
1434
+ recommendations: health.recommendations
1435
+ },
1436
+ metrics: {
1437
+ violations: {
1438
+ total: health.report.total_violations,
1439
+ warnings: health.report.warnings,
1440
+ errors: health.report.errors,
1441
+ recent: health.report.recent_violations
1442
+ },
1443
+ governance: {
1444
+ contracts_validated: health.report.contracts_validated,
1445
+ immutability_enforced: health.report.immutability_enforced,
1446
+ semantic_decisions: health.report.semantic_decisions
1447
+ },
1448
+ analyses: {
1449
+ total_executed: health.report.analyses_executed,
1450
+ by_type: health.report.analysis_by_type,
1451
+ avg_duration_ms: health.report.avg_duration_ms,
1452
+ slowest: health.report.slowest_analysis
1453
+ }
1454
+ }
1455
+ };
1456
+ break;
1457
+ }
1458
+ return {
1459
+ content: [{ type: "text", text: JSON.stringify(reportData, null, 2) }]
1460
+ };
1461
+ }
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
+ case "analyze_breakout_players": {
1481
+ try {
1482
+ const result = await executeBreakoutAnalysis(args, breakoutAnalysis);
1483
+ return {
1484
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1485
+ };
1486
+ }
1487
+ catch (error) {
1488
+ const errorMessage = error instanceof Error ? error.message : String(error);
1489
+ return {
1490
+ content: [{ type: "text", text: JSON.stringify({
1491
+ error: errorMessage,
1492
+ note: "Breakout analysis failed - this is a semantic intent-driven tool with comprehensive scoring"
1493
+ }, null, 2) }],
1494
+ isError: true
1495
+ };
1496
+ }
1497
+ }
1498
+ case "analyze_weekend_streams": {
1499
+ try {
1500
+ // Build semantic contract
1501
+ const semanticContract = {
1502
+ chirp_intensity: args?.chirp_intensity || 'ice_cold',
1503
+ personality_mode: args?.personality_mode || 'analytical',
1504
+ enable_chirp: args?.enable_chirp !== false,
1505
+ semantic_intent: "user_requested",
1506
+ tool_context: "weekend_stream_classification"
1507
+ };
1508
+ // Execute weekend stream analysis through template method
1509
+ const result = await weekendStreamAnalysis.executeAnalysis({
1510
+ date_range: args?.date_range,
1511
+ position_filter: args?.position_filter,
1512
+ ownership_max: args?.ownership_max,
1513
+ team_needs: args?.team_needs,
1514
+ min_upside_score: args?.min_upside_score,
1515
+ max_results: args?.max_results
1516
+ }, semanticContract);
1517
+ return {
1518
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
1519
+ };
1520
+ }
1521
+ catch (error) {
1522
+ const errorMessage = error instanceof Error ? error.message : String(error);
1523
+ return {
1524
+ content: [{ type: "text", text: JSON.stringify({
1525
+ error: errorMessage,
1526
+ note: "Weekend stream analysis failed - binary classification for desperation vs genuine opportunities"
1527
+ }, null, 2) }],
1528
+ isError: true
1529
+ };
1530
+ }
1531
+ }
1532
+ default:
1533
+ throw new Error(`Unknown tool: ${name}`);
1534
+ }
1535
+ }
1536
+ catch (error) {
1537
+ return {
1538
+ content: [{ type: "text", text: `Error: ${error.message}` }],
1539
+ isError: true,
1540
+ };
1541
+ }
1542
+ });
1543
+ // Start Server
1544
+ async function main() {
1545
+ const transport = new StdioServerTransport();
1546
+ await server.connect(transport);
1547
+ console.error("🏒❄️ Semantic Chirp Intelligence MCP v3.0 - ICE is ON! (Official API)");
1548
+ }
1549
+ main().catch(console.error);