@zonuexe/techbook-mcp 0.2.2 → 0.2.4

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 (151) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/README.md +39 -20
  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/google-books.d.ts +4 -0
  12. package/dist/adapters/publishers/google-books.d.ts.map +1 -0
  13. package/dist/adapters/publishers/google-books.js +75 -0
  14. package/dist/adapters/publishers/google-books.js.map +1 -0
  15. package/dist/adapters/publishers/isbn-publisher-codes.d.ts +21 -0
  16. package/dist/adapters/publishers/isbn-publisher-codes.d.ts.map +1 -0
  17. package/dist/adapters/publishers/isbn-publisher-codes.js +49 -0
  18. package/dist/adapters/publishers/isbn-publisher-codes.js.map +1 -0
  19. package/dist/adapters/publishers/juse-p.d.ts +3 -0
  20. package/dist/adapters/publishers/juse-p.d.ts.map +1 -0
  21. package/dist/adapters/publishers/juse-p.js +110 -0
  22. package/dist/adapters/publishers/juse-p.js.map +1 -0
  23. package/dist/adapters/publishers/registry.d.ts.map +1 -1
  24. package/dist/adapters/publishers/registry.js +4 -0
  25. package/dist/adapters/publishers/registry.js.map +1 -1
  26. package/dist/adapters/publishers/tatsu-zine.d.ts.map +1 -1
  27. package/dist/adapters/publishers/tatsu-zine.js +6 -18
  28. package/dist/adapters/publishers/tatsu-zine.js.map +1 -1
  29. package/dist/application/get-book-by-isbn.d.ts +13 -0
  30. package/dist/application/get-book-by-isbn.d.ts.map +1 -0
  31. package/dist/application/get-book-by-isbn.js +61 -0
  32. package/dist/application/get-book-by-isbn.js.map +1 -0
  33. package/dist/application/get-book-detail.d.ts.map +1 -1
  34. package/dist/application/get-book-detail.js +16 -1
  35. package/dist/application/get-book-detail.js.map +1 -1
  36. package/dist/application/search-books.d.ts.map +1 -1
  37. package/dist/application/search-books.js +20 -0
  38. package/dist/application/search-books.js.map +1 -1
  39. package/dist/config/credentials.d.ts +8 -0
  40. package/dist/config/credentials.d.ts.map +1 -0
  41. package/dist/config/credentials.js +32 -0
  42. package/dist/config/credentials.js.map +1 -0
  43. package/dist/main.js +15 -1
  44. package/dist/main.js.map +1 -1
  45. package/dist/mcp/server.d.ts.map +1 -1
  46. package/dist/mcp/server.js +10 -0
  47. package/dist/mcp/server.js.map +1 -1
  48. package/dist/mcp/tools.d.ts +13 -0
  49. package/dist/mcp/tools.d.ts.map +1 -1
  50. package/dist/mcp/tools.js +16 -0
  51. package/dist/mcp/tools.js.map +1 -1
  52. package/dist/setup.d.ts +2 -0
  53. package/dist/setup.d.ts.map +1 -0
  54. package/dist/setup.js +43 -0
  55. package/dist/setup.js.map +1 -0
  56. package/flake.lock +61 -0
  57. package/package.json +1 -1
  58. package/.claude/settings.local.json +0 -36
  59. package/.codex/skills/techbook-mcp-release-prep/SKILL.md +0 -105
  60. package/.github/workflows/test.yml +0 -72
  61. package/.oxlintrc.json +0 -12
  62. package/AGENTS.md +0 -100
  63. package/deno.json +0 -3
  64. package/src/adapters/cache/memory-cache.ts +0 -31
  65. package/src/adapters/cache/null-cache.ts +0 -8
  66. package/src/adapters/html/cheerio-parser.ts +0 -50
  67. package/src/adapters/http/fetch-client.ts +0 -47
  68. package/src/adapters/http/mock-client.ts +0 -77
  69. package/src/adapters/publishers/base.ts +0 -279
  70. package/src/adapters/publishers/book-tech.ts +0 -117
  71. package/src/adapters/publishers/born-digital.ts +0 -143
  72. package/src/adapters/publishers/coronasha.ts +0 -139
  73. package/src/adapters/publishers/gihyo.ts +0 -120
  74. package/src/adapters/publishers/impress.ts +0 -103
  75. package/src/adapters/publishers/lambdanote.ts +0 -146
  76. package/src/adapters/publishers/manatee.ts +0 -113
  77. package/src/adapters/publishers/maruzen-publishing.ts +0 -129
  78. package/src/adapters/publishers/optronics.ts +0 -113
  79. package/src/adapters/publishers/oreilly-japan.ts +0 -133
  80. package/src/adapters/publishers/peaks.ts +0 -98
  81. package/src/adapters/publishers/personal-media.ts +0 -168
  82. package/src/adapters/publishers/registry.ts +0 -38
  83. package/src/adapters/publishers/rutles.ts +0 -149
  84. package/src/adapters/publishers/saiensu.ts +0 -136
  85. package/src/adapters/publishers/seshop.ts +0 -121
  86. package/src/adapters/publishers/tatsu-zine.ts +0 -154
  87. package/src/adapters/publishers/techbookfest.ts +0 -179
  88. package/src/application/get-book-detail.ts +0 -24
  89. package/src/application/search-books.ts +0 -44
  90. package/src/domain/book.ts +0 -35
  91. package/src/domain/publisher.ts +0 -18
  92. package/src/main.ts +0 -14
  93. package/src/mcp/server.ts +0 -103
  94. package/src/mcp/tools.ts +0 -54
  95. package/src/ports/cache.ts +0 -5
  96. package/src/ports/html-parser.ts +0 -15
  97. package/src/ports/http.ts +0 -17
  98. package/tests/fixtures/book-tech-detail.html +0 -51
  99. package/tests/fixtures/book-tech-search.html +0 -91
  100. package/tests/fixtures/born-digital-detail.html +0 -62
  101. package/tests/fixtures/born-digital-search.html +0 -51
  102. package/tests/fixtures/coronasha-detail.html +0 -41
  103. package/tests/fixtures/coronasha-search.html +0 -61
  104. package/tests/fixtures/gihyo-detail.html +0 -42
  105. package/tests/fixtures/gihyo-search.json +0 -54
  106. package/tests/fixtures/impress-detail-epub.html +0 -746
  107. package/tests/fixtures/impress-detail-social.html +0 -689
  108. package/tests/fixtures/lambdanote-search.html +0 -66
  109. package/tests/fixtures/manatee-detail.html +0 -53
  110. package/tests/fixtures/manatee-search.html +0 -59
  111. package/tests/fixtures/maruzen-detail.html +0 -51
  112. package/tests/fixtures/maruzen-search.html +0 -60
  113. package/tests/fixtures/optronics-detail.html +0 -30
  114. package/tests/fixtures/optronics-search.html +0 -75
  115. package/tests/fixtures/oreilly-detail.html +0 -52
  116. package/tests/fixtures/oreilly-ebook-list.html +0 -53
  117. package/tests/fixtures/peaks-detail.html +0 -39
  118. package/tests/fixtures/peaks-top.html +0 -50
  119. package/tests/fixtures/personal-media-detail.html +0 -32
  120. package/tests/fixtures/personal-media-search.html +0 -39
  121. package/tests/fixtures/rutles-detail.html +0 -32
  122. package/tests/fixtures/rutles-search.html +0 -62
  123. package/tests/fixtures/saiensu-detail.html +0 -41
  124. package/tests/fixtures/saiensu-search.html +0 -65
  125. package/tests/fixtures/seshop-detail.html +0 -45
  126. package/tests/fixtures/seshop-search.html +0 -58
  127. package/tests/fixtures/tatsu-zine-detail-free.html +0 -22
  128. package/tests/fixtures/tatsu-zine-search.html +0 -40
  129. package/tests/fixtures/techbookfest-search.json +0 -73
  130. package/tests/unit/adapters/base.test.ts +0 -441
  131. package/tests/unit/adapters/publishers/book-tech.test.ts +0 -186
  132. package/tests/unit/adapters/publishers/born-digital.test.ts +0 -194
  133. package/tests/unit/adapters/publishers/coronasha.test.ts +0 -207
  134. package/tests/unit/adapters/publishers/gihyo.test.ts +0 -137
  135. package/tests/unit/adapters/publishers/impress.test.ts +0 -129
  136. package/tests/unit/adapters/publishers/lambdanote.test.ts +0 -85
  137. package/tests/unit/adapters/publishers/manatee.test.ts +0 -165
  138. package/tests/unit/adapters/publishers/maruzen-publishing.test.ts +0 -179
  139. package/tests/unit/adapters/publishers/optronics.test.ts +0 -208
  140. package/tests/unit/adapters/publishers/oreilly-japan.test.ts +0 -194
  141. package/tests/unit/adapters/publishers/peaks.test.ts +0 -177
  142. package/tests/unit/adapters/publishers/personal-media.test.ts +0 -199
  143. package/tests/unit/adapters/publishers/rutles.test.ts +0 -173
  144. package/tests/unit/adapters/publishers/saiensu.test.ts +0 -169
  145. package/tests/unit/adapters/publishers/seshop.test.ts +0 -174
  146. package/tests/unit/adapters/publishers/tatsu-zine.test.ts +0 -172
  147. package/tests/unit/adapters/publishers/techbookfest.test.ts +0 -94
  148. package/tests/unit/adapters/registry.test.ts +0 -37
  149. package/tests/unit/application/get-book-detail.test.ts +0 -102
  150. package/tests/unit/application/search-books.test.ts +0 -137
  151. package/tsconfig.json +0 -17
