@echoes-io/mcp-server 1.0.0 → 1.2.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.
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ import { getTimeline } from '../utils.js';
3
+ export const episodeInfoSchema = z.object({
4
+ arc: z.string().describe('Arc name'),
5
+ episode: z.number().describe('Episode number'),
6
+ });
7
+ export async function episodeInfo(args, tracker) {
8
+ try {
9
+ const timeline = getTimeline();
10
+ const episode = await tracker.getEpisode(timeline, args.arc, args.episode);
11
+ if (!episode) {
12
+ throw new Error(`Episode not found: ${timeline}/${args.arc}/ep${args.episode}`);
13
+ }
14
+ const chapters = await tracker.getChapters(timeline, args.arc, args.episode);
15
+ return {
16
+ content: [
17
+ {
18
+ type: 'text',
19
+ text: JSON.stringify({
20
+ timeline,
21
+ arc: args.arc,
22
+ episodeInfo: {
23
+ number: episode.number,
24
+ title: episode.title,
25
+ slug: episode.slug,
26
+ description: episode.description,
27
+ },
28
+ chapters: chapters.map((ch) => ({
29
+ number: ch.number,
30
+ pov: ch.pov,
31
+ title: ch.title,
32
+ words: ch.words,
33
+ })),
34
+ stats: {
35
+ totalChapters: chapters.length,
36
+ totalWords: chapters.reduce((sum, ch) => sum + ch.words, 0),
37
+ },
38
+ }, null, 2),
39
+ },
40
+ ],
41
+ };
42
+ }
43
+ catch (error) {
44
+ throw new Error(`Failed to get episode info: ${error instanceof Error ? error.message : 'Unknown error'}`);
45
+ }
46
+ }
@@ -0,0 +1,27 @@
1
+ import type { Tracker } from '@echoes-io/tracker';
2
+ import { z } from 'zod';
3
+ export declare const episodeUpdateSchema: z.ZodObject<{
4
+ arc: z.ZodString;
5
+ episode: z.ZodNumber;
6
+ description: z.ZodOptional<z.ZodString>;
7
+ title: z.ZodOptional<z.ZodString>;
8
+ slug: z.ZodOptional<z.ZodString>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ arc: string;
11
+ episode: number;
12
+ title?: string | undefined;
13
+ description?: string | undefined;
14
+ slug?: string | undefined;
15
+ }, {
16
+ arc: string;
17
+ episode: number;
18
+ title?: string | undefined;
19
+ description?: string | undefined;
20
+ slug?: string | undefined;
21
+ }>;
22
+ export declare function episodeUpdate(args: z.infer<typeof episodeUpdateSchema>, tracker: Tracker): Promise<{
23
+ content: {
24
+ type: "text";
25
+ text: string;
26
+ }[];
27
+ }>;
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ import { getTimeline } from '../utils.js';
3
+ export const episodeUpdateSchema = z.object({
4
+ arc: z.string().describe('Arc name'),
5
+ episode: z.number().describe('Episode number'),
6
+ description: z.string().optional().describe('Episode description'),
7
+ title: z.string().optional().describe('Episode title'),
8
+ slug: z.string().optional().describe('Episode slug'),
9
+ });
10
+ export async function episodeUpdate(args, tracker) {
11
+ try {
12
+ const timeline = getTimeline();
13
+ const existing = await tracker.getEpisode(timeline, args.arc, args.episode);
14
+ if (!existing) {
15
+ throw new Error(`Episode not found: ${timeline}/${args.arc}/ep${args.episode}`);
16
+ }
17
+ const updateData = {};
18
+ if (args.description !== undefined)
19
+ updateData.description = args.description;
20
+ if (args.title !== undefined)
21
+ updateData.title = args.title;
22
+ if (args.slug !== undefined)
23
+ updateData.slug = args.slug;
24
+ await tracker.updateEpisode(timeline, args.arc, args.episode, updateData);
25
+ return {
26
+ content: [
27
+ {
28
+ type: 'text',
29
+ text: JSON.stringify({
30
+ timeline,
31
+ arc: args.arc,
32
+ episode: args.episode,
33
+ updated: updateData,
34
+ message: 'Episode successfully updated',
35
+ }, null, 2),
36
+ },
37
+ ],
38
+ };
39
+ }
40
+ catch (error) {
41
+ throw new Error(`Failed to update episode: ${error instanceof Error ? error.message : 'Unknown error'}`);
42
+ }
43
+ }
@@ -0,0 +1,12 @@
1
+ export { chapterDelete, chapterDeleteSchema } from './chapter-delete.js';
2
+ export { chapterInfo, chapterInfoSchema } from './chapter-info.js';
3
+ export { chapterInsert, chapterInsertSchema } from './chapter-insert.js';
4
+ export { chapterRefresh, chapterRefreshSchema } from './chapter-refresh.js';
5
+ export { episodeInfo, episodeInfoSchema } from './episode-info.js';
6
+ export { episodeUpdate, episodeUpdateSchema } from './episode-update.js';
7
+ export { ragContext, ragContextSchema } from './rag-context.js';
8
+ export { ragIndex, ragIndexSchema } from './rag-index.js';
9
+ export { ragSearch, ragSearchSchema } from './rag-search.js';
10
+ export { stats, statsSchema } from './stats.js';
11
+ export { timelineSync, timelineSyncSchema } from './timeline-sync.js';
12
+ export { wordsCount, wordsCountSchema } from './words-count.js';
@@ -0,0 +1,12 @@
1
+ export { chapterDelete, chapterDeleteSchema } from './chapter-delete.js';
2
+ export { chapterInfo, chapterInfoSchema } from './chapter-info.js';
3
+ export { chapterInsert, chapterInsertSchema } from './chapter-insert.js';
4
+ export { chapterRefresh, chapterRefreshSchema } from './chapter-refresh.js';
5
+ export { episodeInfo, episodeInfoSchema } from './episode-info.js';
6
+ export { episodeUpdate, episodeUpdateSchema } from './episode-update.js';
7
+ export { ragContext, ragContextSchema } from './rag-context.js';
8
+ export { ragIndex, ragIndexSchema } from './rag-index.js';
9
+ export { ragSearch, ragSearchSchema } from './rag-search.js';
10
+ export { stats, statsSchema } from './stats.js';
11
+ export { timelineSync, timelineSyncSchema } from './timeline-sync.js';
12
+ export { wordsCount, wordsCountSchema } from './words-count.js';
@@ -0,0 +1,24 @@
1
+ import type { RAGSystem } from '@echoes-io/rag';
2
+ import { z } from 'zod';
3
+ export declare const ragContextSchema: z.ZodObject<{
4
+ query: z.ZodString;
5
+ arc: z.ZodOptional<z.ZodString>;
6
+ pov: z.ZodOptional<z.ZodString>;
7
+ maxChapters: z.ZodOptional<z.ZodNumber>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ query: string;
10
+ arc?: string | undefined;
11
+ pov?: string | undefined;
12
+ maxChapters?: number | undefined;
13
+ }, {
14
+ query: string;
15
+ arc?: string | undefined;
16
+ pov?: string | undefined;
17
+ maxChapters?: number | undefined;
18
+ }>;
19
+ export declare function ragContext(args: z.infer<typeof ragContextSchema>, rag: RAGSystem): Promise<{
20
+ content: {
21
+ type: "text";
22
+ text: string;
23
+ }[];
24
+ }>;
@@ -0,0 +1,49 @@
1
+ import { z } from 'zod';
2
+ import { getTimeline } from '../utils.js';
3
+ export const ragContextSchema = z.object({
4
+ query: z.string().describe('Context query'),
5
+ arc: z.string().optional().describe('Filter by arc name'),
6
+ pov: z.string().optional().describe('Filter by POV character'),
7
+ maxChapters: z.number().optional().describe('Maximum number of chapters (default: 5)'),
8
+ });
9
+ export async function ragContext(args, rag) {
10
+ try {
11
+ const timeline = getTimeline();
12
+ const results = await rag.getContext({
13
+ query: args.query,
14
+ timeline,
15
+ arc: args.arc,
16
+ pov: args.pov,
17
+ maxChapters: args.maxChapters,
18
+ });
19
+ return {
20
+ content: [
21
+ {
22
+ type: 'text',
23
+ text: JSON.stringify({
24
+ query: args.query,
25
+ timeline,
26
+ filters: {
27
+ arc: args.arc || null,
28
+ pov: args.pov || null,
29
+ },
30
+ context: results.map((r) => ({
31
+ chapter: {
32
+ arc: r.metadata.arcName,
33
+ episode: r.metadata.episodeNumber,
34
+ chapter: r.metadata.number,
35
+ pov: r.metadata.pov,
36
+ title: r.metadata.title,
37
+ },
38
+ similarity: r.similarity,
39
+ content: r.content,
40
+ })),
41
+ }, null, 2),
42
+ },
43
+ ],
44
+ };
45
+ }
46
+ catch (error) {
47
+ throw new Error(`Failed to get context: ${error instanceof Error ? error.message : 'Unknown error'}`);
48
+ }
49
+ }
@@ -0,0 +1,19 @@
1
+ import type { RAGSystem } from '@echoes-io/rag';
2
+ import type { Tracker } from '@echoes-io/tracker';
3
+ import { z } from 'zod';
4
+ export declare const ragIndexSchema: z.ZodObject<{
5
+ arc: z.ZodOptional<z.ZodString>;
6
+ episode: z.ZodOptional<z.ZodNumber>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ arc?: string | undefined;
9
+ episode?: number | undefined;
10
+ }, {
11
+ arc?: string | undefined;
12
+ episode?: number | undefined;
13
+ }>;
14
+ export declare function ragIndex(args: z.infer<typeof ragIndexSchema>, tracker: Tracker, rag: RAGSystem): Promise<{
15
+ content: {
16
+ type: "text";
17
+ text: string;
18
+ }[];
19
+ }>;
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+ import { getTimeline } from '../utils.js';
3
+ export const ragIndexSchema = z.object({
4
+ arc: z.string().optional().describe('Index specific arc only'),
5
+ episode: z.number().optional().describe('Index specific episode only (requires arc)'),
6
+ });
7
+ export async function ragIndex(args, tracker, rag) {
8
+ try {
9
+ const timeline = getTimeline();
10
+ let chapters = [];
11
+ // Get chapters based on filters
12
+ if (args.arc && args.episode) {
13
+ chapters = await tracker.getChapters(timeline, args.arc, args.episode);
14
+ }
15
+ else if (args.arc) {
16
+ const episodes = await tracker.getEpisodes(timeline, args.arc);
17
+ for (const ep of episodes) {
18
+ const epChapters = await tracker.getChapters(timeline, args.arc, ep.number);
19
+ chapters.push(...epChapters);
20
+ }
21
+ }
22
+ else {
23
+ const arcs = await tracker.getArcs(timeline);
24
+ for (const arc of arcs) {
25
+ const episodes = await tracker.getEpisodes(timeline, arc.name);
26
+ for (const ep of episodes) {
27
+ const epChapters = await tracker.getChapters(timeline, arc.name, ep.number);
28
+ chapters.push(...epChapters);
29
+ }
30
+ }
31
+ }
32
+ // Convert to embedding format and add to RAG
33
+ const embeddingChapters = chapters.map((ch) => ({
34
+ id: `${ch.timelineName}-${ch.arcName}-${ch.episodeNumber}-${ch.number}`,
35
+ metadata: ch,
36
+ content: '', // Content will be loaded by RAG system if needed
37
+ }));
38
+ await rag.addChapters(embeddingChapters);
39
+ return {
40
+ content: [
41
+ {
42
+ type: 'text',
43
+ text: JSON.stringify({
44
+ indexed: embeddingChapters.length,
45
+ timeline,
46
+ arc: args.arc || 'all',
47
+ episode: args.episode || 'all',
48
+ }, null, 2),
49
+ },
50
+ ],
51
+ };
52
+ }
53
+ catch (error) {
54
+ throw new Error(`Failed to index chapters: ${error instanceof Error ? error.message : 'Unknown error'}`);
55
+ }
56
+ }
@@ -0,0 +1,24 @@
1
+ import type { RAGSystem } from '@echoes-io/rag';
2
+ import { z } from 'zod';
3
+ export declare const ragSearchSchema: z.ZodObject<{
4
+ query: z.ZodString;
5
+ arc: z.ZodOptional<z.ZodString>;
6
+ pov: z.ZodOptional<z.ZodString>;
7
+ maxResults: z.ZodOptional<z.ZodNumber>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ query: string;
10
+ arc?: string | undefined;
11
+ pov?: string | undefined;
12
+ maxResults?: number | undefined;
13
+ }, {
14
+ query: string;
15
+ arc?: string | undefined;
16
+ pov?: string | undefined;
17
+ maxResults?: number | undefined;
18
+ }>;
19
+ export declare function ragSearch(args: z.infer<typeof ragSearchSchema>, rag: RAGSystem): Promise<{
20
+ content: {
21
+ type: "text";
22
+ text: string;
23
+ }[];
24
+ }>;
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ import { getTimeline } from '../utils.js';
3
+ export const ragSearchSchema = z.object({
4
+ query: z.string().describe('Search query'),
5
+ arc: z.string().optional().describe('Filter by arc name'),
6
+ pov: z.string().optional().describe('Filter by POV character'),
7
+ maxResults: z.number().optional().describe('Maximum number of results (default: 10)'),
8
+ });
9
+ export async function ragSearch(args, rag) {
10
+ try {
11
+ const timeline = getTimeline();
12
+ const results = await rag.search(args.query, {
13
+ timeline,
14
+ arc: args.arc,
15
+ pov: args.pov,
16
+ maxResults: args.maxResults,
17
+ });
18
+ return {
19
+ content: [
20
+ {
21
+ type: 'text',
22
+ text: JSON.stringify({
23
+ query: args.query,
24
+ timeline,
25
+ filters: {
26
+ arc: args.arc || null,
27
+ pov: args.pov || null,
28
+ },
29
+ results: results.map((r) => ({
30
+ chapter: {
31
+ arc: r.metadata.arcName,
32
+ episode: r.metadata.episodeNumber,
33
+ chapter: r.metadata.number,
34
+ pov: r.metadata.pov,
35
+ title: r.metadata.title,
36
+ },
37
+ similarity: r.similarity,
38
+ preview: r.content.substring(0, 200) + '...',
39
+ })),
40
+ }, null, 2),
41
+ },
42
+ ],
43
+ };
44
+ }
45
+ catch (error) {
46
+ throw new Error(`Failed to search: ${error instanceof Error ? error.message : 'Unknown error'}`);
47
+ }
48
+ }
@@ -0,0 +1,21 @@
1
+ import type { Tracker } from '@echoes-io/tracker';
2
+ import { z } from 'zod';
3
+ export declare const statsSchema: z.ZodObject<{
4
+ arc: z.ZodOptional<z.ZodString>;
5
+ episode: z.ZodOptional<z.ZodNumber>;
6
+ pov: z.ZodOptional<z.ZodString>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ arc?: string | undefined;
9
+ episode?: number | undefined;
10
+ pov?: string | undefined;
11
+ }, {
12
+ arc?: string | undefined;
13
+ episode?: number | undefined;
14
+ pov?: string | undefined;
15
+ }>;
16
+ export declare function stats(args: z.infer<typeof statsSchema>, tracker: Tracker): Promise<{
17
+ content: {
18
+ type: "text";
19
+ text: string;
20
+ }[];
21
+ }>;
@@ -0,0 +1,133 @@
1
+ import { z } from 'zod';
2
+ import { getTimeline } from '../utils.js';
3
+ export const statsSchema = z.object({
4
+ arc: z.string().optional().describe('Filter by arc name'),
5
+ episode: z.number().optional().describe('Filter by episode number'),
6
+ pov: z.string().optional().describe('Filter by POV character'),
7
+ });
8
+ export async function stats(args, tracker) {
9
+ try {
10
+ const timeline = getTimeline();
11
+ let chapters = [];
12
+ // Get chapters based on filters
13
+ if (args.arc && args.episode) {
14
+ chapters = await tracker.getChapters(timeline, args.arc, args.episode);
15
+ }
16
+ else if (args.arc) {
17
+ const episodes = await tracker.getEpisodes(timeline, args.arc);
18
+ for (const ep of episodes) {
19
+ const epChapters = await tracker.getChapters(timeline, args.arc, ep.number);
20
+ chapters.push(...epChapters);
21
+ }
22
+ }
23
+ else {
24
+ const arcs = await tracker.getArcs(timeline);
25
+ for (const arc of arcs) {
26
+ const episodes = await tracker.getEpisodes(timeline, arc.name);
27
+ for (const ep of episodes) {
28
+ const epChapters = await tracker.getChapters(timeline, arc.name, ep.number);
29
+ chapters.push(...epChapters);
30
+ }
31
+ }
32
+ }
33
+ // Filter by POV if specified
34
+ if (args.pov) {
35
+ chapters = chapters.filter((ch) => ch.pov === args.pov);
36
+ }
37
+ // Calculate statistics
38
+ const totalWords = chapters.reduce((sum, ch) => sum + ch.words, 0);
39
+ const totalChapters = chapters.length;
40
+ // POV distribution
41
+ const povStats = {};
42
+ for (const ch of chapters) {
43
+ if (!povStats[ch.pov]) {
44
+ povStats[ch.pov] = { chapters: 0, words: 0 };
45
+ }
46
+ povStats[ch.pov].chapters++;
47
+ povStats[ch.pov].words += ch.words;
48
+ }
49
+ // Arc breakdown (if not filtered by arc)
50
+ const arcStats = {};
51
+ if (!args.arc) {
52
+ for (const ch of chapters) {
53
+ if (!arcStats[ch.arcName]) {
54
+ arcStats[ch.arcName] = { chapters: 0, words: 0, episodes: new Set() };
55
+ }
56
+ arcStats[ch.arcName].chapters++;
57
+ arcStats[ch.arcName].words += ch.words;
58
+ arcStats[ch.arcName].episodes.add(ch.episodeNumber);
59
+ }
60
+ }
61
+ // Episode breakdown (if filtered by arc but not episode)
62
+ const episodeStats = {};
63
+ if (args.arc && !args.episode) {
64
+ for (const ch of chapters) {
65
+ if (!episodeStats[ch.episodeNumber]) {
66
+ episodeStats[ch.episodeNumber] = { chapters: 0, words: 0 };
67
+ }
68
+ episodeStats[ch.episodeNumber].chapters++;
69
+ episodeStats[ch.episodeNumber].words += ch.words;
70
+ }
71
+ }
72
+ const result = {
73
+ timeline,
74
+ filters: {
75
+ arc: args.arc || null,
76
+ episode: args.episode || null,
77
+ pov: args.pov || null,
78
+ },
79
+ summary: {
80
+ totalChapters,
81
+ totalWords,
82
+ averageChapterLength: totalChapters > 0 ? Math.round(totalWords / totalChapters) : 0,
83
+ },
84
+ povDistribution: Object.entries(povStats).map(([pov, stats]) => ({
85
+ pov,
86
+ chapters: stats.chapters,
87
+ words: stats.words,
88
+ percentage: totalWords > 0 ? Math.round((stats.words / totalWords) * 100) : 0,
89
+ })),
90
+ };
91
+ if (!args.arc && Object.keys(arcStats).length > 0) {
92
+ result.arcBreakdown = Object.entries(arcStats).map(([arc, stats]) => ({
93
+ arc,
94
+ chapters: stats.chapters,
95
+ words: stats.words,
96
+ episodes: stats.episodes.size,
97
+ }));
98
+ }
99
+ if (args.arc && !args.episode && Object.keys(episodeStats).length > 0) {
100
+ result.episodeBreakdown = Object.entries(episodeStats).map(([episode, stats]) => ({
101
+ episode: Number(episode),
102
+ chapters: stats.chapters,
103
+ words: stats.words,
104
+ }));
105
+ }
106
+ if (totalChapters > 0) {
107
+ const sortedByWords = [...chapters].sort((a, b) => b.words - a.words);
108
+ result.extremes = {
109
+ longest: {
110
+ title: sortedByWords[0].title,
111
+ pov: sortedByWords[0].pov,
112
+ words: sortedByWords[0].words,
113
+ },
114
+ shortest: {
115
+ title: sortedByWords[sortedByWords.length - 1].title,
116
+ pov: sortedByWords[sortedByWords.length - 1].pov,
117
+ words: sortedByWords[sortedByWords.length - 1].words,
118
+ },
119
+ };
120
+ }
121
+ return {
122
+ content: [
123
+ {
124
+ type: 'text',
125
+ text: JSON.stringify(result, null, 2),
126
+ },
127
+ ],
128
+ };
129
+ }
130
+ catch (error) {
131
+ throw new Error(`Failed to get stats: ${error instanceof Error ? error.message : 'Unknown error'}`);
132
+ }
133
+ }
@@ -0,0 +1,15 @@
1
+ import type { Tracker } from '@echoes-io/tracker';
2
+ import { z } from 'zod';
3
+ export declare const timelineSyncSchema: z.ZodObject<{
4
+ contentPath: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ contentPath: string;
7
+ }, {
8
+ contentPath: string;
9
+ }>;
10
+ export declare function timelineSync(args: z.infer<typeof timelineSyncSchema>, tracker: Tracker): Promise<{
11
+ content: {
12
+ type: "text";
13
+ text: string;
14
+ }[];
15
+ }>;