Rromcp / docs
V3 DOCUMENTATION

Build with romcp.

Connect your AI tools to Roblox Studio. Inspect your game, make changes, and check the result.

These docs cover the v3 development build. The public npm download is v2.11.4 and does not yet include the v3 tools or SDK examples below.

Overview

romcp is an MCP server and Roblox Studio plugin. Your MCP client supplies the AI model; romcp gives it tools for instances, scripts, building, UI, output, and testing. Open Cloud tools connect to Roblox services using your own scoped API key.

Your AI clientLocal romcp bridgeRoblox Studio

romcp does not require a separate AI API key. Keep your existing Codex, Claude Code, Cursor, or other MCP client setup.

Installation

Install Node.js 20 or newer and pnpm, then run:

pnpm install -g dominus-cli@latest
dominus setup

The guided setup installs the Studio plugin, provisions a local bridge credential, and helps configure supported MCP clients. Open Roblox Studio and your experience, then check the plugin’s connection panel. Reload Studio if the new plugin does not appear.

Check the installation or update it later:

dominus doctor
dominus update

The v3 build also provides romcp, romcp-mcp, and romcp-install-plugin aliases. Existing Dominus commands and configuration names remain supported during the transition.

Working in Studio

  1. Ask your AI client to check dominus_status.
  2. If several Studio sessions are open, select the intended one with dominus_select_studio.
  3. Inspect a narrow part of the scene before editing it.
  4. Check the resulting instances, output, or playtest evidence.
Inspect the models under Workspace.Map, then identify
unanchored decorative parts. Show me the proposed changes.

Use instance references returned by the tools instead of guessing paths. Prefer batched instance operations when changing several objects. Arbitrary Luau execution requires the plugin’s explicit execution toggle; enable it only for clients you trust.

Open Cloud keys

Create a key in Roblox Creator Dashboard with access to the intended experience or creator. Permissions vary by operation. Never paste keys into game scripts, public repositories, or screenshots.

Ask your client to run show_dominus_ui. In Open Cloud, enter the key and choose Save encrypted key. Encrypted storage currently uses Windows DPAPI for your Windows account. On macOS and Linux, use an environment variable in the process that launches romcp:

# PowerShell — use your actual key locally
$env:ROBLOX_OPEN_CLOUD_API_KEY = 'your-key'

Credential priority is environment variable, encrypted store, then legacy openCloudApiKey configuration. Removing a saved key can reveal an older configured source. Saving verifies local storage, not Roblox permissions or key validity.

Operation Permission
Read saved player fields universe-datastores.objects:read
Read analytics universe.analytics:read
Assets and commerce Grant the operation-specific permissions shown by Roblox’s key editor.

Roblox API key documentation ↗

API & SDK

The developer API runs locally and is disabled by default. Build from source with pnpm install --frozen-lockfile and pnpm build. Start a fresh bridge with a separate API token and a free port:

$env:DOMINUS_API_PORT = '18090'
$env:DOMINUS_API_TOKEN = node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('base64url'))"
node dist/bridge-daemon.js

Install the package in your development project to use the SDK. The package root starts MCP; import the /sdk subpath for developer code:

import { createClient } from 'dominus-cli/sdk';

const client = createClient({
  baseUrl: 'http://127.0.0.1:18090/v1',
  token: process.env.DOMINUS_API_TOKEN,
  timeoutMs: 30000,
});

console.log(await client.capabilities());
const connections = await client.studios.list();
// Choose an exact connection ID from this list.
const studio = await client.studios.connect({
  connectionId: process.env.DOMINUS_CONNECTION_ID,
});
const tree = await studio.scene.tree({ maxDepth: 2, maxNodes: 200 });

The API accepts non-browser clients on 127.0.0.1 only. Keep its token separate from both the Studio bridge token and your Open Cloud key. It is not an internet-hosted API or a Roblox runtime endpoint.

Route Purpose
GET /v1/capabilities Available API operations
GET /v1/studios Connected Studio sessions
POST /v1/studios/{id}/scene/tree Bounded scene hierarchy
POST /v1/studios/{id}/scene/inspect Instance properties
POST /v1/studios/{id}/scene/selection Selected instances
POST /v1/studios/{id}/output Incremental logs
POST /v1/studios/{id}/tests/{action} start, status, stop, or context
POST /v1/cloud/data-stores/read Selected saved-data fields
POST /v1/cloud/analytics/read Metric query or operation check
POST /v1/cloud/analytics/dimensions Discover dimension values or check a discovery operation

