Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ packages/
│ ├── crawler # オーケストレーター + 型定義 + ユーティリティ + アーカイブ
│ ├── core # Nitpicker プラグインシステム
│ ├── types # 共有型定義
│ ├── query # アーカイブクエリ API(SQL レベルのフィルタ・集計)
│ ├── mcp-server # MCP サーバー(AI アシスタントからのアーカイブクエリ)
│ ├── analyze-* # 各種 analyze プラグイン
│ └── report-google-sheets # Google Sheets レポーター
└── test-server/ # E2Eテスト用 Hono サーバー
Expand All @@ -24,10 +26,13 @@ packages/
@d-zero/beholder(外部)
└── crawler ── @nitpicker/cli ← @d-zero/roar(外部)
↑ ↑ ↑ ↑
│ core │ report-google-sheets ← @d-zero/google-sheets(外部)
│ ↑ │ ↑
│ analyze-* プラグイン │
↑ ↑ ↑ ↑ ↑
│ │ core │ report-google-sheets ← @d-zero/google-sheets(外部)
│ │ ↑ │ ↑
│ │ analyze-* プラグイン │
│ └── query │
│ ↑ │
│ mcp-server ← @modelcontextprotocol/sdk
└── @d-zero/dealer(外部)──┘
```

Expand Down Expand Up @@ -154,6 +159,41 @@ crawler/src/
└── write-queue.ts # Archive 書き込み直列化キュー
```

### @nitpicker/query

`.nitpicker` アーカイブファイルに対する SQL レベルのクエリ API。大規模データセット(10,000+ ページ、500,000+ レコード)向けに最適化。

**主要クラス・関数:**

- **`ArchiveManager`**: アーカイブのライフサイクル管理(open / get / close / closeAll)。同一ファイルの重複オープンは参照カウントで管理し、untar を再実行しない
- **`listPages`**: ページ一覧取得(ステータス・メタデータ欠損・URL パターンなどでフィルタ)
- **`getSummary`**: サイト全体の統計(ページ数、ステータス分布、メタデータ充足率)
- **`getPageDetail`**: 単一ページの詳細情報(メタデータ、アウトバウンド/インバウンドリンク、リダイレクト元)
- **`getPageHtml`**: HTML スナップショット取得(truncation サポート)
- **`listLinks`**: リンク分析(broken / external / orphaned)
- **`listResources`**: サブリソース一覧(CSS, JS, 画像、フォント)
- **`listImages`**: 画像一覧(alt 欠損、寸法欠損、オーバーサイズ検出)
- **`getViolations`**: 分析プラグインの違反データ取得
- **`findDuplicates`**: 重複タイトル・説明の検出
- **`findMismatches`**: メタデータ不一致の検出(canonical, og:title, og:description)
- **`getResourceReferrers`**: リソースを参照しているページの特定
- **`checkHeaders`**: セキュリティヘッダーチェック(CSP, X-Frame-Options, X-Content-Type-Options, HSTS)

**依存:** `@nitpicker/crawler`(`Archive`, `ArchiveAccessor` を使用)

### @nitpicker/mcp-server

