kite-lite について
kite-liteは、実ブラウザーなしでページの取得、確認、簡単なフォーム操作、描画を行うエージェント向け軽量ウェブエンジン兼MCPサーバーです。HTMLを解析しますがページのJavaScriptは実行しないため、静的コンテンツや構造化監査に適し、JavaScript依存サイトには向きません。
主な機能
- 01HTML解析とページテキスト抽出
- 02Cookieとリダイレクト対応の状態付きナビゲーション
- 03ページのJavaScriptを実行しないフォーム操作
- 04分離されたJavaScript評価
- 05SVG、PNG、PDF描画
- 06ウェブ、アクセシビリティ、SEO、ソーシャル監査
活用シーン
- 01HTMLページからテキストとリンクを抽出
- 02クライアント側JavaScriptに依存しない複数手順の操作
- 03スクリーンショットによる基本レイアウト確認
- 04決定的なアクセシビリティとSEOチェック
MCP 対応
kite-liteはstdio経由でMCPサーバーとして実行できます。ツールを通じて、MCPクライアントはページの取得・確認、セッションのナビゲーション、簡単なフォーム操作、分離されたJavaScript評価、スクリーンショット描画を行えます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
kite-lite
mcp-name: io.github.MauricioPerera/kite-lite
軽量で自己完結型の、エージェント向けウェブエンジンです。Cloudflare の外部にある VPS やコンテナで実行するよう設計されており、HTML パーサー、隔離された JavaScript、決定論的な SVG レンダリング、小規模な CDP サーフェスを備えています。
現在の状態
Rust の DOM コアは次の機能を備えています。
- HTML5 互換のパーサーで HTML を解析します。
- タイトル、テキスト、リンクを JSON 形式で抽出し、各リンクを最終ページ URL を基準に絶対 URL へ解決します。
- CSS、JavaScript、レンダリングを追加できるように設計されたツリー表現を維持します。
- 解析とローカル評価でグローバル状態を回避します。
- CDP セッション内に最終 URL(リダイレクト後)とソース HTML を保持します。
- 独自の Cookie jar を持つ HTTP クライアントを使用して、同一セッション内のナビゲーション間(
fetch/CDP)で Cookie を保持します。 - 固定されたタグ別デフォルトスタイルシート(h1-h6 のサイズ/太字、p/li/blockquote のマージン)とテキストの折り返し(ワードラップ)を使用して、要素ごとの最小限のレイアウト(
Element.layoutの{x, y, width, height})を計算します。制限については「最小限のレイアウト」セクションをご覧ください。 - ページの JavaScript を実行せずに、CDP からフォームのクリック/入力/送信を行えます。「操作:クリック、入力、フォーム送信」をご覧ください。
- SVG に加えて PNG と PDF へのレンダリングにも対応しています。「PNG/PDF レンダリング」をご覧ください。
- stdio 経由で MCP サーバーとして実行できます。「MCP サーバー」をご覧ください。
エージェント向けの機能
kite-liteを使うと、エージェントは実際のブラウザー(Chromium/Playwright)を動かさずにWebへアクセスできます。測定されたメモリ使用量のピークは約4~7 MiB(「最小リソース」を参照)で、ミリ秒単位で起動し、--network=none/--cap-drop=ALL/--read-onlyの環境でも問題なく動作します。これによりコストの計算が変わります。共有の重いインスタンスを使う代わりに、小規模なVPSでタスクごとにインスタンスを起動し、並列実行することが現実的になります。
具体的には、次のことが可能になります。
- 低コストのページ読み取り(
fetch_page)—軽量な要約(タイトル/テキスト/リンク)を返します。レイアウトを含む完全なDOMではありません。エージェントが「このページに何が書いてあるか」だけを知りたい場合、生のHTMLを返すよりコンテキストを節約できます。 - 状態を保持するセッション(
browser_navigate+永続的なCookie/リダイレクト)—クライアント側のJavaScriptに依存しない限り、複数ステップのフロー(ログイン→保護されたページ)を扱えます。 - ページのJavaScriptを実行しないフォーム操作—クリック、入力、送信が可能です。JavaScriptを無効にしたブラウザーのように動作します(「操作:クリック、入力、フォームの送信」を参照)。タグ、
type、nameでセレクターを指定できます。 - 分離され、検証可能なJS(
eval_js)—計算された値を取得したり、式をテストしたりできます。実効性のあるサンドボックス保証があり、ネットワークもファイルシステムも使えず、reqwest/tokioをリンクすることさえできない別プロセスで実行されます(「MCP サーバー」を参照)。 - 低コストの視覚表示(
render_screenshot/browser_screenshot)—概略の確認やレポート作成向けであり、ピクセル単位で正確なビジュアルQAには適しません(「最小限のレイアウト」を参照)。 - LLMの判断に依存しない構造化監査—4つのリンター(
webmcp-lint、a11y-lint、seo-lint、social-lint)により、サイトを構築またはレビューするエージェントは、<meta description>が欠けているかを確認するためにモデルを呼び出すことなく、決定論的かつ低コストのゲートを利用できます。 - 宣言型WebMCP(
browser_call_tool)—対象サイトが注釈付きフォームを公開している場合、エージェントはセレクターを推測せず、スキーマを持つインターフェースを利用できます(「宣言型WebMCPのサポート」を参照)。
設計上の明確な制限があります。これはバグではなく、前述の機能の直接的な裏返しです(理由については「今後のレイヤー」を参照)。
- クライアント側JavaScriptに依存するコンテンツは扱えません—SPA、クライアント側の
fetchで読み込まれるコンテンツ、ブラウザーでレンダリングするあらゆるフレームワークは表示されません。 - 実際の操作はできません—
onclickや実際のイベントは実行されず、ナビゲーション(<a>)とGET送信のみ可能です。 - フォームで使えるのはGETのみです—実際の
method="post"は本文を付けて送信できません。 - 複数タブには対応しません。ドラッグ&ドロップ、ファイルのアップロード、無限スクロールもできません。
- Playwrightではありません—WebSocket CDPには接続できますが、生きたDOMに対する
Runtime.callFunctionOnに依存する操作(Playwrightが内部で使う方式)は動作しません(「CDPの互換性」を参照)。
適しているのは、プレーンなHTMLで配信されるコンテンツ(ブログ、ドキュメント、大半のコンテンツサイト、HTMLを返すAPI、単純なGETフォーム)、実際のブラウザーのコストに見合わない、大量の読み取り/抽出/監査、または分離(JSからネットワークにアクセスできない、生きたDOMがない)が制限ではなくセキュリティ上の利点となるケースです。SPA、複雑な操作、クライアント側JavaScriptで検証するサイトには実際のブラウザーが必要です。近道はありません。
試してみる
cargo test
cargo run -- https://example.com
cargo run -- https://example.com --svg example.svg --js "1 + 2"
cargo run -- https://example.com --png example.png
cargo run -- https://example.com --pdf example.pdf
cargo run -- fetch https://example.com --output page.json
cargo run -- eval page.json --js "document.title"
cargo run -- render page.json --output page.svg
cargo run -- render page.json --output page.png
cargo run -- render page.json --output page.pdf
cargo run -- serve 127.0.0.1:8787
cargo run -- cdp page.json 127.0.0.1:9222
cargo run -- cdp 127.0.0.1:9222
render(および ?format=png|pdf|svg 経由の POST /v1/render)では、出力形式は --output のパスの拡張子から選択されます(それ以外の場合は .svg がデフォルトです)。
--js は、ファイルシステム、ネットワーク、ホストバインディングのない新しい Boa JavaScript コンテキストでスクリプトを評価します。ページスナップショットでは、document.title、document.body.innerText、および最初の h1、h2、h3、p、a、または button を対象とする限定的な document.querySelector() が公開されます。--svg は、以下で説明する最小限のレイアウト(「最小限のレイアウト」を参照)に基づく決定的なレンダリングを書き出します。テキストを持つ各末端要素は、タグごとに固定されたデフォルトスタイルに従って折り返され、配置されます。
例:
cargo run -- https://example.com --js "document.title + ' / ' + document.querySelector('h1').innerText"
JavaScript コンテキストには、ソースサイズ、再帰、VM スタックの制限が設けられました。さらに CLI では、ページスクリプトを別プロセスの kite-lite-js で実行し、1.5 秒後に終了させます。この評価器のバイナリには reqwest/tokio がリンクされていないため、スクリプトが何を試みてもネットワークリクエストを行えません。これはコードが何を呼び出すかではなく、実行ファイルにコンパイルされた内容による分離です。これはローカルでの有用な分離境界ですが、本番環境では OS レベルのリソース制限とコンテナまたはマイクロ VM も追加する必要があります。
システム制限を適用して実行する
Docker イメージは、権限のないユーザーとしてバイナリを実行します。厳格なリソース制限を適用して試す場合:
docker build -t kite-lite .
docker run --rm `
--memory=256m --cpus=0.5 --pids-limit=64 `
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=16m `
--cap-drop=ALL --security-opt=no-new-privileges `
kite-lite https://example.com --js "document.title"
このモードでは、URL をダウンロードするためにネットワークアクセスが必要です。シリアライズ済みのページの場合は、ネットワークなしで評価器を実行できます。また、評価器(kite-lite-js)は reqwest も tokio もリンクしていない別個のバイナリであるため、--network=none はコンテナ設定だけではなく、バイナリによって実際に保証されます。
docker run --rm --network=none --memory=256m --cpus=0.5 --pids-limit=64 `
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=16m `
--cap-drop=ALL --security-opt=no-new-privileges `
-v "${PWD}/page.json:/input/page.json:ro" `
kite-lite eval /input/page.json --js "document.title"
レンダリングもネットワークなしで実行できます。
docker run --rm --network=none --read-only --cap-drop=ALL `
-v "${PWD}/page.json:/input/page.json:ro" `
-v "${PWD}:/output" `
kite-lite render /input/page.json --output /output/page.svg
最小限のレイアウト
fetch、CDP の Page.navigate/Page.reload、および POST /v1/parse は、ツリー上で単純なブロックレイアウトを計算し、Page を返す前に Element.layout({x, y, width, height})に保存します。render_svg は、指定された正確な幅で内部的にレイアウトを再計算します(異なる幅を指定された場合にずれないよう、JSON に保存されたレイアウトは再利用しません)。
これはCSS エンジンではありません:
<style>、style="..."、クラス/ID はありません。サイズ、太字、余白はタグ名(h1-h6、p、li、blockquote、strong/b)ごとに固定されており、デフォルトのユーザースタイルシートを最小限に模倣しています。- 実際のインラインフローはありません。子要素を持つ要素は、子要素をすべて幅いっぱいにして縦に積み重ねます。これは単なる簡略化ではなく、現在の DOM ツリーが持つ実際の制限です。パーサーは、要素の単独テキストと子要素のテキストを、順序や兄弟テキストノードを保持せず、単一の
textフィールドにまとめます。そのため、子要素と並ぶ単独テキスト(たとえば<p>Hola <a href="/x">link</a></p>)は、親要素に子要素がある場合、個別にレイアウトする場所がありません。折り返されて描画されるのは、リーフノード自身のテキストだけです。 xは常に0.0です。レイアウトに水平方向の軸はなく、垂直方向に積み重ねるだけです。- 余白の相殺、フロート、位置指定はありません。
PNG/PDF のレンダリング
render_svg は PNG にラスタライズできます(resvg と tiny-skia を使用。純粋な Rust で、ブラウザーや外部ツールへの依存はありません)。その PNG は 1 ページの PDF に変換できます(printpdf)。
これはバイナリを実行するシステムにフォントがインストールされている必要があります。render_svg が描画するものはすべてテキストで(長方形、画像、その他のグラフィックはありません)、ラスタライザーは fontdb::load_system_fonts() を使用します。フォントは埋め込まれていません。フォントがない場合、PNG/PDF ではテキストが欠落します(ほかの要素も存在しないため表示されません)。Docker イメージではこのために fonts-dejavu-core をインストールします。Docker を使わずにローカルでビルドする場合は、OS で利用可能なフォントがあるかどうかに依存します。
PDF はベクター形式ではありません。96 DPI の PDF ページにラスタライズされた PNG 画像を埋め込んだもので、選択や検索が可能なテキストはありません。本当のベクター PDF を作成するには、ラスタライズとは独立した独自のフォントパイプラインが必要です(printpdf は SVG のネイティブサポートを備えていますが、内部ではフォントを読み込まず、注入する方法もない Options::default() を使う古いバージョンの usvg を使用するため、テキストには適していません)。
ローカル制御 API
サーバーはデフォルトでループバックにバインドされ、以下を公開します。
GET /healthPOST /v1/parseは body に HTML を指定します(このエンドポイントはリンク解決用のベース URL を受け取らないため、リンクは相対のままになります)。POST /v1/renderは JSON スナップショットを指定します —?format=svg(デフォルト)、png、またはpdf。POST /v1/evalは{ "page": ..., "script": "..." }を指定します。
インターネットに直接公開しないでください。リモートアクセスが必要な場合は、認証とプロキシの背後に配置してください。
CDP の実験的サポート
cdp モードでは、Chrome DevTools Protocol の一部の機能に対応した WebSocket を開きます。
Browser.getVersion;Runtime.enable、Page.enable、Network.enable、DOM.enable;Runtime.evaluate;Page.getNavigationHistory、Page.getResourceTree;Page.navigate、Page.reload、Page.captureSnapshot;DOM.getDocument、DOM.querySelector、DOM.querySelectorAll;DOM.getOuterHTML、DOM.getAttributes、DOM.getBoxModel;Input.dispatchMouseEvent、Input.dispatchKeyEvent;Target.getTargets、Target.getTargetInfo、Target.attachToTarget、Target.attachToBrowserTarget、Target.setAutoAttach、Target.setDiscoverTargets、Target.closeTarget、Target.disposeBrowserContext— 固定のターゲットが 1 つだけで、実際のマルチタブには対応していません (詳しくは下の「CDP の互換性」を参照してください);- 基本的なイベント
Page.frameStartedLoading、Page.loadEventFired、Page.frameStoppedLoading(Page.navigate/reloadの後だけでなく、ページ遷移が発生するクリックの後にも発火します)、およびTarget.attachedToTarget。
JSON スナップショットを指定して cdp を実行すると、シリアライズ済みのページからセッションを開始します。スナップショットを指定しない場合は、空のページから開始します。
cargo run -- cdp page.json 127.0.0.1:9222
Page.navigate と Page.reload は、CDP セッションごとに永続化される HTTP クライアントを使って HTML をダウンロードします(実行中の cdp プロセスの存続期間中)。その後、DOM を再構築し、リダイレクトに従った後の最終的な URL に更新します。最初にリクエストした URL ではありません。サイトが設定した Cookie(リダイレクトの途中で設定されたものも含む)は、そのセッションの Cookie jar に保存され、同じ cdp プロセス内で後続のナビゲーション時に再送信されます。たとえば、ログインページから後続のページへ移動する間も認証状態を維持する必要があるエージェントが期待する動作です。Runtime.evaluate で公開されるのはドキュメントの簡易スナップショットのみで、fetch、ファイルシステム、ホストバインディングは利用できません。
自動的に再送信されるだけでなく、サーバーが設定した各 Cookie(Set-Cookie ヘッダー)も Page.cookies で参照できます(名前、値、および指定されている場合は domain/path/secure/http_only/same_site などのフラグ)。以前は reqwest の内部 Cookie jar に保存されるだけで、読み取る方法がありませんでした。Cookie がある場合、MCP の fetch_page と browser_navigate にはこのフィールドが含まれます。制限として、kite-lite が行うフェッチの HTTP ヘッダーに含まれる Cookie のみ参照できます。ページの JavaScript は実行されないため、JavaScript が設定する Cookie は対象外です。
CDP 呼び出しの概念的な例:
{"id":1,"method":"Page.navigate","params":{"url":"https://example.com"}}
{"id":2,"method":"DOM.querySelector","params":{"nodeId":1,"selector":"h1"}}
{"id":3,"method":"DOM.getOuterHTML","params":{"nodeId":6}}
現時点では、CDP 経由の PNG/PDF キャプチャには対応していません(その場合は render または /v1/render?format=png|pdf を使用してください。詳しくは「PNG/PDF レンダリング」を参照)。ページスクリプトの実行やネットワークイベントにも対応していません。
CDP 互換性
cdp サーバーは、同じポートで WebSocket と、実際の Chrome が
remote-debugging ポートで公開する検出用 HTTP エンドポイント
(GET /json/version、GET /json、GET /json/list)の両方を公開します。
これらは、汎用ツール(chrome-remote-interface や、
webSocketDebuggerUrl を要求して起動するスクリプトなど)が
WebSocket に接続する前に問い合わせるエンドポイントと同じです。また、
Target ドメインの最低限の機能(getTargets、attachToTarget、
setAutoAttach、...)も実装しているため、実際の CDP のフロー
(操作前に target にアタッチし、各レスポンス/ページイベントに
sessionId が付くこと)を想定するクライアントが、届かないものを
待ち続けることはありません。ただし、シミュレートされた固定の target
が 1 つだけです。実際の複数のタブ/target はなく、
attachToTarget は常に同じ sessionId を返します。また、前述のとおり、
すべての WebSocket 接続で共有される Page は 1 つだけです。
これで実際の Playwright が使えるようになるわけではありません。
playwright.chromium.connectOverCDP() は、これによって接続して
アタッチできる場合があります。しかし、Playwright の各操作
(page.click()、page.fill()、locator())は、操作の前に可視性/
スクロール/操作可能性を確認するため、実行中の DOM に対して
(Runtime.callFunctionOn 経由で)JavaScript を注入して実行します。
これは、このプロジェクトが意図的に避けている、DOM と JavaScript の
連携および永続性を厳密に要求します。その理由については「操作」と
「最小限のレイアウト」を参照してください。これをシミュレートする
ことは試みていません。最初の実際のクリックまでは動作するように
見えてしまうためです。
操作:クリック、文字入力、フォームの送信
Input.dispatchMouseEvent(type: "mousePressed")と
Input.dispatchKeyEvent(type: "char"/"keyDown")は、
ページの JavaScript を実行しない操作を実装しています。
JavaScript を無効にしたブラウザーのように動作します。
- レイアウトボックスにイベントの
y座標が含まれるノードをクリックした場合:<a href>→ その URL に移動します(Page.navigateと同じ経路を 通るため、セッションの Cookie、リダイレクト、URL 解決が同様に適用されます)。<input>/<textarea>→ フォーカスします。これにより、Input.dispatchKeyEventが入力先を判別できます(フォーカスは ナビゲーションが発生すると失われます)。<button>または<input type="submit">→ 最も近い祖先の<form>を探し、子孫の<input>/<textarea>のnameと現在の値を 集めてクエリー文字列を作成し、action?queryに移動します (actionがない場合は現在の URL に移動します)。
type:"char"のInput.dispatchKeyEventは、フォーカス中のノードのvalueにtextを追加します。type:"keyDown"かつkey:"Backspace"の場合は、最後の文字を削除します。
クリックによって onclick が実行されたり、ページの <script> が
実行されたりすることはありません。「最小限のレイアウト」で説明した
理由により、JavaScript に紐づく実行中の DOM は依然としてありません。
その他の制限事項として、クリックは y 座標のみを使用します
(レイアウトに x 軸はありません。「最小限のレイアウト」を参照)。
送信は GET のみです(method/action が POST の場合はサポートされず、
リクエストボディもありません)。<select>/チェックボックス/ラジオボタン
には固有の動作はなく、特別なアクションのない通常のノードとして扱われます。
クリックする位置を知るには、実際の CDP クライアントはまず
DOM.querySelector を実行し、次に座標を取得するため
DOM.getBoxModel を実行します。Playwright/Chrome DevTools も同様です。
MCP サーバー
cargo run -- mcp は、stdio 経由で kite-lite を MCP(Model Context Protocol)サーバーとして実行します。stdin/stdout 上で改行区切りの JSON-RPC 2.0 を使用します。これは Claude Desktop/Claude Code がローカルツールを起動する際に使うのと同じトランスポートです。serde_json を使って手動で実装しており(MCP SDK は使用していません)、CDP を含むプロジェクト全体と同じ考え方に基づいています。必要なインターフェース(initialize、tools/list、tools/call)は小規模です。
公開するツールは、すべて既存のエンジンを使用します。
fetch_page(url)— 軽量な要約{url, title, text, links}を返します。レイアウトを含む完全な DOM ツリーは返しません(エージェントのコンテキストには JSON が大きすぎるためです)。ページの JavaScript は実行せず、永続セッションにも触れません。render_screenshot(url, format?)— URL を取得して PNG(base64 エンコードされた画像)または SVG(テキスト)にレンダリングします。「PNG/PDF レンダリング」と同じ制限があり、システムにフォントがない場合は白紙になります。eval_js(url, script)— URL を取得して、スナップショットに対して JavaScript を分離して評価します。常に同じサンドボックスを使用します(ネットワーク/ファイルシステム/実際の DOM にはアクセスできません)。browser_navigate(url)、browser_click(selector)、browser_type(text, selector?)、browser_get_dom(selector?)、browser_screenshot(format?)— プロセスごとにナビゲーションセッションを 1 つだけ保持します(cdpと同様で、複数タブには対応しません)。Cookie やリダイレクトの動作はプロジェクトの他の部分と同じです。browser_clickは「インタラクション:クリック、入力、フォームの送信」と同じロジックを再利用しますが、座標yではなくセレクターで要素を特定します。MCP ツールとして、こちらのほうが自然です。セレクターは実際の CSS ではありません。tag(正確な名前。例:button)、任意のタグを表す*、またはtag[attr=value]/[attr=value](typeまたはnameで絞り込み。例:input[type=submit])を指定できます。内部 DOM が独立したフィールドとして保持する属性は、この 2 つだけです。クラス、ID、結合子には対応していません。これは実際の使用時に重要です。<input type=text>と<input type=submit>は同じタグを共有するため、属性で絞り込まないと、文書順で最初の要素にしかアクセスできません。
セレクターに一致する要素がない、取得に失敗するなど、ツールでエラーが発生した場合は、内容にメッセージを含めて isError: true を返します。これにより、JSON-RPC 呼び出し全体を失敗させるのではなく、エージェントがエラーを確認して対応できます。
また、プロトコルを手動でやり取りするだけでなく、実際の MCP クライアントでも検証しました。scripts/mcp_ollama_bridge.py は、ネイティブのツール呼び出しに対応する Ollama Cloud のモデルを kite-lite の MCP サーバーに接続します。これにより、モデルが呼び出すツールと引数を決定できます。この方法で、9 つすべてのツールをテストしました。browser_navigate → browser_click → browser_type → フォーム送信という一連の操作と、両方の形式でのスクリーンショット取得も含まれます。Claude Desktop 用に claude_desktop_config.json にも設定されていますが、この連携については、アプリ内で実際に会話する検証は行っていません。
WebMCP の宣言的サポート
独自のツールに加えて、kite-lite は
WebMCP
の宣言的属性(toolname、tooldescription、toolparamdescription、toolautosubmit、
required)が付与されたフォームを検出し、追加のツールとして公開します。
- ページの概要(
fetch_page、browser_navigate、browser_click)には、検出されたツールを含むtoolsフィールドが 追加されます。各ツールの名前、説明、autosubmitと、フォームのフィールドから 生成された JSON のinputSchemaが含まれます(<select>は各<option value>を使った文字列のenumになります。type="checkbox"/"number"はboolean/numberとして推定され、それ以外はstringになります)。 browser_call_tool(name, arguments?)は、argumentsで各フィールドを埋め (引数がない場合はフィールドの現在の値を使用)、browser_clickで送信ボタンを クリックした場合と同様にフォームを送信します。
明示的な制限事項: サポート対象は宣言的サブセット(HTML 属性)のみです。
命令型 API(JS の navigator.modelContext.registerTool())には対応していません。
これにはページの JS をライブ DOM に対して実行する必要があり、kite-lite が意図的に
避けていることだからです(詳しくは下記の「今後のレイヤー」を参照してください)。
フォームの送信は GET のみで、リクエストボディはありません。クリックによる送信と
同じ制限です。
WebMCP 宣言的リンター
kite-lite webmcp-lint <url|page.json|archivo.html> [--json] は、公開前に
ページ内の toolname="..." フォームを実用的なルールに照らして検証します。
cargo run -- webmcp-lint ./mi-pagina.html
cargo run -- webmcp-lint https://mi-sitio.com/checkout
cargo run -- webmcp-lint ./mi-pagina.html --json # para consumir desde otro script
チェック項目(重大度の高い順):
- Error(終了コード ≠ 0。CI のゲートとしての使用を想定):
tooldescriptionがない。 ページ内に同じtoolnameを持つフォームが 2 つある(エージェントがどちらを 呼び出すべきか判別できません)。 - Warning:
toolnameに[A-Za-z0-9_-]以外の文字が含まれている(一部の tool-calling バックエンドで拒否されます)。フォームにactionがない。nameのないフィールドがある(スキーマから除外され、エージェントから見えません)。<select>に<option>がない(空の enum)。 - Info:フォームが
method="post"になっている。kite-lite は GET 送信しか シミュレートできないため、browser_call_toolは実際の動作を反映しません。 WebMCP ネイティブ対応ブラウザーでもテストすることをお勧めします。toolparamdescriptionのないフィールドがある(必須ではありませんが、エージェントに 役立ちます)。
WebMCP を有効にした実際のブラウザーでのテストに代わるものではありません。 実際のブラウザーで試す前に、よくあるエラーを素早くローカルで確認するためのものです。
アクセシビリティリンター(a11y)
kite-lite a11y-lint <url|page.json|archivo.html> [--json] は、
webmcp-lint と同じ入力・出力形式ですが、WCAG の完全な監査ではなく、
実用的なアクセシビリティルールをいくつかチェックします。
cargo run -- a11y-lint ./mi-pagina.html
cargo run -- a11y-lint https://mi-sitio.com
ルールはすべて、現時点では Warning です(問題が見つかってもコマンドは
終了コード 0 で終了します。重大度の区分は、ビルドを失敗させるべきルールが
必要になった場合に備えて、すでに用意されています)。
alt属性のない<img>(装飾目的のalt=""は未設定とはみなしません)。- テキストがなく、空でない
altを持つ子孫の画像もない<a>。 - ページ内に
<h1>が複数ある。 - 見出しレベルが飛んでいる(
<h1>の次に<h2>を挟まず<h3>がある)。 lang属性のない<html>。
ソーシャルメディアプレビュー(Open Graph / Twitter Card)
kite-lite social-lint <url|page.json|archivo.html> [--json] は、リンクを共有したときに Twitter/Slack/Facebook/WhatsApp のボットにどのように表示されるかをシミュレートします。クローラーが使用するのと同じフォールバック順(Open Graph → Twitter Card → meta/シンプルな <title>)に従ってタイトル、説明、画像を解決し、プレビューの品質が低下する場合に警告します。
cargo run -- social-lint ./mi-pagina.html
cargo run -- social-lint https://mi-sitio.com
- Error(exit code ≠ 0):どの方法でもタイトルを解決できません(
og:title、twitter:title、<title>のいずれもありません)。ほとんどのボットには URL だけが表示されます。 - Warning:画像(
og:image/twitter:image)がありません。説明を解決できません(meta/OG/Twitter のいずれにもなく、ページのテキストにもありません)。 - Info:説明が約 200 文字を超えています(ほとんどのプラットフォームではそこで切り詰められます)。解決された画像が絶対 URL ではありません(多くのクローラーはページを基準に解決せず、画像を破棄します)。
この機能のため、kite-lite が <head> の <meta>(Page.meta)を取得するようにしました。これまでは <head> の残りとともに完全に破棄されていました。
オンページ SEO 監査
kite-lite seo-lint <url|page.json|archivo.html> [--json] — SEO の基本項目をチェックする、4 つ目のリンターです。a11y-lint の見出しチェック(複数の <h1>、見出しレベルの飛び越し)は繰り返しません。
cargo run -- seo-lint ./mi-pagina.html
cargo run -- seo-lint https://mi-sitio.com
- Error:
<title>がありません。<meta name="robots">にnoindexが含まれています(ページがインデックス登録から明示的に除外されています。本番環境で有効なままにしてしまいがちです)。 - Warning:
<title>が 10~60 文字の範囲外です。<meta name="description">がありません。説明が 50~160 文字の範囲外です。ページに<h1>がありません。 - Info:ページの単語数が 200 未満です(コンテンツが薄い状態です)。
VPS へのデプロイ
テスト済みのデプロイでは、マルチステージイメージと非特権ユーザーを使用します。
docker build -t kite-lite:dev .
docker run --rm \
--memory=32m --cpus=0.2 --pids-limit=16 \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=16m \
--cap-drop=ALL --security-opt=no-new-privileges \
kite-lite:dev cdp 0.0.0.0:9222
CDP ポートはループバックに維持するか、トンネルと認証で保護してください。インターネットに直接公開することは推奨されません。
最小リソース(推定値ではなく実測値)
実際の VPS 上で、Docker、cgroup v2、memory.peak を使用し、https://example.com に対して --memory/--cpus の値を二分探索して障害発生点を特定しました。
| モード | 実測メモリピーク |
|---|---|
fetch/--js(レンダリングなし) | 約 4.1~4.2 MiB |
--png | 約 4.5 MiB |
--pdf | 約 5.3 MiB |
アイドル状態の serve | 約 4.0~4.4 MiB |
parse+render png 実行後の serve | 約 6.0~7.0 MiB |
アイドル状態の cdp | 約 4.0 MiB |
mcp(initialize+fetch_page) | 約 3.9~4.5 MiB |
Docker 自体が拒否する(「Minimum memory limit allowed is 6MB」)ため、メモリ上限を 6 MB 未満にはできませんでした。これは kite-lite の下限ではなく、コンテナエンジンの下限です。本番環境でのプロセス(計測時点で実稼働時間が 7 時間以上)でも同じ結果が確認されました。上限が 256 MB のとき、memory.peak は 4.46 MiB で、約 55 倍の過剰割り当てでした。
--cpus の割り当ては、どの結果の正しさにも影響しませんでした。--cpus=0.01(コアの 1%)でも、レンダリングは失敗せず約 11 秒で完了しました。この負荷では、CPU は同時実行時のレイテンシには影響しますが、動作するかどうかには影響しません。--pids-limit=16 で十分です。アイドル状態のプロセスが使用するスレッドは 4 つです(tokio のランタイムは --cpus と無関係に、ホストのコアごとに 1 つ起動します)。
上記の 32m/0.2 は、余裕を持たせた推奨最小値であり、正確な下限である 6 MB/6 MiB ではありません。シンプルなサンプルページより重いページ(より大きな DOM、高解像度の PNG)に対応するためです。本番環境で大きなページを扱う場合は、上限を引き下げる前に、必ず独自のコンテンツで再計測してください。
今後のレイヤー
当初のロードマップ(fetcher/分離された JS、URL/Cookie/リダイレクト、最小限のレイアウト、インタラクション、PNG/PDF、CDP 互換性、MCP サーバー)は完了しており、その後追加された WebMCP の宣言的サポートも実装済みです。残された最大の制約は、このプロジェクトの分離モデルを壊さずに解決する方法がないことです。kite-lite では、レンダリングに JavaScript が必要なもの(SPA、クライアント側の fetch で読み込まれるコンテンツ)は表示できません。これは、JavaScript に結び付いたライブ DOM を持たないことの直接的な裏返しであり、実際のインタラクション、Playwright、WebMCP の命令型 API が機能しない理由でもあります。これを解決するロードマップ項目はありません。解決するには、その設計上の判断を拡張するのではなく、放棄する必要があるためです。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。