@semanticintent/semantic-chirp-intelligence-mcp 3.0.0 → 3.1.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/config/tool-metadata.js +14 -0
- package/build/index.js +217 -1
- package/package.json +7 -10
|
@@ -109,5 +109,19 @@ export const TOOL_METADATA = {
|
|
|
109
109
|
intent_category: "market_intelligence",
|
|
110
110
|
hockey_context: "waiver_trends",
|
|
111
111
|
chirp_potential: "trend_opportunities"
|
|
112
|
+
},
|
|
113
|
+
chirp_opponent: {
|
|
114
|
+
chirp_style: "savage_trash_talk",
|
|
115
|
+
discovery_tags: ["opponent", "chirp", "trash_talk", "weaknesses", "rivalry"],
|
|
116
|
+
intent_category: "psychological_warfare",
|
|
117
|
+
hockey_context: "head_to_head_dominance",
|
|
118
|
+
chirp_potential: "opponent_destruction"
|
|
119
|
+
},
|
|
120
|
+
analyze_trade: {
|
|
121
|
+
chirp_style: "trade_verdict",
|
|
122
|
+
discovery_tags: ["trade", "analysis", "value", "categories", "decision"],
|
|
123
|
+
intent_category: "trade_intelligence",
|
|
124
|
+
hockey_context: "roster_maneuvering",
|
|
125
|
+
chirp_potential: "trade_reality"
|
|
112
126
|
}
|
|
113
127
|
};
|
package/build/index.js
CHANGED
|
@@ -935,6 +935,175 @@ async function getTrendingPlayers(trendType = "add", count = 25) {
|
|
|
935
935
|
});
|
|
936
936
|
return { trend_type: trendType, players };
|
|
937
937
|
}
|
|
938
|
+
// Tool: Chirp Opponent
|
|
939
|
+
async function chirpOpponent(chirpIntensity = 'savage', personalityMode = 'roast_master') {
|
|
940
|
+
const data = await yahooApiRequest(`/team/nhl.l.${LEAGUE_ID}.t.${TEAM_ID}/matchups`);
|
|
941
|
+
const matchups = data.fantasy_content.team[1].matchups;
|
|
942
|
+
const currentMatchup = findCurrentMatchup(matchups);
|
|
943
|
+
if (!currentMatchup) {
|
|
944
|
+
return { message: "No active matchup — nobody to chirp right now." };
|
|
945
|
+
}
|
|
946
|
+
const matchupData = currentMatchup["0"];
|
|
947
|
+
const teams = matchupData?.teams;
|
|
948
|
+
if (!teams)
|
|
949
|
+
return { message: "Could not read matchup teams." };
|
|
950
|
+
const opponentTeamArray = teams['1']?.team?.[0];
|
|
951
|
+
const opponentTeamKey = opponentTeamArray?.find((item) => item.team_key)?.team_key;
|
|
952
|
+
const opponentName = opponentTeamArray?.find((item) => item.name)?.name || 'Unknown';
|
|
953
|
+
if (!opponentTeamKey)
|
|
954
|
+
return { message: "Could not find opponent team key." };
|
|
955
|
+
const rosterData = await yahooApiRequest(`/team/${opponentTeamKey}/roster`);
|
|
956
|
+
const rawPlayers = rosterData.fantasy_content.team[1].roster["0"].players;
|
|
957
|
+
const playerKeys = Object.keys(rawPlayers).filter(k => k !== 'count');
|
|
958
|
+
const players = playerKeys.map(key => {
|
|
959
|
+
const pd = rawPlayers[key].player[0];
|
|
960
|
+
const posData = rawPlayers[key].player[1];
|
|
961
|
+
return {
|
|
962
|
+
name: pd.find((item) => item.name)?.name?.full || 'Unknown',
|
|
963
|
+
position: pd.find((item) => item.display_position)?.display_position || '?',
|
|
964
|
+
team: pd.find((item) => item.editorial_team_abbr)?.editorial_team_abbr || '?',
|
|
965
|
+
status: pd.find((item) => item.status)?.status || '',
|
|
966
|
+
selected_position: posData?.selected_position?.find((item) => item.position)?.position || 'BN',
|
|
967
|
+
};
|
|
968
|
+
});
|
|
969
|
+
const injured = players.filter(p => p.status && p.status !== '');
|
|
970
|
+
const onIR = players.filter(p => p.selected_position === 'IR');
|
|
971
|
+
const onBench = players.filter(p => p.selected_position === 'BN');
|
|
972
|
+
const injuredActive = injured.filter(p => p.selected_position !== 'IR' && p.selected_position !== 'BN');
|
|
973
|
+
const activeCount = players.length - onBench.length - onIR.length;
|
|
974
|
+
const style = CHIRP_STYLES[chirpIntensity] || CHIRP_STYLES['savage'];
|
|
975
|
+
const personality = PERSONALITY_MODES[personalityMode] || PERSONALITY_MODES['roast_master'];
|
|
976
|
+
const chirpLines = [];
|
|
977
|
+
if (injured.length >= 3) {
|
|
978
|
+
chirpLines.push(`${opponentName} is running a hospital roster with ${injured.length} banged-up players. They should rename the team to "The Walking Wounded."`);
|
|
979
|
+
}
|
|
980
|
+
else if (injured.length > 0) {
|
|
981
|
+
chirpLines.push(`${injured.length} of ${opponentName}'s key players are dinged up. Couldn't happen to a nicer team.`);
|
|
982
|
+
}
|
|
983
|
+
if (injuredActive.length > 0) {
|
|
984
|
+
chirpLines.push(`They've got ${injuredActive.length} injured player${injuredActive.length > 1 ? 's' : ''} still in active slots — not even using their IR correctly. Amateur hour.`);
|
|
985
|
+
}
|
|
986
|
+
if (onBench.length >= 5) {
|
|
987
|
+
chirpLines.push(`${onBench.length} players collecting dust on the bench. That's not a fantasy team, that's a waiting room.`);
|
|
988
|
+
}
|
|
989
|
+
if (chirpLines.length === 0) {
|
|
990
|
+
chirpLines.push(`${opponentName} looks healthy on paper — but paper doesn't win categories. Execution does. That's your edge.`);
|
|
991
|
+
}
|
|
992
|
+
const weaknesses = [
|
|
993
|
+
...(injured.length >= 3 ? [`${injured.length} players injured`] : []),
|
|
994
|
+
...(injuredActive.length > 0 ? [`${injuredActive.length} injured players in active slots`] : []),
|
|
995
|
+
...(onBench.length >= 5 ? [`Heavy bench (${onBench.length} players)`] : []),
|
|
996
|
+
];
|
|
997
|
+
return {
|
|
998
|
+
opponent: opponentName,
|
|
999
|
+
week: currentMatchup.week,
|
|
1000
|
+
opponent_roster_summary: {
|
|
1001
|
+
total_players: players.length,
|
|
1002
|
+
active_players: activeCount,
|
|
1003
|
+
bench_players: onBench.length,
|
|
1004
|
+
ir_players: onIR.length,
|
|
1005
|
+
injured_players: injured.length,
|
|
1006
|
+
injured_active: injuredActive.length,
|
|
1007
|
+
},
|
|
1008
|
+
weaknesses: weaknesses.length > 0 ? weaknesses : ["No obvious weaknesses detected — they're running a clean roster."],
|
|
1009
|
+
chirp: {
|
|
1010
|
+
style: style.tone,
|
|
1011
|
+
personality: personality.voice,
|
|
1012
|
+
main_chirp: `${style.prefix} ${chirpLines.join(' ')} ${style.suffix}`,
|
|
1013
|
+
ice_cold_truth: `❄️ Bottom line: know your enemy. ${opponentName} has gaps — your job is to exploit them.`,
|
|
1014
|
+
},
|
|
1015
|
+
opponent_roster: players,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
// Tool: Analyze Trade
|
|
1019
|
+
async function analyzeTradeImpact(giving, receiving, chirpIntensity = 'standard') {
|
|
1020
|
+
const statIdLabels = {
|
|
1021
|
+
'1': 'G', '2': 'A', '3': '+/-', '4': 'PIM', '5': 'SOG',
|
|
1022
|
+
'8': 'PPP', '31': 'W', '32': 'GAA', '33': 'SV%',
|
|
1023
|
+
};
|
|
1024
|
+
const categories = ['G', 'A', '+/-', 'PIM', 'SOG', 'PPP', 'W', 'GAA', 'SV%'];
|
|
1025
|
+
const lowerIsBetter = new Set(['GAA']);
|
|
1026
|
+
async function findPlayerStats(name) {
|
|
1027
|
+
const searchData = await yahooApiRequest(`/league/nhl.l.${LEAGUE_ID}/players;search=${encodeURIComponent(name)};count=1`);
|
|
1028
|
+
const playersData = searchData.fantasy_content.league[1].players;
|
|
1029
|
+
const playerKeys = Object.keys(playersData).filter(k => k !== 'count');
|
|
1030
|
+
if (playerKeys.length === 0)
|
|
1031
|
+
return { name, found: false, stats: {}, position: '', team: '' };
|
|
1032
|
+
const pd = playersData[playerKeys[0]].player[0];
|
|
1033
|
+
const playerId = pd.find((item) => item.player_id)?.player_id;
|
|
1034
|
+
const foundName = pd.find((item) => item.name)?.name?.full || name;
|
|
1035
|
+
const position = pd.find((item) => item.display_position)?.display_position || '?';
|
|
1036
|
+
const team = pd.find((item) => item.editorial_team_abbr)?.editorial_team_abbr || '?';
|
|
1037
|
+
if (!playerId)
|
|
1038
|
+
return { name, found: false, stats: {}, position, team };
|
|
1039
|
+
const statsData = await yahooApiRequest(`/player/nhl.p.${playerId}/stats`);
|
|
1040
|
+
const statsArray = statsData.fantasy_content.player[1]?.player_stats?.stats || [];
|
|
1041
|
+
const stats = {};
|
|
1042
|
+
statsArray.forEach((sw) => {
|
|
1043
|
+
const stat = sw.stat;
|
|
1044
|
+
if (stat && statIdLabels[stat.stat_id]) {
|
|
1045
|
+
stats[statIdLabels[stat.stat_id]] = parseFloat(stat.value) || 0;
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
return { name: foundName, found: true, playerId, position, team, stats };
|
|
1049
|
+
}
|
|
1050
|
+
const [givingPlayers, receivingPlayers] = await Promise.all([
|
|
1051
|
+
Promise.all(giving.map(name => findPlayerStats(name))),
|
|
1052
|
+
Promise.all(receiving.map(name => findPlayerStats(name))),
|
|
1053
|
+
]);
|
|
1054
|
+
function sumStats(players) {
|
|
1055
|
+
const totals = {};
|
|
1056
|
+
players.filter(p => p.found).forEach(p => {
|
|
1057
|
+
categories.forEach(cat => {
|
|
1058
|
+
totals[cat] = (totals[cat] || 0) + (p.stats[cat] || 0);
|
|
1059
|
+
});
|
|
1060
|
+
});
|
|
1061
|
+
return totals;
|
|
1062
|
+
}
|
|
1063
|
+
const givingStats = sumStats(givingPlayers);
|
|
1064
|
+
const receivingStats = sumStats(receivingPlayers);
|
|
1065
|
+
let receivingWins = 0;
|
|
1066
|
+
let givingWins = 0;
|
|
1067
|
+
const categoryBreakdown = categories
|
|
1068
|
+
.filter(cat => (givingStats[cat] || 0) !== 0 || (receivingStats[cat] || 0) !== 0)
|
|
1069
|
+
.map(cat => {
|
|
1070
|
+
const gVal = givingStats[cat] || 0;
|
|
1071
|
+
const rVal = receivingStats[cat] || 0;
|
|
1072
|
+
const lowerBetter = lowerIsBetter.has(cat);
|
|
1073
|
+
let winner;
|
|
1074
|
+
if (Math.abs(rVal - gVal) < 0.01) {
|
|
1075
|
+
winner = 'push';
|
|
1076
|
+
}
|
|
1077
|
+
else if (lowerBetter ? rVal < gVal : rVal > gVal) {
|
|
1078
|
+
winner = 'receiving';
|
|
1079
|
+
receivingWins++;
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
winner = 'giving';
|
|
1083
|
+
givingWins++;
|
|
1084
|
+
}
|
|
1085
|
+
return { category: cat, giving: gVal, receiving: rVal, winner };
|
|
1086
|
+
});
|
|
1087
|
+
const verdict = receivingWins > givingWins ? 'ACCEPT' : receivingWins < givingWins ? 'DECLINE' : 'PUSH';
|
|
1088
|
+
const style = CHIRP_STYLES[chirpIntensity] || CHIRP_STYLES['standard'];
|
|
1089
|
+
const chirpText = verdict === 'ACCEPT'
|
|
1090
|
+
? `${style.prefix} you're gaining ${receivingWins} categories vs ${givingWins}. That's not a trade — that's a heist. Pull the trigger. ${style.suffix}`
|
|
1091
|
+
: verdict === 'DECLINE'
|
|
1092
|
+
? `${style.prefix} you'd be handing away ${givingWins} categories for only ${receivingWins} back. Your opponent is praying you say yes. Hard pass. ${style.suffix}`
|
|
1093
|
+
: `${style.prefix} dead even at ${receivingWins} categories each. Unless this fixes a positional need, don't bother. ${style.suffix}`;
|
|
1094
|
+
const iceColdTruth = verdict === 'ACCEPT'
|
|
1095
|
+
? '❄️ ICE Cold Truth: Smart managers see value before their opponent does. This is that moment.'
|
|
1096
|
+
: verdict === 'DECLINE'
|
|
1097
|
+
? '🔥 Savage Reality: This trade has "I got played" written all over it. Close the chat.'
|
|
1098
|
+
: '💡 Real Talk: Push trades only make sense when they fix a roster hole. Otherwise, pass.';
|
|
1099
|
+
return {
|
|
1100
|
+
trade: { giving, receiving },
|
|
1101
|
+
players: { giving: givingPlayers, receiving: receivingPlayers },
|
|
1102
|
+
category_breakdown: categoryBreakdown,
|
|
1103
|
+
summary: { receiving_wins: receivingWins, giving_wins: givingWins, verdict },
|
|
1104
|
+
chirp: { verdict_chirp: chirpText, ice_cold_truth: iceColdTruth },
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
938
1107
|
// Initialize MCP Server
|
|
939
1108
|
const server = new Server({
|
|
940
1109
|
name: "semantic-chirp-intelligence-mcp",
|
|
@@ -1212,7 +1381,38 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1212
1381
|
},
|
|
1213
1382
|
required: ["date_range"]
|
|
1214
1383
|
}
|
|
1215
|
-
}
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
name: "chirp_opponent",
|
|
1387
|
+
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.",
|
|
1388
|
+
inputSchema: {
|
|
1389
|
+
type: "object",
|
|
1390
|
+
properties: {
|
|
1391
|
+
...baseChirpSchema,
|
|
1392
|
+
},
|
|
1393
|
+
},
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
name: "analyze_trade",
|
|
1397
|
+
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.",
|
|
1398
|
+
inputSchema: {
|
|
1399
|
+
type: "object",
|
|
1400
|
+
properties: {
|
|
1401
|
+
giving: {
|
|
1402
|
+
type: "array",
|
|
1403
|
+
items: { type: "string" },
|
|
1404
|
+
description: "Player names you are giving away (e.g. [\"Nathan MacKinnon\", \"Mitch Marner\"])",
|
|
1405
|
+
},
|
|
1406
|
+
receiving: {
|
|
1407
|
+
type: "array",
|
|
1408
|
+
items: { type: "string" },
|
|
1409
|
+
description: "Player names you are receiving (e.g. [\"Auston Matthews\"])",
|
|
1410
|
+
},
|
|
1411
|
+
...baseChirpSchema,
|
|
1412
|
+
},
|
|
1413
|
+
required: ["giving", "receiving"],
|
|
1414
|
+
},
|
|
1415
|
+
},
|
|
1216
1416
|
],
|
|
1217
1417
|
};
|
|
1218
1418
|
});
|
|
@@ -1529,6 +1729,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1529
1729
|
};
|
|
1530
1730
|
}
|
|
1531
1731
|
}
|
|
1732
|
+
case "chirp_opponent": {
|
|
1733
|
+
const intensity = args?.chirp_intensity || 'savage';
|
|
1734
|
+
const mode = args?.personality_mode || 'roast_master';
|
|
1735
|
+
const result = await chirpOpponent(intensity, mode);
|
|
1736
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1737
|
+
}
|
|
1738
|
+
case "analyze_trade": {
|
|
1739
|
+
const giving = args?.giving;
|
|
1740
|
+
const receiving = args?.receiving;
|
|
1741
|
+
if (!giving || !receiving || giving.length === 0 || receiving.length === 0) {
|
|
1742
|
+
throw new Error("Both 'giving' and 'receiving' arrays are required and must not be empty.");
|
|
1743
|
+
}
|
|
1744
|
+
const intensity = args?.chirp_intensity || 'standard';
|
|
1745
|
+
const result = await analyzeTradeImpact(giving, receiving, intensity);
|
|
1746
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1747
|
+
}
|
|
1532
1748
|
default:
|
|
1533
1749
|
throw new Error(`Unknown tool: ${name}`);
|
|
1534
1750
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@semanticintent/semantic-chirp-intelligence-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Semantic Intent pattern implementation for fantasy hockey intelligence - AI analysis that chirps you into championships",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -26,25 +26,22 @@
|
|
|
26
26
|
],
|
|
27
27
|
"author": "semanticintent",
|
|
28
28
|
"license": "MIT",
|
|
29
|
-
"publishConfig": {
|
|
30
|
-
"access": "public"
|
|
31
|
-
},
|
|
32
29
|
"repository": {
|
|
33
30
|
"type": "git",
|
|
34
|
-
"url": "
|
|
31
|
+
"url": "https://github.com/semanticintent/semantic-chirp-intelligence-mcp.git"
|
|
35
32
|
},
|
|
36
33
|
"devDependencies": {
|
|
37
34
|
"@types/node": "^24.7.0",
|
|
38
35
|
"@types/xml2js": "^0.4.14",
|
|
39
|
-
"@vitest/coverage-v8": "^
|
|
40
|
-
"@vitest/ui": "^
|
|
36
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
37
|
+
"@vitest/ui": "^4.1.10",
|
|
41
38
|
"typescript": "^5.9.3",
|
|
42
|
-
"vitest": "^
|
|
39
|
+
"vitest": "^4.1.10"
|
|
43
40
|
},
|
|
44
41
|
"dependencies": {
|
|
45
42
|
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
46
|
-
"dotenv": "^17.2
|
|
47
|
-
"selfsigned": "^
|
|
43
|
+
"dotenv": "^17.4.2",
|
|
44
|
+
"selfsigned": "^5.5.0",
|
|
48
45
|
"xml2js": "^0.6.2"
|
|
49
46
|
}
|
|
50
47
|
}
|