[Model Context Protocol](https://modelcontextprotocol.io/) サーバー。AI アシスタント(Claude 等)から `.nitpicker` アーカイブを直接クエリするための 14 ツールを提供。

**構成:**

- **`mcp-server.ts`**: `createServer()` で MCP Server インスタンスを構築。低レベル `Server` API を使用(`McpServer` + Zod スキーマの深い型インスタンス化問題を回避)
- **`tool-definitions.ts`**: 14 ツールの JSON Schema 定義

**バイナリ:** `nitpicker-mcp`(stdio トランスポート)

**依存:** `@modelcontextprotocol/sdk`, `@nitpicker/query`

### @nitpicker/cli

`@d-zero/roar` ベースの統合 CLI。4つのサブコマンドを提供。全 analyze プラグインを `dependencies` に含んでおり、`npx` 実行時に `@nitpicker/core` の動的 `import()` がプラグインモジュールを解決できるようにしている。
Expand Down
13 changes: 9 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ packages/
│ ├── crawler/ # クローラーエンジン(オーケストレーター + アーカイブ + ユーティリティ)
│ ├── core/ # 監査エンジン(Nitpicker クラス + bounded Promise pool による並列処理)
│ ├── types/ # 監査型定義(Report, ConfigJSON)
│ ├── query/ # アーカイブクエリ API(SQL レベルのフィルタ・集計)
│ ├── mcp-server/ # MCP サーバー(AI アシスタント連携、bin: nitpicker-mcp)
│ ├── analyze-axe/ # アクセシビリティ監査
│ ├── analyze-lighthouse/ # Lighthouse 監査
│ ├── analyze-main-contents/ # メインコンテンツ検出
Expand All @@ -31,10 +33,13 @@ packages/
@d-zero/beholder(外部)
└── crawler ── @nitpicker/cli ← @d-zero/roar(外部)
↑ ↑ ↑ ↑
│ core │ report-google-sheets
│ ↑ │
│ analyze-* プラグイン
↑ ↑ ↑ ↑ ↑
│ │ core │ report-google-sheets
│ │ ↑ │
│ │ analyze-* プラグイン
│ └── query
│ ↑
│ mcp-server ← @modelcontextprotocol/sdk(外部)
└── @d-zero/dealer(外部)
```

Expand Down
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,49 @@ $ npx @nitpicker/cli pipeline https://example.com --all --silent --strict
#### 終了コード

crawl コマンドと同じ終了コード体系に従う。詳細は [crawl の終了コード](#終了コード) を参照。

### MCP Server

`.nitpicker` アーカイブファイルを AI アシスタント(Claude 等)から直接クエリするための [Model Context Protocol](https://modelcontextprotocol.io/) サーバー。14 のツールを提供し、サイト構造・メタデータ・リンク・リソース・画像・セキュリティヘッダーなどを対話的に分析できる。

#### セットアップ(Claude Desktop)

`claude_desktop_config.json` に以下を追加:

```json
{
"mcpServers": {
"nitpicker": {
"command": "npx",
"args": ["@nitpicker/mcp-server"]
}
}
}
```

#### 利用可能なツール

| ツール | 説明 |
| ------------------------ | --------------------------------------------------------------------------- |
| `open_archive` | `.nitpicker` ファイルを開く(他のツール使用前に必須) |
| `close_archive` | アーカイブを閉じてリソースを解放 |
| `get_summary` | サイト全体の概要(ページ数、ステータス分布、メタデータ充足率) |
| `list_pages` | ページ一覧(ステータス・メタデータ欠損・noindex・URL パターン等で絞り込み) |
| `get_page_detail` | 特定ページの全詳細(メタデータ、リンク、リダイレクト、ヘッダー) |
| `get_page_html` | ページの HTML スナップショットを取得 |
| `list_links` | リンク分析(broken / external / orphaned) |
| `list_resources` | サブリソース一覧(CSS, JS, 画像、フォント) |
| `list_images` | 画像一覧(alt 欠損、寸法欠損、オーバーサイズ検出) |
| `get_violations` | 分析プラグインの違反データ(axe, markuplint, textlint, lighthouse) |
| `find_duplicates` | 重複タイトル・説明の検出 |
| `find_mismatches` | メタデータ不一致の検出(canonical, og:title, og:description) |
| `get_resource_referrers` | 特定リソースを参照しているページの特定 |
| `check_headers` | セキュリティヘッダーチェック(CSP, X-Frame-Options, HSTS 等) |

#### 使用例

```
> .nitpicker ファイルを開いて、404 エラーのページを教えてください

AI: open_archive で読み込み → list_pages で status=404 のページをフィルタ → 結果を表示
```
5 changes: 4 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
"qmark",
"dedup",
"unstarted",
"mmm"
"mmm",

// Security headers
"HSTS"
]
}
11 changes: 9 additions & 2 deletions packages/@nitpicker/crawler/src/archive/archive-accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
async getConfig(): Promise<Config> {
return this.#db.getConfig();
}

/**
* Reads custom data stored in the archive by name.
* @param name - The base name of the data file (without extension).
Expand All @@ -103,7 +102,6 @@ export class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
}
return await readText(filePath);
}

/**
* Reads the HTML content of a page snapshot from the archive.
* Supports reading from both unzipped directories and zipped snapshot archives.
Expand Down Expand Up @@ -146,6 +144,15 @@ export class ArchiveAccessor extends EventEmitter<DatabaseEvent> {
log('Succeeded: Extracts %s from zipped snapshots', name);
return html;
}
/**
* Returns the underlying Knex query builder instance for direct SQL access.
* Enables advanced queries (GROUP BY, HAVING, JOINs) at the database layer
* for performance-critical operations on large datasets.
* @returns The Knex instance connected to the SQLite database.
*/
getKnex() {
return this.#db.getKnex();
}

/**
* Retrieves all pages from the archive, optionally filtered by type.
Expand Down
15 changes: 9 additions & 6 deletions packages/@nitpicker/crawler/src/archive/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ export class Database extends EventEmitter<DatabaseEvent> {
t.integer('order').unsigned().nullable().defaultTo(null);
});
}

/**
* Forces a WAL checkpoint, writing all pending WAL data back to the main database file.
* Uses TRUNCATE mode to reset the WAL file to zero bytes after checkpointing.
Expand All @@ -105,7 +104,6 @@ export class Database extends EventEmitter<DatabaseEvent> {
async clearHtmlPath(pageId: number) {
await this.#instance<DB_Page>('pages').where('id', pageId).update({ html: null });
}

/**
* Destroys the database connection, releasing all pooled resources.
*/
Expand Down Expand Up @@ -136,7 +134,6 @@ export class Database extends EventEmitter<DatabaseEvent> {
.where('anchors.pageId', pageId);
return res;
}

/**
* Retrieves the base URL of the crawl session from the `info` table.
* @returns The base URL string.
Expand All @@ -152,7 +149,6 @@ export class Database extends EventEmitter<DatabaseEvent> {
const [{ baseUrl }] = selected;
return baseUrl || '';
}

/**
* Retrieves the full crawl configuration from the `info` table.
* Deserializes JSON-encoded fields (`excludes`, `excludeKeywords`, `scope`).
Expand All @@ -179,7 +175,6 @@ export class Database extends EventEmitter<DatabaseEvent> {
dbLog('Table `info`: %O => %O', config, opt);
return opt;
}

/**
* Retrieves the current crawling state by listing scraped and pending URLs.
* @returns An object with `scraped` (completed URLs) and `pending` (remaining URLs) arrays.
Expand All @@ -203,7 +198,6 @@ export class Database extends EventEmitter<DatabaseEvent> {
pending,
};
}

/**
* Retrieves the HTML snapshot file path for a specific page.
* @param pageId - The database ID of the page.
Expand All @@ -220,6 +214,15 @@ export class Database extends EventEmitter<DatabaseEvent> {
return html || null;
});
}
/**
* Returns the underlying Knex query builder instance for direct SQL access.
* This enables advanced queries (GROUP BY, HAVING, JOINs) at the database
* layer for performance with large datasets.
* @returns The Knex instance connected to the SQLite database.
*/
getKnex(): Knex {
return this.#instance;
}

/**
* Retrieves the crawl session name from the `info` table.
Expand Down
30 changes: 30 additions & 0 deletions packages/@nitpicker/crawler/src/archive/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,36 @@ export interface DB_Referrer {
textContent: string | null;
}

/**
* Raw database row representing an image element found on a page in the `images` table.
*/
export interface DB_Image {
/** Auto-incremented primary key. */
id: number;
/** Foreign key to the page that contains this image. */
pageId: number;
/** The `src` attribute value of the image element. */
src: string | null;
/** The actual loaded source URL of the image (after srcset/picture resolution). */
currentSrc: string | null;
/** The `alt` attribute value, or null if not present. */
alt: string | null;
/** The rendered width of the image in CSS pixels. */
width: number;
/** The rendered height of the image in CSS pixels. */
height: number;
/** The intrinsic width of the image in pixels. */
naturalWidth: number;
/** The intrinsic height of the image in pixels. */
naturalHeight: number;
/** Whether the image uses lazy loading. */
isLazy: number | null;
/** The viewport width at the time of capture. */
viewportWidth: number;
/** The raw HTML source code of the image element. */
sourceCode: string | null;
}

/**
* Raw database row representing a sub-resource (CSS, JS, image, etc.) in the `resources` table.
*/
Expand Down
49 changes: 49 additions & 0 deletions packages/@nitpicker/mcp-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# @nitpicker/mcp-server

`.nitpicker` アーカイブファイルを AI アシスタントから操作するための MCP サーバー。

## 概要

[Model Context Protocol (MCP)](https://modelcontextprotocol.io/) を介して、`.nitpicker` アーカイブの内容を AI アシスタント(Claude Desktop 等)から直接クエリできるサーバーです。stdio トランスポートで動作し、14 のツールを提供します。

内部では `@nitpicker/query` パッケージのクエリ関数を呼び出しています。

### 提供ツール

| ツール | 説明 |
| ------------------------ | ---------------------------------------------------- |
| `open_archive` | `.nitpicker` ファイルを読み込み、archiveId を返す |
| `close_archive` | アーカイブを閉じてリソースを解放 |
| `get_summary` | サイト全体の概要統計 |
| `list_pages` | ページ一覧(フィルタ・ソート・ページネーション対応) |
| `get_page_detail` | 特定ページの詳細情報 |
| `get_page_html` | HTML スナップショットの取得 |
| `list_links` | リンク分析(壊れたリンク、外部リンク、孤立ページ) |
| `list_resources` | サブリソース一覧(CSS、JS、画像、フォント) |
| `list_images` | 画像品質チェック(alt 欠落、サイズ欠落、過大画像) |
| `get_violations` | 分析プラグインの違反結果 |
| `find_duplicates` | メタデータ重複検出 |
| `find_mismatches` | メタデータ不一致検出 |
| `get_resource_referrers` | リソース参照元ページの検出 |
| `check_headers` | セキュリティヘッダー確認 |

## セットアップ

Claude Desktop の設定ファイルに以下を追加してください。

```json
{
"mcpServers": {
"nitpicker": {
"command": "npx",
"args": ["@nitpicker/mcp-server"]
}
}
}
```

このパッケージは [Nitpicker](../../README.md) モノレポの内部パッケージです。

## ライセンス

Apache-2.0
4 changes: 4 additions & 0 deletions packages/@nitpicker/mcp-server/bin/nitpicker-mcp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node
import { startServer } from '../lib/mcp-server.js';

startServer();
38 changes: 38 additions & 0 deletions packages/@nitpicker/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@nitpicker/mcp-server",
"version": "0.4.4",
"description": "MCP server for querying .nitpicker archive files via AI assistants",
"author": "D-ZERO",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/d-zero-dev/nitpicker.git",
"directory": "packages/@nitpicker/mcp-server"
},
"publishConfig": {
"access": "public"
},
"files": [
"bin",
"lib"
],
"type": "module",
"exports": {
".": {
"import": "./lib/mcp-server.js",
"types": "./lib/mcp-server.d.ts"
}
},
"bin": {
"nitpicker-mcp": "./bin/nitpicker-mcp.js"
},
"scripts": {
"build": "tsc",
"clean": "tsc --build --clean"
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.12.1",
"@nitpicker/query": "0.4.4"
},
"gitHead": "32b83ee38eba7dfd237adb1b41f69e049e8d4ceb"
}
Loading
Loading