@@ -1,179 +0,0 @@
1
- import type { PublisherAdapter, PublisherDeps } from "../../domain/publisher.js";
2
- import type { BookRecord, SearchQuery } from "../../domain/book.js";
3
- import { fetchText, parseJapanesePrice, extractEbookStoresFromDoc } from "./base.js";
4
-
5
- const BASE_URL = "https://techbookfest.org";
6
- const GRAPHQL_URL = `${BASE_URL}/api/graphql`;
7
- const XSRF_CACHE_KEY = "techbookfest:xsrf-token";
8
- const XSRF_TTL_SECONDS = 3600;
9
-
10
- const DEFAULT_HEADERS = {
11
- "User-Agent": "techbook-mcp/0.1.0 (+https://github.com/zonuexe/techbook-mcp; bibliographic search bot)",
12
- "Accept": "application/json",
13
- };
14
-
15
- // node.product は ProductInfoSearchResult のインラインフラグメント経由でアクセスする
16
- const SEARCH_QUERY = `
17
- query MarketSearchQuery($query: String!, $first: Int!) {
18
- searchProducts(first: $first, query: $query, orderBy: CREATED_AT_DESC) {
19
- pageInfo { hasNextPage endCursor }
20
- edges {
21
- node {
22
- ... on ProductInfoSearchResult {
23
- product {
24
- id
25
- databaseID
26
- name
27
- description
28
- organization { name }
29
- coverImage { url }
30
- ebookVariant: productVariant(kind: MARKET_EBOOK) { price }
31
- firstPublishedAt
32
- status
33
- }
34
- }
35
- }
36
- }
37
- }
38
- }
39
- `.trim();
40
-
41
- interface TechbookfestProduct {
42
- id: string;
43
- databaseID: string;
44
- name: string;
45
- description: string | null;
46
- organization: { name: string } | null;
47
- coverImage: { url: string } | null;
48
- ebookVariant: { price: number } | null;
49
- firstPublishedAt: string | null;
50
- status: string;
51
- }
52
-
53
- interface GraphQLResponse {
54
- data?: {
55
- searchProducts?: {
56
- edges: Array<{ node: { product?: TechbookfestProduct } }>;
57
- };
58
- };
59
- }
60
-
61
- /**
62
- * トップページの Set-Cookie から XSRF-TOKEN を取得してキャッシュする。
63
- * 技術書典の GraphQL API は XSRF トークンを Cookie + X-XSRF-TOKEN ヘッダーの
64
- * ダブルサブミット方式で検証する。
65
- */
66
- async function fetchXsrfToken(deps: PublisherDeps): Promise<string> {
67
- const cached = await deps.cache.get(XSRF_CACHE_KEY);
68
- if (cached !== null) return cached;
69
-
70
- const response = await deps.http.get(BASE_URL, { headers: DEFAULT_HEADERS });
71
- const setCookie = response.header("set-cookie") ?? "";
72
-
73
- // Set-Cookie: XSRF-TOKEN=<urlencoded-value>; Path=/; Secure; SameSite=Lax
74
- const match = setCookie.match(/XSRF-TOKEN=([^;,\s]+)/);
75
- if (!match) throw new Error("techbookfest: XSRF-TOKEN not found in Set-Cookie");
76
-
77
- const token = decodeURIComponent(match[1]);
78
- await deps.cache.set(XSRF_CACHE_KEY, token, XSRF_TTL_SECONDS);
79
- return token;
80
- }
81
-
82
- function productToBookRecord(product: TechbookfestProduct): BookRecord {
83
- const url = `${BASE_URL}/product/${product.databaseID}`;
84
- const publishedAt = product.firstPublishedAt
85
- ? product.firstPublishedAt.slice(0, 10)
86
- : undefined;
87
-
88
- return {
89
- title: product.name,
90
- authors: product.organization ? [product.organization.name] : [],
91
- publisher: "技術書典",
92
- url,
93
- price: product.ebookVariant?.price,
94
- description: product.description ?? undefined,
95
- coverImageUrl: product.coverImage?.url,
96
- publishedAt,
97
- ebookStores: [{ name: "技術書典", url, drm: "free" }],
98
- };
99
- }
100
-
101
- export const techbookfestAdapter: PublisherAdapter = {
102
- id: "techbookfest",
103
- name: "技術書典オンラインマーケット",
104
- baseUrl: BASE_URL,
105
-
106
- async search(query: SearchQuery, deps: PublisherDeps): Promise<BookRecord[]> {
107
- const word = [query.title, query.author].filter(Boolean).join(" ");
108
- if (!word) return [];
109
-
110
- const limit = query.limit ?? 10;
111
- const xsrf = await fetchXsrfToken(deps);
112
-
113
- const body = JSON.stringify({
114
- operationName: "MarketSearchQuery",
115
- query: SEARCH_QUERY,
116
- variables: { query: word, first: limit },
117
- });
118
-
119
- const response = await deps.http.post(GRAPHQL_URL, body, {
120
- headers: {
121
- ...DEFAULT_HEADERS,
122
- "Content-Type": "application/json",
123
- "Cookie": `XSRF-TOKEN=${encodeURIComponent(xsrf)}`,
124
- "X-XSRF-TOKEN": xsrf,
125
- },
126
- });
127
-
128
- if (response.status !== 200) {
129
- throw new Error(`HTTP ${response.status}: ${GRAPHQL_URL}`);
130
- }
131
-
132
- const json = JSON.parse(await response.text()) as GraphQLResponse;
133
- const edges = json.data?.searchProducts?.edges ?? [];
134
-
135
- return edges
136
- .map(e => e.node.product)
137
- .filter((p): p is TechbookfestProduct => p != null)
138
- .slice(0, limit)
139
- .map(productToBookRecord);
140
- },
141
-
142
- async getDetail(url: string, deps: PublisherDeps): Promise<BookRecord> {
143
- const html = await fetchText(url, deps);
144
- const doc = deps.parser.parse(html);
145
-
146
- const title =
147
- doc.selectOne('meta[property="og:title"]')?.attr("content") ??
148
- doc.selectOne("h1")?.text() ??
149
- "";
150
-
151
- const description =
152
- doc.selectOne('meta[property="og:description"]')?.attr("content") ??
153
- doc.selectOne('meta[name="description"]')?.attr("content") ??
154
- undefined;
155
-
156
- const coverImageUrl =
157
- doc.selectOne('meta[property="og:image"]')?.attr("content") ??
158
- undefined;
159
-
160
- const priceText = doc.selectOne('[class*="price"]')?.text();
161
- const price = priceText ? parseJapanesePrice(priceText) : undefined;
162
-
163
- const ebookStores = extractEbookStoresFromDoc(doc);
164
- if (!ebookStores.some(s => s.name === "技術書典")) {
165
- ebookStores.unshift({ name: "技術書典", url, drm: "free" });
166
- }
167
-
168
- return {
169
- title,
170
- authors: [],
171
- publisher: "技術書典",
172
- url,
173
- price,
174
- description,
175
- coverImageUrl,
176
- ebookStores,
177
- };
178
- },
179
- };
@@ -1,24 +0,0 @@
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
-
5
- export async function getBookDetail(
6
- url: string,
7
- publishers: readonly PublisherAdapter[],
8
- deps: PublisherDeps,
9
- ): Promise<BookRecord> {
10
- const publisher = publishers.find(p => url.startsWith(p.baseUrl));
11
- if (!publisher) {
12
- throw new Error(
13
- `このURLに対応する出版社アダプターがありません: ${url}\n` +
14
- `対応URL: ${publishers.map(p => p.baseUrl).join(", ")}`,
15
- );
16
- }
17
-
18
- const allowed = await checkRobotsTxt(url, deps);
19
- if (!allowed) {
20
- throw new Error(`robots.txt によりアクセスが禁止されています: ${url}`);
21
- }
22
-
23
- return publisher.getDetail(url, deps);
24
- }
@@ -1,44 +0,0 @@
1
- import type { BookRecord, SearchQuery } from "../domain/book.js";
2
- import type { PublisherAdapter, PublisherDeps } from "../domain/publisher.js";
3
- import { checkRobotsTxt } from "../adapters/publishers/base.js";
4
-
5
- export interface SearchBooksResult {
6
- books: BookRecord[];
7
- errors: Array<{ publisherId: string; message: string }>;
8
- }
9
-
10
- export async function searchBooks(
11
- query: SearchQuery,
12
- publishers: readonly PublisherAdapter[],
13
- deps: PublisherDeps,
14
- ): Promise<SearchBooksResult> {
15
- const targets = query.publisherId
16
- ? publishers.filter(p => p.id === query.publisherId)
17
- : publishers;
18
-
19
- const results = await Promise.allSettled(
20
- targets.map(async (p) => {
21
- const allowed = await checkRobotsTxt(p.baseUrl, deps);
22
- if (!allowed) throw new Error(`robots.txt によりアクセスが禁止されています: ${p.baseUrl}`);
23
- return p.search(query, deps);
24
- }),
25
- );
26
-
27
- const books: BookRecord[] = [];
28
- const errors: Array<{ publisherId: string; message: string }> = [];
29
-
30
- for (let i = 0; i < results.length; i++) {
31
- const result = results[i];
32
- const publisher = targets[i];
33
- if (result.status === "fulfilled") {
34
- books.push(...result.value);
35
- } else {
36
- const message = result.reason instanceof Error
37
- ? result.reason.message
38
- : String(result.reason);
39
- errors.push({ publisherId: publisher.id, message });
40
- }
41
- }
42
-
43
- return { books, errors };
44
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * - `"free"` : 技術的DRMなし (DRM-free PDF/EPUB)
3
- * - `"social"` : ソーシャルDRM (購入者情報を透かし刻印、技術的制限なし)
4
- * - `"password_pdf"` : パスワード認証付きPDF (標準PDFビューアで閲覧可、パスワード必須)
5
- * - `"drm"` : 技術的DRM付き (専用ビューアー必須)
6
- */
7
- export type DrmType = "free" | "social" | "password_pdf" | "drm";
8
-
9
- export interface EbookStore {
10
- name: string;
11
- url: string;
12
- drm: DrmType;
13
- }
14
-
15
- export interface BookRecord {
16
- title: string;
17
- authors: string[];
18
- publisher: string;
19
- publishedAt?: string; // "YYYY-MM-DD"
20
- isbn?: string; // ISBN-13、ハイフンなし数字のみ
21
- asin?: string; // Amazon ASIN (Amazonリンクが存在する場合)
22
- url: string; // 出版社公式ページURL
23
- price?: number; // 税込価格(円)
24
- coverImageUrl?: string;
25
- description?: string;
26
- tags?: string[];
27
- ebookStores?: EbookStore[];
28
- }
29
-
30
- export interface SearchQuery {
31
- title?: string;
32
- author?: string;
33
- publisherId?: string; // 出版社IDでフィルタ (例: "gihyo", "lambdanote")
34
- limit?: number; // デフォルト: 10
35
- }
@@ -1,18 +0,0 @@
1
- import type { BookRecord, SearchQuery } from "./book.js";
2
- import type { HttpClient } from "../ports/http.js";
3
- import type { HtmlParser } from "../ports/html-parser.js";
4
- import type { CacheStore } from "../ports/cache.js";
5
-
6
- export interface PublisherDeps {
7
- http: HttpClient;
8
- parser: HtmlParser;
9
- cache: CacheStore;
10
- }
11
-
12
- export interface PublisherAdapter {
13
- readonly id: string;
14
- readonly name: string;
15
- readonly baseUrl: string;
16
- search(query: SearchQuery, deps: PublisherDeps): Promise<BookRecord[]>;
17
- getDetail(url: string, deps: PublisherDeps): Promise<BookRecord>;
18
- }
package/src/main.ts DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env node
2
- import { startServer } from "./mcp/server.js";
3
- import { DEFAULT_PUBLISHERS } from "./adapters/publishers/registry.js";
4
- import { FetchHttpClient } from "./adapters/http/fetch-client.js";
5
- import { CheerioHtmlParser } from "./adapters/html/cheerio-parser.js";
6
- import { MemoryCacheStore } from "./adapters/cache/memory-cache.js";
7
-
8
- const deps = {
9
- http: new FetchHttpClient(),
10
- parser: new CheerioHtmlParser(),
11
- cache: new MemoryCacheStore(),
12
- };
13
-
14
- await startServer(DEFAULT_PUBLISHERS, deps);
package/src/mcp/server.ts DELETED
@@ -1,103 +0,0 @@
1
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import {
4
- CallToolRequestSchema,
5
- ListToolsRequestSchema,
6
- } from "@modelcontextprotocol/sdk/types.js";
7
- import type { PublisherAdapter, PublisherDeps } from "../domain/publisher.js";
8
- import type { BookRecord, EbookStore, DrmType, SearchQuery } from "../domain/book.js";
9
- import { searchBooks } from "../application/search-books.js";
10
- import { getBookDetail } from "../application/get-book-detail.js";
11
- import { TOOLS } from "./tools.js";
12
-
13
- // --- 出力フォーマット ---
14
-
15
- const DRM_LABELS: Record<DrmType, string> = {
16
- free: "DRMフリー",
17
- social: "DRMフリー (ソーシャル)",
18
- password_pdf: "パスワード付きPDF",
19
- drm: "DRM付き",
20
- };
21
-
22
- function formatEbookStore(store: EbookStore): Record<string, unknown> {
23
- return { ...store, drmLabel: DRM_LABELS[store.drm] };
24
- }
25
-
26
- function formatBook(book: BookRecord): Record<string, unknown> {
27
- if (!book.ebookStores) return book as unknown as Record<string, unknown>;
28
- return { ...book, ebookStores: book.ebookStores.map(formatEbookStore) };
29
- }
30
-
31
- export function createServer(
32
- publishers: readonly PublisherAdapter[],
33
- deps: PublisherDeps,
34
- ): Server {
35
- const server = new Server(
36
- {
37
- name: "@zonuexe/techbook-mcp",
38
- version: "0.1.0",
39
- },
40
- {
41
- capabilities: { tools: {} },
42
- },
43
- );
44
-
45
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
46
- tools: TOOLS,
47
- }));
48
-
49
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
50
- const { name, arguments: args = {} } = request.params;
51
-
52
- switch (name) {
53
- case "search_books": {
54
- const query: SearchQuery = {
55
- title: typeof args["title"] === "string" ? args["title"] : undefined,
56
- author: typeof args["author"] === "string" ? args["author"] : undefined,
57
- publisherId: typeof args["publisher"] === "string" ? args["publisher"] : undefined,
58
- limit: typeof args["limit"] === "number" ? Math.min(args["limit"], 50) : 10,
59
- };
60
- const { books, errors } = await searchBooks(query, publishers, deps);
61
- const output: Record<string, unknown> = { books: books.map(formatBook) };
62
- if (errors.length > 0) output["errors"] = errors;
63
- return {
64
- content: [{ type: "text", text: JSON.stringify(output, null, 2) }],
65
- };
66
- }
67
-
68
- case "get_book_detail": {
69
- const url = args["url"];
70
- if (typeof url !== "string") throw new Error("url は必須です");
71
- const book = await getBookDetail(url, publishers, deps);
72
- return {
73
- content: [{ type: "text", text: JSON.stringify(formatBook(book), null, 2) }],
74
- };
75
- }
76
-
77
- case "list_publishers": {
78
- const list = publishers.map(p => ({
79
- id: p.id,
80
- name: p.name,
81
- baseUrl: p.baseUrl,
82
- }));
83
- return {
84
- content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
85
- };
86
- }
87
-
88
- default:
89
- throw new Error(`未知のツール: ${name}`);
90
- }
91
- });
92
-
93
- return server;
94
- }
95
-
96
- export async function startServer(
97
- publishers: readonly PublisherAdapter[],
98
- deps: PublisherDeps,
99
- ): Promise<void> {
100
- const server = createServer(publishers, deps);
101
- const transport = new StdioServerTransport();
102
- await server.connect(transport);
103
- }
package/src/mcp/tools.ts DELETED
@@ -1,54 +0,0 @@
1
- export const TOOLS = [
2
- {
3
- name: "search_books",
4
- description:
5
- "書名・著者名から日本語技術書を検索し、書誌情報の一覧を返します。" +
6
- "複数の出版社を横断して検索します。",
7
- inputSchema: {
8
- type: "object",
9
- properties: {
10
- title: {
11
- type: "string",
12
- description: "書名(部分一致)",
13
- },
14
- author: {
15
- type: "string",
16
- description: "著者名(部分一致)",
17
- },
18
- publisher: {
19
- type: "string",
20
- description:
21
- "出版社IDで検索対象を絞り込みます。指定しない場合は全出版社を検索します。" +
22
- "利用可能なIDは list_publishers で確認できます。",
23
- },
24
- limit: {
25
- type: "number",
26
- description: "1出版社あたりの最大取得件数(デフォルト: 10、最大: 50)",
27
- default: 10,
28
- },
29
- },
30
- },
31
- },
32
- {
33
- name: "get_book_detail",
34
- description: "書籍の公式ページURLから詳細な書誌情報を取得します。",
35
- inputSchema: {
36
- type: "object",
37
- required: ["url"],
38
- properties: {
39
- url: {
40
- type: "string",
41
- description: "書籍の公式ページURL(出版社サイトのURL)",
42
- },
43
- },
44
- },
45
- },
46
- {
47
- name: "list_publishers",
48
- description: "対応している出版社の一覧とIDを返します。",
49
- inputSchema: {
50
- type: "object",
51
- properties: {},
52
- },
53
- },
54
- ] as const;
@@ -1,5 +0,0 @@
1
- export interface CacheStore {
2
- get(key: string): Promise<string | null>;
3
- set(key: string, value: string, ttlSeconds?: number): Promise<void>;
4
- delete(key: string): Promise<void>;
5
- }
@@ -1,15 +0,0 @@
1
- export interface HtmlElement {
2
- text(): string;
3
- html(): string | null;
4
- attr(name: string): string | undefined;
5
- find(selector: string): HtmlElement[];
6
- }
7
-
8
- export interface HtmlDocument {
9
- select(selector: string): HtmlElement[];
10
- selectOne(selector: string): HtmlElement | null;
11
- }
12
-
13
- export interface HtmlParser {
14
- parse(html: string): HtmlDocument;
15
- }
package/src/ports/http.ts DELETED
@@ -1,17 +0,0 @@
1
- export interface RequestOptions {
2
- headers?: Record<string, string>;
3
- timeout?: number;
4
- }
5
-
6
- export interface HttpResponse {
7
- readonly status: number;
8
- readonly url: string;
9
- text(): Promise<string>;
10
- /** ヘッダー値を取得する。複数値は `, ` 結合。存在しない場合は null。 */
11
- header(name: string): string | null;
12
- }
13
-
14
- export interface HttpClient {
15
- get(url: string, options?: RequestOptions): Promise<HttpResponse>;
16
- post(url: string, body: string, options?: RequestOptions): Promise<HttpResponse>;
17
- }
@@ -1,51 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="ja">
3
- <head><meta charset="utf-8"><title>BOOK TECH | 次のステップへ!React実践開発 サクサク作って学ぶ UI/テスト/デプロイ</title></head>
4
- <body>
5
- <div class="contents-base">
6
- <div class="contents-book">
7
- <div class="contents-book-inner">
8
- <div class="contents-book-item">
9
- <div class="contents-book-item-detail-wrap">
10
- <div class="contents-book-item-detail-thumb">
11
- <img src="https://booktech-share.s3-ap-northeast-1.amazonaws.com/books/d80ffe3d.webp" class="thumb" alt="次のステップへ!React実践開発 サクサク作って学ぶ UI/テスト/デプロイ">
12
- </div>
13
- <div class="contents-book-item-detail-price_include_tax">
14
- 2,178円<span class="tax">(税込)</span>
15
- </div>
16
- </div>
17
- </div>
18
-
19
- <div class="contents-book-about">
20
- <div class="contents-book-about-title">
21
- <h1>次のステップへ!React実践開発 サクサク作って学ぶ UI/テスト/デプロイ</h1>
22
- </div>
23
-
24
- <div class="contents-book-about-publicationdate my-1" style="font-size: 0.85em;">
25
- 発売日:
26
- 2026/2/20
27
- </div>
28
-
29
- <div class="d-flex flex-wrap align-items-center gap-1">
30
- <a class="badge bg-light text-dark border" href="/books?q%5Bpublisher_relations_publisher_id_in%5D%5B%5D=4">
31
- <i class="fas fa-building me-1"></i>インプレス NextPublishing
32
- </a>
33
- <a class="badge bg-light text-dark border" href="/books?q%5Bauthor_relations_author_id_in%5D%5B%5D=2205">
34
- <i class="fas fa-user me-1"></i>philosophy (著)
35
- </a>
36
- </div>
37
-
38
- <div class="contents-book-about-id" style="font-size: 0.85em;">
39
- ISBN:
40
- 9784295604136
41
- </div>
42
-
43
- <div class="contents-book-about-description scroll">
44
- TypeScriptによる型安全な開発、各種ホスティングサービスへの公開方法などを解説します。
45
- </div>
46
- </div>
47
- </div>
48
- </div>
49
- </div>
50
- </body>
51
- </html>
@@ -1,91 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="ja">
3
- <head><meta charset="utf-8"><title>BOOK TECH | 「TypeScript」の検索結果</title></head>
4
- <body>
5
- <div class="contents-base">
6
- <div class="contents-index">
7
- <div class="contents-index-wrap">
8
-
9
- <div class="contents-index-item">
10
- <div class="contents-index-item-lang">
11
- <a class="contents-index-item-large-lang-wrap" href="/books?q%5Bbook_category_in%5D%5B%5D=%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%BBIT">コンピュータ・IT</a>
12
- </div>
13
- <div class="contents-index-item-detail-wrap">
14
- <div class="contents-index-item-detail">
15
- <div class="contents-index-item-detail-thumb">
16
- <a class="book-ribbon-link" href="/books/d80ffe3d-f3fe-458b-95ee-b4dd3327fab2">
17
- <img src="https://booktech-share.s3-ap-northeast-1.amazonaws.com/books/d80ffe3d.webp" class="thumb" alt="次のステップへ!React実践開発 サクサク作って学ぶ UI/テスト/デプロイ">
18
- </a>
19
- </div>
20
- </div>
21
- <div class="contents-index-item-detail">
22
- <div class="contents-index-item-detail-title">
23
- 次のステップへ!React実践開発 サクサク作って学ぶ UI/テスト/デプロイ
24
- </div>
25
- <div class="my-1" style="font-size: 0.85em;">
26
- 発売日: 2026/2/20
27
- </div>
28
- <div class="d-none d-lg-block d-flex flex-wrap align-items-center gap-1">
29
- <a class="badge bg-light text-dark border" href="/books?q%5Bpublisher_relations_publisher_id_in%5D%5B%5D=4">
30
- <i class="fas fa-building me-1"></i>インプレス NextPublishing
31
- </a>
32
- <a class="badge bg-light text-dark border" href="/books?q%5Bauthor_relations_author_id_in%5D%5B%5D=2205">
33
- <i class="fas fa-user me-1"></i>philosophy (著)
34
- </a>
35
- </div>
36
- <div class="contents-index-item-detail-description scroll">
37
- TypeScriptによる型安全な開発を解説します。
38
- </div>
39
- <div class="contents-index-item-detail-price_include_tax">
40
- <div>
41
- 2,178円<span class="tax">(税込)</span>
42
- </div>
43
- </div>
44
- </div>
45
- </div>
46
- </div>
47
-
48
- <div class="contents-index-item">
49
- <div class="contents-index-item-lang">
50
- <a class="contents-index-item-large-lang-wrap" href="/books?q%5Bbook_category_in%5D%5B%5D=%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%BBIT">コンピュータ・IT</a>
51
- </div>
52
- <div class="contents-index-item-detail-wrap">
53
- <div class="contents-index-item-detail">
54
- <div class="contents-index-item-detail-thumb">
55
- <a class="book-ribbon-link" href="/books/a52f6467-5d0c-4d76-9351-19a0ab76eb96">
56
- <img src="https://booktech-share.s3-ap-northeast-1.amazonaws.com/books/a52f6467.webp" class="thumb" alt="React環境構築の教科書">
57
- </a>
58
- </div>
59
- </div>
60
- <div class="contents-index-item-detail">
61
- <div class="contents-index-item-detail-title">
62
- React環境構築の教科書
63
- </div>
64
- <div class="my-1" style="font-size: 0.85em;">
65
- 発売日: 2020/8/21
66
- </div>
67
- <div class="d-none d-lg-block d-flex flex-wrap align-items-center gap-1">
68
- <a class="badge bg-light text-dark border" href="/books?q%5Bpublisher_relations_publisher_id_in%5D%5B%5D=4">
69
- <i class="fas fa-building me-1"></i>インプレス NextPublishing
70
- </a>
71
- <a class="badge bg-light text-dark border" href="/books?q%5Bauthor_relations_author_id_in%5D%5B%5D=107">
72
- <i class="fas fa-user me-1"></i>井手 優太 (著)
73
- </a>
74
- </div>
75
- <div class="contents-index-item-detail-description scroll">
76
- TypeScriptを使ったコンパイル環境を解説します。
77
- </div>
78
- <div class="contents-index-item-detail-price_include_tax">
79
- <div>
80
- 3,300円<span class="tax">(税込)</span>
81
- </div>
82
- </div>
83
- </div>
84
- </div>
85
- </div>
86
-
87
- </div>
88
- </div>
89
- </div>
90
- </body>
91
- </html>