---
title: "Adopt an existing app"
description: "Bring a Next.js or Node app, including one from Lovable, Bolt or v0, onto MainSDK one module and one pull request at a time, with its behaviour locked first."
section: "interfaces"
status: "Building for 0.5 · ships as a beta API"
spec: "migration-skill"
canonical: "https://mainsdk.dev/docs/interfaces/adopt"
updated: "2026-10-28"
---

# Adopt an existing app

> Bring a Next.js or Node app, including one from Lovable, Bolt or v0, onto MainSDK one module and one pull request at a time, with its behaviour locked first.

Status: Building for 0.5 · ships as a beta API · Canonical: https://mainsdk.dev/docs/interfaces/adopt · Updated: 2026-10-28

`mainsdk adopt` moves an existing app onto MainSDK without a rewrite. It scans the app, ranks the gaps, locks the flows that must keep working, then changes one module per branch.

> **Note:** Built for 0.5 (spec `migration-skill`). It runs from the repository and is not published to npm yet. Phase 1 covers Next.js and Node apps only; other stacks are reported as unsupported.

## At a glance

- Status: Building for 0.5 · ships as a beta API
- Phase: 1 (Core, public beta)
- Milestone: 0.5 Pilot ready
- Plan: free in your app (Apache-2.0)
- Spec: https://github.com/hubxero/mainsdk/blob/develop/docs/specs/migration-skill.md
- Category: Developer experience

Calls:

- `mainsdk adopt scan`
- `mainsdk adopt report`
- `mainsdk adopt lock`
- `mainsdk adopt next`
- `mainsdk adopt finish`

In main.yaml:

```yaml

```

Guarantees, whatever the driver:

- The scan is deterministic and changes no code; it writes main.yaml only
- What the app already runs is kept and tagged, never replaced
- Nothing changes until the key flows are locked
- Each module is one branch that passes check and every locked flow; a failed step keeps its branch and leaves the default branch untouched
- Runs fully locally and sends nothing


## Drivers

Pick one in main.yaml with `adopt.driver`.

| Driver | Id in main.yaml | Default | Note |
| --- | --- | --- | --- |
| Built in (reads files, runs git) | `builtin` | yes |  |

## Codes

| Code | Where | Meaning |
| --- | --- | --- |
| `MAIN-ADOPT-0002` | check | The stack is not Next.js or Node; `adopt` stops and changes nothing |
| `MAIN-ADOPT-0003` | check | A route changes or reads data and never checks who is calling (access gap) |
| `MAIN-ADOPT-0004` | check | The browser queries the database directly, so row rules are the only protection (data gap) |
| `MAIN-ADOPT-0007` | check | A call still sits behind an interface's `raw()` escape hatch; listed by `finish` with file and line |
| `MAIN-ADOPT-0001` | runtime | `adopt next` before key flows are locked |
| `MAIN-ADOPT-0006` | runtime | There is nothing to migrate in the module, or it is not a module (`secrets`, `auth`, `data`, `other`) |
| `MAIN-ADOPT-0008` | runtime | The working tree has uncommitted changes; a module step starts from a clean tree |
| `MAIN-ADOPT-0009` | runtime | No sign-in or checkout flow was found to lock, or a named flow points at text that is not there |
| `MAIN-ADOPT-0010` | runtime | A module step is still open, or `verify` ran with none open |
| `MAIN-ADOPT-0012` | runtime | The folder is not a git repository, or the module branch could not be created |
| `MAIN-ADOPT-0013` | runtime | The name is not an adopt tool, or not an agent adopt writes a skill for |

## Features

| Feature | Phase | Milestone | Spec ticket |
| --- | --- | --- | --- |
| Deterministic scan that keeps what you already run | 1 | 0.5 | `migration-skill#scan` |
| Gap report in plain language | 1 | 0.5 | `migration-skill#gap-report` |
| Locked flows before any change | 1 | 0.5 | `migration-skill#lock-flows` |
| One module per branch | 1 | 0.5 | `migration-skill#module-step` |
| Launch report | 1 | 0.5 | `migration-skill#finish-report` |
| Agent skill and the adopt MCP tools | 1 | 0.5 | `migration-skill#agent-skill` |

## MainSDK Cloud

Everything on this page is free in your app.


## Quick start

