cfb-stats-mcp 0.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/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/cache.d.ts +4 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +17 -0
- package/dist/cache.js.map +1 -0
- package/dist/cfbd-client.d.ts +9 -0
- package/dist/cfbd-client.d.ts.map +1 -0
- package/dist/cfbd-client.js +49 -0
- package/dist/cfbd-client.js.map +1 -0
- package/dist/cfbd-client.test.d.ts +2 -0
- package/dist/cfbd-client.test.d.ts.map +1 -0
- package/dist/cfbd-client.test.js +37 -0
- package/dist/cfbd-client.test.js.map +1 -0
- package/dist/embedded-resources.d.ts +3 -0
- package/dist/embedded-resources.d.ts.map +1 -0
- package/dist/embedded-resources.js +300 -0
- package/dist/embedded-resources.js.map +1 -0
- package/dist/exports.d.ts +7 -0
- package/dist/exports.d.ts.map +1 -0
- package/dist/exports.js +33 -0
- package/dist/exports.js.map +1 -0
- package/dist/exports.test.d.ts +2 -0
- package/dist/exports.test.d.ts.map +1 -0
- package/dist/exports.test.js +80 -0
- package/dist/exports.test.js.map +1 -0
- package/dist/formatters/advanced-formatter.d.ts +3 -0
- package/dist/formatters/advanced-formatter.d.ts.map +1 -0
- package/dist/formatters/advanced-formatter.js +106 -0
- package/dist/formatters/advanced-formatter.js.map +1 -0
- package/dist/formatters/box-score-formatter.d.ts +3 -0
- package/dist/formatters/box-score-formatter.d.ts.map +1 -0
- package/dist/formatters/box-score-formatter.js +154 -0
- package/dist/formatters/box-score-formatter.js.map +1 -0
- package/dist/formatters/conference-ratings-formatter.d.ts +4 -0
- package/dist/formatters/conference-ratings-formatter.d.ts.map +1 -0
- package/dist/formatters/conference-ratings-formatter.js +62 -0
- package/dist/formatters/conference-ratings-formatter.js.map +1 -0
- package/dist/formatters/conference-ratings-formatter.test.d.ts +2 -0
- package/dist/formatters/conference-ratings-formatter.test.d.ts.map +1 -0
- package/dist/formatters/conference-ratings-formatter.test.js +153 -0
- package/dist/formatters/conference-ratings-formatter.test.js.map +1 -0
- package/dist/formatters/game-formatter.d.ts +3 -0
- package/dist/formatters/game-formatter.d.ts.map +1 -0
- package/dist/formatters/game-formatter.js +46 -0
- package/dist/formatters/game-formatter.js.map +1 -0
- package/dist/formatters/head-to-head-formatter.d.ts +3 -0
- package/dist/formatters/head-to-head-formatter.d.ts.map +1 -0
- package/dist/formatters/head-to-head-formatter.js +162 -0
- package/dist/formatters/head-to-head-formatter.js.map +1 -0
- package/dist/formatters/head-to-head-formatter.test.d.ts +2 -0
- package/dist/formatters/head-to-head-formatter.test.d.ts.map +1 -0
- package/dist/formatters/head-to-head-formatter.test.js +160 -0
- package/dist/formatters/head-to-head-formatter.test.js.map +1 -0
- package/dist/formatters/ratings-formatter.d.ts +3 -0
- package/dist/formatters/ratings-formatter.d.ts.map +1 -0
- package/dist/formatters/ratings-formatter.js +75 -0
- package/dist/formatters/ratings-formatter.js.map +1 -0
- package/dist/formatters/ratings-formatter.test.d.ts +2 -0
- package/dist/formatters/ratings-formatter.test.d.ts.map +1 -0
- package/dist/formatters/ratings-formatter.test.js +137 -0
- package/dist/formatters/ratings-formatter.test.js.map +1 -0
- package/dist/formatters/season-advanced-formatter.d.ts +3 -0
- package/dist/formatters/season-advanced-formatter.d.ts.map +1 -0
- package/dist/formatters/season-advanced-formatter.js +160 -0
- package/dist/formatters/season-advanced-formatter.js.map +1 -0
- package/dist/formatters/season-advanced-formatter.test.d.ts +2 -0
- package/dist/formatters/season-advanced-formatter.test.d.ts.map +1 -0
- package/dist/formatters/season-advanced-formatter.test.js +239 -0
- package/dist/formatters/season-advanced-formatter.test.js.map +1 -0
- package/dist/formatters/season-records-formatter.d.ts +3 -0
- package/dist/formatters/season-records-formatter.d.ts.map +1 -0
- package/dist/formatters/season-records-formatter.js +64 -0
- package/dist/formatters/season-records-formatter.js.map +1 -0
- package/dist/formatters/season-stats-formatter.d.ts +3 -0
- package/dist/formatters/season-stats-formatter.d.ts.map +1 -0
- package/dist/formatters/season-stats-formatter.js +156 -0
- package/dist/formatters/season-stats-formatter.js.map +1 -0
- package/dist/formatters/season-stats-formatter.test.d.ts +2 -0
- package/dist/formatters/season-stats-formatter.test.d.ts.map +1 -0
- package/dist/formatters/season-stats-formatter.test.js +76 -0
- package/dist/formatters/season-stats-formatter.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +16 -0
- package/dist/logger.js.map +1 -0
- package/dist/prompts.d.ts +3 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +242 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +3 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +12 -0
- package/dist/resources.js.map +1 -0
- package/dist/tools/advanced-stats.d.ts +3 -0
- package/dist/tools/advanced-stats.d.ts.map +1 -0
- package/dist/tools/advanced-stats.js +67 -0
- package/dist/tools/advanced-stats.js.map +1 -0
- package/dist/tools/box-score.d.ts +3 -0
- package/dist/tools/box-score.d.ts.map +1 -0
- package/dist/tools/box-score.js +50 -0
- package/dist/tools/box-score.js.map +1 -0
- package/dist/tools/build-glossary.d.ts +8 -0
- package/dist/tools/build-glossary.d.ts.map +1 -0
- package/dist/tools/build-glossary.js +125 -0
- package/dist/tools/build-glossary.js.map +1 -0
- package/dist/tools/build-glossary.test.d.ts +2 -0
- package/dist/tools/build-glossary.test.d.ts.map +1 -0
- package/dist/tools/build-glossary.test.js +182 -0
- package/dist/tools/build-glossary.test.js.map +1 -0
- package/dist/tools/conference-ratings.d.ts +3 -0
- package/dist/tools/conference-ratings.d.ts.map +1 -0
- package/dist/tools/conference-ratings.js +74 -0
- package/dist/tools/conference-ratings.js.map +1 -0
- package/dist/tools/export-subtitles.d.ts +23 -0
- package/dist/tools/export-subtitles.d.ts.map +1 -0
- package/dist/tools/export-subtitles.js +102 -0
- package/dist/tools/export-subtitles.js.map +1 -0
- package/dist/tools/export-subtitles.test.d.ts +2 -0
- package/dist/tools/export-subtitles.test.d.ts.map +1 -0
- package/dist/tools/export-subtitles.test.js +152 -0
- package/dist/tools/export-subtitles.test.js.map +1 -0
- package/dist/tools/games.d.ts +3 -0
- package/dist/tools/games.d.ts.map +1 -0
- package/dist/tools/games.js +64 -0
- package/dist/tools/games.js.map +1 -0
- package/dist/tools/head-to-head.d.ts +3 -0
- package/dist/tools/head-to-head.d.ts.map +1 -0
- package/dist/tools/head-to-head.js +74 -0
- package/dist/tools/head-to-head.js.map +1 -0
- package/dist/tools/ratings.d.ts +3 -0
- package/dist/tools/ratings.d.ts.map +1 -0
- package/dist/tools/ratings.js +63 -0
- package/dist/tools/ratings.js.map +1 -0
- package/dist/tools/read-transcript.d.ts +9 -0
- package/dist/tools/read-transcript.d.ts.map +1 -0
- package/dist/tools/read-transcript.js +72 -0
- package/dist/tools/read-transcript.js.map +1 -0
- package/dist/tools/read-transcript.test.d.ts +2 -0
- package/dist/tools/read-transcript.test.d.ts.map +1 -0
- package/dist/tools/read-transcript.test.js +94 -0
- package/dist/tools/read-transcript.test.js.map +1 -0
- package/dist/tools/season-advanced.d.ts +3 -0
- package/dist/tools/season-advanced.d.ts.map +1 -0
- package/dist/tools/season-advanced.js +103 -0
- package/dist/tools/season-advanced.js.map +1 -0
- package/dist/tools/season-records.d.ts +3 -0
- package/dist/tools/season-records.d.ts.map +1 -0
- package/dist/tools/season-records.js +51 -0
- package/dist/tools/season-records.js.map +1 -0
- package/dist/tools/season-stats.d.ts +3 -0
- package/dist/tools/season-stats.d.ts.map +1 -0
- package/dist/tools/season-stats.js +75 -0
- package/dist/tools/season-stats.js.map +1 -0
- package/dist/tools/status.d.ts +3 -0
- package/dist/tools/status.d.ts.map +1 -0
- package/dist/tools/status.js +19 -0
- package/dist/tools/status.js.map +1 -0
- package/dist/tools/transcript-types.d.ts +36 -0
- package/dist/tools/transcript-types.d.ts.map +1 -0
- package/dist/tools/transcript-types.js +2 -0
- package/dist/tools/transcript-types.js.map +1 -0
- package/dist/tools/write-chapters.d.ts +40 -0
- package/dist/tools/write-chapters.d.ts.map +1 -0
- package/dist/tools/write-chapters.js +143 -0
- package/dist/tools/write-chapters.js.map +1 -0
- package/dist/tools/write-chapters.test.d.ts +2 -0
- package/dist/tools/write-chapters.test.d.ts.map +1 -0
- package/dist/tools/write-chapters.test.js +167 -0
- package/dist/tools/write-chapters.test.js.map +1 -0
- package/dist/tools/write-clip-candidates.d.ts +30 -0
- package/dist/tools/write-clip-candidates.d.ts.map +1 -0
- package/dist/tools/write-clip-candidates.js +130 -0
- package/dist/tools/write-clip-candidates.js.map +1 -0
- package/dist/tools/write-clip-candidates.test.d.ts +2 -0
- package/dist/tools/write-clip-candidates.test.d.ts.map +1 -0
- package/dist/tools/write-clip-candidates.test.js +213 -0
- package/dist/tools/write-clip-candidates.test.js.map +1 -0
- package/dist/tools/write-description.d.ts +11 -0
- package/dist/tools/write-description.d.ts.map +1 -0
- package/dist/tools/write-description.js +71 -0
- package/dist/tools/write-description.js.map +1 -0
- package/dist/tools/write-description.test.d.ts +2 -0
- package/dist/tools/write-description.test.d.ts.map +1 -0
- package/dist/tools/write-description.test.js +42 -0
- package/dist/tools/write-description.test.js.map +1 -0
- package/dist/tools/write-transcript.d.ts +20 -0
- package/dist/tools/write-transcript.d.ts.map +1 -0
- package/dist/tools/write-transcript.js +133 -0
- package/dist/tools/write-transcript.js.map +1 -0
- package/dist/tools/write-transcript.test.d.ts +2 -0
- package/dist/tools/write-transcript.test.d.ts.map +1 -0
- package/dist/tools/write-transcript.test.js +133 -0
- package/dist/tools/write-transcript.test.js.map +1 -0
- package/package.json +59 -0
- package/resources/football-glossary.json +69 -0
- package/resources/workflow.md +228 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { vi, describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
vi.mock("node:fs", () => ({
|
|
3
|
+
readFileSync: vi.fn(),
|
|
4
|
+
}));
|
|
5
|
+
vi.mock("../logger.js", () => ({
|
|
6
|
+
logger: {
|
|
7
|
+
debug: vi.fn(),
|
|
8
|
+
info: vi.fn(),
|
|
9
|
+
warn: vi.fn(),
|
|
10
|
+
error: vi.fn(),
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
import { readTranscript } from "./read-transcript.js";
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
const mockReadFileSync = vi.mocked(readFileSync);
|
|
16
|
+
const VALID_TRANSCRIPT = {
|
|
17
|
+
version: "1.0",
|
|
18
|
+
language: "en",
|
|
19
|
+
segments: [
|
|
20
|
+
{
|
|
21
|
+
start: 0.0,
|
|
22
|
+
end: 5.0,
|
|
23
|
+
text: "Hello world",
|
|
24
|
+
words: [
|
|
25
|
+
{ word: "Hello", start: 0.0, end: 0.5, score: 0.95, speaker: "SPEAKER_00" },
|
|
26
|
+
{ word: "world", start: 0.6, end: 1.0, score: 0.98, speaker: "SPEAKER_00" },
|
|
27
|
+
],
|
|
28
|
+
speaker: "SPEAKER_00",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
chapters: [],
|
|
32
|
+
metadata: null,
|
|
33
|
+
};
|
|
34
|
+
describe("readTranscript", () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
it("reads and returns a valid transcript JSON", () => {
|
|
39
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(VALID_TRANSCRIPT));
|
|
40
|
+
const result = readTranscript("/path/to/transcript.json");
|
|
41
|
+
expect(result).toEqual(VALID_TRANSCRIPT);
|
|
42
|
+
expect(mockReadFileSync).toHaveBeenCalledWith("/path/to/transcript.json", "utf-8");
|
|
43
|
+
});
|
|
44
|
+
it("throws if file not found", () => {
|
|
45
|
+
mockReadFileSync.mockImplementation(() => {
|
|
46
|
+
throw Object.assign(new Error("ENOENT"), { code: "ENOENT" });
|
|
47
|
+
});
|
|
48
|
+
expect(() => readTranscript("/missing/file.json")).toThrow(/not found/i);
|
|
49
|
+
});
|
|
50
|
+
it("throws if version is not 1.0", () => {
|
|
51
|
+
mockReadFileSync.mockReturnValue(JSON.stringify({ ...VALID_TRANSCRIPT, version: "2.0" }));
|
|
52
|
+
expect(() => readTranscript("/path/to/transcript.json")).toThrow(/version/i);
|
|
53
|
+
});
|
|
54
|
+
it("throws if JSON is invalid", () => {
|
|
55
|
+
mockReadFileSync.mockReturnValue("not json {{{");
|
|
56
|
+
expect(() => readTranscript("/path/to/transcript.json")).toThrow();
|
|
57
|
+
});
|
|
58
|
+
it("returns clip_candidates when present in transcript JSON", () => {
|
|
59
|
+
const clipCandidates = [
|
|
60
|
+
{
|
|
61
|
+
title: "Jalen Milroe scramble",
|
|
62
|
+
start: 45.0,
|
|
63
|
+
end: 90.0,
|
|
64
|
+
excerpt: "Watch how Milroe reads the linebacker...",
|
|
65
|
+
reasoning: "Shows tactical awareness",
|
|
66
|
+
status: "pending",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
title: "Defensive stunt package",
|
|
70
|
+
start: 200.0,
|
|
71
|
+
end: 260.0,
|
|
72
|
+
excerpt: "The twist game on third down...",
|
|
73
|
+
reasoning: "High-energy play",
|
|
74
|
+
status: "approved",
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
const transcriptWithClips = {
|
|
78
|
+
...VALID_TRANSCRIPT,
|
|
79
|
+
clip_candidates: clipCandidates,
|
|
80
|
+
};
|
|
81
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(transcriptWithClips));
|
|
82
|
+
const result = readTranscript("/path/to/transcript.json");
|
|
83
|
+
expect(result.clip_candidates).toBeDefined();
|
|
84
|
+
expect(result.clip_candidates).toHaveLength(2);
|
|
85
|
+
expect(result.clip_candidates[0].title).toBe("Jalen Milroe scramble");
|
|
86
|
+
expect(result.clip_candidates[0].start).toBe(45.0);
|
|
87
|
+
expect(result.clip_candidates[0].end).toBe(90.0);
|
|
88
|
+
expect(result.clip_candidates[0].excerpt).toBe("Watch how Milroe reads the linebacker...");
|
|
89
|
+
expect(result.clip_candidates[0].reasoning).toBe("Shows tactical awareness");
|
|
90
|
+
expect(result.clip_candidates[0].status).toBe("pending");
|
|
91
|
+
expect(result.clip_candidates[1].status).toBe("approved");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=read-transcript.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-transcript.test.js","sourceRoot":"","sources":["../../src/tools/read-transcript.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"season-advanced.d.ts","sourceRoot":"","sources":["../../src/tools/season-advanced.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmDzE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiHlE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getAdjustedTeamSeasonStats, getAdvancedSeasonStats, getPredictedPointsAddedByTeam, } from "cfbd";
|
|
3
|
+
import { cachedFetch } from "../cfbd-client.js";
|
|
4
|
+
import { formatSeasonAdvanced } from "../formatters/season-advanced-formatter.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
// See games.ts for why `as any` is needed on inputSchema.
|
|
7
|
+
const inputSchema = {
|
|
8
|
+
team: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("Team school name (e.g. 'Alabama', 'Ohio State')"),
|
|
11
|
+
year: z
|
|
12
|
+
.number()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Season year (e.g. 2024) — defaults to current season"),
|
|
15
|
+
startWeek: z
|
|
16
|
+
.number()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Start week for filtering (e.g. 5). Note: only raw PPA/success rate/havoc data is filtered — EPA and PPA-by-down are always full season."),
|
|
19
|
+
endWeek: z
|
|
20
|
+
.number()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("End week for filtering (e.g. 10). Note: only raw PPA/success rate/havoc data is filtered — EPA and PPA-by-down are always full season."),
|
|
23
|
+
includeGarbageTime: z
|
|
24
|
+
.boolean()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Include garbage time plays (default: false, garbage time excluded)"),
|
|
27
|
+
};
|
|
28
|
+
export function registerSeasonAdvancedTool(server) {
|
|
29
|
+
server.registerTool("cfbd_season_advanced", {
|
|
30
|
+
title: "Advanced Season Analytics",
|
|
31
|
+
description: "Get season-wide advanced analytics for a team: EPA (opponent-adjusted efficiency), PPA (raw predicted points), success rate, havoc, explosiveness, rushing detail, and PPA by down. Garbage time excluded by default. Week range filters raw stats only — EPA and PPA-by-down are always full season.",
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
inputSchema: inputSchema,
|
|
34
|
+
}, async (args) => {
|
|
35
|
+
const year = args.year ?? new Date().getFullYear();
|
|
36
|
+
// Garbage time polarity mapping (CONTEXT.md decision #3)
|
|
37
|
+
const excludeGarbageTime = args.includeGarbageTime === true ? false : true;
|
|
38
|
+
const hasWeekRange = args.startWeek != null || args.endWeek != null;
|
|
39
|
+
try {
|
|
40
|
+
const [adjustedAll, advancedAll, ppaAll] = await Promise.all([
|
|
41
|
+
// Source 1: EPA — NO week range, NO garbage time param
|
|
42
|
+
cachedFetch("getAdjustedTeamSeasonStats", { year, team: args.team }, () => getAdjustedTeamSeasonStats({
|
|
43
|
+
query: { year, team: args.team },
|
|
44
|
+
})),
|
|
45
|
+
// Source 2: Raw PPA/success/havoc — supports week range AND garbage time
|
|
46
|
+
cachedFetch("getAdvancedSeasonStats", {
|
|
47
|
+
year,
|
|
48
|
+
team: args.team,
|
|
49
|
+
excludeGarbageTime,
|
|
50
|
+
startWeek: args.startWeek,
|
|
51
|
+
endWeek: args.endWeek,
|
|
52
|
+
}, () => getAdvancedSeasonStats({
|
|
53
|
+
query: {
|
|
54
|
+
year,
|
|
55
|
+
team: args.team,
|
|
56
|
+
excludeGarbageTime,
|
|
57
|
+
startWeek: args.startWeek,
|
|
58
|
+
endWeek: args.endWeek,
|
|
59
|
+
},
|
|
60
|
+
})),
|
|
61
|
+
// Source 3: PPA by down — NO week range, supports garbage time
|
|
62
|
+
cachedFetch("getPredictedPointsAddedByTeam", { year, team: args.team, excludeGarbageTime }, () => getPredictedPointsAddedByTeam({
|
|
63
|
+
query: {
|
|
64
|
+
year,
|
|
65
|
+
team: args.team,
|
|
66
|
+
excludeGarbageTime,
|
|
67
|
+
},
|
|
68
|
+
})),
|
|
69
|
+
]);
|
|
70
|
+
const teamLower = args.team.toLowerCase();
|
|
71
|
+
const adjusted = adjustedAll.find((r) => r.team.toLowerCase() === teamLower);
|
|
72
|
+
const advanced = advancedAll.find((r) => r.team.toLowerCase() === teamLower);
|
|
73
|
+
const ppa = ppaAll.find((r) => r.team.toLowerCase() === teamLower);
|
|
74
|
+
const formatted = formatSeasonAdvanced(adjusted, advanced, ppa, args.team, year, hasWeekRange);
|
|
75
|
+
logger.info("cfbd_season_advanced tool invoked", {
|
|
76
|
+
team: args.team,
|
|
77
|
+
year,
|
|
78
|
+
startWeek: args.startWeek,
|
|
79
|
+
endWeek: args.endWeek,
|
|
80
|
+
includeGarbageTime: args.includeGarbageTime,
|
|
81
|
+
hasAdjusted: !!adjusted,
|
|
82
|
+
hasAdvanced: !!advanced,
|
|
83
|
+
hasPpa: !!ppa,
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: formatted }],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const message = `Failed to fetch advanced season stats: ${error instanceof Error ? error.message : String(error)}`;
|
|
91
|
+
logger.error("cfbd_season_advanced tool error", {
|
|
92
|
+
team: args.team,
|
|
93
|
+
year,
|
|
94
|
+
error: message,
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
content: [{ type: "text", text: message }],
|
|
98
|
+
isError: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=season-advanced.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"season-advanced.js","sourceRoot":"","sources":["../../src/tools/season-advanced.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"season-records.d.ts","sourceRoot":"","sources":["../../src/tools/season-records.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA2BzE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8CjE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getRecords } from "cfbd";
|
|
3
|
+
import { cachedFetch } from "../cfbd-client.js";
|
|
4
|
+
import { formatSeasonRecords } from "../formatters/season-records-formatter.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
// See games.ts for why `as any` is needed on inputSchema.
|
|
7
|
+
const inputSchema = {
|
|
8
|
+
team: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("Team school name (e.g. 'Alabama', 'Ohio State', 'Texas A&M')"),
|
|
11
|
+
year: z
|
|
12
|
+
.number()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Season year (e.g. 2024) — defaults to current season if omitted"),
|
|
15
|
+
};
|
|
16
|
+
export function registerSeasonRecordsTool(server) {
|
|
17
|
+
server.registerTool("cfbd_records", {
|
|
18
|
+
title: "Season Records",
|
|
19
|
+
description: "Get a team's season record with overall, conference, home/away, and postseason splits plus expected wins. Use once per team per season — returns all record breakdowns in a single call.",
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
inputSchema: inputSchema,
|
|
22
|
+
}, async (args) => {
|
|
23
|
+
const year = args.year ?? new Date().getFullYear();
|
|
24
|
+
const params = { year, team: args.team };
|
|
25
|
+
try {
|
|
26
|
+
const records = await cachedFetch("getRecords", params, () => getRecords({ query: params }));
|
|
27
|
+
const formatted = formatSeasonRecords(records, args.team);
|
|
28
|
+
logger.info("cfbd_records tool invoked", {
|
|
29
|
+
team: args.team,
|
|
30
|
+
year,
|
|
31
|
+
recordCount: records.length,
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: formatted }],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const message = `Failed to fetch season records: ${error instanceof Error ? error.message : String(error)}`;
|
|
39
|
+
logger.error("cfbd_records tool error", {
|
|
40
|
+
team: args.team,
|
|
41
|
+
year,
|
|
42
|
+
error: message,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: message }],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=season-records.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"season-records.js","sourceRoot":"","sources":["../../src/tools/season-records.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAoB,MAAM,MAAM,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,0DAA0D;AAC1D,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,QAAQ,CACP,8DAA8D,CAC/D;IACH,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,iEAAiE,CAClE;CACJ,CAAC;AAOF,MAAM,UAAU,yBAAyB,CAAC,MAAiB;IACzD,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,0LAA0L;QAC5L,8DAA8D;QAC9D,WAAW,EAAE,WAAkB;KAChC,EACD,KAAK,EAAE,IAAwB,EAAE,EAAE;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAEzC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,CAC/B,YAAY,EACZ,MAAM,EACN,GAAG,EAAE,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CACpC,CAAC;YAEF,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC,2BAA2B,EAAE;gBACvC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI;gBACJ,WAAW,EAAE,OAAO,CAAC,MAAM;aAC5B,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;aACtD,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,mCAAmC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5G,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE;gBACtC,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI;gBACJ,KAAK,EAAE,OAAO;aACf,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;gBACnD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"season-stats.d.ts","sourceRoot":"","sources":["../../src/tools/season-stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqCzE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAmE/D"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getTeamStats } from "cfbd";
|
|
3
|
+
import { cachedFetch } from "../cfbd-client.js";
|
|
4
|
+
import { formatSeasonStats } from "../formatters/season-stats-formatter.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
// See games.ts for why `as any` is needed on inputSchema.
|
|
7
|
+
const inputSchema = {
|
|
8
|
+
team: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("Team school name (e.g. 'Alabama', 'Ohio State')"),
|
|
11
|
+
year: z
|
|
12
|
+
.number()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Season year (e.g. 2024) — defaults to current season"),
|
|
15
|
+
startWeek: z
|
|
16
|
+
.number()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Start week for filtering (e.g. 5) — omit for full season"),
|
|
19
|
+
endWeek: z
|
|
20
|
+
.number()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("End week for filtering (e.g. 10) — omit for full season"),
|
|
23
|
+
};
|
|
24
|
+
export function registerSeasonStatsTool(server) {
|
|
25
|
+
server.registerTool("cfbd_season_stats", {
|
|
26
|
+
title: "Season Statistics",
|
|
27
|
+
description: "Get season-wide traditional stat aggregates (yards, turnovers, 3rd down, completions, etc.) for a team. Returns totals/averages organized by category. Use startWeek/endWeek to filter to a specific week range for trend analysis.",
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
inputSchema: inputSchema,
|
|
30
|
+
}, async (args) => {
|
|
31
|
+
const year = args.year ?? new Date().getFullYear();
|
|
32
|
+
const params = {
|
|
33
|
+
year,
|
|
34
|
+
team: args.team,
|
|
35
|
+
};
|
|
36
|
+
if (args.startWeek != null)
|
|
37
|
+
params.startWeek = args.startWeek;
|
|
38
|
+
if (args.endWeek != null)
|
|
39
|
+
params.endWeek = args.endWeek;
|
|
40
|
+
try {
|
|
41
|
+
const stats = await cachedFetch("getTeamStats", params, () => getTeamStats({
|
|
42
|
+
query: {
|
|
43
|
+
year,
|
|
44
|
+
team: args.team,
|
|
45
|
+
startWeek: args.startWeek,
|
|
46
|
+
endWeek: args.endWeek,
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
const formatted = formatSeasonStats(stats, args.team, year, args.startWeek, args.endWeek);
|
|
50
|
+
logger.info("cfbd_season_stats tool invoked", {
|
|
51
|
+
team: args.team,
|
|
52
|
+
year,
|
|
53
|
+
startWeek: args.startWeek,
|
|
54
|
+
endWeek: args.endWeek,
|
|
55
|
+
statCount: stats.length,
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: formatted }],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const message = `Failed to fetch season stats: ${error instanceof Error ? error.message : String(error)}`;
|
|
63
|
+
logger.error("cfbd_season_stats tool error", {
|
|
64
|
+
team: args.team,
|
|
65
|
+
year,
|
|
66
|
+
error: message,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: message }],
|
|
70
|
+
isError: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=season-stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"season-stats.js","sourceRoot":"","sources":["../../src/tools/season-stats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIzE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAqB1D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { checkCfbdStatus } from "../cfbd-client.js";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
3
|
+
export function registerStatusTool(server) {
|
|
4
|
+
server.registerTool("cfbd_status", {
|
|
5
|
+
title: "CFBD Status",
|
|
6
|
+
description: "Check CFBD API connectivity and authentication status",
|
|
7
|
+
}, async () => {
|
|
8
|
+
const result = await checkCfbdStatus();
|
|
9
|
+
logger.info("cfbd_status tool invoked", {
|
|
10
|
+
ok: result.ok,
|
|
11
|
+
message: result.message,
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: result.message }],
|
|
15
|
+
isError: !result.ok,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,UAAU,kBAAkB,CAAC,MAAiB;IAClD,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,KAAK,EAAE,aAAa;QACpB,WAAW,EACT,uDAAuD;KAC1D,EACD,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;YACtC,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YAC1D,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE;SACpB,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface Word {
|
|
2
|
+
word: string;
|
|
3
|
+
start: number | null;
|
|
4
|
+
end: number | null;
|
|
5
|
+
score: number | null;
|
|
6
|
+
speaker: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface Segment {
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
text: string;
|
|
12
|
+
words: Word[];
|
|
13
|
+
speaker: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface Chapter {
|
|
16
|
+
title: string;
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ClipCandidate {
|
|
21
|
+
title: string;
|
|
22
|
+
start: number;
|
|
23
|
+
end: number;
|
|
24
|
+
excerpt: string;
|
|
25
|
+
reasoning: string;
|
|
26
|
+
status?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface Transcript {
|
|
29
|
+
version: string;
|
|
30
|
+
language: string;
|
|
31
|
+
segments: Segment[];
|
|
32
|
+
chapters: Chapter[];
|
|
33
|
+
clip_candidates?: ClipCandidate[];
|
|
34
|
+
metadata: Record<string, unknown> | null;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=transcript-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-types.d.ts","sourceRoot":"","sources":["../../src/tools/transcript-types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,aAAa,EAAE,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-types.js","sourceRoot":"","sources":["../../src/tools/transcript-types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export interface ChapterInput {
|
|
3
|
+
title: string;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Validate chapters against YouTube format constraints.
|
|
9
|
+
* - First chapter must start at 0:00 (within 0.5s tolerance for segment alignment)
|
|
10
|
+
* - Minimum 3 chapters required
|
|
11
|
+
* - Each chapter must be at least 10 seconds long
|
|
12
|
+
* - Chapters must be in ascending order by start time
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateChapters(chapters: ChapterInput[]): {
|
|
15
|
+
valid: boolean;
|
|
16
|
+
errors: string[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Format seconds as YouTube chapter timestamp.
|
|
20
|
+
* Short form (M:SS): "0:00", "1:23", "45:30"
|
|
21
|
+
* Long form (H:MM:SS): "0:00:00", "1:02:15"
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatChapterTimestamp(seconds: number, isLongForm: boolean): string;
|
|
24
|
+
/**
|
|
25
|
+
* Format chapters as YouTube chapter list.
|
|
26
|
+
* Auto-detects long form if any chapter.end exceeds 3600 seconds.
|
|
27
|
+
* Output: "0:00 Title\n1:23 Next Topic\n..."
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatYouTubeChapters(chapters: ChapterInput[]): string;
|
|
30
|
+
/**
|
|
31
|
+
* Validate chapters, persist to transcript JSON, and return YouTube-formatted output.
|
|
32
|
+
* Reads the existing transcript, replaces only the chapters field, writes back.
|
|
33
|
+
*/
|
|
34
|
+
export declare function writeChapters(transcriptPath: string, chapters: ChapterInput[]): {
|
|
35
|
+
success: boolean;
|
|
36
|
+
message: string;
|
|
37
|
+
youtubeChapters?: string;
|
|
38
|
+
};
|
|
39
|
+
export declare function registerWriteChaptersTool(server: McpServer): void;
|
|
40
|
+
//# sourceMappingURL=write-chapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-chapters.d.ts","sourceRoot":"","sources":["../../src/tools/write-chapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAMzE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EAAE,GACvB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAgCtC;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,OAAO,GAClB,MAAM,CAYR;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM,CAMtE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,YAAY,EAAE,GACvB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAiCjE;AAiBD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0CjE"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { logger } from "../logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* Validate chapters against YouTube format constraints.
|
|
6
|
+
* - First chapter must start at 0:00 (within 0.5s tolerance for segment alignment)
|
|
7
|
+
* - Minimum 3 chapters required
|
|
8
|
+
* - Each chapter must be at least 10 seconds long
|
|
9
|
+
* - Chapters must be in ascending order by start time
|
|
10
|
+
*/
|
|
11
|
+
export function validateChapters(chapters) {
|
|
12
|
+
const errors = [];
|
|
13
|
+
if (chapters.length < 3) {
|
|
14
|
+
errors.push("Minimum 3 chapters required for YouTube chapters");
|
|
15
|
+
}
|
|
16
|
+
if (chapters.length > 0 && chapters[0].start > 0.5) {
|
|
17
|
+
errors.push("First chapter must start at 0:00 (within 0.5s tolerance)");
|
|
18
|
+
}
|
|
19
|
+
for (let i = 0; i < chapters.length; i++) {
|
|
20
|
+
const chapter = chapters[i];
|
|
21
|
+
const duration = chapter.end - chapter.start;
|
|
22
|
+
if (duration < 10) {
|
|
23
|
+
errors.push(`Chapter "${chapter.title}" is ${duration.toFixed(1)}s — minimum 10 seconds required`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (let i = 1; i < chapters.length; i++) {
|
|
27
|
+
if (chapters[i].start < chapters[i - 1].start) {
|
|
28
|
+
errors.push(`Chapters must be in ascending order: "${chapters[i].title}" (${chapters[i].start}s) comes after "${chapters[i - 1].title}" (${chapters[i - 1].start}s)`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { valid: errors.length === 0, errors };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Format seconds as YouTube chapter timestamp.
|
|
35
|
+
* Short form (M:SS): "0:00", "1:23", "45:30"
|
|
36
|
+
* Long form (H:MM:SS): "0:00:00", "1:02:15"
|
|
37
|
+
*/
|
|
38
|
+
export function formatChapterTimestamp(seconds, isLongForm) {
|
|
39
|
+
const totalSecs = Math.floor(seconds);
|
|
40
|
+
const s = totalSecs % 60;
|
|
41
|
+
const totalMins = Math.floor(totalSecs / 60);
|
|
42
|
+
if (isLongForm) {
|
|
43
|
+
const m = totalMins % 60;
|
|
44
|
+
const h = Math.floor(totalMins / 60);
|
|
45
|
+
return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
46
|
+
}
|
|
47
|
+
return `${totalMins}:${String(s).padStart(2, "0")}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Format chapters as YouTube chapter list.
|
|
51
|
+
* Auto-detects long form if any chapter.end exceeds 3600 seconds.
|
|
52
|
+
* Output: "0:00 Title\n1:23 Next Topic\n..."
|
|
53
|
+
*/
|
|
54
|
+
export function formatYouTubeChapters(chapters) {
|
|
55
|
+
const isLongForm = chapters.some((ch) => ch.end > 3600);
|
|
56
|
+
return chapters
|
|
57
|
+
.map((ch) => `${formatChapterTimestamp(ch.start, isLongForm)} ${ch.title}`)
|
|
58
|
+
.join("\n");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate chapters, persist to transcript JSON, and return YouTube-formatted output.
|
|
62
|
+
* Reads the existing transcript, replaces only the chapters field, writes back.
|
|
63
|
+
*/
|
|
64
|
+
export function writeChapters(transcriptPath, chapters) {
|
|
65
|
+
const validation = validateChapters(chapters);
|
|
66
|
+
if (!validation.valid) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
message: `Chapter validation failed:\n${validation.errors.join("\n")}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const raw = readFileSync(transcriptPath, "utf-8");
|
|
74
|
+
const transcript = JSON.parse(raw);
|
|
75
|
+
transcript.chapters = chapters.map((ch) => ({
|
|
76
|
+
title: ch.title,
|
|
77
|
+
start: ch.start,
|
|
78
|
+
end: ch.end,
|
|
79
|
+
}));
|
|
80
|
+
writeFileSync(transcriptPath, JSON.stringify(transcript, null, 2), "utf-8");
|
|
81
|
+
const youtubeChapters = formatYouTubeChapters(chapters);
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
message: `Wrote ${chapters.length} chapters to ${transcriptPath}`,
|
|
85
|
+
youtubeChapters,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
message: `Failed to write chapters: ${error instanceof Error ? error.message : String(error)}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const inputSchema = {
|
|
96
|
+
transcript_path: z
|
|
97
|
+
.string()
|
|
98
|
+
.describe("Path to the transcript JSON file"),
|
|
99
|
+
chapters: z
|
|
100
|
+
.array(z.object({
|
|
101
|
+
title: z.string().describe("Chapter title"),
|
|
102
|
+
start: z.number().describe("Start time in seconds"),
|
|
103
|
+
end: z.number().describe("End time in seconds"),
|
|
104
|
+
}))
|
|
105
|
+
.describe("Array of chapter objects with title, start, and end times"),
|
|
106
|
+
};
|
|
107
|
+
export function registerWriteChaptersTool(server) {
|
|
108
|
+
server.registerTool("write_chapters", {
|
|
109
|
+
title: "Write Chapters",
|
|
110
|
+
description: "Validates YouTube chapter format constraints and writes chapters to the transcript JSON. " +
|
|
111
|
+
"Returns YouTube-formatted chapter list for pasting into video description.",
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
+
inputSchema: inputSchema,
|
|
114
|
+
}, (args) => {
|
|
115
|
+
try {
|
|
116
|
+
const result = writeChapters(args.transcript_path, args.chapters);
|
|
117
|
+
logger.info("write_chapters tool invoked", {
|
|
118
|
+
transcript_path: args.transcript_path,
|
|
119
|
+
chapter_count: args.chapters.length,
|
|
120
|
+
success: result.success,
|
|
121
|
+
});
|
|
122
|
+
if (!result.success) {
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: "text", text: result.message }],
|
|
125
|
+
isError: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const text = `${result.message}\n\nYouTube Chapters:\n${result.youtubeChapters}`;
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: "text", text }],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const message = `Failed to write chapters: ${error instanceof Error ? error.message : String(error)}`;
|
|
135
|
+
logger.error("write_chapters tool error", { error: message });
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: message }],
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=write-chapters.js.map
|