-
-
Notifications
You must be signed in to change notification settings - Fork 47
ADFA-5083: Host-side MCP server with the first CoGo-aware tool #1659
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hal-eisen-adfa
wants to merge
17
commits into
stage
Choose a base branch
from
ADFA-5083-mcp
base: stage
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
141b102
ADFA-5083: Design spec for minimal HTTP MCP server
hal-eisen-adfa ae6b687
ADFA-5083: Implementation plan for the hello-world MCP server
hal-eisen-adfa 1e8ee28
ADFA-5083: Standalone Gradle build for the MCP server
hal-eisen-adfa 62a9803
ADFA-5083: Correct two plan errors found while scaffolding
hal-eisen-adfa 4a14ffd
ADFA-5083: Add the ping tool and prove the transport end to end
hal-eisen-adfa 2a12a3f
ADFA-5083: Correct the tool-handler signature in the plan
hal-eisen-adfa 940a10b
ADFA-5083: Document how to run and register the MCP server
hal-eisen-adfa f55a6a1
ADFA-5083: Add is_cogo_installed, the first adb-backed tool
hal-eisen-adfa 03515b0
ADFA-5083: Document is_cogo_installed and the Adb testing seam
hal-eisen-adfa 32bdf77
ADFA-5083: Make the server self-describing
hal-eisen-adfa 4f02aae
ADFA-5083: Add cogo_home, which navigates to the IDE home screen
hal-eisen-adfa 7e5382a
ADFA-5083: Stop debug builds registering two launcher activities
hal-eisen-adfa 90a41ea
ADFA-5083: Add scored tool priorities and share adbFailure
hal-eisen-adfa 38f9fdf
ADFA-5083: Add list_projects, list_templates and list_project_files
hal-eisen-adfa c41db8f
ADFA-5083: Split tools into one file each and share the adb idioms
hal-eisen-adfa 824aa65
ADFA-5083: Record code-review defects in the backlog
hal-eisen-adfa 0b0ead4
ADFA-5083: Add a status checkpoint for the MCP server work
hal-eisen-adfa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <resources> | ||
| <!-- LeakCanary registers leakcanary.internal.activity.LeakLauncherActivity as a second MAIN/LAUNCHER activity-alias, | ||
| so a debug build advertises two launcher entries. Any generic launch (monkey -c LAUNCHER, cmd package resolve-activity, tapping | ||
| the icon) then resolves to the system ResolverActivity instead of the IDE, which breaks tooling that drives the app. Debug | ||
| builds are the norm for on-device automation, so drop the extra icon. Leak reports are unaffected: this only disables the | ||
| launcher alias. LeakActivity itself stays registered and is still reachable from LeakCanary's notification. --> | ||
| <bool name="leak_canary_add_launcher_icon">false</bool> | ||
| </resources> |
578 changes: 578 additions & 0 deletions
578
docs/superpowers/plans/2026-08-11-mcp-server-hello-world.md
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| # ADFA-5083: CodeOnTheGo MCP server - hello world | ||
|
|
||
| **Status:** design approved, not implemented | ||
| **Ticket:** ADFA-5083 - "It is difficult for AI coding agents to navigate the app using low level adb commands. Let's give them a tool to be more successful." | ||
|
|
||
| ## Goal | ||
|
|
||
| Stand up the smallest possible MCP server over HTTP, prove the transport end to end, and merge it. Everything of actual value gets added incrementally on top. This PR is scaffolding, deliberately. | ||
|
|
||
| ## Decisions | ||
|
|
||
| | Decision | Choice | Why | | ||
| |---|---|---| | ||
| | Where the server runs | Host-side (dev machine), not in the APK | Zero APK risk, iterates independently of app releases, works against any build. Trades away privileged access to IDE internals - revisit only if a future tool actually needs it. | | ||
| | Language | Kotlin/JVM | Matches the repo's primary language. Official MCP Kotlin SDK exists and supports Streamable HTTP first-class. | | ||
| | Where the code lives | Standalone Gradle build at `mcp/`, absent from the root `settings.gradle.kts` | Exact pattern of the existing `apk-viewer-plugin/` and `markdown-preview-plugin/`. Keeps Ktor and kotlinx.serialization out of `:app`'s classpath and out of `gradle/libs.versions.toml`. | | ||
| | Transport | Streamable HTTP, `http://127.0.0.1:<port>/mcp` | Current MCP standard. SSE is deprecated and exists only for backward compatibility. | | ||
| | TLS | None | Loopback only - there is no network hop to intercept, and TLS would cost a self-signed cert plus per-client trust config for no security gain. TLS becomes mandatory the day the server binds a non-loopback interface; that is a separate ticket. | | ||
| | Tool surface | One tool, `ping` | Isolates the transport and handshake from every other concern. If it fails, the cause is unambiguous. | | ||
|
|
||
| ### Premise worth stating | ||
|
|
||
| A generic `android-mcp-server` (`npx -y android-mcp-server`) is already registered in the user-scope `~/.claude.json` with `get_ui_tree`, `tap_element`, `screenshot`, and `scroll_to_element`. It overlaps ADFA-5083's stated goal. | ||
|
|
||
| The justification for a bespoke server is that it can be **CoGo-aware** - it can know the IDE's screens, project state, and build status rather than treating the app as an opaque view hierarchy. That is the differentiator, and it does not exist in this PR. It should be written into the ticket before PR #2 defines any real tool. | ||
|
|
||
| ## Architecture | ||
|
|
||
| ``` | ||
| Claude Code (or any MCP client) | ||
| | | ||
| | Streamable HTTP, JSON-RPC 2.0 | ||
| v | ||
| http://127.0.0.1:3000/mcp | ||
| | | ||
| Ktor CIO embedded server | ||
| | | ||
| mcpStreamableHttp { } <- io.modelcontextprotocol:kotlin-sdk-server | ||
| | | ||
| Server(serverInfo, options) | ||
| | | ||
| tool: ping -> "pong" | ||
| ``` | ||
|
|
||
| Single process, single responsibility. No adb, no device, no state. | ||
|
|
||
| ## Layout | ||
|
|
||
| ``` | ||
| mcp/ | ||
| settings.gradle.kts rootProject.name = "cogo-mcp" | ||
| gradle/wrapper/ Gradle 8.14.4 (matches root wrapper) | ||
| gradlew, gradlew.bat | ||
| build.gradle.kts kotlin("jvm") + application, Java 17 | ||
| src/main/kotlin/com/itsaky/androidide/mcp/Main.kt | ||
| src/test/kotlin/com/itsaky/androidide/mcp/PingTest.kt | ||
| README.md how to run, how to register | ||
| .gitignore | ||
| ``` | ||
|
|
||
| `mcp/` is invisible to the root build. The root `settings.gradle.kts` is not touched. | ||
|
|
||
| ## Pinned versions | ||
|
|
||
| Verified against Maven Central on 2026-08-10, not assumed: | ||
|
|
||
| | Artifact | Version | Note | | ||
| |---|---|---| | ||
| | `io.modelcontextprotocol:kotlin-sdk-server` | `0.15.0` | Latest release, published 2026-07-28 | | ||
| | `io.ktor:ktor-server-cio` | `3.5.1` | Matches the SDK's own transitive Ktor; latest is 3.5.2 | | ||
| | Kotlin | `2.4.10` | Latest stable. **Must be >= 2.4.0**: the SDK is built against `kotlin-stdlib 2.4.0`, so a 2.3.0 compiler would reject its metadata. The root repo's 2.3.0 is irrelevant here - separate build. | | ||
| | Java | `17` | Repo-wide standard (`BuildConfig.JAVA_VERSION`, `CONTRIBUTING.md`). Provided by flox; the bare shell has JDK 21. | | ||
| | Gradle | `8.14.4` | Matches the root wrapper | | ||
|
|
||
| The SDK does **not** pull a Ktor server engine transitively - the engine must be declared explicitly. | ||
|
|
||
| The kotlinx.serialization **compiler plugin is not needed**: hello-world declares no `@Serializable` classes, and the `kotlinx-serialization-json` runtime arrives transitively via `ktor-serialization-kotlinx-json`. | ||
|
|
||
| ## Behavior | ||
|
|
||
| ```kotlin | ||
| fun main(args: Array<String>) { | ||
| val port = args.firstOrNull()?.toIntOrNull() ?: 3000 | ||
|
|
||
| val server = Server( | ||
| serverInfo = Implementation(name = "cogo-mcp", version = "0.1.0"), | ||
| options = ServerOptions( | ||
| capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)), | ||
| ), | ||
| ) | ||
|
|
||
| server.addTool( | ||
| name = "ping", | ||
| description = "Health check. Returns pong.", | ||
| inputSchema = ToolSchema(properties = JsonObject(emptyMap())), | ||
| ) { CallToolResult(content = listOf(TextContent("pong"))) } | ||
|
|
||
| embeddedServer(CIO, host = "127.0.0.1", port = port) { | ||
| mcpStreamableHttp { server } | ||
| }.start(wait = true) | ||
| } | ||
| ``` | ||
|
|
||
| Port is `args[0]`, defaulting to 3000. Host is hardcoded to `127.0.0.1` - binding `0.0.0.0` would expose an unauthenticated tool server to the local network, and there is no reason to. | ||
|
|
||
| ## Verification | ||
|
|
||
| **Test first.** `PingTest` starts the server on an ephemeral port, connects using the SDK's own MCP **client** over Streamable HTTP, and drives the real protocol: | ||
|
|
||
| 1. `initialize` - handshake succeeds, server reports name `cogo-mcp` | ||
| 2. `tools/list` - returns exactly one tool named `ping` | ||
| 3. `tools/call ping` - returns text content `"pong"` | ||
|
|
||
| Calling the lambda directly would prove nothing about the transport, which is the entire point of this PR. | ||
|
|
||
| Manual check, documented in `mcp/README.md`: | ||
|
|
||
| ```bash | ||
| # from mcp/ | ||
| flox activate -d ../flox/local -- ./gradlew run | ||
| ``` | ||
|
|
||
| ## Registration | ||
|
|
||
| `mcp/README.md` documents the snippet; **PR #1 does not edit the tracked `.mcp.json`.** | ||
|
|
||
| ```json | ||
| { | ||
| "mcpServers": { | ||
| "cogo": { "type": "http", "url": "http://127.0.0.1:3000/mcp" } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Reason: `.mcp.json` is committed and shared. An `http` entry pointing at a process nobody launched makes Claude Code report a connection failure at startup for every developer on the team. Registering it becomes worthwhile once the server does something worth connecting to. | ||
|
|
||
| ## Formatting | ||
|
|
||
| Root Spotless **does** cover `mcp/` - its Kotlin target is `fileTree(rootDir)` with `**/src/*/kotlin/**/*.kt`, and `kotlinGradle` targets `**/*.gradle.kts`. Neither excludes top-level standalone directories, which is why `apk-viewer-plugin/` and `markdown-preview-plugin/` are already formatted by it. | ||
|
|
||
| Consequences: **tabs** for indentation in all `mcp/` Kotlin and `.gradle.kts` sources, ktlint rules apply, and `./gradlew spotlessApply` runs from the **root**, not from `mcp/`. | ||
|
|
||
| Markdown is not a Spotless target, so this document is free-form. | ||
|
|
||
| ## Out of scope | ||
|
|
||
| Explicitly deferred, in rough priority order for later PRs: | ||
|
|
||
| - Any adb-backed tool (`list_devices`, `screenshot`, `get_ui_tree`, `tap`, `launch_app`) | ||
| - CoGo-specific awareness: IDE screen identification, project/build state, editor contents | ||
| - TLS | ||
| - CI wiring - no workflow builds standalone directories today; `mcp/` is verified locally in PR #1. Worth a follow-up ticket. | ||
| - Authentication - unnecessary while bound to loopback, mandatory the moment it is not | ||
| - Packaging beyond `./gradlew run` (a distributable start script via `installDist`, a daemon, a launcher) | ||
|
|
||
| ## Risks | ||
|
|
||
| 1. **SDK API surface.** The `mcpStreamableHttp { }` builder and the `io.modelcontextprotocol.kotlin.sdk.types.*` package paths come from the SDK's README on `main`, which may be ahead of the 0.15.0 release. First implementation step is to compile against 0.15.0 and correct the imports and signatures to whatever that version actually ships. Do not assume the README matches the release. | ||
| 2. **Kotlin version floor.** If the 2.4.10 toolchain causes trouble, the fallback is 2.4.0 (the SDK's own stdlib version), not 2.3.0. | ||
| 3. **Spotless build-output pruning.** `buildOutputExcludes` is derived from the root build's `allprojects`, so `mcp/build/` is not pruned from the Spotless walk. Low impact - build output does not match `**/src/*/kotlin/**` - but if `spotlessCheck` starts complaining about generated files, that is the cause. | ||
|
|
||
| ## Definition of done | ||
|
|
||
| - [ ] `flox activate -d flox/local -- ./gradlew run` from `mcp/` starts a server on `127.0.0.1:3000` | ||
| - [ ] `PingTest` passes: initialize, tools/list, tools/call all succeed over real Streamable HTTP | ||
| - [ ] Root `./gradlew spotlessCheck` passes | ||
| - [ ] `mcp/README.md` documents run and registration | ||
| - [ ] Root build is unaffected - `settings.gradle.kts` and `gradle/libs.versions.toml` unchanged | ||
| - [ ] PR into `stage` from `ADFA-5083-mcp` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| build/ | ||
| .gradle/ |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the documented flox invocation.
These commands activate flox from
mcp/. The plan states that the flox activation hook rejects activation outside the repository root. Start from the repository root, then change intomcp/inside the activated shell.Proposed correction
Also applies to: 164-164
🤖 Prompt for AI Agents