All routes require Authorization: Bearer <API token>. POST bodies use application/json. Responses contain data or error, plus a requestId. Requests are limited to 32 KiB, responses to 1 MiB, and concurrency to eight. Reconnect using a newly discovered Studio ID after the target disconnects.

Create products together

Use roblox_create_developer_products for up to 30 products in one call. It checks the catalog once and returns an ID and status for each item. Existing names are reused without changing their settings.

{
  "universeId": 123,
  "products": [
    { "name": "100 Coins", "price": 10 },
    { "name": "500 Coins", "price": 40 }
  ],
  "confirm": true
}

If any item has status unknown, inspect the catalog before retrying. Previously created products remain created. Use returned product IDs when wiring offers into your game.

Developer-product updates

Use roblox_update_developer_product to change a product’s price, sale availability, regional pricing, or store-page visibility. Supply an explicit universe and product ID. Only the fields in changes are updated.

{
  "universeId": 123,
  "productId": 456,
  "changes": { "price": 99, "isForSale": true },
  "confirm": true
}

The tool reads the configuration back after updating it. Check both applied and verified; if verification fails, inspect the product before repeating the update. This operation requires both developer-product read and write permissions. Regional pricing is separate from the game’s Managed Pricing enrollment flow.

Scheduled discounts

From your own code, use client.discounts.schedule(), list(), cancel(), and resolve(). The HTTP equivalents are POST /v1/cloud/discounts/schedule, /list, /cancel, and /resolve under the same prefix.

const { job } = await client.discounts.schedule({
  plan: {
    universeId: 123, productId: 456,
    originalPrice: 100, discountPrice: 50,
    startsAt: '2030-10-01T00:00:00Z',
    endsAt: '2030-10-02T00:00:00Z',
  },
  confirm: true,
});
const page = await client.discounts.list({ offset: 0 });
// Cancel before execution, using the latest job revision:
await client.discounts.cancel({
  id: job.id, revision: job.revision, confirm: true,
});

Choose your own future dates. Listing returns up to 50 jobs plus offset and total; increase the offset by the number returned to read the next page. After a timeout or error, inspect the job list and product before retrying. A lost response can still leave a saved job or completed price change.

Schedule a developer-product discount with roblox_schedule_discount. Provide the universe and product IDs, expected original price, lower discount price, start and end timestamps with a UTC offset, and confirm: true.

The local bridge must stay running to apply and restore prices. Open the control panel with show_dominus_ui and select Refresh jobs under Scheduled discounts to inspect prices, times, and execution notes. You can cancel jobs that have not started. Active and uncertain jobs may still need price restoration.

To schedule visually, expand Schedule a discount in the control panel, enter the product and universe IDs, prices, and both times in UTC, then select Schedule discount and restoration. For a job needing attention, use Close if original price is present or the button showing the original price to restore it now. romcp checks the current product price and job revision before proceeding.

If a job needs attention, ask your agent to inspect its ID and current product price before using roblox_resolve_discount. A completed request or a saved schedule is not proof that a future price change has already run.

Player data

Use roblox_read_data_store or the SDK to read exact fields from one entry. Supply the universe, store, scope, key, and field paths:

const save = await client.dataStores.read({
  universeId: 123,
  dataStore: 'PlayerData',
  scope: 'global',
  key: 'Player_42',
  fields: [['Receipts', 'purchase-id'], ['Inventory', '0']],
});

Replace the example target with your game’s actual schema. Array indexes are zero-based strings. Missing fields return found: false; saved false and null stay distinct. Available revision metadata accompanies the selected values.

Saved data is evidence from your game, not Roblox transaction history. A missing receipt does not prove that a player never purchased something. To verify a purchase, interpret the record using your game’s receipt schema and fulfillment logic. This operation reads selected fields from one entry; it does not modify saved data.

Analytics

Use roblox_read_analytics or client.analytics.read() for aggregate metrics. Each call sends one request:

const result = await client.analytics.read({
  universeId: 123,
  query: {
    metric: 'DailyActiveUsers',
    granularity: 'OneDay',
    startTime: '2026-01-01T00:00:00Z',
    endTime: '2026-02-01T00:00:00Z',
  },
});

