Shuttle MCP server
컴포넌트 간에 대용량 Serializable 객체를 안전하게 전달하여 TransactionTooLargeException 크래시를 방지하는 Android 프레임워크입니다.
다른 이름 grarcht/Shuttle
- 스타
- 87
- Tools
- —
- 다운로드, 30일
- —
- 최신 버전
- 4.0.0
- 마지막 커밋
- 2026년 8월 2일
- 라이선스
- MIT
Shuttle MCP란?
Shuttle은(는) MCP server입니다. 컴포넌트 간에 대용량 Serializable 객체를 안전하게 전달하여 TransactionTooLargeException 크래시를 방지하는 Android 프레임워크입니다.
프로젝트 README에서 생성된 요약
Shuttle MCP server 정보
Android 개발자는 Shuttle을 사용하여 컴포넌트 간에 대용량 Serializable 객체를 전달함으로써 TransactionTooLargeException 크래시를 방지할 수 있습니다. 대량의 데이터를 Intent나 Bundle을 통해 직접 전달하면 실제 서비스 환경에서 크래시가 발생하기 쉬운데, 이 프레임워크는 객체를 저장소에 보관하고 binder transaction을 통해서는 작은 식별자만 전달합니다. 이 방식은 데이터의 단일 진실 공급원(single source of truth)을 유지하면서 binder transaction이 안전한 크기 제한 내에 머물도록 보장합니다.
이를 구현하기 위해 개발자는 Kotlin과 Android Gradle Plugin을 사용하여 Maven Central을 통해 Shuttle을 통합합니다. 설정 과정에는 Shuttle Cargo Gradle plugin 구성과 KSP를 통한 직렬화 코드 자동 생성을 위한 @ShuttleCargo 어노테이션 사용이 포함됩니다. 이 프레임워크는 영속성을 위한 Room이나 데이터 이동을 용이하게 하는 Navigation Component 등 다양한 통합 패턴을 지원합니다. 또한 데이터의 전체 생명주기를 관리하며, 저장 공간 낭비를 방지하기 위해 자동 또는 요청 시 데이터 정리 기능을 제공합니다.
활용 사례
- 1binder transaction 크래시 방지Intent나 Bundle을 통해 대용량 객체를 전달할 때 발생하는 실제 서비스 크래시를 방지합니다.
- 2객체 직렬화 자동화어노테이션을 사용하여 수동 작업 없이 cargo 클래스에 필요한 직렬화 코드를 생성합니다.
- 3대용량 데이터 전송 관리대용량 객체를 저장소에 보관하고 목적지 컴포넌트에는 작은 식별자만 전달합니다.
- 4데이터 정리 자동화전달이 완료된 cargo가 저장소에서 자동으로 삭제되도록 관리합니다.
프로젝트의 README를 바탕으로 작성되었습니다. 모든 활용 사례는 해당 내용의 인용문과 대조하여 확인합니다.
Shuttle MCP 설치 방법
형식 확인: 2026년 11월 10일Cursor에 추가Goose에 추가Claude Code에 Shuttle MCP 설치
$ claude mcp add shuttle -- docker run -i --rm ghcr.io/grarcht/shuttle-mcp:4.0.0Cursor에 Shuttle MCP 설치
~/.cursor/mcp.json
{
"mcpServers": {
"shuttle": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/grarcht/shuttle-mcp:4.0.0"
]
}
}
}VS Code에 Shuttle MCP 설치
.vscode/mcp.json
{
"servers": {
"shuttle": {
"type": "stdio",
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/grarcht/shuttle-mcp:4.0.0"
]
}
}
}VS Code CLI에 Shuttle MCP 설치
$ code --add-mcp '{"name":"shuttle","type":"stdio","command":"docker","args":["run","-i","--rm","ghcr.io/grarcht/shuttle-mcp:4.0.0"]}'Claude Desktop에 Shuttle MCP 설치
claude_desktop_config.json
{
"mcpServers": {
"shuttle": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/grarcht/shuttle-mcp:4.0.0"
]
}
}
}Codex에 Shuttle MCP 설치
$ codex mcp add shuttle -- docker run -i --rm ghcr.io/grarcht/shuttle-mcp:4.0.0Codex config.toml에 Shuttle MCP 설치
~/.codex/config.toml
[mcp_servers.shuttle] command = "docker" args = ["run", "-i", "--rm", "ghcr.io/grarcht/shuttle-mcp:4.0.0"]
Gemini CLI에 Shuttle MCP 설치
$ gemini mcp add -s user shuttle docker -- run -i --rm ghcr.io/grarcht/shuttle-mcp:4.0.0Gemini settings.json에 Shuttle MCP 설치
~/.gemini/settings.json
{
"mcpServers": {
"shuttle": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/grarcht/shuttle-mcp:4.0.0"
]
}
}
}자동 검사
2026년 11월 14일에 실행됨전체 보고서 →아직 인증되지 않았습니다. 서버는 80점 이상의 점수, 심각한 결함 없음, 그리고 검사에 충분한 데이터를 갖추어야 합니다. 검사 방식 · 점수를 높이기 위해 수정할 항목
- 소스 저장소 연결 및 접근 가능grarcht/shuttle is public and reachablegithub.com/grarcht/shuttle ↗통과
- 패키지가 이 저장소를 소스로 명시검사되지 않음
- 빌드 출처(Sigstore 증명)검사되지 않음
- 오픈 소스 라이선스 (OSI)MITspdx.org/licenses/MIT.html ↗통과
- 최근 1년 내 커밋 또는 릴리스Last commit or release 2026-08-02github.com/grarcht/shuttle ↗통과
- OpenSSF Scorecard검사되지 않음
- 패키지 이름이 인기 패키지와 유사하지 않음검사되지 않음
- 패키지 및 해당 종속성에 중요한 보안 권고 없음검사되지 않음
- 모델에 대한 숨겨진 지침 없음0 tool descriptions and the README read; no override phrases, credential paths or hidden characters통과
- 파괴적인 도구에 라벨 표시됨검사되지 않음
- 원격 엔드포인트가 MCP 핸드셰이크에 응답검사되지 않음
README
GitHub에서 보기
Get To It Quick
- API Documentation
- Why Shuttle?
- How It Works
- Requirements
- Quick Start
- Usage
- Architecture
- Demo Apps
- Heads Up: Know the Tradeoffs
- AI
- Contributing
- License
🚨 Why Shuttle?
🗞️ Featured in Android Weekly #594 and Android Weekly #455, validated by the Android community twice.
You've seen this before. Maybe last week:
android.os.TransactionTooLargeException: data parcel size X bytes
It didn't show up in dev. It didn't show up in QA. It showed up at 2am, in production, for real users. Your Play Store rating took the hit before anyone on the team even knew.
So you triaged it. Filed the ticket. Wrote the fix. Reviewed the PR. Ran QA again. Cut the hotfix. And then you added it to the code review checklist, hoping the next engineer would catch it before it happened again.
They won't. Not reliably. You can't review your way out of a structural problem.
Shuttle provides a modern, guarded way to pass large Serializable objects with Intent objects or save them in Bundle objects to avoid app crashes. The crash class is structurally prevented, not governed against.
Why keep spending more time and money on governance through code reviews? Why not embrace the problem by providing a solution for it?
Shuttle reduces the high level of governance needed to catch TransactionTooLargeException inducing code by:
- storing the
Serializableand passing an identifier for theSerializable - using a small-sized
Bundlefor binder transactions - avoiding app crashes from
TransactionTooLargeExceptions - enabling retrieval of the stored
Serializableat the destination
Shuttle also excels by:
- providing a solution with maven artifacts
- providing Solution Building Blocks (SBBs) for building on
- saving time by avoiding DB and table setup, especially when creating many tables for the content of different types of objects
When envisioning, designing, and creating the architecture, quality attributes and best practices were kept in mind. These attributes include usability, readability, recognizability, reusability, maintainability, and more.
| Without Shuttle | With Shuttle |
|---|---|
Large Serializable passed in Intent/Bundle |
Object stored in a warehouse; only a small identifier is passed |
| Silent in dev, catastrophic in production | Binder transaction stays within safe size limits, everywhere |
| Time and money spent on crash investigation, fixes, QA, and hotfixes | Crash class is structurally impossible |
| Requires constant code review governance | Ship with confidence |
| Engineers manually manage object lifecycles | Automatic or on-demand cargo cleanup built in |
⚙️ How It Works
The Shuttle framework takes its name from cargo transportation in the freight industry. Moving and storage companies experience scenarios where large moving trucks cannot transport cargo the entire way to the destination (warehouses, houses, et cetera). These scenarios might occur from road restrictions, trucks being overweight from large cargo, and more. As a result, companies use small Shuttle vans to transport smaller cargo groups on multiple trips to deliver the entire shipment.
After the delivery is complete, employees remove the cargo remnants from the shuttle vans and trucks. This clean-up task is one of the last steps for the job.
The Shuttle framework takes its roots in these scenarios:
- creating a smaller cargo bundle object to use in successfully delivering the data to the destination
- shuttling the corresponding large cargo to a warehouse and storing it for pickup
- linking the smaller cargo with the larger cargo by an identifier
- providing a single source of truth (Shuttle interface) to use for transporting cargo
- providing convenience functions to remove cargo (automatically or on-demand)
Shuttle applies this same logic to Android's binder transaction limit:
┌─────────────────────────────────────────────────────────────┐
│ Source Component │
│ 1. Large Serializable -> stored in Warehouse (Room/DB) │
│ 2. Small cargo ID -> passed in Intent/Bundle │
└──────────────────────────────┬──────────────────────────────┘
│ (tiny binder transaction)
┌──────────────────────────────▼──────────────────────────────┐
│ Destination Component │
│ 3. Cargo ID received -> retrieved from Warehouse │
│ 4. Large Serializable -> delivered via Kotlin Channel │
│ 5. Cleanup -> cargo removed from Warehouse automatically │
└─────────────────────────────────────────────────────────────┘
Requirements
| Requirement | Minimum |
|---|---|
| Android min SDK | 26 |
| Kotlin | 2.2.10 |
| AGP (Android Gradle Plugin) | 8.0+ |
| KSP | Required only when using @ShuttleCargo |
| Java | 21 |
🚀 Quick Start
1. Add Dependencies
settings.gradle.kts — apply the Shuttle Gradle plugin so the @ShuttleCargo annotation processor is wired into your build:
@ShuttleCargo annotates the cargo class you want to transport between screens. At build time, the annotation processor generates the serialization code Shuttle needs to store and retrieve cargo, without any manual Serializable boilerplate.
pluginManagement {
plugins {
id("com.grarcht.shuttle.cargo") version "4.0.0"
}
}
The Shuttle Cargo Gradle plugin configures KSP and registers the Shuttle compiler plugin automatically. Without it, @ShuttleCargo-annotated classes will not generate the required serialization glue code.
build.gradle.kts:
plugins {
id("com.grarcht.shuttle.cargo")
}
dependencies {
implementation(platform("com.grarcht.shuttle:shuttle-bom:4.0.0"))
implementation("com.grarcht.shuttle:framework")
implementation("com.grarcht.shuttle:framework-integrations-persistence")
implementation("com.grarcht.shuttle:framework-integrations-extensions-room")
implementation("com.grarcht.shuttle:framework-addons-navigation-component") // Optional
// Annotation-based API — use @ShuttleCargo to own the serialization contract
implementation("com.grarcht.shuttle:framework-annotations")
ksp("com.grarcht.shuttle:framework-annotations-processor")
}
Version Catalog (libs.versions.toml):
[versions]
shuttle = "4.0.0"
[plugins]
shuttle-cargo = { id = "com.grarcht.shuttle.cargo", version.ref = "shuttle" }
[libraries]
shuttle-bom = { group = "com.grarcht.shuttle", name = "shuttle-bom", version.ref = "shuttle" }
shuttle-framework = { group = "com.grarcht.shuttle", name = "framework" }
shuttle-persistence = { group = "com.grarcht.shuttle", name = "framework-integrations-persistence" }
shuttle-room = { group = "com.grarcht.shuttle", name = "framework-integrations-extensions-room" }
shuttle-navigation = { group = "com.grarcht.shuttle", name = "framework-addons-navigation-component" }
shuttle-annotations = { group = "com.grarcht.shuttle", name = "framework-annotations" }
shuttle-annotations-processor = { group = "com.grarcht.shuttle", name = "framework-annotations-processor" }
2. Initialize Shuttle
Wire CargoShuttle once as a singleton. With Hilt:
@Provides @Singleton
fun provideShuttle(facade: ShuttleFacade, warehouse: ShuttleWarehouse): Shuttle =
CargoShuttle(facade, warehouse)
@Provides @Singleton
fun provideShuttleFacade(
@ApplicationContext context: Context,
warehouse: ShuttleWarehouse
): ShuttleFacade = ShuttleCargoFacade(context as Application, warehouse)
@Provides @Singleton
fun provideShuttleWarehouse(
dao: ShuttleDataAccessObject,
factory: ShuttleDataModelFactory,
@ApplicationContext context: Context,
gateway: ShuttleFileSystemGateway
): ShuttleWarehouse = ShuttleRepository(dao, factory, context.filesDir.absolutePath, gateway)
The ShuttleDataAccessObject comes from ShuttleRoomDataDb.getInstance(ShuttleRoomDbConfig(context)).shuttleDataAccessObject. Inject Shuttle wherever you need to transport data.
3. Ship Your First Cargo
// Source: transport a large Serializable via Intent
shuttle.intentCargoWith(context, DestinationActivity::class.java)
.transport(cargoId, myLargeSerializable)
.cleanShuttleOnReturnTo(SourceFragment::class.java, DestinationActivity::class.java, cargoId)
.deliver(context)
// Destination: pick up the cargo
lifecycleScope.launch {
getShuttleChannel()
.consumeAsFlow()
.collectLatest { result ->
when (result) {
is ShuttlePickupCargoResult.Success<*> -> render(result.data as MyModel)
is ShuttlePickupCargoResult.Error<*> -> showError()
ShuttlePickupCargoResult.Loading -> showLoading()
}
}
}
That's it. No custom DB setup. No table management. No crash.
📦 Usage
The recommended entry point is the Shuttle interface with CargoShuttle as the implementation. It's a single source of truth for all cargo transport operations.
Transport with Intents
Source component:
val cargoId = ImageMessageType.ImageData.value
shuttle.intentCargoWith(context, MVCSecondControllerActivity::class.java)
.transport(cargoId, imageModel)
.cleanShuttleOnReturnTo(
MVCFirstControllerFragment::class.java,
MVCSecondControllerActivity::class.java,
cargoId
)
.deliver(context)
ℹ️
cleanShuttleOnReturnTois important. It ensures cargo is purged from the Warehouse when it's no longer needed.
Transport with the Navigation Component
Source fragment:
val cargoId = ImageMessageType.ImageData.value
navController.navigateWithShuttle(shuttle, R.id.MVVMNavSecondViewActivity)
?.logTag(LOG_TAG)
?.transport(cargoId, imageModel as Serializable)
?.cleanShuttleOnReturnTo(
MVVMNavFirstViewFragment::class.java,
MVVMNavSecondViewActivity::class.java,
cargoId
)
?.deliver()
Pick Up Cargo at the Destination
In a Fragment/Activity:
lifecycleScope.launch {
getShuttleChannel()
.consumeAsFlow()
.collectLatest { result ->
when (result) {
ShuttlePickupCargoResult.Loading -> initLoadingView(view)
is ShuttlePickupCargoResult.Success<*> -> { showSuccessView(view, result.data as ImageModel); cancel() }
is ShuttlePickupCargoResult.Error<*> -> { showErrorView(view); cancel() }
}
}
}
In a ViewModel:
viewModelScope.launch {
shuttle.pickupCargo<Serializable>(cargoId = cargoId)
.consumeAsFlow()
.collectLatest { result ->
pickupCargoMutableStateFlow.value = result
when (result) {
is ShuttlePickupCargoResult.Success<*>,
is ShuttlePickupCargoResult.Error<*> -> cancel()
else -> { /* await */ }
}
}
}
Cargo States (LCE Pattern)
Shuttle returns sealed class results that promote the Loading-Content-Error (LCE) pattern, giving consumers full control over UI state, analytics, and error handling.
| Operation | Return Type | States |
|---|---|---|
| Store cargo | Channel<ShuttleStoreCargoResult> |
Storing, Success, Error |
| Pick up cargo | Channel<ShuttlePickupCargoResult> |
Loading, Success, Error |
| Remove cargo | Channel<ShuttleRemoveCargoResult> |
Removing, Success, Error |
Using @ShuttleCargo
Annotate any data class you want to transport through Shuttle. The annotation processor generates the serialization code at build time so you never write it by hand:
@ShuttleCargo
data class ImageModel(
val id: String,
val title: String,
val byteArray: ByteArray
)
That's the entire declaration. No Serializable implementation, no custom read/write methods. Then transport it the same way as any other object:
shuttle.intentCargoWith(context, DestinationActivity::class.java)
.transport(cargoId, imageModel)
.deliver(context)
Cleaning Up
Cargo is automatically removed when using cleanShuttleOnReturnTo. For manual control:
// Remove a specific cargo item
shuttle.removeCargoBy(cargoId)
// Remove all cargo
shuttle.removeAllCargo()
🏗️ Architecture
Shuttle is a layered Solution Building Block (SBB) framework. Each layer has a well-defined responsibility and no layer forces technology choices on consumers.
Module Overview
| Module | Role | Required? |
|---|---|---|
framework |
Core interfaces, transport logic, sealed result types | ✅ Yes |
framework-integrations-persistence |
Persistence abstraction/interfaces | ✅ Yes |
framework-integrations-extensions-room |
Room implementation of persistence interfaces | ⚡ Default (swappable) |
framework-addons-navigation-component |
Navigation Component integration | ➕ Optional |
framework-annotations |
@ShuttleCargo annotation for marking data classes as transportable |
➕ Optional |
framework-annotations-processor |
KSP processor that generates serialization code at build time | ➕ Optional |
framework-annotations-gradle-plugin |
Gradle plugin that wires KSP and the compiler plugin automatically | ➕ Optional |
Context Diagram

