COCO MCP Toolbox
COCOA (Code Context Agent) is a FastMCP server that publishes CLDK’s Java analysis as Model Context Protocol tools. Point it at a Java project and any MCP host (Claude Desktop, an MCP-aware IDE, or your own agent) can call get_callers, get_callees, reachability, CRUD discovery, and the rest over a standard wire protocol, each answer backed by real static analysis rather than a guess.
Where the in-process Code Context Agent plugin shells out to CLDK via Bash, the toolbox runs CLDK as a long-lived server and exposes it as fixed tools. Same analysis layer, different delivery.
How state is managed: dependency injection
Section titled “How state is managed: dependency injection”Building a JavaAnalysis is the expensive step (it parses the project and constructs the symbol table and call graph). The toolbox does it once, when the server starts, and then injects that single instance into every tool call through the FastMCP lifespan context, so no tool ever re-analyzes the project.
A CLDKAnalysis dataclass holds the instance and a lifespan yields it as the server’s context:
@dataclassclass CLDKAnalysis: project_path: Path analysis_instance: JavaAnalysis | None = field(default=None, init=False)
def __post_init__(self): # Built ONCE, at server startup. self.analysis_instance = CLDK("java").analysis(project_path=str(self.project_path))
def create_lifespan(project_path: Path): @asynccontextmanager async def coco_lifespan(server: FastMCP) -> AsyncIterator[CLDKAnalysis]: yield CLDKAnalysis(project_path=project_path) # becomes the lifespan context return coco_lifespan
mcp = FastMCP(name="cocoa", lifespan=create_lifespan(project_path), ...)Every tool takes a FastMCP Context and pulls the shared analysis off the lifespan context, that is the dependency injection:
async def get_callers_tool(ctx: Context, target_class_name: str, target_method_declaration: str, using_symbol_table: bool = True): # Injected, not rebuilt: the same JavaAnalysis every call. analysis = ctx.request_context.lifespan_context.analysis_instance return json.dumps(analysis.get_callers(target_class_name, target_method_declaration, using_symbol_table))Tools are registered by naming convention at startup: every function ending in _tool (and every schema _explainer) is discovered and handed to mcp.add_tool(...), so adding a capability is just adding a function.
What’s in the toolbox
Section titled “What’s in the toolbox”Over fifty MCP tools mirror the JavaAnalysis facade, each returning JSON (Pydantic model_dump()) or a NetworkX node-link graph:
- Structure:
get_symbol_table,get_compilation_units,get_classes,get_class,get_methods,get_method,get_fields,get_constructors,get_nested_classes,get_application_view. - Call graph:
get_call_graph(and_json),get_callers,get_callees,get_class_call_graph. - Hierarchy:
get_sub_classes,get_extended_classes,get_implemented_interfaces,get_entry_point_classes,get_entry_point_methods. - CRUD:
get_all_crud_operationsplus per-verb create/read/update/delete variants. - Comments: comments and docstrings by method, class, file, or whole project;
remove_all_comments. - Schema explainers: companion tools that describe CLDK’s data models (
JType,JCallable,JComment, and friends) so the model knows the shape of what it gets back.
Run it
Section titled “Run it”-
Install
uv:Terminal window curl -LsSf https://astral.sh/uv/install.sh | sh -
Run the server straight from the repo against a Java project:
Terminal window uvx --from git+https://github.com/codellm-devkit/cocoa-py cocoa toolbox --project-path /path/to/java/projectOr clone and run locally:
Terminal window git clone https://github.com/codellm-devkit/cocoa-py.gitcd cocoa-pyuv run cocoa toolbox --project-path /path/to/java/project
Connect a host
Section titled “Connect a host”Register the server with any MCP host over stdio, for example in a Claude Desktop / MCP client config:
{ "mcpServers": { "cocoa": { "command": "uvx", "args": [ "--from", "git+https://github.com/codellm-devkit/cocoa-py", "cocoa", "toolbox", "--project-path", "/path/to/java/project" ] } }}Or drive it directly with an MCP client:
server_params = StdioServerParameters( command="cocoa", args=["toolbox", "--project-path", "/path/to/java/project"],)
async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool( "get_callers_tool", arguments={ "target_class_name": "org.example.Service", "target_method_declaration": "save(Order)", }, ) print(result.content[0].text)