The InternalsAgent is a self-hosting docs agent: each of its skills corresponds to a source file in the framework. Exposed over MCP, your IDE's AI assistant (Cursor, Claude Desktop, etc.) can call those skills as tools to get curated, authoritative knowledge about how agents-kt works — straight from the source.
From the project root:
./gradlew runInternalsAgentThe server binds to http://localhost:8765/mcp and prints a list of every skill it exposes.
To pick a different port:
./gradlew runInternalsAgent --args="9000"The server runs until you Ctrl+C it.
If your MCP client prefers to spawn tools over stdio, pass --stdio to the same InternalsAgent entrypoint. Stdio mode reads one JSON-RPC envelope per stdin line and writes only MCP response envelopes to stdout. Use a packaged app/JAR entrypoint for IDE config; Gradle itself prints build output to stdout and is not a clean stdio MCP command.
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (Mac) or %APPDATA%\Claude\claude_desktop_config.json (Windows). Add:
{
"mcpServers": {
"agents-kt-internals": {
"url": "http://localhost:8765/mcp"
}
}
}Restart Claude Desktop. The agents-kt-internals tools will appear in the tool list. Ask things like:
- "What does Pipeline.kt do?" → routes to
composition_pipeline_pipeline_kt - "How does the agentic loop handle budget caps?" → routes to
model_agenticloop_ktandmodel_budgetconfig_kt - "Walk me through how a Branch streams events." → routes to
composition_branch_branchsessionextension_kt
Edit ~/.cursor/mcp.json with the same shape:
{
"mcpServers": {
"agents-kt-internals": {
"url": "http://localhost:8765/mcp"
}
}
}Restart Cursor (or reload the MCP config from the command palette).
Anything that speaks the MCP Streamable HTTP transport can connect — point it at http://localhost:8765/mcp. Stdio-capable clients can instead spawn a runner process with --stdio. See the MCP spec for client conformance.
Example stdio shape for a packaged InternalsAgent command:
{
"mcpServers": {
"agents-kt-internals": {
"command": "/path/to/agents-kt-internals",
"args": ["--stdio"]
}
}
}Every skill is named <package-path>_<filename>_kt, derived mechanically from the source file's path. The path is the authoritative key.
| Source file | Skill name |
|---|---|
src/main/kotlin/agents_engine/core/Agent.kt |
core_agent_kt |
src/main/kotlin/agents_engine/composition/branch/BranchBuilder.kt |
composition_branch_branchbuilder_kt |
src/main/kotlin/agents_engine/mcp/McpClient.kt |
mcp_mcpclient_kt |
agents-kt-ksp/src/main/kotlin/agents_engine/ksp/SchemaEmitter.kt |
ksp_schemaemitter_kt |
If your IDE LLM is having trouble picking the right skill, you can name it directly: "call composition_pipeline_pipeline_kt and tell me what then returns".
Each skill returns a curated markdown adjunct for the corresponding source file. The adjunct covers:
- The file's purpose (what it owns, what it does NOT own).
- Public API shape and DSL surface.
- Subtle behaviors and contracts (freeze semantics, fail-fast points, race-safe patterns).
- Pointers to related files for cross-reference.
The adjuncts live alongside the source in src/main/resources/internals-agent/<path>.md. They're kept synchronized with each file's top-of-file /** ... */ KDoc — deliberate redundancy so a human reading source sees the same story the IDE LLM sees.
Drop a new adjunct under src/main/resources/internals-agent/<package>/<File>.md with this shape:
---
description: <one-line tool description shown to the IDE LLM — be specific so the LLM picks it correctly>
---
# `<package>/<File>.kt` — <short purpose>
<body — markdown, anything that helps the LLM understand the file>The description: line in the frontmatter is what the IDE's LLM sees when picking a tool. Make it precise: name the file, name the public types, name the key behaviors. The body is what the LLM gets back as the tool result — write for someone who needs to reason about the file but can't easily open it.
That's it — no InternalsAgent.kt edit needed. The agent scans src/main/resources/internals-agent/ at construction time, derives the skill name from the path, and registers the skill automatically.
buildInternalsAgent() returns an Agent<String, String> with no model { } configured — the IDE's LLM does all the reasoning. Each skill is implementedBy { _ -> loadResource(path) }: a pure data fetch.
McpServer.from(agent) exposes every skill as an MCP tool over Streamable HTTP. Tool name = skill name; tool description = adjunct's description: frontmatter; tool result = the rest of the adjunct's body.
See:
src/main/kotlin/agents_engine/runtime/internals/InternalsAgent.kt— the scanner + registration.src/main/kotlin/agents_engine/runtime/internals/Main.kt— the MCP server runner.src/main/kotlin/agents_engine/mcp/McpServer.kt— the HTTP/MCP transport and shared server dispatch.src/main/kotlin/agents_engine/mcp/McpStdioServer.kt— the server-side stdio transport.src/main/kotlin/agents_engine/core/Resources.kt—loadResourceclasspath helper.
Port already in use. Pass a different port:
./gradlew runInternalsAgent --args="9999"Claude Desktop doesn't show the tools. Confirm the JSON config is valid (jq . < ~/Library/Application\ Support/Claude/claude_desktop_config.json). Restart Claude Desktop fully (quit + relaunch, not just close the window). The server logs every incoming MCP request, so check runInternalsAgent's stdout while sending a query.
Skill returns "Resource not found". Should never happen — the agent fails fast at construction if any adjunct is missing. If it does, file an issue with the skill name and the server's stderr.
Adjunct count drift. A new test (InternalsAgentTest.kt) verifies the skill count matches the adjunct file count. If you add a new adjunct, bump the expected count in the test.