Module Dependency Diagram
graph TD
A[Your Application]
A --> B[framework / core]
A --> C[framework-addons-navigation-component]
A --> D[your custom integration]
B --> E[framework-integrations-persistence / abstraction layer]
E --> F[framework-integrations-extensions-room / default implementation]
Why this layering matters: The persistence abstraction means you can swap Room for any other storage implementation without touching the framework or your application code. Bring your own persistence layer by implementing the integration interfaces.
Shuttle avoids bundling large reactive libraries. Asynchronous communication runs on Kotlin Coroutines and Channels only, which keeps the transitive dependency footprint lean.
🎬 Demo Apps
The demo apps show both the crash scenario and the Shuttle solution side-by-side, using image data transport. Image data is one of the most common real-world contributors to TransactionTooLargeException.
Two architecture patterns are covered:
MVVM: Activities/Fragments as View, ViewModel as state owner and liaison, Kotlin Channels for async notification.
Flow 1: Navigate with Shuttle ✅
Tap "Navigate using Shuttle" -> image loads successfully via warehouse pickup.
| Main Menu | Cargo Unloaded |
|---|---|
Flow 2: Navigate Normally ❌
Tap "Navigate Normally" -> app crashes with TransactionTooLargeException.
| Main Menu | After Crash |
|---|---|
ℹ️ For image loading in production, use Glide or Coil. The demo uses raw image data intentionally to trigger the crash condition.
⚠️ Heads Up: Know the Tradeoffs
Other Parcelable objects in the same Intent can still crash your app. Shuttle protects the Serializable payload. It doesn't protect unrelated Parcelable data you're also passing.
Serializable is slower than Parcelable. Parcelable is optimized for IPC and faster to load, but it's unsafe for disk storage. Google recommends serialization for persistence, which is why Shuttle uses it. The LCE state pattern (loading state) gives your UI the hook it needs to handle the slightly longer load time gracefully.
These are documented tradeoffs, not bugs. Architecture is always about weighing options. This one is worth it.
🤖 AI
Model Context Protocol (MCP)
The Model Context Protocol is an open standard that lets AI assistants connect to external tools and data sources. Rather than relying solely on training data, an MCP-enabled assistant can call live tools the moment you need them, generating accurate boilerplate, analysing your actual code, and returning up-to-date documentation without ever leaving your editor.
Shuttle publishes an MCP server to the MCP Registry, which means any developer can point their MCP-compatible editor at it and get Shuttle expertise inside their own project, without cloning or opening this repo. Your AI assistant can scaffold a complete Shuttle integration with ready-to-paste Kotlin boilerplate for Hilt, Koin, or manual wiring; detect TransactionTooLargeException risk in any Kotlin or Java snippet you paste; and retrieve the authoritative Shuttle spec on demand, covering core concepts, setup, transport, pickup, cleanup, and annotations.
For teams, this is the governance multiplier that code review alone cannot be. Every developer, regardless of experience level or editor preference, gets the same accurate, up-to-date guidance on integration patterns and risk from day one.
See the MCP README file for the full list of supported editors and the verification checklist.
🤝 Contributing
Pull requests are welcome. Check the Contributing Guide and Code of Conduct before opening one.
- Fork the repo
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'Add: my feature' - Push the branch:
git push origin feature/my-feature - Open a Pull Request
For bugs and feature requests, open an issue.
📄 License
MIT. See LICENSE.md for full terms.
Copyright © 2023 Craft & Graft LLC · GRARCHT ™ 2021
Shuttle MCP 질문
Shuttle MCP를 Claude Code에 추가하려면 어떻게 하나요?
터미널에서 claude mcp add shuttle -- docker run -i --rm ghcr.io/grarcht/shuttle-mcp:4.0.0를 실행합니다. 모든 프로젝트에서 사용할 수 있게 하려면 --scope user를 추가합니다.
Shuttle MCP를 Cursor에 추가하려면 어떻게 하나요?
~/.cursor/mcp.json를 열고 mcpServers 아래에 서버를 추가합니다. 위 설치 섹션에 표시된 Cursor 구성을 사용합니다.
Shuttle가 해결하는 문제는 무엇인가요?
대용량 Serializable 객체를 별도의 저장소에 보관하고 Intent나 Bundle을 통해서는 작은 식별자만 전달함으로써 TransactionTooLargeException 크래시를 방지합니다.
Shuttle를 사용하려면 수동으로 직렬화 코드를 작성해야 하나요?
아니요, @ShuttleCargo 어노테이션을 사용하면 어노테이션 프로세서가 빌드 타임에 필요한 직렬화 코드를 자동으로 생성합니다.