MCP で Claude Desktop から Cloudflare Workers に Script をデプロイしコードを Github で管理する
MCP とは
The Model Context Protocol (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools. Whether you’re building an AI-powered IDE, enhancing a chat interface, or creating custom AI workflows, MCP provides a standardized way to connect LLMs with the context they need.
Model Context Protocol (MCP)は、LLMアプリケーションと外部データソース・ツールをシームレスに統合することを可能にするオープンプロトコルです。AI搭載のIDEの構築、チャットインターフェースの機能強化、カスタムAIワークフローの作成など、どのような用途であっても、MCPはLLMが必要とするコンテキストを接続するための標準的な方法を提供します。
MCP は、アプリケーションが LLM にコンテキストを提供する方法を標準化するオープン プロトコルです。MCP は、AI アプリケーション用の USB-C ポートのようなものです。USB-C がデバイスをさまざまな周辺機器やアクセサリに接続するための標準化された方法を提供するのと同様に、MCP は AI モデルをさまざまなデータ ソースやツールに接続するための標準化された方法を提供します。
MCP の一般的なアーキテクチャ
登場する概念
- MCP Host: Claude Desktop、IDE、または MCP を介してリソースにアクセスする AI ツールなどのプログラム
- MCP Client: Server との 1:1 接続を維持するプロトコル クライアント
- MCP Server: 標準化されたモデル コンテキスト プロトコルを通じて特定の機能を公開する軽量プログラム
- Local Resource: MCP Server が安全にアクセスできるコンピュータのリソース (データベース、ファイル、サービス)
- Remote Resource: MCP Server が接続できるインターネット経由 (API 経由など) で利用可能なリソース
Client は、Host の中で動作し、Server とのコネクションを 1:1 で維持する
MCP Server が Your Computer の中に書かれているが、仕様としては MCP Server は リモートにあっても問題ない。
ただし、代表的な Client である Claude Desktop はMCP サポートは現在開発者プレビュー段階であり、マシン上で実行されているローカル MCP Server への接続のみをサポートしている。
Claude Desktop のリモート MCP 接続はまだサポートされていない。
また、この統合は Claude Desktop アプリでのみ利用可能であり、Claude Web インターフェイス (claude.ai) では利用できない。
MCP Client の一覧はこんな感じ。
分かりづらい表現だが、ここでは Claude Desktop はホストでもあるが、クライアントでもある。
Claude Desktop の他には、Editor やコーディングアシスタントがクライアントとして紹介されている。
Host, Client, Server の役割
ざっくりとだが
Host が取りまとめ役で、複数の Client <-> Server の 1:1 のコネクションを取りまとめている感じ。
Host が Client を作成し、Client は Server といい感じの双方向のコネクションを持つ。
Server は標準化されたプロトコル(MCP のこと)に沿って、いろんな機能を提供してくれる。
少し細かく仕様を眺めてみる
Host の役割
- 複数のクライアントインスタンスを作成および管理します
- クライアント接続の権限とライフサイクルを制御する
- セキュリティポリシーと同意要件を強制する
- ユーザーの承認決定を処理する
- AI/LLMの統合とサンプリングを調整
- クライアント間のコンテキスト集約を管理します
Client の役割
各クライアントはホストによって作成され、分離されたサーバー接続を維持します。
Host は複数の Client を作成および管理し、各 Client は特定の Server と 1:1 の関係を持ちます。
- サーバーごとに1つのステートフルセッションを確立します
- プロトコルネゴシエーションと機能交換を処理する
- プロトコルメッセージを双方向にルーティングする
- サブスクリプションと通知を管理する
- サーバー間のセキュリティ境界を維持
Server の役割
サーバーは特殊なコンテキストと機能を提供します:
- MCPプリミティブを介してリソース、ツール、プロンプトを公開する
- 責任を明確にして独立して運営する
- クライアントインターフェースを通じてサンプリングを要求する
- セキュリティ上の制約を尊重する必要がある
- ローカルプロセスまたはリモートサービスどちらにもなれる
サンプリング
は特殊そうなので一旦脇においてみる。
そうするとピンときづらいのは
Host
- ユーザーの承認決定を処理する
Server
- MCPプリミティブを介してリソース、ツール、プロンプトを公開する
MCPプリミティブを介してリソース、ツール、プロンプトを公開する
についてはサーバーが、クライアントに与える構成要素を標準化したものとみえる。
以下の 3 つが主だったもの。
- プロンプト: 言語モデルのインタラクションをガイドする定義済みのテンプレートまたは指示
- リソース: モデルに追加のコンテキストを提供する構造化データまたはコンテンツ
- ツール: モデルがアクションを実行したり情報を取得したりできるようにする実行可能な関数
いろんな機能を Server のプログラムとして実装できるが、Client に公開するにはこの MCP に従ってねという感じ。
プロンプト、リソース、ツールは制御する主体が異なる
ユーザーの承認決定を処理する
この理解のためにも、Client と Server でどのようにやり取りされるかのライフサイクルを見てみる。
3 つのステップに分かれている
- 初期化:
Capability Negotiation
とプロトコルバージョンの合意 - 動作:通常のプロトコル通信
- シャットダウン: 接続の正常な終了
初期化の Capability Negotiation
がこのやり取りを特徴づけてる。
より細かい Capability Negotiation
のシーケンスをみてみる。
Client 側から Server へツール、リソース、プロンプトの Capacity を使いたいと要求したり、
Server 側から Client の roots(あるパス以下のファイルシステムへのアクセス)、 sampling(言語モデル
の利用) を要求したりして、OK であればその Capacity を使えるようにする感じ。
主な機能の一覧
サンプリングに関しては、さらにユーザーの許可をもらうような形式になる。
通信の仕組みは 2 つある。
- stdio、標準入力と標準出力を介した通信
- サーバー送信イベント(SSE)を使用した HTTP
MCP で使われるメッセージプロトコルは大きく 3 種類ある。
それぞれ JSON で形式が決まっている。
- Request: リクエストはクライアントからサーバーに送信されるか、またはその逆に送信される。応答が期待される。
- Response: リクエストへの返答。
- Notification: 通知はクライアントからサーバーに、またはその逆に送信されます。応答は期待されない。
このあたりはまた後で見てみようと思う
前後するが、Tool に関してはモデルによって制御されるようになっている。
これはいわゆる Agent を実装するときのイメージに近いので納得。
また Tool についてはセキュリティに対する推奨事項として次の事が書いてある。
Host のアプリケーションで次を実現すべきだといっている。MUST ではなくSHOULD。
- AIモデルに公開されているツールを明確に示すUIを提供する
- ツールが呼び出されたときに明確な視覚的なインジケーターを挿入する
- 人間がループ内にいることを確認するために、操作時にユーザーに確認プロンプトを表示します。
Claude Desktop で Tool を使用すると確認などが求められるデザインになっているのは、これを守っている感じ。
冒頭の Host の "ユーザーの承認決定を処理する" は
この Tool や前の Sampling についての承認だと思われる。
ただし現時点では、Sampling については Claude Desktop は未対応。
クイックスタートを試してみる
クイックスタートはローカルの PC 上で SQLite の DB に対して Claude Desktop から操作を行うもの。
構成は次のよう
- Host: Claude Desktop
- Client: Claude Desktop
- Server: SQLite MCP Server
前提条件 を参考に。
Python, uv, sqlite が使えるようにしておく。
特定の PATH で sqlite の DB を作成しておく。
# Create a new SQLite database
sqlite3 ~/${YOUR_PATH}/test.db <<EOF
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT,
price REAL
);
INSERT INTO products (name, price) VALUES
('Widget', 19.99),
('Gadget', 29.99),
('Gizmo', 39.99),
('Smart Watch', 199.99),
('Wireless Earbuds', 89.99),
('Portable Charger', 24.99),
('Bluetooth Speaker', 79.99),
('Phone Stand', 15.99),
('Laptop Sleeve', 34.99),
('Mini Drone', 299.99),
('LED Desk Lamp', 45.99),
('Keyboard', 129.99),
('Mouse Pad', 12.99),
('USB Hub', 49.99),
('Webcam', 69.99),
('Screen Protector', 9.99),
('Travel Adapter', 27.99),
('Gaming Headset', 159.99),
('Fitness Tracker', 119.99),
('Portable SSD', 179.99);
EOF
Claude Desktop に MCP Server の存在を知らせる。
知らせるために設定用の json ファイルを作成する。
次のファイルを作成する。
~/Library/Application Support/Claude/claude_desktop_config.json
設定を書き込む。
{
"mcpServers": {
"sqlite": {
"command": "uvx",
"args": ["mcp-server-sqlite", "--db-path", "/Users/${YOUR_USERNAME}/${YOUR_PATH}/test.db"]
}
}
}
ファイルを保存して Claude Desktop を再起動すると、この SQLite DB が MCP Server 経由で、Claude Desktop から操作できるようになる。
実際に動作を試してみる。
1. テーブルを作成してみる
動作を開始すると利用するツールに対して、使ってもいいか聞かれる。
2. テーブルにサンプルデータを追加する
データ作成し、追加し、作成できたか確認してくれる。
3. テーブルの中身を表示してみる
mcp-server-sqlite をみてみる
MCP Client に教えている SQLite に関する情報はこちら
{
"mcpServers": {
"sqlite": {
"command": "uvx",
"args": [
"mcp-server-sqlite",
"--db-path",
"/Users/YOUR_USERNAME/${YOUR_PATH}/test.db"
]
}
}
}
mcp-server-sqlite に引数を渡して実行してる模様。
Github に公開されている mcp-server-sqlite をみてみる。
[project]
name = "mcp-server-sqlite"
version = "0.6.2"
description = "A simple SQLite MCP server"
readme = "README.md"
requires-python = ">=3.10"
dependencies = ["mcp>=1.0.0"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
dev-dependencies = ["pyright>=1.1.389"]
[project.scripts]
mcp-server-sqlite = "mcp_server_sqlite:main"
ファイル構造はシンプル。
ls src/mcp_server_sqlite
__init__.py
server.py
引数処理して、server.py から server.main() 実行してるだけ。
from . import server
import asyncio
import argparse
def main():
"""Main entry point for the package."""
parser = argparse.ArgumentParser(description='SQLite MCP Server')
parser.add_argument('--db-path',
default="./sqlite_mcp_server.db",
help='Path to SQLite database file')
args = parser.parse_args()
asyncio.run(server.main(args.db_path))
# Optionally expose other important items at package level
__all__ = ["main", "server"]
server.py には SqliteDatabase という SQLite を扱うための Class と main() 関数がある。
SqliteDatabase はさっと流し読み。
class SqliteDatabase:
def __init__(self, db_path: str):
self.db_path = str(Path(db_path).expanduser())
...
def _init_database(self):
"""Initialize connection to the SQLite database"""
...
def _synthesize_memo(self) -> str:
"""Synthesizes business insights into a formatted memo"""
logger.debug(f"Synthesizing memo with {len(self.insights)} insights")
...
def _execute_query(self, query: str, params: dict[str, Any] | None = None) -> list[dict[str, Any]]:
"""Execute a SQL query and return results as a list of dictionaries"""
logger.debug(f"Executing query: {query}")
...
Tool. Prompt, Resource それぞれの記載があるが、Tool に絞ってみていく。
Client, Server そして LLM の間でのメッセージフローは以下の通り。
Client が Server に Tool ないか確認しにいって、あとは LLM が Client 経由で Server の関数を呼び出すか決めて使っていく。
Tool に関するわかりやすいところを抜粋する。
...
from mcp.server.models import InitializationOptions
import mcp.types as types
from mcp.server import NotificationOptions, Server
import mcp.server.stdio
...
PROMPT_TEMPLATE = """
The assistants goal is to walkthrough an informative demo of MCP. To demonstrate the Model Context Protocol (MCP) we will leverage this example server to interact with an SQLite database.
....
"""
async def main(db_path: str):
logger.info(f"Starting SQLite MCP Server with DB path: {db_path}")
db = SqliteDatabase(db_path)
server = Server("sqlite-manager")
...
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""List available tools"""
return [
types.Tool(
name="read-query",
description="Execute a SELECT query on the SQLite database",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "SELECT SQL query to execute"},
},
"required": ["query"],
},
),
types.Tool(
name="write-query",
description="Execute an INSERT, UPDATE, or DELETE query on the SQLite database",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "SQL query to execute"},
},
"required": ["query"],
},
),
types.Tool(
name="create-table",
description="Create a new table in the SQLite database",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "CREATE TABLE SQL statement"},
},
"required": ["query"],
},
),
...
]
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict[str, Any] | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""Handle tool execution requests"""
try:
...
if name == "read-query":
if not arguments["query"].strip().upper().startswith("SELECT"):
raise ValueError("Only SELECT queries are allowed for read-query")
results = db._execute_query(arguments["query"])
return [types.TextContent(type="text", text=str(results))]
elif name == "write-query":
if arguments["query"].strip().upper().startswith("SELECT"):
raise ValueError("SELECT queries are not allowed for write-query")
results = db._execute_query(arguments["query"])
return [types.TextContent(type="text", text=str(results))]
elif name == "create-table":
if not arguments["query"].strip().upper().startswith("CREATE TABLE"):
raise ValueError("Only CREATE TABLE statements are allowed")
db._execute_query(arguments["query"])
return [types.TextContent(type="text", text="Table created successfully")]
else:
raise ValueError(f"Unknown tool: {name}")
except sqlite3.Error as e:
return [types.TextContent(type="text", text=f"Database error: {str(e)}")]
except Exception as e:
return [types.TextContent(type="text", text=f"Error: {str(e)}")]
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
logger.info("Server running with stdio transport")
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="sqlite",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
以下のようなデコレーターをハンドラーの関数にそれぞれ付与している。
@server.list_tools() - 利用可能なツールの一覧を提供
@server.call_tool() - ツールの呼び出しを処理
もとの mcp.server から読まれているので
mcp の python-sdk を見に行ってみる
@server.list_tools() については
デコレータを適用した関数を実行し、ServerResult
でラップして返却してる
class Server:
...
def list_tools(self):
def decorator(func: Callable[[], Awaitable[list[types.Tool]]]):
logger.debug("Registering handler for ListToolsRequest")
async def handler(_: Any):
tools = await func()
return types.ServerResult(types.ListToolsResult(tools=tools))
self.request_handlers[types.ListToolsRequest] = handler
return func
return decorator
デコレーターで装飾する関数の戻り値は Tool
配列を期待されている
class ListToolsResult(PaginatedResult):
"""The server's response to a tools/list request from the client."""
tools: list[Tool]
class Tool(BaseModel):
"""Definition for a tool the client can call."""
name: str
"""The name of the tool."""
description: str | None = None
"""A human-readable description of the tool."""
inputSchema: dict[str, Any]
"""A JSON Schema object defining the expected parameters for the tool."""
model_config = ConfigDict(extra="allow")
この Tool が @server.list_tools() で装飾された handle_list_tools 関数で使用されていたもの。
MCP で定義される MCPサーバーのうちの Tool を定義する部分。
入力値として次のような Dict を期待することが書かれている、
{ 'query': 'テキストで書かれたSQL 文' }
types.Tool(
name="read-query",
description="Execute a SELECT query on the SQLite database",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "SELECT SQL query to execute"},
},
"required": ["query"],
},
),
@server.call_tool() は各種 Params を設定して、デコレートした関数を実行している。
class Server:
...
def call_tool(self):
def decorator(
func: Callable[
...,
Awaitable[
Sequence[
types.TextContent | types.ImageContent | types.EmbeddedResource
]
],
],
):
logger.debug("Registering handler for CallToolRequest")
async def handler(req: types.CallToolRequest):
try:
results = await func(req.params.name, (req.params.arguments or {}))
return types.ServerResult(
types.CallToolResult(content=list(results), isError=False)
)
except Exception as e:
return types.ServerResult(
types.CallToolResult(
content=[types.TextContent(type="text", text=str(e))],
isError=True,
)
)
self.request_handlers[types.CallToolRequest] = handler
return func
return decorator
呼ばれた Tool の名前ごとに arguments を検証し、期待している query の Dict があるかチェックしている。
そして抽出した query Key に対応する値(SQL 文を期待している) を実行している。
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict[str, Any] | None
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""Handle tool execution requests"""
try:
...
if name == "read-query":
if not arguments["query"].strip().upper().startswith("SELECT"):
raise ValueError("Only SELECT queries are allowed for read-query")
results = db._execute_query(arguments["query"])
return [types.TextContent(type="text", text=str(results))]
...
LLM が与えられた Tool を list_tools, call_tool でうまく使っている。
このあたりに書かれている。
Tool に関してのより Request, Response プロトコルなどの仕様はこちらにある。
Tool が変更になったときに Client に知らせる仕様なども定められている。
MCP の仕様に則っていれば MCP Server を実装することで、他のサービスと MCP Client が繋げられることがわかった。
これから Claude Desktop <> Cloudflare MCP Server <> Cloudflare API を試していく。
Cloudflare MCP Server
こんなことができるようになるMCP Server
新しいWorkerをデプロイして、Durable Objectsのサンプルを追加してください。
D1データベース「...」内のデータについて教えてください。
KVネームスペース「...」のすべてのエントリをR2バケット「...」にコピーしてください。
Setup の手順は少ない
npx @cloudflare/mcp-server-cloudflare init
の後に Claude Desktop を再起動するだけ。
init の中身をみてみる。めぼしいとこだけ
Cloudflare の AuthToken を取得して、MCP Server を設定する claude_desktop_config.json
にいい感じに設定を追加してくれてる。
export async function init(accountTag: string | undefined) {
...
try {
getAuthTokens()
} catch (e: any) {
...
}
if (isAccessTokenExpired()) {
updateStatus(`Access token expired, refreshing...`, false)
if (await refreshToken()) {
updateStatus('Successfully refreshed access token')
} else {
throw new Error('Failed to refresh access token')
}
}
const { result: accounts } = await fetchInternal<FetchResult<AccountInfo[]>>('/accounts')
...
const claudeConfigPath = path.join(
os.homedir(),
'Library',
'Application Support',
'Claude',
'claude_desktop_config.json',
)
const cloudflareConfig = {
command: (await which('node')).trim(),
args: [__filename, 'run', account],
}
const configDirExists = isDirectory(path.dirname(claudeConfigPath))
if (configDirExists) {
const existingConfig = fs.existsSync(claudeConfigPath)
? JSON.parse(fs.readFileSync(claudeConfigPath, 'utf8'))
: { mcpServers: {} }
if ('cloudflare' in (existingConfig?.mcpServers || {})) {
updateStatus(
`${chalk.green('Note:')} Replacing existing Cloudflare MCP config:\n${chalk.gray(JSON.stringify(existingConfig.mcpServers.cloudflare))}`,
)
}
const newConfig = {
...existingConfig,
mcpServers: {
...existingConfig.mcpServers,
cloudflare: cloudflareConfig,
},
}
fs.writeFileSync(claudeConfigPath, JSON.stringify(newConfig, null, 2))
} else {
...
}
}
実行してみる
npx @cloudflare/mcp-server-cloudflare init
無事に claude_desktop_config.json
が更新されたので Claude Desktop を再起動する
実際に依頼をしてみる
申し訳ありません。セキュリティの制限により KV namespace の直接作成ができないようです。
KV namespace を作成する Tool はないので、この操作は実行できない。
こちらで作成して、名前空間を伝えて更新してもらう。
ちゃんと名前空間を確認して、ID を取得している。
(スクショには ID 乗せてないが出力に入ってる)
先ほどアップしたコードの中身もアップデートして、作成した KV の名前空間を使用するようにしてくれる。
Cloudflare のダッシュボードでも確認できた。
作成した API のエンドポイントから ToDo を作成し、無事登録できている。
ちなみに、次のようなコードが生成されていた。
ライブラリを使わないようにしたので UUID とかも自作しようとしている。
// UUID生成のヘルパー関数
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// CORSヘッダーの設定
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
// レスポンスヘルパー関数
function jsonResponse(body, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: {
'Content-Type': 'application/json',
...corsHeaders,
},
});
}
export default {
async fetch(request, env) {
// プリフライトリクエストの処理
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: corsHeaders,
});
}
const url = new URL(request.url);
const path = url.pathname;
// ToDo一覧の取得
if (request.method === 'GET' && path === '/todos') {
try {
const todoList = await env.TODO_STORE.get('todos');
return jsonResponse(todoList ? JSON.parse(todoList) : []);
} catch (error) {
return jsonResponse({ error: 'Failed to fetch todos' }, 500);
}
}
// ToDo の追加
if (request.method === 'POST' && path === '/todos') {
try {
const body = await request.json();
if (!body.title) {
return jsonResponse({ error: 'Title is required' }, 400);
}
const todo = {
id: generateUUID(),
title: body.title,
completed: false,
createdAt: new Date().toISOString(),
};
const existingTodos = await env.TODO_STORE.get('todos');
const todos = existingTodos ? JSON.parse(existingTodos) : [];
todos.push(todo);
await env.TODO_STORE.put('todos', JSON.stringify(todos));
return jsonResponse(todo, 201);
} catch (error) {
return jsonResponse({ error: 'Failed to create todo' }, 500);
}
}
// ToDo の更新
if (request.method === 'PUT' && path.startsWith('/todos/')) {
try {
const id = path.split('/')[2];
const body = await request.json();
const existingTodos = await env.TODO_STORE.get('todos');
if (!existingTodos) {
return jsonResponse({ error: 'Todo not found' }, 404);
}
const todos = JSON.parse(existingTodos);
const todoIndex = todos.findIndex(todo => todo.id === id);
if (todoIndex === -1) {
return jsonResponse({ error: 'Todo not found' }, 404);
}
todos[todoIndex] = {
...todos[todoIndex],
...body,
updatedAt: new Date().toISOString(),
};
await env.TODO_STORE.put('todos', JSON.stringify(todos));
return jsonResponse(todos[todoIndex]);
} catch (error) {
return jsonResponse({ error: 'Failed to update todo' }, 500);
}
}
// ToDo の削除
if (request.method === 'DELETE' && path.startsWith('/todos/')) {
try {
const id = path.split('/')[2];
const existingTodos = await env.TODO_STORE.get('todos');
if (!existingTodos) {
return jsonResponse({ error: 'Todo not found' }, 404);
}
const todos = JSON.parse(existingTodos);
const filteredTodos = todos.filter(todo => todo.id !== id);
if (filteredTodos.length === todos.length) {
return jsonResponse({ error: 'Todo not found' }, 404);
}
await env.TODO_STORE.put('todos', JSON.stringify(filteredTodos));
return jsonResponse({ message: 'Todo deleted successfully' });
} catch (error) {
return jsonResponse({ error: 'Failed to delete todo' }, 500);
}
}
// 404 Not Found
return jsonResponse({ error: 'Not found' }, 404);
}
};
Cloudflare MCP Server はいろんな機能が公開されていたが今回使ったのは以下の Tool
Workers Management
- worker_list: List all Workers in your account
- worker_get: Get a Worker's script content
- worker_put: Create or update a Worker script
KV Store Management
- get_kvs: List all KV namespaces in your account
- kv_list: List keys in a KV namespace
Hono による試みと失敗
もともとは Hono でやろうと思っていたが、Contxt Window の問題で実現できなかった。
Cloudflare Workers にファイルをアップする時に、コードの中身がそのままリクエストに展開されるようになっている。
バンドルした際のファイルが Context Window に対しては大きかったので(Hono は十分に軽量で非常にすきなライブラリです。あくまで Context Window に対して)、上限に達してアップできなかった。
やろうとしていたことは以下の通り。
最後だけ Context Window の問題でだめだった。
ここで使っている Fetch, Filesysytem はどちらも公式がメンテしている。
気になる方は Githubでまとまっているので参考までに。
- ✅️ Fetch MCP Server で Hono のクイックスタートを URL を渡してよみこむ
- ✅️ Claude にサンプルコードを作成してもらう
- ✅️ Filesysytem MCP Server でローカル PC の特定のディレクトリにコードを置く
- ✅️ (Claude Desktop を離れ)
npx wrangler deploy --dry-run --outdir dist
でバンドル (Claude Desktop に戻る) - ✅️ Filesysytem MCP Server でバンドルした成果物のパスを指定して読み込んでもらう
- ❌️ Cloudflare MCP Server で Cloudflare Workers にコードをあげる
正直、Hono は Cloudflare Workers と非常に連携しやすいのでこんな方法を取る必要はない。
wrangler deploy
コマンドで事足りるし、Github Actions か、Clpudflare Build でデプロイメントパイプラインまで組んだほうが良い。
ただ、Claude Desktop から離れずにほとんどのことが完結する体験は面白い。
自作で MCP Server を作成したら、他にも繋げられるので面白そう。
Github へのコードのプッシュ
ついでなので作成したコードを Github にプッシュしておく。
おなじみの claude_desktop_config.json
に設定する。
Personal Access Token を使う。
Fine Grained でも適切に権限が付与されていれば大丈夫そう。
{
"mcpServers": {
...
"github": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-github"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
}
}
}
}
かなり広範囲なことができる。
- 自動ブランチ作成: ファイルを作成/更新したり変更をプッシュしたりするときに、ブランチが存在しない場合は自動的に作成されます。
- 包括的なエラー処理: 一般的な問題に対する明確なエラーメッセージ
- Git 履歴の保存: 強制プッシュなしで適切な Git 履歴を維持する操作
- バッチ操作: 単一ファイルと複数ファイルの操作の両方をサポート
- 高度な検索: コード、問題/PR、ユーザーの検索をサポート
Tool としても 16 種類くらいある。多い。
Tool を使いながら Repsoitory の作成とコードのプッシュを実施してくれた。
関連でこれおもしろいので試してみたい。