novelforge-agent 0.1.0 → 0.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.
- package/README.md +82 -14
- package/dist/src/cli/index.js +92 -2
- package/dist/src/cli/install.js +224 -0
- package/dist/src/core/bibleStore.js +36 -0
- package/dist/src/core/characterStore.js +74 -0
- package/dist/src/core/contextBuilder.js +44 -1
- package/dist/src/core/fileNames.js +4 -0
- package/dist/src/core/index.js +4 -0
- package/dist/src/core/projectOps.js +187 -0
- package/dist/src/core/projectStore.js +11 -0
- package/dist/src/core/prompts/en-US.js +117 -13
- package/dist/src/core/prompts/zh-CN.js +116 -12
- package/dist/src/core/retrieval/index.js +8 -0
- package/dist/src/core/schemas.js +98 -1
- package/dist/src/core/steps/architecture.js +7 -1
- package/dist/src/core/steps/chapter.js +11 -1
- package/dist/src/core/steps/chapterReview.js +25 -1
- package/dist/src/core/steps/chapterRevision.js +17 -0
- package/dist/src/core/steps/memoryCard.js +4 -0
- package/dist/src/core/steps/novelMetadata.js +4 -2
- package/dist/src/core/threadStore.js +150 -0
- package/dist/src/core/workflow.js +3 -3
- package/dist/src/mcp/tools.js +198 -18
- package/package.json +5 -1
- package/src/cli/index.ts +94 -1
- package/src/cli/install.ts +275 -0
- package/src/core/bibleStore.ts +57 -0
- package/src/core/characterStore.ts +93 -0
- package/src/core/contextBuilder.ts +44 -4
- package/src/core/fileNames.ts +5 -0
- package/src/core/index.ts +4 -0
- package/src/core/projectOps.ts +243 -0
- package/src/core/projectStore.ts +11 -0
- package/src/core/prompts/en-US.ts +126 -22
- package/src/core/prompts/types.ts +2 -1
- package/src/core/prompts/zh-CN.ts +118 -14
- package/src/core/retrieval/index.ts +10 -0
- package/src/core/schemas.ts +108 -1
- package/src/core/steps/architecture.ts +7 -1
- package/src/core/steps/chapter.ts +11 -1
- package/src/core/steps/chapterReview.ts +27 -1
- package/src/core/steps/chapterRevision.ts +18 -0
- package/src/core/steps/memoryCard.ts +4 -0
- package/src/core/steps/novelMetadata.ts +4 -2
- package/src/core/threadStore.ts +173 -0
- package/src/core/types.ts +102 -1
- package/src/core/workflow.ts +3 -3
- package/src/mcp/tools.ts +322 -19
package/src/mcp/tools.ts
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
3
4
|
import {
|
|
5
|
+
amendStoryBible,
|
|
6
|
+
assertProjectPath,
|
|
4
7
|
buildContext,
|
|
5
|
-
chapterFileName,
|
|
6
8
|
createProject,
|
|
9
|
+
deleteChapter,
|
|
10
|
+
forkProject,
|
|
7
11
|
getNextStep,
|
|
8
12
|
getProjectStatus,
|
|
9
13
|
listProjects,
|
|
14
|
+
listStoryBibleVersions,
|
|
15
|
+
loadState,
|
|
16
|
+
loadThreads,
|
|
17
|
+
redoStep,
|
|
10
18
|
requestSideTrack,
|
|
11
19
|
retrieve,
|
|
12
|
-
saveMarkdownFile,
|
|
13
20
|
submitStepResult,
|
|
21
|
+
updateThread,
|
|
14
22
|
} from '../core/index.js';
|
|
15
23
|
|
|
24
|
+
const MCP_SERVER_VERSION = '0.2.0';
|
|
25
|
+
|
|
16
26
|
export interface CreateNovelAgentServerOptions {
|
|
17
27
|
workspaceRoot: string;
|
|
18
28
|
}
|
|
@@ -27,9 +37,19 @@ function textResult(value: unknown) {
|
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
export function createNovelAgentServer(options: CreateNovelAgentServerOptions): McpServer {
|
|
40
|
+
function checkedProjectPath(projectPath: string): string {
|
|
41
|
+
assertProjectPath(options.workspaceRoot, projectPath);
|
|
42
|
+
return projectPath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkedOutputDir(outputDir: string): string {
|
|
46
|
+
assertProjectPath(options.workspaceRoot, resolve(options.workspaceRoot, outputDir));
|
|
47
|
+
return outputDir;
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
const server = new McpServer({
|
|
31
51
|
name: 'novelforge-agent',
|
|
32
|
-
version:
|
|
52
|
+
version: MCP_SERVER_VERSION,
|
|
33
53
|
});
|
|
34
54
|
|
|
35
55
|
server.tool(
|
|
@@ -59,21 +79,22 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
59
79
|
{
|
|
60
80
|
outputDir: z.string().default('novels'),
|
|
61
81
|
},
|
|
62
|
-
async ({ outputDir }) =>
|
|
82
|
+
async ({ outputDir }) =>
|
|
83
|
+
textResult(await listProjects({ workspaceRoot: options.workspaceRoot, outputDir: checkedOutputDir(outputDir) }))
|
|
63
84
|
);
|
|
64
85
|
|
|
65
86
|
server.tool(
|
|
66
87
|
'get_project_status',
|
|
67
88
|
'Return a compact, one-screen summary of a project: current step, chapters written, open threads, latest review verdict, completion state.',
|
|
68
89
|
{ projectPath: z.string().min(1) },
|
|
69
|
-
async ({ projectPath }) => textResult(await getProjectStatus(projectPath))
|
|
90
|
+
async ({ projectPath }) => textResult(await getProjectStatus(checkedProjectPath(projectPath)))
|
|
70
91
|
);
|
|
71
92
|
|
|
72
93
|
server.tool(
|
|
73
94
|
'get_next_step',
|
|
74
95
|
'Return the next required generation step for a novel project.',
|
|
75
96
|
{ projectPath: z.string().min(1) },
|
|
76
|
-
async ({ projectPath }) => textResult(await getNextStep(projectPath))
|
|
97
|
+
async ({ projectPath }) => textResult(await getNextStep(checkedProjectPath(projectPath)))
|
|
77
98
|
);
|
|
78
99
|
|
|
79
100
|
server.tool(
|
|
@@ -95,7 +116,8 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
95
116
|
]),
|
|
96
117
|
content: z.string(),
|
|
97
118
|
},
|
|
98
|
-
async ({ projectPath, step, content }) =>
|
|
119
|
+
async ({ projectPath, step, content }) =>
|
|
120
|
+
textResult(await submitStepResult({ projectPath: checkedProjectPath(projectPath), step, content }))
|
|
99
121
|
);
|
|
100
122
|
|
|
101
123
|
server.tool(
|
|
@@ -117,7 +139,7 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
117
139
|
},
|
|
118
140
|
async ({ projectPath, purpose, chapterNumber, start, end }) =>
|
|
119
141
|
textResult(await buildContext({
|
|
120
|
-
projectPath,
|
|
142
|
+
projectPath: checkedProjectPath(projectPath),
|
|
121
143
|
purpose,
|
|
122
144
|
chapterNumber,
|
|
123
145
|
range: start && end ? { start, end } : undefined,
|
|
@@ -126,7 +148,7 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
126
148
|
|
|
127
149
|
server.tool(
|
|
128
150
|
'save_chapter',
|
|
129
|
-
'
|
|
151
|
+
'Submit a generated chapter Markdown draft through the workflow state machine. This requires currentStep="chapter" and advances to chapter_review.',
|
|
130
152
|
{
|
|
131
153
|
projectPath: z.string().min(1),
|
|
132
154
|
chapterNumber: z.number().int().positive(),
|
|
@@ -134,9 +156,18 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
134
156
|
content: z.string().min(1),
|
|
135
157
|
},
|
|
136
158
|
async ({ projectPath, chapterNumber, title, content }) => {
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
159
|
+
const checked = checkedProjectPath(projectPath);
|
|
160
|
+
const state = await loadState(checked);
|
|
161
|
+
if (state.currentStep !== 'chapter' || state.currentChapter !== chapterNumber) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`save_chapter requires currentStep="chapter" and currentChapter=${chapterNumber}; got currentStep="${state.currentStep}", currentChapter=${state.currentChapter}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return textResult(await submitStepResult({
|
|
167
|
+
projectPath: checked,
|
|
168
|
+
step: 'chapter',
|
|
169
|
+
content: `# ${title}\n\n${content}`,
|
|
170
|
+
}));
|
|
140
171
|
}
|
|
141
172
|
);
|
|
142
173
|
|
|
@@ -149,8 +180,8 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
149
180
|
},
|
|
150
181
|
async ({ projectPath, chapterNumber }) =>
|
|
151
182
|
textResult({
|
|
152
|
-
context: await buildContext({ projectPath, purpose: 'chapter_generation', chapterNumber }),
|
|
153
|
-
hint: 'Persist the result via
|
|
183
|
+
context: await buildContext({ projectPath: checkedProjectPath(projectPath), purpose: 'chapter_generation', chapterNumber }),
|
|
184
|
+
hint: 'Persist the result via submit_step_result(step="chapter") when the workflow currentStep is "chapter"; the workflow then requires chapter_review before memory_card.',
|
|
154
185
|
})
|
|
155
186
|
);
|
|
156
187
|
|
|
@@ -163,7 +194,7 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
163
194
|
},
|
|
164
195
|
async ({ projectPath, chapterNumber }) =>
|
|
165
196
|
textResult({
|
|
166
|
-
context: await buildContext({ projectPath, purpose: 'memory_extraction', chapterNumber }),
|
|
197
|
+
context: await buildContext({ projectPath: checkedProjectPath(projectPath), purpose: 'memory_extraction', chapterNumber }),
|
|
167
198
|
hint: 'Submit the extracted memory card via submit_step_result with step="memory_card" when the workflow currentStep matches.',
|
|
168
199
|
})
|
|
169
200
|
);
|
|
@@ -176,7 +207,7 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
176
207
|
chapterNumber: z.number().int().positive(),
|
|
177
208
|
},
|
|
178
209
|
async ({ projectPath, chapterNumber }) =>
|
|
179
|
-
textResult(await requestSideTrack({ projectPath, step: 'chapter_review', chapterNumber }))
|
|
210
|
+
textResult(await requestSideTrack({ projectPath: checkedProjectPath(projectPath), step: 'chapter_review', chapterNumber }))
|
|
180
211
|
);
|
|
181
212
|
|
|
182
213
|
server.tool(
|
|
@@ -188,7 +219,7 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
188
219
|
feedback: z.string().optional(),
|
|
189
220
|
},
|
|
190
221
|
async ({ projectPath, chapterNumber, feedback }) =>
|
|
191
|
-
textResult(await requestSideTrack({ projectPath, step: 'chapter_revision', chapterNumber, feedback }))
|
|
222
|
+
textResult(await requestSideTrack({ projectPath: checkedProjectPath(projectPath), step: 'chapter_revision', chapterNumber, feedback }))
|
|
192
223
|
);
|
|
193
224
|
|
|
194
225
|
server.tool(
|
|
@@ -204,7 +235,7 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
204
235
|
},
|
|
205
236
|
async ({ projectPath, query, topK, types, chapterStart, chapterEnd }) => {
|
|
206
237
|
const chapterRange = chapterStart && chapterEnd ? { start: chapterStart, end: chapterEnd } : undefined;
|
|
207
|
-
const hits = await retrieve(projectPath, query, { topK, types, chapterRange });
|
|
238
|
+
const hits = await retrieve(checkedProjectPath(projectPath), query, { topK, types, chapterRange });
|
|
208
239
|
return textResult({ query, hits });
|
|
209
240
|
}
|
|
210
241
|
);
|
|
@@ -219,9 +250,281 @@ export function createNovelAgentServer(options: CreateNovelAgentServerOptions):
|
|
|
219
250
|
},
|
|
220
251
|
async ({ projectPath, start, end }) => {
|
|
221
252
|
const range = start && end ? { start, end } : undefined;
|
|
222
|
-
return textResult(await requestSideTrack({ projectPath, step: 'cross_chapter_review', range }));
|
|
253
|
+
return textResult(await requestSideTrack({ projectPath: checkedProjectPath(projectPath), step: 'cross_chapter_review', range }));
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// ----- v0.2 tools -----
|
|
258
|
+
|
|
259
|
+
server.tool(
|
|
260
|
+
'amend_story_bible',
|
|
261
|
+
'Replace the story bible with a revised version. Old version is auto-archived under story-bible-versions/ and the lexical index is rebuilt for the new content.',
|
|
262
|
+
{
|
|
263
|
+
projectPath: z.string().min(1),
|
|
264
|
+
content: z.string().min(1),
|
|
265
|
+
reason: z.string().optional(),
|
|
266
|
+
},
|
|
267
|
+
async ({ projectPath, content, reason }) =>
|
|
268
|
+
textResult(await amendStoryBible({ projectPath: checkedProjectPath(projectPath), content, reason }))
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
server.tool(
|
|
272
|
+
'list_bible_versions',
|
|
273
|
+
'List archived story-bible versions for a project (filenames sorted oldest first).',
|
|
274
|
+
{ projectPath: z.string().min(1) },
|
|
275
|
+
async ({ projectPath }) => textResult({ versions: await listStoryBibleVersions(checkedProjectPath(projectPath)) })
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
server.tool(
|
|
279
|
+
'list_threads',
|
|
280
|
+
'List foreshadow threads for a project, optionally filtered by status. Threads are aggregated from memory_card.threadActions.',
|
|
281
|
+
{
|
|
282
|
+
projectPath: z.string().min(1),
|
|
283
|
+
status: z.enum(['planted', 'building', 'paid', 'dropped']).optional(),
|
|
284
|
+
},
|
|
285
|
+
async ({ projectPath, status }) => {
|
|
286
|
+
const all = await loadThreads(checkedProjectPath(projectPath));
|
|
287
|
+
const filtered = status ? all.filter((t) => t.status === status) : all;
|
|
288
|
+
return textResult({ threads: filtered });
|
|
223
289
|
}
|
|
224
290
|
);
|
|
225
291
|
|
|
292
|
+
server.tool(
|
|
293
|
+
'update_thread',
|
|
294
|
+
'Update a single foreshadow thread (override status, plannedPayoffAt, paidOffAt, droppedAt, description, notes).',
|
|
295
|
+
{
|
|
296
|
+
projectPath: z.string().min(1),
|
|
297
|
+
id: z.string().min(1),
|
|
298
|
+
status: z.enum(['planted', 'building', 'paid', 'dropped']).optional(),
|
|
299
|
+
plannedPayoffAt: z.number().int().positive().nullable().optional(),
|
|
300
|
+
paidOffAt: z.number().int().positive().nullable().optional(),
|
|
301
|
+
droppedAt: z.number().int().positive().nullable().optional(),
|
|
302
|
+
description: z.string().min(1).optional(),
|
|
303
|
+
notes: z.string().nullable().optional(),
|
|
304
|
+
},
|
|
305
|
+
async ({ projectPath, id, ...patch }) =>
|
|
306
|
+
textResult(await updateThread(checkedProjectPath(projectPath), id, patch))
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
server.tool(
|
|
310
|
+
'fork_project',
|
|
311
|
+
'Copy an existing project to a new sibling directory with a new projectId. Use to try alternate plot branches without losing the original.',
|
|
312
|
+
{
|
|
313
|
+
sourceProjectPath: z.string().min(1),
|
|
314
|
+
label: z.string().optional(),
|
|
315
|
+
},
|
|
316
|
+
async ({ sourceProjectPath, label }) =>
|
|
317
|
+
textResult(await forkProject({ sourceProjectPath: checkedProjectPath(sourceProjectPath), label }))
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
server.tool(
|
|
321
|
+
'delete_chapter',
|
|
322
|
+
'Delete a chapter, its memory card, its single-chapter review, and all archived versions. Removes the chapter from the lexical index and rewinds the workflow if needed.',
|
|
323
|
+
{
|
|
324
|
+
projectPath: z.string().min(1),
|
|
325
|
+
chapterNumber: z.number().int().positive(),
|
|
326
|
+
},
|
|
327
|
+
async ({ projectPath, chapterNumber }) =>
|
|
328
|
+
textResult(await deleteChapter({ projectPath: checkedProjectPath(projectPath), chapterNumber }))
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
server.tool(
|
|
332
|
+
'redo_step',
|
|
333
|
+
'Roll the workflow back to a specific step. Files produced by that step (and dependent chapter content for chapter/memory_card steps) are removed; the host must regenerate.',
|
|
334
|
+
{
|
|
335
|
+
projectPath: z.string().min(1),
|
|
336
|
+
step: z.enum([
|
|
337
|
+
'novel_metadata',
|
|
338
|
+
'story_bible',
|
|
339
|
+
'architecture',
|
|
340
|
+
'chapter',
|
|
341
|
+
'memory_card',
|
|
342
|
+
'continuity_review',
|
|
343
|
+
]),
|
|
344
|
+
chapterNumber: z.number().int().positive().optional(),
|
|
345
|
+
},
|
|
346
|
+
async ({ projectPath, step, chapterNumber }) =>
|
|
347
|
+
textResult(await redoStep({ projectPath: checkedProjectPath(projectPath), step, chapterNumber }))
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// ===== MCP Prompts (slash commands) =====
|
|
351
|
+
|
|
352
|
+
server.prompt(
|
|
353
|
+
'nf-start',
|
|
354
|
+
'Start a brand new novel project under the configured workspace.',
|
|
355
|
+
{
|
|
356
|
+
prompt: z.string().describe('User idea / premise / genre, in any language.'),
|
|
357
|
+
chapters: z.string().optional().describe('Target number of chapters as a string. Defaults to 5.'),
|
|
358
|
+
},
|
|
359
|
+
({ prompt, chapters }) => ({
|
|
360
|
+
messages: [{
|
|
361
|
+
role: 'user' as const,
|
|
362
|
+
content: {
|
|
363
|
+
type: 'text' as const,
|
|
364
|
+
text: `Use the novelforge MCP server. Call start_novel_project with prompt="${prompt}", targetChapters=${chapters ?? '5'}, then enter the autonomous loop: read next.instruction, generate the requested content, call submit_step_result, repeat until currentStep is "complete". Show me the projectPath after start_novel_project returns.`,
|
|
365
|
+
},
|
|
366
|
+
}],
|
|
367
|
+
})
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
server.prompt(
|
|
371
|
+
'nf-next',
|
|
372
|
+
'Continue the current novelforge workflow by one step.',
|
|
373
|
+
{
|
|
374
|
+
projectPath: z.string().describe('Absolute path to the project.'),
|
|
375
|
+
},
|
|
376
|
+
({ projectPath }) => ({
|
|
377
|
+
messages: [{
|
|
378
|
+
role: 'user' as const,
|
|
379
|
+
content: {
|
|
380
|
+
type: 'text' as const,
|
|
381
|
+
text: `Use the novelforge MCP server. Call get_next_step with projectPath="${projectPath}". Read the returned instruction + context and produce the requested artifact, then call submit_step_result. Show me what step was advanced.`,
|
|
382
|
+
},
|
|
383
|
+
}],
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
server.prompt(
|
|
388
|
+
'nf-list',
|
|
389
|
+
'List all novelforge projects in the workspace.',
|
|
390
|
+
{},
|
|
391
|
+
() => ({
|
|
392
|
+
messages: [{
|
|
393
|
+
role: 'user' as const,
|
|
394
|
+
content: {
|
|
395
|
+
type: 'text' as const,
|
|
396
|
+
text: 'Use the novelforge MCP server: call list_projects with no arguments. Show me the result in a compact table (title, currentStep, chaptersWritten/targetChapters, updatedAt, projectPath).',
|
|
397
|
+
},
|
|
398
|
+
}],
|
|
399
|
+
})
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
server.prompt(
|
|
403
|
+
'nf-status',
|
|
404
|
+
'Show a one-screen status for a novelforge project.',
|
|
405
|
+
{
|
|
406
|
+
projectPath: z.string(),
|
|
407
|
+
},
|
|
408
|
+
({ projectPath }) => ({
|
|
409
|
+
messages: [{
|
|
410
|
+
role: 'user' as const,
|
|
411
|
+
content: {
|
|
412
|
+
type: 'text' as const,
|
|
413
|
+
text: `Use the novelforge MCP server: call get_project_status with projectPath="${projectPath}". Summarize: title, current step, chapters written, open threads count, latest review verdict.`,
|
|
414
|
+
},
|
|
415
|
+
}],
|
|
416
|
+
})
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
server.prompt(
|
|
420
|
+
'nf-review-chapter',
|
|
421
|
+
'Run a single-chapter editorial review.',
|
|
422
|
+
{
|
|
423
|
+
projectPath: z.string(),
|
|
424
|
+
chapterNumber: z.string(),
|
|
425
|
+
},
|
|
426
|
+
({ projectPath, chapterNumber }) => ({
|
|
427
|
+
messages: [{
|
|
428
|
+
role: 'user' as const,
|
|
429
|
+
content: {
|
|
430
|
+
type: 'text' as const,
|
|
431
|
+
text: `Use the novelforge MCP server: call review_chapter with projectPath="${projectPath}", chapterNumber=${chapterNumber}. Read the returned instruction + context, produce the JSON chapter review, then call submit_step_result with step="chapter_review". Summarize the findings for me.`,
|
|
432
|
+
},
|
|
433
|
+
}],
|
|
434
|
+
})
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
server.prompt(
|
|
438
|
+
'nf-revise-chapter',
|
|
439
|
+
'Revise a chapter based on review feedback or new instructions.',
|
|
440
|
+
{
|
|
441
|
+
projectPath: z.string(),
|
|
442
|
+
chapterNumber: z.string(),
|
|
443
|
+
feedback: z.string().optional(),
|
|
444
|
+
},
|
|
445
|
+
({ projectPath, chapterNumber, feedback }) => ({
|
|
446
|
+
messages: [{
|
|
447
|
+
role: 'user' as const,
|
|
448
|
+
content: {
|
|
449
|
+
type: 'text' as const,
|
|
450
|
+
text: `Use the novelforge MCP server: call revise_chapter with projectPath="${projectPath}", chapterNumber=${chapterNumber}${feedback ? `, feedback=${JSON.stringify(feedback)}` : ''}. Read the returned instruction + context, produce the revised Markdown chapter, then call submit_step_result with step="chapter_revision". Confirm the previous version was archived.`,
|
|
451
|
+
},
|
|
452
|
+
}],
|
|
453
|
+
})
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
server.prompt(
|
|
457
|
+
'nf-cross-review',
|
|
458
|
+
'Cross-chapter continuity review over a range.',
|
|
459
|
+
{
|
|
460
|
+
projectPath: z.string(),
|
|
461
|
+
start: z.string().optional(),
|
|
462
|
+
end: z.string().optional(),
|
|
463
|
+
},
|
|
464
|
+
({ projectPath, start, end }) => ({
|
|
465
|
+
messages: [{
|
|
466
|
+
role: 'user' as const,
|
|
467
|
+
content: {
|
|
468
|
+
type: 'text' as const,
|
|
469
|
+
text: `Use the novelforge MCP server: call cross_chapter_review with projectPath="${projectPath}"${start && end ? `, start=${start}, end=${end}` : ''}. Read the returned instruction + context, produce the JSON cross-chapter review, then call submit_step_result with step="cross_chapter_review". Summarize verdict and any issues.`,
|
|
470
|
+
},
|
|
471
|
+
}],
|
|
472
|
+
})
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
server.prompt(
|
|
476
|
+
'nf-retrieve',
|
|
477
|
+
'Lexical retrieval over a project (BM25-style).',
|
|
478
|
+
{
|
|
479
|
+
projectPath: z.string(),
|
|
480
|
+
query: z.string(),
|
|
481
|
+
},
|
|
482
|
+
({ projectPath, query }) => ({
|
|
483
|
+
messages: [{
|
|
484
|
+
role: 'user' as const,
|
|
485
|
+
content: {
|
|
486
|
+
type: 'text' as const,
|
|
487
|
+
text: `Use the novelforge MCP server: call retrieve with projectPath="${projectPath}", query=${JSON.stringify(query)}, topK=8. List the hits with chapter attribution and short excerpts.`,
|
|
488
|
+
},
|
|
489
|
+
}],
|
|
490
|
+
})
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
server.prompt(
|
|
494
|
+
'nf-amend-bible',
|
|
495
|
+
'Amend the story bible with new content (previous version auto-archived).',
|
|
496
|
+
{
|
|
497
|
+
projectPath: z.string(),
|
|
498
|
+
reason: z.string().optional(),
|
|
499
|
+
},
|
|
500
|
+
({ projectPath, reason }) => ({
|
|
501
|
+
messages: [{
|
|
502
|
+
role: 'user' as const,
|
|
503
|
+
content: {
|
|
504
|
+
type: 'text' as const,
|
|
505
|
+
text: `Use the novelforge MCP server. First call get_project_status with projectPath="${projectPath}" to confirm the project exists, then read the current story-bible.md (you may use the host's filesystem tools). Apply the following amendment intent and produce a complete revised story bible Markdown:\n\n${reason ?? '(no specific reason supplied — ask the user what to change)'}\n\nThen call amend_story_bible with projectPath="${projectPath}" and the new content. Confirm the archived version path.`,
|
|
506
|
+
},
|
|
507
|
+
}],
|
|
508
|
+
})
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
server.prompt(
|
|
512
|
+
'nf-threads',
|
|
513
|
+
'Show active foreshadow threads for a project.',
|
|
514
|
+
{
|
|
515
|
+
projectPath: z.string(),
|
|
516
|
+
status: z.enum(['planted', 'building', 'paid', 'dropped']).optional(),
|
|
517
|
+
},
|
|
518
|
+
({ projectPath, status }) => ({
|
|
519
|
+
messages: [{
|
|
520
|
+
role: 'user' as const,
|
|
521
|
+
content: {
|
|
522
|
+
type: 'text' as const,
|
|
523
|
+
text: `Use the novelforge MCP server: call list_threads with projectPath="${projectPath}"${status ? `, status="${status}"` : ''}. Show me the threads as a compact list: id, status, plantedAt, plannedPayoffAt (if set), description.`,
|
|
524
|
+
},
|
|
525
|
+
}],
|
|
526
|
+
})
|
|
527
|
+
);
|
|
528
|
+
|
|
226
529
|
return server;
|
|
227
530
|
}
|