Skip to content

Conversation

@ochafik
Copy link
Collaborator

@ochafik ochafik commented Dec 11, 2025

Summary

Kotlin low-level SDK (AppBridge) for hosting MCP Apps in Android applications, and basic example Android app.

To test, ensure you have an Android emulator & run:

./examples/basic-host-kotlin/scripts/run.sh
Show commands to install an Android emulator on Mac
brew install --cask android-commandlinetools

# Accept licenses
yes | sdkmanager --licenses

# Install required components
sdkmanager "platform-tools" "emulator" "platforms;android-34" "system-images;android-34;google_apis;arm64-v8a"

# Create an AVD (Android Virtual Device)
avdmanager create avd -n Pixel_8 -k "system-images;android-34;google_apis;arm64-v8a" -d pixel_8

# Add to PATH (add to ~/.zshrc)
export ANDROID_HOME=/opt/homebrew/share/android-commandlinetools
export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH

# Start emulator
emulator -avd Pixel_8

What's included

SDK (kotlin/)

  • AppBridge: Host-side protocol handler for MCP Apps communication
  • Transport abstraction: McpAppsTransport interface for different transport implementations
  • Generated types: From generated JSON Schema via scripts/generate-kotlin-types.ts
  • Full MCP Apps protocol support

Example App (examples/basic-host-kotlin/)

  • Jetpack Compose app demonstrating SDK usage
  • Bottom toolbar UI with server/tool pickers
  • WebViewTransport: Android WebView communication (in example, not SDK - Android-specific)
  • Tool forwarding to MCP servers
  • Server name displayed in tool call cards
  • Toast notifications for messages/logs

CI

  • Kotlin job with JDK 21 on ubuntu-latest
  • Builds and tests the SDK

Architecture Note

The WebViewTransport is in the example app rather than the SDK because:

  • The SDK is pure JVM (not Android-specific)
  • Android WebView APIs require Android AAR dependencies
  • Swift SDK can include WKWebViewTransport because Swift compiles natively for iOS

How to test

cd examples/basic-host-kotlin
export ANDROID_HOME=/path/to/android-sdk
./scripts/run.sh Pixel_8

🤖 Generated with Claude Code

ochafik and others added 3 commits December 11, 2025 03:30
Kotlin SDK for hosting MCP Apps in Android applications.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Kotlin SDK for hosting MCP Apps in Android applications.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Kotlin SDK for hosting MCP Apps in Android applications:

SDK (kotlin/):
- AppBridge: Host-side protocol handler
- Transport abstraction (McpAppsTransport interface)
- Generated types from schema.json
- Full MCP Apps protocol support

Example App (examples/basic-host-kotlin/):
- Jetpack Compose app demonstrating SDK usage
- Bottom toolbar UI (server/tool pickers)
- WebViewTransport for Android WebView
- Tool forwarding to MCP servers
- Server name displayed in tool call cards
- Toast notifications for messages/logs

Type Generator:
- scripts/generate-kotlin-types.ts
- Generates Kotlin data classes from JSON Schema

CI:
- Kotlin job with JDK 21 on ubuntu-latest
- Builds and tests SDK

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Comment on lines +54 to +72
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: "21"
distribution: "temurin"

- name: Build Kotlin SDK
working-directory: kotlin
run: |
chmod +x gradlew
./gradlew build
- name: Test Kotlin SDK
working-directory: kotlin
run: ./gradlew test

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 1 day ago

The best fix is to explicitly add a permissions block at the root level of the workflow file .github/workflows/ci.yml. This will apply the specified minimal permissions to all jobs (unless any override them individually). As recommended, setting contents: read is the least privilege needed for typical CI build/test workflows. No other changes are required unless future workflow steps are added that require additional permissions.

To implement this, insert the following YAML section directly after the name: CI line (so it applies globally):

permissions:
  contents: read

This ensures the workflow restricts GitHub token permissions to only those required for read access to repository contents.

Suggested changeset 1
.github/workflows/ci.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,4 +1,6 @@
 name: CI
+permissions:
+  contents: read
 
 on:
   push:
EOF
@@ -1,4 +1,6 @@
name: CI
permissions:
contents: read

on:
push:
Copilot is powered by AI and may make mistakes. Always verify output.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 11, 2025

Open in StackBlitz

npm i https://pkg.pr.new/modelcontextprotocol/ext-apps/@modelcontextprotocol/ext-apps@134

commit: e873118

@ochafik ochafik requested a review from liady December 11, 2025 14:36
@ochafik ochafik marked this pull request as draft December 11, 2025 14:46
ochafik and others added 10 commits December 11, 2025 20:41
Apply the same two-phase close pattern introduced in basic-host:
- Add isDestroying state to ToolCallState for tracking teardown phase
- Implement requestClose() and completeClose() for two-phase teardown
- Send ui/resource-teardown request when closing apps
- Wait for app response before removing (with 3s timeout)
- Dim card and show "Closing" status during teardown
- Non-app results close immediately

This follows the spec: "Host SHOULD wait for a response before
tearing down the resource (to prevent data loss)."

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Removed the basic-host-swift example app and the Swift type generation
script as they are no longer needed for this branch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The swift job referenced a swift/ directory that doesn't exist.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…cture

Add testable protocol handler and comprehensive test coverage:

- McpAppBridgeProtocol: Extracted protocol logic into testable class
  - Handles JSON-RPC message parsing and dispatch
  - Manages protocol state (initialization, teardown)
  - Provides callbacks for all protocol events

- Unit tests (17 tests, all passing):
  - Initialization handshake (initialize, initialized)
  - App→Host: size-changed, message, open-link, logging, tools/call
  - Host→App: tool-input, tool-result, tool-cancelled
  - Teardown flow with request/response tracking
  - Edge cases: unknown methods, malformed JSON

- Test HTML app (test-app.html):
  - Implements full MCP Apps protocol
  - Visual protocol log for debugging
  - Buttons to trigger App→Host messages
  - Can be used for manual testing and instrumentation tests

- Android instrumentation test skeleton:
  - Loads test app in WebView
  - Verifies protocol handshake
  - Tests two-way communication

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Add link to basic-host-kotlin in top-level README examples section
- Add collapsible Mac installation instructions for Android SDK/emulator
- Fix stale ext-apps2 references to ext-apps

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The size-changed notification from Apps was being logged but not applied.
Now the WebView properly resizes when the App requests a different height.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Changed from a fixed 3-second delay to polling every 50ms with a 500ms
max timeout. This makes card dismissal feel much snappier while still
giving apps time to respond to teardown requests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Expose an optional timeout parameter with a sensible 500ms default.
This allows hosts to control how long to wait for the App to complete cleanup.

`sendResourceTeardown(timeout: Duration = 500.milliseconds)`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants