Rustの勉強がてらで。
ローカルで動けばいい雑なMCPサーバーの実装自体はやったことあるけど、JS/TSで書けるなら、Rustでもできるはず。
作ったもの
leaysgur/oxc-ast-mcp https://github.com/leaysgur/oxc-ast-mcp/
OXCのASTがどういう風になってるか、どういうものがあり、どんな構造になってるのかを知る補助となるツール。
また、コード片を渡してASTにしたり、それが生成されたコードなら、validなシンタックスがどうかチェックできるというやつ。
地味に便利なはず。
RustのSDK探し
さっと調べた限り、この2つがあった。
前者のほうが公式っぽさはあるけど、サンプルをビルドしてみたら、最新のClaudeCodeからはtoolsが認識できなかった。
原因はよくわからんけど、MCPの実装とClaudeCodeの実装で何か問題があるのであろう・・・。
No tools available on Claude desktop · Issue #326 · modelcontextprotocol/rust-sdk https://github.com/modelcontextprotocol/rust-sdk/issues/326
たぶんこれと同じ状況だと思われるけど、ちゃんとは調べてない。
rust-mcp-sdk
は、最新の2025-06-18
のSpecにも対応してるしちゃんと動作するようだったので、こっちを使うことにした。
コード
そもそも要件もミニマムなので、大して詰まるところもなかった。リポジトリ参照。
いちおうメイン。
#[tokio::main]
async fn main() -> SdkResult<()> {
let server_details = InitializeResult {
server_info: Implementation {
name: "OXC AST MCP Server".to_string(),
version: "0.1.0".to_string(),
title: None,
},
capabilities: ServerCapabilities {
tools: Some(ServerCapabilitiesTools { list_changed: None }),
..Default::default()
},
meta: None,
instructions: Some("This server helps you to understand how ASTs are structured by OXC parser. Also useful to quick check for your code which should be valid syntax.".to_string()),
protocol_version: LATEST_PROTOCOL_VERSION.to_string(),
};
let server = server_runtime::create_server(
server_details,
StdioTransport::new(TransportOptions::default())?,
MyServerHandler,
);
server.start().await
}
あとは、create_server(details, transport, handler)
のhandler
で、ServerHandler
traitを実装しておくだけ。
struct MyServerHandler;
#[async_trait]
impl ServerHandler for MyServerHandler {
async fn handle_list_tools_request(
&self,
_request: ListToolsRequest,
_runtime: &dyn McpServer,
) -> std::result::Result<ListToolsResult, RpcError> {
Ok(ListToolsResult {
meta: None,
next_cursor: None,
tools: MyTools::tools(),
})
}
async fn handle_call_tool_request(
&self,
request: CallToolRequest,
_runtime: &dyn McpServer,
) -> std::result::Result<CallToolResult, CallToolError> {
let tool_params = MyTools::try_from(request.params).map_err(CallToolError::new)?;
match tool_params {
MyTools::DocsTool(docs_tool) => docs_tool.call(),
MyTools::ParseTool(parse_tool) => parse_tool.call(),
MyTools::CheckTool(check_tool) => check_tool.call(),
}
}
}
よしなにtoolsを呼び分けるくらいしかやることない。
#[async_trait]
は、async
な関数をtrait
として実装する場合に必要とのこと。
why async fn in traits are hard · baby steps https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/
あとはOXCを使っていつものコードを書いてるだけ。
docs
toolで返すための元ネタを生成するのが一番大変だった。
特定のcrateで公開されてるstructとenumを列挙する | Memory ice cubes https://leaysgur.github.io/posts/2025/08/01/095617/
まとめ
RustでもMCPは簡単に実装できる。(少なくとも、ローカルでstdioで使う程度の用途なら) というかただのJSON-RPCサーバーなので、何も難しいことはなかった。
まぁ作ってみたはいいものの、AI関連のツールは、実装時の設計やデザインもさることながら、実装後の評価が一番難しいよな〜って。