@zonuexe/techbook-mcp 0.2.1 → 0.2.3

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 (45) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/CHANGELOG.md +21 -1
  3. package/dist/adapters/calil.d.ts +10 -0
  4. package/dist/adapters/calil.d.ts.map +1 -0
  5. package/dist/adapters/calil.js +45 -0
  6. package/dist/adapters/calil.js.map +1 -0
  7. package/dist/adapters/openbd.d.ts +57 -0
  8. package/dist/adapters/openbd.d.ts.map +1 -0
  9. package/dist/adapters/openbd.js +87 -0
  10. package/dist/adapters/openbd.js.map +1 -0
  11. package/dist/adapters/publishers/tatsu-zine.d.ts.map +1 -1
  12. package/dist/adapters/publishers/tatsu-zine.js +64 -53
  13. package/dist/adapters/publishers/tatsu-zine.js.map +1 -1
  14. package/dist/application/get-book-by-isbn.d.ts +12 -0
  15. package/dist/application/get-book-by-isbn.d.ts.map +1 -0
  16. package/dist/application/get-book-by-isbn.js +42 -0
  17. package/dist/application/get-book-by-isbn.js.map +1 -0
  18. package/dist/application/get-book-detail.d.ts.map +1 -1
  19. package/dist/application/get-book-detail.js +16 -1
  20. package/dist/application/get-book-detail.js.map +1 -1
  21. package/dist/application/search-books.d.ts.map +1 -1
  22. package/dist/application/search-books.js +20 -0
  23. package/dist/application/search-books.js.map +1 -1
  24. package/dist/mcp/server.d.ts.map +1 -1
  25. package/dist/mcp/server.js +10 -0
  26. package/dist/mcp/server.js.map +1 -1
  27. package/dist/mcp/tools.d.ts +13 -0
  28. package/dist/mcp/tools.d.ts.map +1 -1
  29. package/dist/mcp/tools.js +16 -0
  30. package/dist/mcp/tools.js.map +1 -1
  31. package/package.json +3 -2
  32. package/src/adapters/calil.ts +57 -0
  33. package/src/adapters/openbd.ts +142 -0
  34. package/src/adapters/publishers/tatsu-zine.ts +7 -19
  35. package/src/application/get-book-by-isbn.ts +50 -0
  36. package/src/application/get-book-detail.ts +17 -1
  37. package/src/application/search-books.ts +20 -0
  38. package/src/mcp/server.ts +10 -0
  39. package/src/mcp/tools.ts +17 -0
  40. package/tests/fixtures/calil-book.html +987 -0
  41. package/tests/fixtures/openbd-response.json +110 -0
  42. package/tests/fixtures/tatsu-zine-detail-free.html +14 -12
  43. package/tests/unit/adapters/calil.test.ts +69 -0
  44. package/tests/unit/adapters/openbd.test.ts +185 -0
  45. package/tests/unit/application/get-book-by-isbn.test.ts +176 -0
@@ -3,6 +3,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
3
3
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
4
  import { searchBooks } from "../application/search-books.js";
5
5
  import { getBookDetail } from "../application/get-book-detail.js";
6
+ import { getBookByIsbn } from "../application/get-book-by-isbn.js";
6
7
  import { TOOLS } from "./tools.js";
7
8
  // --- 出力フォーマット ---
8
9
  const DRM_LABELS = {
@@ -66,6 +67,15 @@ export function createServer(publishers, deps) {
66
67
  content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
67
68
  };
68
69
  }
70
+ case "get_book_by_isbn": {
71
+ const isbn = args["isbn"];
72
+ if (typeof isbn !== "string")
73
+ throw new Error("isbn は必須です");
74
+ const book = await getBookByIsbn(isbn, publishers, deps);
75
+ return {
76
+ content: [{ type: "text", text: JSON.stringify(formatBook(book), null, 2) }],
77
+ };
78
+ }
69
79
  default:
70
80
  throw new Error(`未知のツール: ${name}`);
71
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -43,5 +43,18 @@ export declare const TOOLS: readonly [{
43
43
  readonly type: "object";
44
44
  readonly properties: {};
45
45
  };
46
+ }, {
47
+ readonly name: "get_book_by_isbn";
48
+ readonly description: string;
49
+ readonly inputSchema: {
50
+ readonly type: "object";
51
+ readonly required: readonly ["isbn"];
52
+ readonly properties: {
53
+ readonly isbn: {
54
+ readonly type: "string";
55
+ readonly description: "ISBN-13(ハイフンあり・なし両対応、例: 978-4-908686-20-7)";
56
+ };
57
+ };
58
+ };
46
59
  }];
