jp_lit_export_session
Export a current or past research session to a structured file (Markdown, JSON, or CSL-JSON) for documentation and sharing.
Instructions
現在の調査セッション、または session_id で指定した過去セッションを repo 内の exports/ に書き出す。既定は Markdown で、人間が読み返しやすい形に整形する
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| session_id | No | ||
| format | No | markdown | |
| profile | No | full_log | |
| output_path | No | ||
| include_unselected | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| session_id | Yes | ||
| format | Yes | ||
| profile | Yes | ||
| path | Yes | ||
| exported_at | Yes | ||
| item_count | Yes |
Implementation Reference
- src/tools/jpLitExportSession.ts:9-45 (handler)The main handler function for the jp_lit_export_session tool. It parses input via exportSessionInputSchema, reads a session (current or by session_id), delegates to the SessionExporter to write the export file, and returns structured output with session metadata and item count.
export function createJpLitExportSessionTool( sessionStore: SessionStore, exporter: SessionExporter ) { return async (input: unknown) => { const parsed = exportSessionInputSchema.parse(input); const session = parsed.session_id ? await sessionStore.readById(parsed.session_id) : await sessionStore.readCurrent(); const exported = await exporter.exportSession({ session, format: parsed.format, profile: parsed.profile, outputPath: parsed.output_path, includeUnselected: parsed.include_unselected }); const structuredContent: ExportSessionOutput = exportSessionOutputSchema.parse({ session_id: session.session_id, format: parsed.format, profile: parsed.profile, path: exported.path, exported_at: new Date().toISOString(), item_count: exported.itemCount }); return { content: [ { type: "text" as const, text: JSON.stringify(structuredContent, null, 2) } ], structuredContent }; }; } - src/lib/schemas.ts:323-331 (schema)Input schema (Zod) for the export tool: session_id (optional regex), format (markdown/json/csl-json, default markdown), profile (full_log/selected/unselected, default full_log), output_path (optional), include_unselected (boolean, default true).
export const exportSessionInputSchema = z.object({ session_id: z.string().trim().regex(/^\d{4}-\d{2}-\d{2}-\d{6}$/).optional(), format: z.enum(["markdown", "json", "csl-json"]).default("markdown"), profile: z .enum(["full_log", "selected", "unselected"]) .default("full_log"), output_path: z.string().trim().min(1).optional(), include_unselected: z.boolean().default(true) }); - src/lib/schemas.ts:333-340 (schema)Output schema (Zod) for the export tool: session_id, format, profile, path (the written file path), exported_at (ISO timestamp), item_count (non-negative integer).
export const exportSessionOutputSchema = z.object({ session_id: z.string(), format: z.enum(["markdown", "json", "csl-json"]), profile: z.enum(["full_log", "selected", "unselected"]), path: z.string(), exported_at: z.string(), item_count: z.number().int().nonnegative() }); - src/server.ts:437-445 (registration)Registers the tool with the MCP server under name 'jp_lit_export_session', providing description, inputSchema, and outputSchema, with the handler created by createJpLitExportSessionTool(sessions, sessionExporter).
server.registerTool( "jp_lit_export_session", { description: "現在の調査セッション、または session_id で指定した過去セッションを repo 内の exports/ に書き出す。既定は Markdown で、人間が読み返しやすい形に整形する", inputSchema: exportSessionInputSchema, outputSchema: exportSessionOutputSchema }, exportSessionTool ); - The SessionExporter implementation (createSessionExporter). Handles the actual file writing in three formats: markdown (renderMarkdown), JSON (session data with filtered items), and csl-json (CSL-JSON array). Resolves cached items via FileCache for unselected item inclusion.
export function createSessionExporter( cache: FileCache, baseDir = process.cwd() ): SessionExporter { return { async exportSession({ session, format, profile, outputPath, includeUnselected }) { const target = outputPath ?? defaultExportPath(baseDir, session.session_id, profile, format); const unresolvedItems = new Map<string, Array<Record<string, unknown>>>(); await mkdir(path.dirname(target), { recursive: true }); if ((profile === "full_log" && includeUnselected) || profile === "unselected") { for (const entry of session.entries) { const envelope = await cache.read<unknown>(entry.result_ref.tool, entry.result_ref.cache_key); unresolvedItems.set(entry.cache_key, extractUnselectedItems(envelope)); } } let itemCount = session.entries.reduce((sum, entry) => { const selectedCount = filterSelectedItems(entry, profile).length; const shouldCountUnselected = format !== "csl-json" && profile === "full_log" && includeUnselected; const unselectedCount = shouldCountUnselected || profile === "unselected" ? filterUnselectedItems( unresolvedItems.get(entry.cache_key) ?? [], entry.selected_items ).length : 0; return sum + selectedCount + unselectedCount; }, 0); if (format === "csl-json") { const cslItems = []; for (const entry of session.entries) { const cachedItems = await readEntryItems(cache, entry); for (const selectedItem of filterSelectedItems(entry, profile)) { cslItems.push(toCslItem(selectedItem, findCslSourceItem(cachedItems, selectedItem))); } if (profile === "unselected") { for (const unselectedItem of filterUnselectedItems(cachedItems, entry.selected_items)) { cslItems.push(toCslItem(toFallbackSelectedItem(unselectedItem), unselectedItem)); } } } itemCount = cslItems.length; await writeFile(target, JSON.stringify(cslItems, null, 2), "utf8"); } else if (format === "json") { const payload = { ...session, entries: session.entries.map((entry) => { const baseEntry = { ...entry, selected_items: filterSelectedItems(entry, profile) }; if ((profile === "full_log" && includeUnselected) || profile === "unselected") { return { ...baseEntry, unselected_items: filterUnselectedItems( unresolvedItems.get(entry.cache_key) ?? [], entry.selected_items ) }; } return baseEntry; }) }; await writeFile(target, JSON.stringify(payload, null, 2), "utf8"); } else { await writeFile( target, renderMarkdown(session, profile, includeUnselected, unresolvedItems), "utf8" ); } return { path: target, itemCount }; } }; }