// If pending, check later using the returned ID.
if (!result.operation.done) {
  const status = await client.analytics.read({
    universeId: 123,
    operationId: result.operationId,
  });
}

Check operation.done and operation.error before using results. The end time is exclusive. Projected values can change; statistically insignificant samples do not establish regressions. Text metrics use stringValues. Avoid resubmitting a pending query.

Discover filter values

Use roblox_discover_analytics_dimensions or the SDK to find dimension IDs before filtering a metric. Keep the returned value as the filter ID; displayValue is only a label.

const discovery = await client.analytics.dimensions({
  universeId: 123,
  query: {
    metric: 'DailyActiveUsers',
    dimensions: ['Country'],
    startTime: '2026-01-01T00:00:00Z',
    endTime: '2026-02-01T00:00:00Z',
    limit: 10,
  },
});

If the discovery is pending, call client.analytics.dimensions() again with its universeId and operationId. Dimension discoveries use a different operation route from metric queries.

Supported Roblox metrics, filters, and granularities ↗

UI to code

studio_export_ui captures a supported UI tree and returns React Luau or plain Luau code. The SDK also exports the pure convertUiSnapshot converter. Review warnings for omitted nodes or properties.

The converter preserves supported visual properties, layout objects, and styled values. It does not infer event handlers, gameplay behavior, or React state. Choose React Luau for a Roblox React project, or plain Luau for an Instance-based UI. Check the generated result in Studio.

Local source & sync

For filesystem-backed projects, set DOMINUS_PROJECT_ROOT in the MCP server environment. Add exact source ownership mappings to .dominus/project.json:

{
  "version": 1,
  "placeId": 123456789,
  "mappings": [{
    "pathSegments": ["ServerScriptService", "Main"],
    "owner": "filesystem",
    "file": "src/server/main.server.luau",
    "provider": "rojo"
  }]
}

Edit mapped source with your editor’s filesystem tools, then verify sync in Studio. Matching script reads omit duplicate source by default. Diverged copies include conflict evidence. Generated files should be changed through their generator. Keep your existing sync process running. Resolve conflicts in the source you own before syncing again.

Building preferences

Use studio_build_style or the local control panel to save place-specific material and surface defaults. These defaults apply to supported part creation and procedural building; explicit properties in the current request take priority.

This helps retain choices such as studded surfaces between build requests. It does not retroactively restyle existing geometry or guarantee a particular visual result. Inspect the finished scene and iterate on specific objects.

Playtesting

studio_test_session and the SDK provide managed start, status, stop, and context operations:

const started = await studio.tests.start({
  mode: 'play', timeoutMs: 120000,
});
if (started.success) {
  console.log(await studio.tests.status(started.runId));
}

A started session is not proof that gameplay is ready or a test passed. Inspect phase, failure evidence, and cleanup status. Stop must target the matching server runtime connection; final status comes from the originating edit connection. Check session context and your scenario’s readiness conditions before sending gameplay input.

Use studio.output.read() with the previous nextCursor for incremental logs. Results report eviction gaps. A timeout in your HTTP client does not cancel an already dispatched Studio operation.

Profiling

studio_profile reports availability and can summarize existing MicroProfiler frame data when the optional Roblox LibMP dependency is installed in the plugin. It reports CPU/GPU timing distributions and slow frames.

The profiler requires Roblox’s LibMP dependency in the plugin. Check availability before requesting a snapshot. Frame timing identifies slow intervals; investigate the relevant scopes in Roblox’s profiler to locate the cause.

Troubleshooting

Studio is disconnected

Run dominus doctor, open the plugin’s connection panel, and reload Studio after an update. Run dominus setup again if the plugin or local credential is missing. Check that your MCP client starts the installed romcp server.

A tool changed the wrong experience

Check connected sessions and explicitly select the intended Studio before continuing. SDK handles retain the exact selected connection ID and do not follow changes to the active Studio.

Open Cloud returns a permission error

Check the active credential source, expiry, intended universe, and operation permissions. A successfully saved key has not necessarily been verified against Roblox.

The SDK reports INVALID_REQUEST or CLOUD_ERROR

For INVALID_REQUEST, check the input schema and limits. CLOUD_ERROR reports a failed upstream read; inspect the sanitized message. Missing saved data is not proof of no purchase.

A result is too large

Reduce scene depth, choose fewer fields, narrow the date range, or remove breakdowns. romcp applies explicit response limits to keep results manageable.