47
60
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqDR,CAAC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsER,CAAC"}
package/dist/mcp/tools.js CHANGED
@@ -49,5 +49,21 @@ export const TOOLS = [
49
49
  properties: {},
50
50
  },
51
51
  },
52
+ {
53
+ name: "get_book_by_isbn",
54
+ description: "ISBNから書誌情報を取得します。" +
55
+ "openBDで出版社を特定し、可能であれば出版社サイトから詳細情報を取得します。" +
56
+ "出版社サイトから取得できない場合はopenBDのデータを返します。",
57
+ inputSchema: {
58
+ type: "object",
59
+ required: ["isbn"],
60
+ properties: {
61
+ isbn: {
62
+ type: "string",
63
+ description: "ISBN-13(ハイフンあり・なし両対応、例: 978-4-908686-20-7)",
64
+ },
65
+ },
66
+ },
67
+ },
52
68
  ];
53
69
  //# sourceMappingURL=tools.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB;QACE,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,kCAAkC;YAClC,mBAAmB;QACrB,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,UAAU;iBACxB;gBACD,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,WAAW;iBACzB;gBACD,SAAS,EAAE;oBACT,IAAI,EAAE,QAAQ;oBACd,WAAW,EACT,uCAAuC;wBACvC,mCAAmC;iBACtC;gBACD,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,kCAAkC;oBAC/C,OAAO,EAAE,EAAE;iBACZ;aACF;SACF;KACF;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,6BAA6B;QAC1C,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,CAAC,KAAK,CAAC;YACjB,UAAU,EAAE;gBACV,GAAG,EAAE;oBACH,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,yBAAyB;iBACvC;aACF;SACF;KACF;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,uBAAuB;QACpC,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,EAAE;SACf;KACF;CACO,CAAC"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB;QACE,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,kCAAkC;YAClC,mBAAmB;QACrB,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,UAAU;iBACxB;gBACD,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,WAAW;iBACzB;gBACD,SAAS,EAAE;oBACT,IAAI,EAAE,QAAQ;oBACd,WAAW,EACT,uCAAuC;wBACvC,mCAAmC;iBACtC;gBACD,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,kCAAkC;oBAC/C,OAAO,EAAE,EAAE;iBACZ;aACF;SACF;KACF;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,6BAA6B;QAC1C,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,CAAC,KAAK,CAAC;YACjB,UAAU,EAAE;gBACV,GAAG,EAAE;oBACH,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,yBAAyB;iBACvC;aACF;SACF;KACF;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,uBAAuB;QACpC,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,EAAE;SACf;KACF;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,WAAW,EACT,mBAAmB;YACnB,0CAA0C;YAC1C,mCAAmC;QACrC,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,CAAC,MAAM,CAAC;YAClB,UAAU,EAAE;gBACV,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,4CAA4C;iBAC1D;aACF;SACF;KACF;CACO,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zonuexe/techbook-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "MCP server for searching Japanese technical book bibliographic information",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
@@ -8,11 +8,12 @@
8
8
  ".": "./dist/main.js"
9
9
  },
10
10
  "bin": {
11
- "techbook-mcp": "./dist/main.js"
11
+ "techbook-mcp": "dist/main.js"
12
12
  },
13
13
  "scripts": {
14
14
  "build": "tsc",
15
15
  "lint": "oxlint --config .oxlintrc.json src/",
16
+ "prepublishOnly": "npm run build",
16
17
  "test": "node --import tsx/esm --test 'tests/**/*.test.ts'",
17
18
  "test:watch": "node --import tsx/esm --test --watch 'tests/**/*.test.ts'"
18
19
  },
