@semanticintent/semantic-chirp-intelligence-mcp 4.0.0 → 4.0.2
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 +1 -1
- package/.chirp-data/roster.json +1 -1
- package/.chirp-data/standings.json +1 -1
- package/README.md +9 -13
- package/build/analyses/DraftPickAnalysis.js +50 -19
- package/build/index.js +13 -3
- package/package.json +2 -3
package/.chirp-data/roster.json
CHANGED
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ occasional chirp.
|
|
|
37
37
|
- 🗓️ **Schedule intelligence** — every game count comes from the NHL's public club-schedule API, per club, per week
|
|
38
38
|
- 🏛️ **Semantic Anchoring Governance** — every tool declares its intent; a dashboard surfaces the health metrics
|
|
39
39
|
- 🧩 **Template Pattern architecture** — analyses are composable, consistent, and testable
|
|
40
|
-
- 🔒 **Read-only & local** —
|
|
40
|
+
- 🔒 **Read-only & local** — no credentials of any kind, no third-party data egress; your roster never leaves your machine
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
@@ -53,7 +53,8 @@ occasional chirp.
|
|
|
53
53
|
├─────────────────────────────────────────────────────────────┤
|
|
54
54
|
│ Intelligence layer (src/analyses/ + src/template/) │
|
|
55
55
|
│ • AnalysisTemplate — shared Template Pattern base │
|
|
56
|
-
│ • Ice /
|
|
56
|
+
│ • Ice / Lineup / GamesInHand / Streaming / WeekendStream │
|
|
57
|
+
│ • Breakout / ScheduleValue / DraftPick │
|
|
57
58
|
├─────────────────────────────────────────────────────────────┤
|
|
58
59
|
│ Services (src/services/) │
|
|
59
60
|
│ • ChirpIntelligence — turns data into chirp │
|
|
@@ -174,10 +175,7 @@ Edit your Claude Desktop config:
|
|
|
174
175
|
"mcpServers": {
|
|
175
176
|
"semantic-chirp-intelligence-mcp": {
|
|
176
177
|
"command": "node",
|
|
177
|
-
"args": ["/absolute/path/to/semantic-chirp-intelligence-mcp/build/index.js"]
|
|
178
|
-
"env": {
|
|
179
|
-
"DOTENV_CONFIG_QUIET": "true"
|
|
180
|
-
}
|
|
178
|
+
"args": ["/absolute/path/to/semantic-chirp-intelligence-mcp/build/index.js"]
|
|
181
179
|
}
|
|
182
180
|
}
|
|
183
181
|
}
|
|
@@ -185,11 +183,9 @@ Edit your Claude Desktop config:
|
|
|
185
183
|
|
|
186
184
|
Use an **absolute path** to `build/index.js` (forward slashes, even on Windows). Restart Claude Desktop — the CHIRP tools will appear.
|
|
187
185
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
the client config. If you prefer to set them there anyway, a client `env` block
|
|
192
|
-
still overrides the file.
|
|
186
|
+
There is no `env` block, because there is nothing to configure. Your pasted
|
|
187
|
+
roster is stored in the project's git-ignored `.chirp-data/` and never leaves
|
|
188
|
+
your machine.
|
|
193
189
|
|
|
194
190
|
---
|
|
195
191
|
|
|
@@ -203,7 +199,7 @@ Once connected, just talk to Claude about your team:
|
|
|
203
199
|
- *"Where do I have a games-in-hand edge over my opponent?"*
|
|
204
200
|
- *"Find me streaming goalies for the weekend — real value, not desperation pickups."*
|
|
205
201
|
- *"Am I winning my matchup? Which categories am I losing?"*
|
|
206
|
-
- *"Who are the
|
|
202
|
+
- *"Who are the best players not on my roster right now?"*
|
|
207
203
|
- *"Show me the governance dashboard."*
|
|
208
204
|
|
|
209
205
|
---
|
|
@@ -232,7 +228,7 @@ semantic-chirp-intelligence-mcp/
|
|
|
232
228
|
│ ├── services/ # NhlScheduleService, NhlStatsService, RosterStore,
|
|
233
229
|
│ │ # LeagueDataService, ChirpIntelligence
|
|
234
230
|
│ ├── config/ # tool-metadata, personality-modes, chirp-styles
|
|
235
|
-
│ ├── domain/ # types, governance, nhl-teams
|
|
231
|
+
│ ├── domain/ # types, governance, nhl-teams
|
|
236
232
|
├── tests/ # vitest tests
|
|
237
233
|
├── scripts/preflight.mjs # build + NHL API check
|
|
238
234
|
├── scripts/smoke.mjs # calls every tool, flags crash-like responses
|
|
@@ -81,7 +81,7 @@ export class DraftPickAnalysis extends AnalysisTemplate {
|
|
|
81
81
|
draftResultsAvailable: false,
|
|
82
82
|
manualDraftedCount: draftedNames.size,
|
|
83
83
|
rosterPositions,
|
|
84
|
-
playoffWindow: this.resolvePlayoffWindow(
|
|
84
|
+
playoffWindow: this.resolvePlayoffWindow(args),
|
|
85
85
|
poolCaveat: rawData.pool_caveat
|
|
86
86
|
};
|
|
87
87
|
}
|
|
@@ -127,26 +127,38 @@ export class DraftPickAnalysis extends AnalysisTemplate {
|
|
|
127
127
|
const contract = this.mergeContractWithDefaults(semanticContract);
|
|
128
128
|
const enhanced = ChirpIntelligence.enhance(this.toolName, analysisResults, contract);
|
|
129
129
|
const top = analysisResults.candidates[0];
|
|
130
|
+
// The playoff clause is only truthful when a window was actually resolved.
|
|
131
|
+
const windowResolved = analysisResults.playoff_window?.resolved === true;
|
|
132
|
+
const scheduleClause = top && windowResolved && top.playoff_games !== null
|
|
133
|
+
? `, and ${top.team} plays ${top.playoff_games} games in your playoff window`
|
|
134
|
+
: '';
|
|
130
135
|
let chirp;
|
|
131
136
|
if (!top) {
|
|
132
137
|
chirp = 'Nobody left worth chirping about. Either the pool is empty or everyone is drafted.';
|
|
133
138
|
}
|
|
134
139
|
else if (top.adp_delta !== null && top.adp_delta >= 12) {
|
|
140
|
+
// v4 has no market ADP. average_pick is this player's rank by production,
|
|
141
|
+
// so the claim is "better player than this slot", not "the room drafts
|
|
142
|
+
// him earlier" — nothing here knows what a room does.
|
|
135
143
|
chirp =
|
|
136
|
-
`${top.name} is
|
|
137
|
-
`
|
|
138
|
-
`
|
|
144
|
+
`${top.name} is the ${this.ordinal(top.average_pick ?? 0)} best producer left and you are ` +
|
|
145
|
+
`picking at ${analysisResults.pick_number}. That is ${Math.round(top.adp_delta)} slots of ` +
|
|
146
|
+
`talent above where you are sitting${scheduleClause}.`;
|
|
139
147
|
}
|
|
140
148
|
else if (top.fills_need) {
|
|
141
149
|
chirp =
|
|
142
|
-
`${top.name} fills the hole you actually have (${top.position})
|
|
143
|
-
`${top.team} plays ${top.playoff_games ?? '?'} games in your playoff window. ` +
|
|
150
|
+
`${top.name} fills the hole you actually have (${top.position})${scheduleClause}. ` +
|
|
144
151
|
`Best available is a luxury; a full lineup is not.`;
|
|
145
152
|
}
|
|
146
153
|
else {
|
|
147
154
|
chirp =
|
|
148
|
-
`${top.name} is the pick. No bargain, no drama — just the best
|
|
149
|
-
`
|
|
155
|
+
`${top.name} is the pick. No bargain, no drama — just the best producer left ` +
|
|
156
|
+
`at ${analysisResults.pick_number}${scheduleClause}.`;
|
|
157
|
+
}
|
|
158
|
+
if (!windowResolved) {
|
|
159
|
+
chirp +=
|
|
160
|
+
' I have not scored your playoff weeks — pass playoff_start_week and ' +
|
|
161
|
+
'playoff_end_week and the schedule becomes a tiebreaker.';
|
|
150
162
|
}
|
|
151
163
|
if (analysisResults.players_off_board === 0) {
|
|
152
164
|
chirp +=
|
|
@@ -249,6 +261,18 @@ export class DraftPickAnalysis extends AnalysisTemplate {
|
|
|
249
261
|
reasoning: this.reasoningFor(player, adpDelta, playoffGames, weekCount, fillsNeed)
|
|
250
262
|
};
|
|
251
263
|
}
|
|
264
|
+
/** 1 -> "1st", 2 -> "2nd", 23 -> "23rd". */
|
|
265
|
+
ordinal(n) {
|
|
266
|
+
const rem100 = n % 100;
|
|
267
|
+
if (rem100 >= 11 && rem100 <= 13)
|
|
268
|
+
return `${n}th`;
|
|
269
|
+
switch (n % 10) {
|
|
270
|
+
case 1: return `${n}st`;
|
|
271
|
+
case 2: return `${n}nd`;
|
|
272
|
+
case 3: return `${n}rd`;
|
|
273
|
+
default: return `${n}th`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
252
276
|
verdictFor(adpDelta) {
|
|
253
277
|
if (adpDelta === null)
|
|
254
278
|
return 'FAIR';
|
|
@@ -391,25 +415,32 @@ export class DraftPickAnalysis extends AnalysisTemplate {
|
|
|
391
415
|
}
|
|
392
416
|
return counts;
|
|
393
417
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
418
|
+
/**
|
|
419
|
+
* Resolve the fantasy playoff weeks to calendar dates.
|
|
420
|
+
*
|
|
421
|
+
* v4 has no league settings to read, so the weeks come from the caller and
|
|
422
|
+
* week 1 is anchored to the NHL season's own opening night. Without stated
|
|
423
|
+
* weeks the window is unresolved, and the schedule component stays neutral
|
|
424
|
+
* rather than silently scoring the wrong three weeks.
|
|
425
|
+
*/
|
|
426
|
+
resolvePlayoffWindow(args) {
|
|
427
|
+
const startWeek = Number(args?.playoff_start_week ?? 0);
|
|
428
|
+
const endWeek = Number(args?.playoff_end_week ?? 0);
|
|
429
|
+
const seasonStart = NHL_SCHEDULE.getSeasonStartDate();
|
|
430
|
+
if (!startWeek || !endWeek || startWeek > endWeek || !seasonStart) {
|
|
401
431
|
return { resolved: false, start: null, end: null, weeks: [] };
|
|
402
432
|
}
|
|
403
|
-
const week1Monday = NhlScheduleService.weekStart(
|
|
433
|
+
const week1Monday = NhlScheduleService.weekStart(seasonStart);
|
|
404
434
|
const weeks = [];
|
|
405
|
-
for (let week =
|
|
435
|
+
for (let week = startWeek; week <= endWeek; week++) {
|
|
406
436
|
weeks.push(NhlScheduleService.addDays(week1Monday, (week - 1) * 7));
|
|
407
437
|
}
|
|
408
438
|
return {
|
|
409
439
|
resolved: true,
|
|
410
|
-
|
|
440
|
+
week_1_anchor: `${week1Monday} (NHL season opener)`,
|
|
441
|
+
playoff_start_week: startWeek,
|
|
411
442
|
end_week: endWeek,
|
|
412
|
-
start: NhlScheduleService.addDays(week1Monday, (
|
|
443
|
+
start: NhlScheduleService.addDays(week1Monday, (startWeek - 1) * 7),
|
|
413
444
|
end: NhlScheduleService.addDays(week1Monday, endWeek * 7 - 1),
|
|
414
445
|
weeks
|
|
415
446
|
};
|
package/build/index.js
CHANGED
|
@@ -1218,8 +1218,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1218
1218
|
},
|
|
1219
1219
|
pool_size: {
|
|
1220
1220
|
type: "number",
|
|
1221
|
-
description: "How deep to pull the player pool (default
|
|
1222
|
-
default:
|
|
1221
|
+
description: "How deep to pull the player pool (default 250, max 400)",
|
|
1222
|
+
default: 250
|
|
1223
|
+
},
|
|
1224
|
+
playoff_start_week: {
|
|
1225
|
+
type: "number",
|
|
1226
|
+
description: "First week of your fantasy playoffs. Supply this with playoff_end_week and each club's playoff-window schedule becomes a tiebreaker between similar players."
|
|
1227
|
+
},
|
|
1228
|
+
playoff_end_week: {
|
|
1229
|
+
type: "number",
|
|
1230
|
+
description: "Final week of your fantasy playoffs (commonly your league's last week)."
|
|
1223
1231
|
},
|
|
1224
1232
|
...baseChirpSchema
|
|
1225
1233
|
}
|
|
@@ -1638,7 +1646,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1638
1646
|
already_drafted: args?.already_drafted,
|
|
1639
1647
|
roster_needs: args?.roster_needs,
|
|
1640
1648
|
max_results: args?.max_results,
|
|
1641
|
-
pool_size: args?.pool_size
|
|
1649
|
+
pool_size: args?.pool_size,
|
|
1650
|
+
playoff_start_week: args?.playoff_start_week,
|
|
1651
|
+
playoff_end_week: args?.playoff_end_week
|
|
1642
1652
|
}, semanticContract);
|
|
1643
1653
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1644
1654
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@semanticintent/semantic-chirp-intelligence-mcp",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "Universal fantasy hockey intelligence \u2014 a Model Context Protocol server that reads NHL schedule and player data and analyses any roster you paste. No account, no API key, no platform lock-in.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
"vitest": "^4.1.10"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@modelcontextprotocol/sdk": "^1.19.1"
|
|
47
|
-
"dotenv": "^17.4.2"
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.19.1"
|
|
48
47
|
}
|
|
49
48
|
}
|