jp_lit_find_sessions
Search past research sessions by theme, keyword, title, or memo to reuse exploration history.
Instructions
過去の調査セッションを主題・キーワード・候補タイトル・メモから検索する。過去の探索履歴を再利用したいときに使う
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| limit | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| limit | Yes | ||
| total | Yes | ||
| items | Yes |
Implementation Reference
- src/tools/jpLitFindSessions.ts:103-146 (handler)The main handler function `createJpLitFindSessionsTool` that implements the search logic. It takes a SessionStore, parses the input using `findSessionsInputSchema`, normalizes the query, iterates over all sessions to find matches across query/selected_title/notes fields, and returns structured output.
export function createJpLitFindSessionsTool(sessionStore: SessionStore) { return async (input: unknown) => { const parsed = findSessionsInputSchema.parse(input); const normalizedQuery = normalizeText(parsed.query); const sessions = await sessionStore.listAll(); const matchedItems = sessions .map((session) => { const matchedFields = collectMatchedFields(session, normalizedQuery); if (matchedFields.length === 0) { return null; } return { session_id: session.session_id, created_at: session.created_at, updated_at: session.updated_at, matched_fields: matchedFields, query_preview: pickQueryPreview(session, normalizedQuery), selected_count: countSelectedItems(session), note_preview: pickNotePreview(session, normalizedQuery) }; }) .filter((item): item is NonNullable<typeof item> => item !== null) .sort((left, right) => right.updated_at.localeCompare(left.updated_at)); const structuredContent: FindSessionsOutput = findSessionsOutputSchema.parse({ query: parsed.query, limit: parsed.limit, total: matchedItems.length, items: matchedItems.slice(0, parsed.limit) }); return { content: [ { type: "text" as const, text: JSON.stringify(structuredContent, null, 2) } ], structuredContent }; }; } - src/lib/schemas.ts:342-345 (schema)Input schema `findSessionsInputSchema` – validates query (non-empty string) and limit (1-50, default 10).
export const findSessionsInputSchema = z.object({ query: z.string().trim().min(1), limit: z.number().int().positive().max(50).default(10) }); - src/lib/schemas.ts:347-368 (schema)Output schema `findSessionsMatchedFieldSchema` and `findSessionsOutputSchema` – defines matched_fields enum and full output structure with session_id, dates, matched_fields, query_preview, selected_count, note_preview.
export const findSessionsMatchedFieldSchema = z.enum([ "query", "selected_title", "notes" ]); export const findSessionsOutputSchema = z.object({ query: z.string(), limit: z.number().int().positive(), total: z.number().int().nonnegative(), items: z.array( z.object({ session_id: z.string(), created_at: z.string(), updated_at: z.string(), matched_fields: z.array(findSessionsMatchedFieldSchema), query_preview: z.string().nullable(), selected_count: z.number().int().nonnegative(), note_preview: z.string().nullable() }) ) }); - src/server.ts:447-455 (registration)Registration of the tool `jp_lit_find_sessions` on the MCP server with description, input/output schemas, and handler.
server.registerTool( "jp_lit_find_sessions", { description: "過去の調査セッションを主題・キーワード・候補タイトル・メモから検索する。過去の探索履歴を再利用したいときに使う", inputSchema: findSessionsInputSchema, outputSchema: findSessionsOutputSchema }, findSessionsTool ); - Helper functions in `jpLitFindSessions.ts`: normalizeText, createPreview, entryMatchesQuery, entryMatchesSelectedTitle, entryMatchesNotes, collectMatchedFields, pickQueryPreview, pickNotePreview, countSelectedItems – all supporting the session search logic.
type MatchedField = "query" | "selected_title" | "notes"; function normalizeText(value: string) { return value .normalize("NFKC") .toLocaleLowerCase("ja-JP") .replace(/\s+/g, " ") .trim(); } function createPreview(value: string | null | undefined, maxLength = 120) { if (!value) { return null; } const compact = value.replace(/\s+/g, " ").trim(); if (compact.length <= maxLength) { return compact; } return `${compact.slice(0, maxLength - 1)}…`; } function entryMatchesQuery(entry: SessionEntry, normalizedQuery: string): boolean { const query = entry.input.query; return typeof query === "string" && normalizeText(query).includes(normalizedQuery); } function entryMatchesSelectedTitle(entry: SessionEntry, normalizedQuery: string): boolean { return entry.selected_items.some((item) => normalizeText(item.title).includes(normalizedQuery) ); } function entryMatchesNotes(entry: SessionEntry, normalizedQuery: string): boolean { return entry.notes.some((note) => normalizeText(note).includes(normalizedQuery)); } function collectMatchedFields( session: SessionDocument, normalizedQuery: string ): MatchedField[] { const matchedFields: MatchedField[] = []; if (session.entries.some((entry) => entryMatchesQuery(entry, normalizedQuery))) { matchedFields.push("query"); } if (session.entries.some((entry) => entryMatchesSelectedTitle(entry, normalizedQuery))) { matchedFields.push("selected_title"); } if (session.entries.some((entry) => entryMatchesNotes(entry, normalizedQuery))) { matchedFields.push("notes"); } return matchedFields; } function pickQueryPreview(session: SessionDocument, normalizedQuery: string) { const matchedEntry = session.entries.find((entry) => entryMatchesQuery(entry, normalizedQuery)) ?? session.entries.find((entry) => typeof entry.input.query === "string"); const query = matchedEntry?.input.query; return typeof query === "string" ? createPreview(query) : null; } function pickNotePreview(session: SessionDocument, normalizedQuery: string) { for (const entry of session.entries) { const matchedNote = entry.notes.find((note) => normalizeText(note).includes(normalizedQuery) ); if (matchedNote) { return createPreview(matchedNote); } } for (const entry of session.entries) { if (entry.notes.length > 0) { return createPreview(entry.notes[0]); } } return null; } function countSelectedItems(session: SessionDocument) { return session.entries.reduce( (count, entry) => count + entry.selected_items.length, 0 ); }