@@ -0,0 +1,57 @@
1
+ import type { PublisherDeps } from "../domain/publisher.js";
2
+ import type { BookRecord } from "../domain/book.js";
3
+ import { fetchText } from "./publishers/base.js";
4
+
5
+ export const CALIL_BASE_URL = "https://calil.jp";
6
+
7
+ /**
8
+ * カーリルの書籍詳細ページ (/book/{isbn}) から書誌情報を取得する。
9
+ * openBD に存在しない書籍(廃業出版社など)のフォールバックとして使用する。
10
+ * @returns 書誌情報が見つかれば BookRecord、ページが存在しなければ null。
11
+ */
12
+ export async function fetchCalilBook(
13
+ isbn: string,
14
+ deps: PublisherDeps,
15
+ ): Promise<BookRecord | null> {
16
+ const url = `${CALIL_BASE_URL}/book/${isbn}`;
17
+ let html: string;
18
+ try {
19
+ html = await fetchText(url, deps);
20
+ } catch {
21
+ return null;
22
+ }
23
+
24
+ const doc = deps.parser.parse(html);
25
+
26
+ const title = doc.selectOne("h1.title[itemprop='name']")?.text().trim();
27
+ if (!title) return null;
28
+
29
+ // 著者: div.author 内の <a> テキストを収集する
30
+ // <div class="author" itemprop="author">
31
+ // <a href="/search?q=author:...">WebビジネスPHP研究部会</a><span>(著)</span>
32
+ // </div>
33
+ const authorLinks = doc.select("div[itemprop='author'] a");
34
+ const authors = authorLinks.map(el => el.text().trim()).filter(Boolean);
35
+
36
+ const publisher = doc.selectOne("span[itemprop='publisher']")?.text().trim() || undefined;
37
+
38
+ // "(2002-02-01)" → "2002-02-01"
39
+ const rawDate = doc.selectOne("span[itemprop='datePublished']")?.text().trim();
40
+ const publishedAt = rawDate ? rawDate.replace(/^\(|\)$/g, "").trim() || undefined : undefined;
41
+
42
+ // ISBN-13: <span itemprop="isbn">ISBN-13:</span> 9784901676038 の形式
43
+ const isbn13Match = html.match(/ISBN-13:[^<]*<\/span>[^<\d]*(97[89]\d{10})/);
44
+ const isbn13 = isbn13Match?.[1] ?? isbn;
45
+
46
+ const coverImageUrl = doc.selectOne("img[itemprop='image']")?.attr("src") || undefined;
47
+
48
+ return {
49
+ title,
50
+ authors,
51
+ publisher: publisher ?? "",
52
+ isbn: isbn13,
53
+ publishedAt,
54
+ url,
55
+ coverImageUrl,
56
+ };
57
+ }
@@ -0,0 +1,142 @@
1
+ import type { PublisherDeps } from "../domain/publisher.js";
2
+ import type { BookRecord } from "../domain/book.js";
3
+ import { fetchText } from "./publishers/base.js";
4
+
5
+ const OPENBD_API_URL = "https://api.openbd.jp/v1/get";
6
+
7
+ // --- 型定義 ---
8
+
9
+ interface OpenBDSummary {
10
+ isbn: string;
11
+ title: string;
12
+ publisher: string;
13
+ pubdate: string; // "YYYYMMDD"
14
+ cover: string; // "https://cover.openbd.jp/{isbn}.jpg"
15
+ author: string;
16
+ }
17
+
18
+ interface OpenBDTextContent {
19
+ TextType: string; // "02": 短い説明, "03": 説明文, "04": 目次
20
+ ContentAudience: string;
21
+ Text: string;
22
+ }
23
+
24
+ interface OpenBDPrice {
25
+ PriceType: string; // "03": 税込定価
26
+ PriceAmount: string;
27
+ CurrencyCode: string;
28
+ }
29
+
30
+ interface OpenBDHanmoto {
31
+ isbn: string;
32
+ storelink?: string;
33
+ [key: string]: unknown;
34
+ }
35
+
36
+ export interface OpenBDEntry {
37
+ summary: OpenBDSummary;
38
+ hanmoto?: OpenBDHanmoto;
39
+ onix: {
40
+ CollateralDetail?: {
41
+ TextContent?: OpenBDTextContent[];
42
+ };
43
+ ProductSupply?: {
44
+ SupplyDetail?: {
45
+ Price?: OpenBDPrice[];
46
+ };
47
+ };
48
+ };
49
+ }
50
+
51
+ // --- ユーティリティ ---
52
+
53
+ function parsePubDate(pubdate: string): string | undefined {
54
+ if (!pubdate || pubdate.length < 8) return undefined;
55
+ return `${pubdate.slice(0, 4)}-${pubdate.slice(4, 6)}-${pubdate.slice(6, 8)}`;
56
+ }
57
+
58
+ function findTextByType(entry: OpenBDEntry, ...types: string[]): string | undefined {
59
+ const texts = entry.onix.CollateralDetail?.TextContent;
60
+ if (!texts) return undefined;
61
+ for (const type of types) {
62
+ const found = texts.find(t => t.TextType === type);
63
+ if (found?.Text) return found.Text;
64
+ }
65
+ return undefined;
66
+ }
67
+
68
+ function getTaxIncludedPrice(entry: OpenBDEntry): number | undefined {
69
+ const prices = entry.onix.ProductSupply?.SupplyDetail?.Price;
70
+ if (!prices) return undefined;
71
+ // PriceType "03" = 税込定価
72
+ const price = prices.find(p => p.PriceType === "03");
73
+ if (!price) return undefined;
74
+ const amount = parseInt(price.PriceAmount, 10);
75
+ return isNaN(amount) ? undefined : amount;
76
+ }
77
+
78
+ // --- 公開API ---
79
+
80
+ /**
81
+ * openBD API から複数ISBNの書誌情報を一括取得する。
82
+ * @returns ISBNをキーとするMapを返す。該当なし・取得失敗のISBNは含まれない。
83
+ */
84
+ export async function fetchOpenBDBooks(
85
+ isbns: string[],
86
+ deps: PublisherDeps,
87
+ ): Promise<Map<string, OpenBDEntry>> {
88
+ if (isbns.length === 0) return new Map();
89
+
90
+ const url = `${OPENBD_API_URL}?isbn=${isbns.join(",")}`;
91
+ const text = await fetchText(url, deps);
92
+ const data: (OpenBDEntry | null)[] = JSON.parse(text);
93
+
94
+ const result = new Map<string, OpenBDEntry>();
95
+ for (let i = 0; i < isbns.length; i++) {
96
+ const entry = data[i];
97
+ if (entry !== null && entry !== undefined) {
98
+ result.set(isbns[i], entry);
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * openBD エントリを BookRecord に変換する。
106
+ * 出版社サイトから取得できない場合のフォールバック用。
107
+ * url には hanmoto.storelink を使用し、なければ openBD API URL を使用する。
108
+ */
109
+ export function openBDEntryToBookRecord(entry: OpenBDEntry): BookRecord {
110
+ const { summary } = entry;
111
+ const storelink = entry.hanmoto?.storelink;
112
+
113
+ const authors = summary.author
114
+ ? summary.author.split(/[\//、,,]/).map(a => a.trim()).filter(Boolean)
115
+ : [];
116
+
117
+ return {
118
+ title: summary.title,
119
+ authors,
120
+ publisher: summary.publisher,
121
+ isbn: summary.isbn,
122
+ publishedAt: parsePubDate(summary.pubdate),
123
+ url: storelink ?? `https://api.openbd.jp/v1/get?isbn=${summary.isbn}`,
124
+ price: getTaxIncludedPrice(entry),
125
+ coverImageUrl: summary.cover || undefined,
126
+ description: findTextByType(entry, "03", "02"),
127
+ };
128
+ }
129
+
130
+ /**
131
+ * openBD の書誌情報で BookRecord の欠損フィールドを補完する。
132
+ * 既存のフィールドは上書きしない。
133
+ */
134
+ export function enrichWithOpenBD(book: BookRecord, entry: OpenBDEntry): BookRecord {
135
+ return {
136
+ ...book,
137
+ publishedAt: book.publishedAt ?? parsePubDate(entry.summary.pubdate),
138
+ price: book.price ?? getTaxIncludedPrice(entry),
139
+ coverImageUrl: book.coverImageUrl ?? (entry.summary.cover || undefined),
140
+ description: book.description ?? findTextByType(entry, "03", "02"),
141
+ };
142
+ }
@@ -118,25 +118,13 @@ export const tatsuZineAdapter: PublisherAdapter = {
118
118
  const imgSrc = imgEl?.attr("src");
119
119
  const coverImageUrl = imgSrc ? resolveUrl(BASE_URL, imgSrc) : undefined;
120
120
 
121
- // 著者・価格: dl > dd 構造を優先し、なければ p 要素を走査
122
- let authors: string[] = [];
123
- let price: number | undefined;
124
-
125
- const candidates = [
126
- ...doc.select("dd"),
127
- ...doc.select("p"),
128
- ];
129
-
130
- for (const el of candidates) {
131
- const text = el.text().trim();
132
- if (!authors.length && /[((][著訳監編]/.test(text)) {
133
- authors = parseAuthors(text);
134
- }
135
- if (price === undefined && /^\d/.test(text) && /円/.test(text)) {
136
- price = parsePrice(text);
137
- }
138
- if (authors.length && price !== undefined) break;
139
- }
121
+ // 著者: <p itemprop="author"> を優先使用
122
+ const authorText = doc.selectOne("p[itemprop='author']")?.text().trim() ?? "";
123
+ const authors = authorText ? parseAuthors(authorText) : [];
124
+
125
+ // 価格: <span itemprop="price"> を優先使用
126
+ const priceText = doc.selectOne("span[itemprop='price']")?.text().trim() ?? "";
127
+ const price = priceText ? parsePrice(priceText) : undefined;
140
128
 
141
129
  // 達人出版会は全書籍で購入者情報を各ページに印字 (ソーシャルDRM)
142
130
  const ebookStores: EbookStore[] = [{ name: "達人出版会", url, drm: "social" }];
@@ -0,0 +1,50 @@
1
+ import type { BookRecord } from "../domain/book.js";
2
+ import type { PublisherAdapter, PublisherDeps } from "../domain/publisher.js";
3
+ import { checkRobotsTxt } from "../adapters/publishers/base.js";
4
+ import { fetchOpenBDBooks, openBDEntryToBookRecord } from "../adapters/openbd.js";
5
+ import { fetchCalilBook } from "../adapters/calil.js";
6
+
7
+ /**
8
+ * ISBNから書籍情報を取得する。
9
+ *
10
+ * 1. openBD で書誌情報と出版社ストアリンクを取得する
11
+ * 2. ストアリンクが既知アダプターと一致する場合は出版社サイトから詳細取得を試みる
12
+ * 3. 取得できない場合は openBD データをそのまま返す
13
+ * 4. openBD にも存在しない場合はカーリルから書誌情報を取得する(廃業出版社など)
14
+ */
15
+ export async function getBookByIsbn(
16
+ isbn: string,
17
+ publishers: readonly PublisherAdapter[],
18
+ deps: PublisherDeps,
19
+ ): Promise<BookRecord> {
20
+ const normalizedIsbn = isbn.replace(/-/g, "");
21
+
22
+ const openBDMap = await fetchOpenBDBooks([normalizedIsbn], deps);
23
+ const entry = openBDMap.get(normalizedIsbn);
24
+
25
+ if (!entry) {
26
+ // openBD にない場合はカーリルをフォールバックとして試みる(廃業出版社など)
27
+ const calilBook = await fetchCalilBook(normalizedIsbn, deps);
28
+ if (calilBook) return calilBook;
29
+ throw new Error(`書誌情報が見つかりません: ${isbn}`);
30
+ }
31
+
32
+ // hanmoto.storelink が既知アダプターの baseUrl と前方一致する場合は
33
+ // 出版社サイトから詳細取得を試みる
34
+ const storelink = entry.hanmoto?.storelink;
35
+ if (storelink) {
36
+ const publisher = publishers.find(p => storelink.startsWith(p.baseUrl));
37
+ if (publisher) {
38
+ const allowed = await checkRobotsTxt(storelink, deps);
39
+ if (allowed) {
40
+ try {
41
+ return await publisher.getDetail(storelink, deps);
42
+ } catch {
43
+ // 出版社サイトからの取得失敗は無視して openBD データで返す
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ return openBDEntryToBookRecord(entry);
50
+ }
@@ -1,6 +1,7 @@
1
1
  import type { BookRecord } from "../domain/book.js";
2
2
  import type { PublisherAdapter, PublisherDeps } from "../domain/publisher.js";
3
3
  import { checkRobotsTxt } from "../adapters/publishers/base.js";
4
+ import { fetchOpenBDBooks, enrichWithOpenBD } from "../adapters/openbd.js";
4
5
 
5
6
  export async function getBookDetail(
6
7
  url: string,
@@ -20,5 +21,20 @@ export async function getBookDetail(
20
21
  throw new Error(`robots.txt によりアクセスが禁止されています: ${url}`);
21
22
  }
22
23
 
23
- return publisher.getDetail(url, deps);
24
+ const book = await publisher.getDetail(url, deps);
25
+
26
+ // ISBNが特定できる場合はopenBDで欠損フィールドを補完
27
+ if (book.isbn !== undefined) {
28
+ try {
29
+ const openBDMap = await fetchOpenBDBooks([book.isbn], deps);
30
+ const entry = openBDMap.get(book.isbn);
31
+ if (entry !== undefined) {
32
+ return enrichWithOpenBD(book, entry);
33
+ }
34
+ } catch {
35
+ // openBD の取得失敗は無視して出版社から取得できた情報を返す
36
+ }
37
+ }
38
+
39
+ return book;
24
40
  }
@@ -1,6 +1,7 @@
1
1
  import type { BookRecord, SearchQuery } from "../domain/book.js";
2
2
  import type { PublisherAdapter, PublisherDeps } from "../domain/publisher.js";
3
3
  import { checkRobotsTxt } from "../adapters/publishers/base.js";
4
+ import { fetchOpenBDBooks, enrichWithOpenBD } from "../adapters/openbd.js";
4
5
 
5
6
  export interface SearchBooksResult {
6
7
  books: BookRecord[];
@@ -40,5 +41,24 @@ export async function searchBooks(
40
41
  }
41
42
  }
42
43
 
44
+ // ISBNが特定できる書籍をopenBDで一括補完
45
+ const isbns = books.map(b => b.isbn).filter((isbn): isbn is string => isbn !== undefined);
46
+ if (isbns.length > 0) {
47
+ try {
48
+ const openBDMap = await fetchOpenBDBooks(isbns, deps);
49
+ for (let i = 0; i < books.length; i++) {
50
+ const isbn = books[i].isbn;
51
+ if (isbn !== undefined) {
52
+ const entry = openBDMap.get(isbn);
53
+ if (entry !== undefined) {
54
+ books[i] = enrichWithOpenBD(books[i], entry);
55
+ }
56
+ }
57
+ }
58
+ } catch {
59
+ // openBD の取得失敗は無視して出版社から取得できた情報を返す
60
+ }
61
+ }
62
+
43
63
  return { books, errors };
44
64
  }
package/src/mcp/server.ts CHANGED
@@ -8,6 +8,7 @@ import type { PublisherAdapter, PublisherDeps } from "../domain/publisher.js";
8
8
  import type { BookRecord, EbookStore, DrmType, SearchQuery } from "../domain/book.js";
9
9
  import { searchBooks } from "../application/search-books.js";
10
10
  import { getBookDetail } from "../application/get-book-detail.js";
11
+ import { getBookByIsbn } from "../application/get-book-by-isbn.js";
11
12
  import { TOOLS } from "./tools.js";
12
13
 
13
14
  // --- 出力フォーマット ---
@@ -85,6 +86,15 @@ export function createServer(
85
86
  };
86
87
  }
87
88
 
89
+ case "get_book_by_isbn": {
90
+ const isbn = args["isbn"];
91
+ if (typeof isbn !== "string") throw new Error("isbn は必須です");
92
+ const book = await getBookByIsbn(isbn, publishers, deps);
93
+ return {
94
+ content: [{ type: "text", text: JSON.stringify(formatBook(book), null, 2) }],
95
+ };
96
+ }
97
+
88
98
  default:
89
99
  throw new Error(`未知のツール: ${name}`);
90
100
  }
package/src/mcp/tools.ts CHANGED
@@ -51,4 +51,21 @@ export const TOOLS = [
51
51
  properties: {},
52
52
  },
53
53
  },
54
+ {
55
+ name: "get_book_by_isbn",
56
+ description:
57
+ "ISBNから書誌情報を取得します。" +
58
+ "openBDで出版社を特定し、可能であれば出版社サイトから詳細情報を取得します。" +
59
+ "出版社サイトから取得できない場合はopenBDのデータを返します。",
60
+ inputSchema: {
61
+ type: "object",
62
+ required: ["isbn"],
63
+ properties: {
64
+ isbn: {
65
+ type: "string",
66
+ description: "ISBN-13(ハイフンあり・なし両対応、例: 978-4-908686-20-7)",
67
+ },
68
+ },
69
+ },
70
+ },
54
71
  ] as const;