jp_lit_search_kaken_projects
Search KAKEN research projects by query or researcher to retrieve themes, keywords, report PDFs, and output lists, with results to be verified via CiNii/J-STAGE.
Instructions
KAKEN から研究課題を検索し、研究テーマ・キーワード・報告書 PDF・成果リストの手がかりを返す補助 tool。論文・図書の文献確定は CiNii / J-STAGE / IRDB / NDL で再確認する
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| limit | No | ||
| page | No | ||
| detail_limit | No | ||
| researcher_name | No | ||
| from_fiscal_year | No | ||
| to_fiscal_year | No | ||
| include_outputs | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| page | Yes | ||
| limit | Yes | ||
| total | Yes | ||
| items | Yes | ||
| cache | No |
Implementation Reference
- The handler function that creates and returns the tool execution function for jp_lit_search_kaken_projects. It parses input, runs the cached tool which calls client.searchProjects, and returns structured content.
export function createJpLitSearchKakenProjectsTool( client: KakenClient, cache: FileCache = createFileCache(), sessions: SessionStore = createSessionStore() ) { return async (input: unknown) => { const parsed = searchKakenProjectsInputSchema.parse(input); const { structuredContent } = await runCachedTool<SearchKakenProjectsOutput>({ tool: "jp_lit_search_kaken_projects", input: parsed as Record<string, unknown>, cache, sessions, live: async () => searchKakenProjectsOutputSchema.parse(await client.searchProjects(parsed)) }); return { content: [{ type: "text" as const, text: JSON.stringify(structuredContent, null, 2) }], structuredContent }; }; } - src/lib/schemas.ts:864-939 (schema)Input schema (searchKakenProjectsInputSchema) and output schema (searchKakenProjectsOutputSchema) defining the Zod validation for the KAKEN projects search tool.
export const searchKakenProjectsInputSchema = z.object({ query: z.string().trim().min(1), limit: z.number().int().positive().max(20).default(10), page: z.number().int().positive().default(1), detail_limit: z.number().int().nonnegative().max(10).default(5), researcher_name: z.string().trim().min(1).optional(), from_fiscal_year: z.number().int().optional(), to_fiscal_year: z.number().int().optional(), include_outputs: z.boolean().default(true) }); const kakenOutputTypeSchema = z.enum([ "journal_article", "book", "conference_presentation", "report", "other" ]); const kakenProjectSchema = z.object({ project_id: z.string(), title: z.string(), url: z.string(), principal_investigator: z.object({ name: z.string(), affiliation: z.string().nullable(), researcher_number: z.string().nullable() }).nullable(), fiscal_years: z.string().nullable(), project_type: z.string().nullable(), fields: z.array(z.string()), keywords: z.array(z.string()), summary: z.string().nullable(), detail_fetched: z.boolean(), detail_omitted_reason: z.enum([ "detail_limit_exceeded", "include_outputs_false", "fetch_failed" ]).nullable(), report_pdf_status: z.enum(["found", "none_found", "not_checked", "fetch_failed"]), report_pdfs: z.array(z.object({ label: z.string(), fiscal_year: z.string().nullable(), url: z.string() })), outputs_preview: z.array(z.object({ type: kakenOutputTypeSchema, raw_type: z.string().nullable(), title: z.string(), authors: z.array(z.string()), year: z.string().nullable(), doi: z.string().nullable(), url: z.string().nullable(), note: z.string().nullable() })), search_hints: z.object({ project_terms: z.array(z.string()), researcher_terms: z.array(z.string()), keyword_terms: z.array(z.string()), caution: z.string() }) }); export const searchKakenProjectsOutputSchema = z.object({ query: z.string(), page: z.number().int().positive(), limit: z.number().int().positive(), total: z.number().int().nonnegative(), items: z.array(kakenProjectSchema), cache: z.object({ hit: z.boolean(), cache_key: z.string(), saved_at: z.string(), refresh_hint: z.string().nullable() }).optional() }); - src/server.ts:397-405 (registration)Tool registration in the MCP server: registers 'jp_lit_search_kaken_projects' with description, input/output schemas, and the handler function.
server.registerTool( "jp_lit_search_kaken_projects", { description: "KAKEN から研究課題を検索し、研究テーマ・キーワード・報告書 PDF・成果リストの手がかりを返す補助 tool。論文・図書の文献確定は CiNii / J-STAGE / IRDB / NDL で再確認する", inputSchema: searchKakenProjectsInputSchema, outputSchema: searchKakenProjectsOutputSchema }, searchKakenProjectsTool ); - src/server.ts:333-333 (registration)Tool creation: calls createJpLitSearchKakenProjectsTool with kakenClient, cache, and sessions dependencies.
const searchKakenProjectsTool = createJpLitSearchKakenProjectsTool(kakenClient, cache, sessions); - src/sources/kaken/client.ts:347-399 (helper)The KAKEN API client (createKakenClient) that implements searchProjects(), building search URLs, parsing XML results, fetching detail pages, extracting report PDFs and outputs.
export function createKakenClient(options: KakenClientOptions = {}) { const searchUrl = options.searchUrl ?? DEFAULT_SEARCH_URL; const detailBaseUrl = options.detailBaseUrl ?? DEFAULT_DETAIL_BASE_URL; const appId = options.appId ?? process.env.CINII_RESEARCH_APP_ID ?? ""; const fetcher = options.fetcher ?? fetchText; return { async searchProjects(input: SearchKakenProjectsInput): Promise<SearchKakenProjectsOutput> { if (!appId.trim()) { throw new Error("KAKEN API requires CINII_RESEARCH_APP_ID."); } const payload = await fetcher(buildSearchUrl(searchUrl, appId, input)); const parsed = parseSearchXml(payload.text, detailBaseUrl); const limited = parsed.items.slice(0, input.limit); const detailLimit = Math.min(input.detail_limit, limited.length); const items: KakenProject[] = []; for (const [index, project] of limited.entries()) { if (index >= detailLimit) { items.push({ ...project, detail_omitted_reason: "detail_limit_exceeded", report_pdf_status: "not_checked" }); continue; } try { const detail = await fetcher(new URL(project.url)); items.push(applyDetail(project, detail.text, input.include_outputs)); } catch { items.push({ ...project, detail_fetched: false, detail_omitted_reason: "fetch_failed", report_pdf_status: "fetch_failed" }); } } return { query: input.query, page: input.page, limit: input.limit, total: parsed.total, items }; } }; } export type KakenClient = ReturnType<typeof createKakenClient>;