Local Development Setup
Set up a complete local Supabase development environment with automated scripts, pre-configured test users, multi-tenant organizations, and RLS-scoped sample data.
Overview
The local development environment gives you:
- Full Supabase stack running in Docker (database, auth, API, Studio)
- 5 pre-seeded test users with different roles and org memberships
- 3 organizations with proper RLS isolation
- Sample MCP servers, agent cards, tools, and resources
- One-command startup with
./scripts/start-local-supabase.sh --quick
Time required: ~5 minutes (first run downloads Docker images)
Prerequisites
| Name | Type | Description |
|---|---|---|
Docker Desktop | required | Install from docker.com/get-docker. Allocate at least 4 GB RAM and 2 CPU cores in Docker Settings > Resources. |
Supabase CLI | required |
|
psql | required |
|
Node.js v24+ | required | Install from nodejs.org or |
Quick Start
Recommended: Use the A²D CLI Tool for a simpler workflow. This page documents the underlying scripts that the CLI wraps.
Using A²D CLI (Recommended)
git clone <a2d-mocks-repo-url> && cd a2d-mocks
git submodule update --init --recursive
./a2d startUsing Direct Scripts (Alternative)
git clone <repo-url> && cd mulesoft-mcp-mock-specs
npm install
chmod +x scripts/*.sh
./scripts/start-local-supabase.sh --quick
cp .env.supabase .env.local
npm run devOpen your browser:
- App: http://localhost:3000
- Supabase Studio: http://127.0.0.1:54323
Log in with alpha-owner@test.local / password123.
Step-by-Step Setup
Step 1: Clone and Install
git clone <repo-url>
cd mulesoft-mcp-mock-specs
npm installStep 2: Make Scripts Executable
chmod +x scripts/*.shYou only need to do this once.
Step 3: Start Local Supabase
./scripts/start-local-supabase.shThe script will:
- Check all prerequisites (Docker, Supabase CLI, psql, Node.js)
- Ask if you want to pull data from a remote environment (optional)
- Run
supabase start(downloads Docker images on first run) - Apply all database migrations automatically
- Seed the database with 5 test users, 3 organizations, and sample assets
- Generate
.env.supabasewith local credentials andA2D_PLATFORM_ADMIN_EMAILS - Optionally start the Next.js dev server
On first run, Docker images download takes 2-5 minutes. Subsequent starts are much faster.
Step 4: Set Up Next.js Environment
If you did not let the script start the dev server, do it manually:
cp .env.supabase .env.local
npm run devStep 5: Verify Everything Works
- Open http://localhost:3000
- Log in with
alpha-owner@test.local/password123 - You should see “Local Test Org Alpha” as your organization
- You should see the “Weather Service” and “Calculator API” MCP servers
Test Users and Credentials
All users share the same password: password123
| Full Name | Primary Org | Role | Notes | |
|---|---|---|---|---|
alpha-owner@test.local | Alpha Owner | Org Alpha | Owner | Created Org Alpha and its assets |
alpha-member@test.local | Alpha Member | Org Alpha | Member | Invited by alpha-owner |
beta-owner@test.local | Beta Owner | Org Beta | Owner | Created Org Beta and its assets |
multi-org@test.local | Multi Org User | Org Alpha (default) | Member (both) | Member of Alpha AND Beta; can switch orgs |
admin@test.local | Platform Admin | Org Admin | Owner | Platform admin via A2D_PLATFORM_ADMIN_EMAILS |
User UUIDs
Use these when debugging queries or inspecting RLS behavior:
| UUID | |
|---|---|
alpha-owner@test.local | aaaa0001-0001-0001-0001-000000000001 |
alpha-member@test.local | aaaa0002-0002-0002-0002-000000000002 |
beta-owner@test.local | bbbb0001-0001-0001-0001-000000000001 |
multi-org@test.local | cccc0001-0001-0001-0001-000000000001 |
admin@test.local | dddd0001-0001-0001-0001-000000000001 |
Organizations
| Organization | UUID | Created By | Members |
|---|---|---|---|
| Local Test Org Alpha | 11111111-1111-1111-1111-111111111111 | alpha-owner | alpha-owner (owner), alpha-member (member), multi-org (member) |
| Local Test Org Beta | 22222222-2222-2222-2222-222222222222 | beta-owner | beta-owner (owner), multi-org (member) |
| Local Test Org Admin | 33333333-3333-3333-3333-333333333333 | admin | admin (owner) |
Membership Breakdown
RLS Visibility Matrix
Row-Level Security is organization-scoped. Each user only sees data belonging to their current_organization_id.
What Each User Can See
| Logged-in User | Current Org | MCP Servers | Agent Cards | Can Switch To |
|---|---|---|---|---|
alpha-owner | Org Alpha | Weather Service, Calculator API | Weather Assistant Agent | — |
alpha-member | Org Alpha | Weather Service, Calculator API | Weather Assistant Agent | — |
beta-owner | Org Beta | Inventory Service | Inventory Manager Agent | — |
multi-org (default) | Org Alpha | Weather Service, Calculator API | Weather Assistant Agent | Org Beta |
multi-org (switched) | Org Beta | Inventory Service | Inventory Manager Agent | Org Alpha |
admin | Org Admin | (none seeded) | (none seeded) | — |
Key RLS Behaviors
- Organization isolation — alpha-owner CANNOT see Org Beta data, and vice versa
- Same-org members see same data — alpha-owner and alpha-member see identical assets
- Owner vs member — Both roles see the same data; owner can manage org settings and invite members
- Multi-org switching — multi-org’s visible data changes when they switch
current_organization_id - Platform admin — admin@test.local gets admin UI access via the
A2D_PLATFORM_ADMIN_EMAILSenv var, not via RLS
Testing RLS Isolation
To verify isolation is working:
- As
alpha-owner— See Weather Service + Calculator API. Do NOT see Inventory Service. - As
beta-owner— See Inventory Service. Do NOT see Weather Service or Calculator API. - As
multi-org— See Alpha data by default. After switching org to Beta, see only Beta data. - As
alpha-member— See exact same data as alpha-owner (same org).
Seeded Test Data
MCP Servers
| Name | Org | Type | Created By |
|---|---|---|---|
| Weather Service (Mock) | Alpha | mock | alpha-owner |
| Calculator API (OpenAPI) | Alpha | openapi | alpha-owner |
| Inventory Service (Mock) | Beta | mock | beta-owner |
MCP Tools
| Tool | Server | Description | Mock Scenario |
|---|---|---|---|
get_weather | Weather Service | Get current weather for a city | San Francisco: 72F, sunny |
get_forecast | Weather Service | Get 5-day forecast for a city | New York: Mon-Tue forecast |
calculate | Calculator API | Evaluate a math expression | 2+2 = 4 |
check_stock | Inventory Service | Check stock level for a product | SKU-001: qty 42, in stock |
Agent Cards
| Name | Org | Type | Skills |
|---|---|---|---|
| Weather Assistant Agent | Alpha | mock | Check Weather, Get Forecast |
| Inventory Manager Agent | Beta | mock | — |
Environment Variables
The start script generates .env.supabase automatically. Copy it to .env.local for Next.js:
cp .env.supabase .env.localGenerated Variables
| Variable | Value | Purpose |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | http://127.0.0.1:54321 | Supabase API endpoint |
NEXT_PUBLIC_SUPABASE_ANON_KEY | (auto-generated) | Public anon key for client-side |
SUPABASE_SERVICE_ROLE_KEY | (auto-generated) | Service role key (server-side only) |
A2D_PLATFORM_ADMIN_EMAILS | admin@test.local | Platform admin email allowlist |
Additional Variables
These are NOT auto-generated. Add them to .env.local manually if needed:
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_A2L_URL=http://a2l.localhost:3000
NEXT_PUBLIC_A2TF_URL=http://a2tf.localhost:3000
CREDENTIALS_ENCRYPTION_KEY=<run: openssl rand -hex 32>
LOG_LEVEL=debug
# Rate limiting configuration (optional)
MAX_PUBLIC_RATE_LIMIT=1000
PLATFORM_RATE_LIMIT=500The CREDENTIALS_ENCRYPTION_KEY is required if you test LLM configuration features. Generate one with openssl rand -hex 32.
Rate Limiting Configuration
Rate limits are configurable via environment variables. In development mode, rate limiting is automatically disabled to make local testing easier.
Environment Variables:
| Variable | Default | Description |
|---|---|---|
MAX_PUBLIC_RATE_LIMIT | 100 | Global ceiling for public MCP endpoints (/api/platform/[id]/mcp) in requests/minute |
PLATFORM_RATE_LIMIT | 500 | Rate limit for platform MCP endpoint (/api/platform-mcp/mcp) in requests/minute |
Setting Custom Limits:
# Add to .env.local
MAX_PUBLIC_RATE_LIMIT=1000
PLATFORM_RATE_LIMIT=500Per-Organization Overrides:
Public MCP endpoints support per-organization rate limit overrides via the database:
-- Set organization-specific limit
UPDATE organizations
SET max_public_rate_limit = 500
WHERE name = 'Premium Tier';
-- View current org limits
SELECT name, max_public_rate_limit
FROM organizations
WHERE max_public_rate_limit IS NOT NULL;
-- Remove override (use global MAX_PUBLIC_RATE_LIMIT)
UPDATE organizations
SET max_public_rate_limit = NULL
WHERE name = 'Premium Tier';The actual limit applied is: min(org.max_public_rate_limit, MAX_PUBLIC_RATE_LIMIT)
Testing Different Rate Limit Scenarios:
By default, rate limiting is disabled in development. To test rate limits locally:
# Enable rate limiting in development
NODE_ENV=production npm run devThen make rapid requests to test the limits:
# Test public endpoint rate limiting
for i in {1..105}; do
echo "Request $i"
curl -X POST http://localhost:3000/api/platform/test-server/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test","version":"1.0.0"},"capabilities":{}}}' \
-w "\nStatus: %{http_code}\n"
sleep 0.5
doneExpected behavior:
- Requests 1-100:
Status: 200(within limit) - Requests 101+:
Status: 429(rate limited)
Remember to unset NODE_ENV after testing, or rate limiting will continue to apply during development.
Logging Configuration
Control logging verbosity with the LOG_LEVEL environment variable:
| Level | Description | Use Case |
|---|---|---|
debug | Most verbose - detailed debugging information | Local development, troubleshooting |
info | General informational messages (default) | Production monitoring |
warn | Warning messages for potentially harmful situations | Production alerts |
error | Error messages only | Production error tracking |
Example:
LOG_LEVEL=debug npm run devThis enables detailed logging for MCP endpoints, rate limiting, and middleware operations.
Local Endpoints
| Service | URL | Notes |
|---|---|---|
| Next.js App | http://localhost:3000 | Your application |
| Supabase Studio | http://127.0.0.1:54323 | Database UI, table editor, SQL editor |
| Supabase API | http://127.0.0.1:54321 | PostgREST API endpoint |
| Database | postgresql://postgres:postgres@127.0.0.1:54322/postgres | Direct psql access |
| Inbucket | http://127.0.0.1:54324 | Catches auth emails (confirmations, resets) |
Common Workflows
Switching multi-org User’s Organization
In psql or Supabase Studio SQL editor:
UPDATE users
SET current_organization_id = '22222222-2222-2222-2222-222222222222'
WHERE email = 'multi-org@test.local';Refresh the browser to see the new org’s data. Switch back with:
UPDATE users
SET current_organization_id = '11111111-1111-1111-1111-111111111111'
WHERE email = 'multi-org@test.local';Creating a New Test User
INSERT INTO auth.users (id, instance_id, email, encrypted_password,
email_confirmed_at, aud, role, raw_user_meta_data, created_at, updated_at)
VALUES (
gen_random_uuid(), '00000000-0000-0000-0000-000000000000',
'newuser@test.local', crypt('password123', gen_salt('bf')),
now(), 'authenticated', 'authenticated',
'{"full_name": "New User"}', now(), now()
);Then get the UUID and create the public user and org membership:
SELECT id FROM auth.users WHERE email = 'newuser@test.local';
INSERT INTO users (id, email, full_name, organization_id, current_organization_id)
VALUES ('<uuid>', 'newuser@test.local', 'New User',
'11111111-1111-1111-1111-111111111111',
'11111111-1111-1111-1111-111111111111');
INSERT INTO organization_members (user_id, organization_id, role)
VALUES ('<uuid>', '11111111-1111-1111-1111-111111111111', 'member');Inspecting RLS as a Specific User
SET LOCAL ROLE authenticated;
SELECT set_config('request.jwt.claims',
'{"sub":"aaaa0001-0001-0001-0001-000000000001"}', true);
SELECT name FROM mcp_servers;
RESET ROLE;Resetting the Database
supabase db reset
psql postgresql://postgres:postgres@127.0.0.1:54322/postgres -f scripts/seed-local.sqlRunning Tests
supabase test db
npm test
npm run test:api
npm run test:actionsScript Reference
start-local-supabase.sh
| Flag | Description |
|---|---|
--quick | Skip all prompts (no data pull, sample seed, no dev server) |
--env ENV | Auto-select environment to pull data from (production, staging, dev) |
--no-seed | Skip database seeding entirely |
--no-dev-server | Skip the Next.js dev server prompt |
--help | Show help |
./scripts/start-local-supabase.sh --quick
./scripts/start-local-supabase.sh --env staging
./scripts/start-local-supabase.sh --no-seedstop-local-supabase.sh
| Flag | Description |
|---|---|
--clean | Remove all data dumps and env files without prompting |
--help | Show help |
./scripts/stop-local-supabase.sh
./scripts/stop-local-supabase.sh --cleanPulling Remote Data
You can optionally pull data from production, staging, or dev environments.
One-Time Setup
supabase login
cp .supabase-projects.json.example .supabase-projects.jsonEdit .supabase-projects.json with your project refs from the Supabase Dashboard:
{
"production": "your-production-ref",
"staging": "your-staging-ref",
"dev": "your-dev-ref"
}Then start with a specific environment:
./scripts/start-local-supabase.sh --env stagingRemote data dumps may contain real user data. They are gitignored and must NEVER be committed. The .supabase-projects.json file is also gitignored.
Rate Limiting in Development
Rate limiting is automatically disabled in development mode to make local testing easier.
How It Works
Rate limiting is only enforced when NODE_ENV=production:
if (process.env.NODE_ENV !== 'production') {
// Skip rate limiting
return { allowed: true }
}In Development:
- Unlimited requests to all MCP endpoints
- No rate limit headers in responses
- No 429 errors
- Logging shows “Rate limiting disabled (not production)”
In Production:
- Public endpoints: 100 requests/minute per IP
- Platform endpoints: 500 requests/minute per organization
- Rate limit headers included in all responses
- 429 errors when limits exceeded
Testing Rate Limits Locally
To test rate limiting in development, temporarily set NODE_ENV:
NODE_ENV=production npm run devRemember to unset NODE_ENV after testing, or rate limiting will continue to apply during development.
Troubleshooting
Docker is not running
Start Docker Desktop and wait for it to fully initialize before retrying.
supabase start fails or hangs
supabase stop
docker system prune -f
supabase startPort already in use
lsof -i :54321
supabase stopLogin fails with “Invalid login credentials”
Verify the seed ran successfully:
psql postgresql://postgres:postgres@127.0.0.1:54322/postgres \
-c "SELECT email FROM auth.users;"If empty, re-run the seed:
psql postgresql://postgres:postgres@127.0.0.1:54322/postgres \
-f scripts/seed-local.sqlAdmin UI not accessible for admin@test.local
Verify the env var is set:
grep A2D_PLATFORM_ADMIN_EMAILS .env.localIf missing, add it and restart the dev server:
echo "A2D_PLATFORM_ADMIN_EMAILS=admin@test.local" >> .env.localComplete fresh start
./scripts/stop-local-supabase.sh --clean
docker system prune -af --volumes
./scripts/start-local-supabase.sh --quick
cp .env.supabase .env.local
npm run devSafety Rules
Never commit secrets or production data. The following files are gitignored for your protection.
| File | Gitignored | Safe to Commit |
|---|---|---|
.supabase-projects.json | Yes | No — contains project refs |
.env.supabase / .env.local | Yes | No — contains API keys |
.local/*.sql | Yes | No — may contain production data |
scripts/seed-local.sql | No | Yes — contains only fabricated test data |
For CI/CD deployments, use supabase db push --linked without seeding.
Next Steps
- Database Setup — production Supabase setup
- MCP Servers — learn about creating MCP servers
- Agent Cards — learn about A2A agent cards
- Architecture: Multi-Tenancy — understand the RLS model
Ready to start? Run ./scripts/start-local-supabase.sh --quick and you are up in under 5 minutes.