```bash
npx mainsdk adopt scan      # main.yaml as the app is, no code change
npx mainsdk adopt report    # the gaps against Main 27: secrets, access, data, the rest
npx mainsdk adopt lock      # record sign-in and checkout as locked flows
npx mainsdk adopt next      # one module, one branch, gated by check and the locked flows
npx mainsdk adopt finish    # Main 27 met, or exactly what remains
```

`mainsdk adopt` with no step runs the next one, so it is safe to re-run. It runs fully on your machine and sends nothing. Opening the pull request is left to you, your agent or `gh`: `adopt` only creates the branch and the commit.

## Configuration

`adopt` has no `main.yaml` section. Its first step writes `main.yaml` for you, and its progress lives in `.mainsdk/adopt.json`, which is git-ignored:

```json
{ "step": "module", "done": ["secrets", "auth"], "next": "data", "locked": 6, "byo": ["byo-identity:clerk"] }
```

`byo` lists what the app already runs and `adopt` keeps: a Clerk sign-in stays `auth.driver: clerk`, an OpenFGA model stays `access.driver: openfga`. Nothing is replaced.

## What you get

| Command | What it does |
| --- | --- |
| `mainsdk adopt scan` | Writes the same `main.yaml` on every run. Lists the interfaces the app uses and changes no other file |
| `mainsdk adopt report` | Plain-language gaps in risk order: secrets, then access, then data, then the rest. Each names the Main 27 rule and the fix. `--json` for tools |
| `mainsdk adopt lock` | Records the sign-in and checkout flows as locked flows. A flow holds while its file exists and still contains the call that makes it work |
| `mainsdk adopt next` | Opens one module (`secrets`, `auth`, `data` or `other`) on its own branch. `--module <name>` picks it. Refuses to run before flows are locked |
| `mainsdk adopt verify` | Checks the open module: only its files changed, `mainsdk check` shows no new finding and every locked flow holds |
| `mainsdk adopt finish` | The launch report: Main 27 is met, or the report lists what remains with file and line. `--json` for tools |

Code that cannot move to an interface yet stays behind that interface's `raw()` escape hatch, and `finish` lists it.

### With your coding agent

`mainsdk adopt mcp` starts a stdio MCP server with six tools: `adopt.scan`, `adopt.report`, `adopt.lock`, `adopt.plan_module`, `adopt.verify_module` and `adopt.finish`. `mainsdk adopt skill` writes the skill for Claude Code (`.claude/skills/mainsdk-adopt/SKILL.md`), Cursor (`.cursor/rules/mainsdk-adopt.mdc`) and any agent that reads `AGENTS.md`. The agent follows the same five steps and cannot skip the lock.

## Errors and checks

| Code | When |
| --- | --- |
| `MAIN-ADOPT-0001` | `adopt next` ran before flows were locked; nothing changed |
| `MAIN-ADOPT-0002` | The stack is not Next.js or Node; `adopt` stops and changes nothing |
| `MAIN-ADOPT-0003` | A route changes or reads data and never checks who is calling |
| `MAIN-ADOPT-0004` | The browser queries the database directly, so row rules are the only protection |
| `MAIN-ADOPT-0006` | Nothing to migrate in the module, or it is not a module |
| `MAIN-ADOPT-0007` | A call still sits behind `raw()`; listed by `finish` with file and line |
| `MAIN-ADOPT-0008` | The working tree has uncommitted changes; a module step starts clean |
| `MAIN-ADOPT-0009` | No sign-in or checkout flow was found to lock, or a named flow points at text that is not there |
| `MAIN-ADOPT-0010` | A module step is still open, or `verify` ran with none open |
| `MAIN-ADOPT-0012` | The folder is not a git repository, or the module branch could not be created |
| `MAIN-ADOPT-0013` | The name is not an adopt tool, or not an agent `adopt` writes a skill for |

The full list is in [check codes](https://mainsdk.dev/docs/reference/check-codes).

## Swapping drivers

`adopt` has one driver, the built-in one, which reads your files and runs git. There is nothing to swap.

## Testing

A failing module step leaves your main branch untouched and keeps its branch for review. The scan is deterministic, so a test can run it twice and compare the two `main.yaml` files. Run `mainsdk check` after any step to see the same findings the gate uses.

## Next

- [Adopt an existing app](https://mainsdk.dev/docs/getting-started/existing-app), the walk-through
- [Migrate from raw providers](https://mainsdk.dev/docs/guides/migrate-from-raw-providers)
- [The CLI reference](https://mainsdk.dev/docs/reference/cli#adopt)
