novelforge-agent 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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -0
  3. package/dist/src/cli/index.js +125 -0
  4. package/dist/src/core/contextBuilder.js +128 -0
  5. package/dist/src/core/fileNames.js +41 -0
  6. package/dist/src/core/index.js +9 -0
  7. package/dist/src/core/projectDiscovery.js +141 -0
  8. package/dist/src/core/projectStore.js +85 -0
  9. package/dist/src/core/prompts/en-US.js +363 -0
  10. package/dist/src/core/prompts/types.js +1 -0
  11. package/dist/src/core/prompts/zh-CN.js +362 -0
  12. package/dist/src/core/prompts.js +15 -0
  13. package/dist/src/core/retrieval/chunker.js +77 -0
  14. package/dist/src/core/retrieval/index.js +125 -0
  15. package/dist/src/core/retrieval/tokenizer.js +43 -0
  16. package/dist/src/core/retrieval/types.js +1 -0
  17. package/dist/src/core/schemas.js +91 -0
  18. package/dist/src/core/steps/architecture.js +16 -0
  19. package/dist/src/core/steps/chapter.js +16 -0
  20. package/dist/src/core/steps/chapterReview.js +16 -0
  21. package/dist/src/core/steps/chapterRevision.js +20 -0
  22. package/dist/src/core/steps/continuityReview.js +13 -0
  23. package/dist/src/core/steps/crossChapterReview.js +15 -0
  24. package/dist/src/core/steps/index.js +20 -0
  25. package/dist/src/core/steps/memoryCard.js +22 -0
  26. package/dist/src/core/steps/novelMetadata.js +12 -0
  27. package/dist/src/core/steps/storyBible.js +13 -0
  28. package/dist/src/core/steps/types.js +7 -0
  29. package/dist/src/core/types.js +1 -0
  30. package/dist/src/core/workflow.js +186 -0
  31. package/dist/src/mcp/server.js +13 -0
  32. package/dist/src/mcp/tools.js +126 -0
  33. package/package.json +61 -0
  34. package/src/cli/index.ts +147 -0
  35. package/src/core/contextBuilder.ts +131 -0
  36. package/src/core/fileNames.ts +48 -0
  37. package/src/core/index.ts +9 -0
  38. package/src/core/projectDiscovery.ts +174 -0
  39. package/src/core/projectStore.ts +111 -0
  40. package/src/core/prompts/en-US.ts +376 -0
  41. package/src/core/prompts/types.ts +28 -0
  42. package/src/core/prompts/zh-CN.ts +375 -0
  43. package/src/core/prompts.ts +27 -0
  44. package/src/core/retrieval/chunker.ts +80 -0
  45. package/src/core/retrieval/index.ts +136 -0
  46. package/src/core/retrieval/tokenizer.ts +44 -0
  47. package/src/core/retrieval/types.ts +24 -0
  48. package/src/core/schemas.ts +101 -0
  49. package/src/core/steps/architecture.ts +17 -0
  50. package/src/core/steps/chapter.ts +17 -0
  51. package/src/core/steps/chapterReview.ts +17 -0
  52. package/src/core/steps/chapterRevision.ts +21 -0
  53. package/src/core/steps/continuityReview.ts +14 -0
  54. package/src/core/steps/crossChapterReview.ts +16 -0
  55. package/src/core/steps/index.ts +25 -0
  56. package/src/core/steps/memoryCard.ts +23 -0
  57. package/src/core/steps/novelMetadata.ts +13 -0
  58. package/src/core/steps/storyBible.ts +14 -0
  59. package/src/core/steps/types.ts +21 -0
  60. package/src/core/types.ts +115 -0
  61. package/src/core/workflow.ts +250 -0
  62. package/src/mcp/server.ts +15 -0
  63. package/src/mcp/tools.ts +227 -0
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createNovelAgentServer } from './tools.js';
4
+
5
+ async function main(): Promise<void> {
6
+ const workspaceRoot = process.env.NOVELFORGE_WORKSPACE || process.cwd();
7
+ const server = createNovelAgentServer({ workspaceRoot });
8
+ const transport = new StdioServerTransport();
9
+ await server.connect(transport);
10
+ }
11
+
12
+ main().catch((error) => {
13
+ console.error((error as Error).message);
14
+ process.exitCode = 1;
15
+ });
@@ -0,0 +1,227 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import {
4
+ buildContext,
5
+ chapterFileName,
6
+ createProject,
7
+ getNextStep,
8
+ getProjectStatus,
9
+ listProjects,
10
+ requestSideTrack,
11
+ retrieve,
12
+ saveMarkdownFile,
13
+ submitStepResult,
14
+ } from '../core/index.js';
15
+
16
+ export interface CreateNovelAgentServerOptions {
17
+ workspaceRoot: string;
18
+ }
19
+
20
+ function textResult(value: unknown) {
21
+ return {
22
+ content: [{
23
+ type: 'text' as const,
24
+ text: typeof value === 'string' ? value : JSON.stringify(value, null, 2),
25
+ }],
26
+ };
27
+ }
28
+
29
+ export function createNovelAgentServer(options: CreateNovelAgentServerOptions): McpServer {
30
+ const server = new McpServer({
31
+ name: 'novelforge-agent',
32
+ version: '0.1.0',
33
+ });
34
+
35
+ server.tool(
36
+ 'start_novel_project',
37
+ 'Create a local novel project and return the first generation instruction.',
38
+ {
39
+ prompt: z.string().min(1),
40
+ language: z.enum(['zh-CN', 'en-US']).default('zh-CN'),
41
+ outputDir: z.string().default('novels'),
42
+ targetChapters: z.number().int().positive().default(3),
43
+ },
44
+ async ({ prompt, language, outputDir, targetChapters }) => {
45
+ const result = await createProject({
46
+ workspaceRoot: options.workspaceRoot,
47
+ prompt,
48
+ language,
49
+ outputDir,
50
+ targetChapters,
51
+ });
52
+ return textResult({ state: result.state, next: await getNextStep(result.state.projectPath) });
53
+ }
54
+ );
55
+
56
+ server.tool(
57
+ 'list_projects',
58
+ 'List all NovelForge projects under the workspace, sorted by most recently updated. Use this to find an existing projectPath instead of asking the user.',
59
+ {
60
+ outputDir: z.string().default('novels'),
61
+ },
62
+ async ({ outputDir }) => textResult(await listProjects({ workspaceRoot: options.workspaceRoot, outputDir }))
63
+ );
64
+
65
+ server.tool(
66
+ 'get_project_status',
67
+ 'Return a compact, one-screen summary of a project: current step, chapters written, open threads, latest review verdict, completion state.',
68
+ { projectPath: z.string().min(1) },
69
+ async ({ projectPath }) => textResult(await getProjectStatus(projectPath))
70
+ );
71
+
72
+ server.tool(
73
+ 'get_next_step',
74
+ 'Return the next required generation step for a novel project.',
75
+ { projectPath: z.string().min(1) },
76
+ async ({ projectPath }) => textResult(await getNextStep(projectPath))
77
+ );
78
+
79
+ server.tool(
80
+ 'submit_step_result',
81
+ 'Submit host-generated content for validation, saving, and workflow advancement.',
82
+ {
83
+ projectPath: z.string().min(1),
84
+ step: z.enum([
85
+ 'novel_metadata',
86
+ 'story_bible',
87
+ 'architecture',
88
+ 'chapter',
89
+ 'memory_card',
90
+ 'continuity_review',
91
+ 'chapter_review',
92
+ 'chapter_revision',
93
+ 'cross_chapter_review',
94
+ 'complete',
95
+ ]),
96
+ content: z.string(),
97
+ },
98
+ async ({ projectPath, step, content }) => textResult(await submitStepResult({ projectPath, step, content }))
99
+ );
100
+
101
+ server.tool(
102
+ 'get_context',
103
+ 'Build purpose-specific context for generation, memory extraction, review, or revision.',
104
+ {
105
+ projectPath: z.string().min(1),
106
+ purpose: z.enum([
107
+ 'chapter_generation',
108
+ 'memory_extraction',
109
+ 'continuity_review',
110
+ 'revision',
111
+ 'chapter_review',
112
+ 'cross_chapter_review',
113
+ ]),
114
+ chapterNumber: z.number().int().positive().optional(),
115
+ start: z.number().int().positive().optional(),
116
+ end: z.number().int().positive().optional(),
117
+ },
118
+ async ({ projectPath, purpose, chapterNumber, start, end }) =>
119
+ textResult(await buildContext({
120
+ projectPath,
121
+ purpose,
122
+ chapterNumber,
123
+ range: start && end ? { start, end } : undefined,
124
+ }))
125
+ );
126
+
127
+ server.tool(
128
+ 'save_chapter',
129
+ 'Save a generated chapter directly as Markdown.',
130
+ {
131
+ projectPath: z.string().min(1),
132
+ chapterNumber: z.number().int().positive(),
133
+ title: z.string().min(1),
134
+ content: z.string().min(1),
135
+ },
136
+ async ({ projectPath, chapterNumber, title, content }) => {
137
+ const fileName = `chapters/${chapterFileName(chapterNumber)}`;
138
+ const savedPath = await saveMarkdownFile(projectPath, fileName, `# ${title}\n\n${content}`);
139
+ return textResult({ savedPath, suggestedNextStep: 'memory_card' });
140
+ }
141
+ );
142
+
143
+ server.tool(
144
+ 'generate_chapter',
145
+ 'Build the chapter-generation context and instruction for a specific chapter without changing workflow state.',
146
+ {
147
+ projectPath: z.string().min(1),
148
+ chapterNumber: z.number().int().positive(),
149
+ },
150
+ async ({ projectPath, chapterNumber }) =>
151
+ textResult({
152
+ context: await buildContext({ projectPath, purpose: 'chapter_generation', chapterNumber }),
153
+ hint: 'Persist the result via save_chapter or submit_step_result(step="chapter") when the workflow currentStep is "chapter".',
154
+ })
155
+ );
156
+
157
+ server.tool(
158
+ 'extract_memory_card',
159
+ 'Build the memory-extraction context for a specific chapter without changing workflow state.',
160
+ {
161
+ projectPath: z.string().min(1),
162
+ chapterNumber: z.number().int().positive(),
163
+ },
164
+ async ({ projectPath, chapterNumber }) =>
165
+ textResult({
166
+ context: await buildContext({ projectPath, purpose: 'memory_extraction', chapterNumber }),
167
+ hint: 'Submit the extracted memory card via submit_step_result with step="memory_card" when the workflow currentStep matches.',
168
+ })
169
+ );
170
+
171
+ server.tool(
172
+ 'review_chapter',
173
+ 'Ask the host to review a specific chapter. Switches the workflow into chapter_review side-track and returns the review prompt + packed context. Resume original step after submit_step_result(step="chapter_review").',
174
+ {
175
+ projectPath: z.string().min(1),
176
+ chapterNumber: z.number().int().positive(),
177
+ },
178
+ async ({ projectPath, chapterNumber }) =>
179
+ textResult(await requestSideTrack({ projectPath, step: 'chapter_review', chapterNumber }))
180
+ );
181
+
182
+ server.tool(
183
+ 'revise_chapter',
184
+ 'Ask the host to rewrite a specific chapter based on prior review feedback and optional extra instructions. Previous version is archived under chapters/.versions/.',
185
+ {
186
+ projectPath: z.string().min(1),
187
+ chapterNumber: z.number().int().positive(),
188
+ feedback: z.string().optional(),
189
+ },
190
+ async ({ projectPath, chapterNumber, feedback }) =>
191
+ textResult(await requestSideTrack({ projectPath, step: 'chapter_revision', chapterNumber, feedback }))
192
+ );
193
+
194
+ server.tool(
195
+ 'retrieve',
196
+ 'Lexical BM25-style retrieval over indexed chapter paragraphs, story-bible sections, and memory cards. Returns ranked snippets with chapter attribution.',
197
+ {
198
+ projectPath: z.string().min(1),
199
+ query: z.string().min(1),
200
+ topK: z.number().int().positive().max(50).default(6),
201
+ types: z.array(z.enum(['chapter', 'bible', 'memory'])).optional(),
202
+ chapterStart: z.number().int().positive().optional(),
203
+ chapterEnd: z.number().int().positive().optional(),
204
+ },
205
+ async ({ projectPath, query, topK, types, chapterStart, chapterEnd }) => {
206
+ const chapterRange = chapterStart && chapterEnd ? { start: chapterStart, end: chapterEnd } : undefined;
207
+ const hits = await retrieve(projectPath, query, { topK, types, chapterRange });
208
+ return textResult({ query, hits });
209
+ }
210
+ );
211
+
212
+ server.tool(
213
+ 'cross_chapter_review',
214
+ 'Ask the host to review a chapter range for cross-chapter continuity conflicts. Defaults to all generated chapters.',
215
+ {
216
+ projectPath: z.string().min(1),
217
+ start: z.number().int().positive().optional(),
218
+ end: z.number().int().positive().optional(),
219
+ },
220
+ async ({ projectPath, start, end }) => {
221
+ const range = start && end ? { start, end } : undefined;
222
+ return textResult(await requestSideTrack({ projectPath, step: 'cross_chapter_review', range }));
223
+ }
224
+ );
225
+
226
+ return server;
227
+ }