Developer GuideLocal Development Setup

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

NameTypeDescription
Docker Desktoprequired

Install from docker.com/get-docker. Allocate at least 4 GB RAM and 2 CPU cores in Docker Settings > Resources.

Supabase CLIrequired

brew install supabase/tap/supabase — verify with supabase --version

psqlrequired

brew install libpq && brew link --force libpq — verify with psql --version

Node.js v24+required

Install from nodejs.org or nvm install 24


Quick Start

Recommended: Use the A²D CLI Tool for a simpler workflow. This page documents the underlying scripts that the CLI wraps.

git clone <a2d-mocks-repo-url> && cd a2d-mocks
git submodule update --init --recursive
./a2d start

Using 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 dev

Open your browser:

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 install

Step 2: Make Scripts Executable

chmod +x scripts/*.sh

You only need to do this once.

Step 3: Start Local Supabase

./scripts/start-local-supabase.sh

The script will:

  1. Check all prerequisites (Docker, Supabase CLI, psql, Node.js)
  2. Ask if you want to pull data from a remote environment (optional)
  3. Run supabase start (downloads Docker images on first run)
  4. Apply all database migrations automatically
  5. Seed the database with 5 test users, 3 organizations, and sample assets
  6. Generate .env.supabase with local credentials and A2D_PLATFORM_ADMIN_EMAILS
  7. 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 dev

Step 5: Verify Everything Works

  1. Open http://localhost:3000
  2. Log in with alpha-owner@test.local / password123
  3. You should see “Local Test Org Alpha” as your organization
  4. You should see the “Weather Service” and “Calculator API” MCP servers

Test Users and Credentials

All users share the same password: password123

EmailFull NamePrimary OrgRoleNotes
alpha-owner@test.localAlpha OwnerOrg AlphaOwnerCreated Org Alpha and its assets
alpha-member@test.localAlpha MemberOrg AlphaMemberInvited by alpha-owner
beta-owner@test.localBeta OwnerOrg BetaOwnerCreated Org Beta and its assets
multi-org@test.localMulti Org UserOrg Alpha (default)Member (both)Member of Alpha AND Beta; can switch orgs
admin@test.localPlatform AdminOrg AdminOwnerPlatform admin via A2D_PLATFORM_ADMIN_EMAILS

User UUIDs

Use these when debugging queries or inspecting RLS behavior:

EmailUUID
alpha-owner@test.localaaaa0001-0001-0001-0001-000000000001
alpha-member@test.localaaaa0002-0002-0002-0002-000000000002
beta-owner@test.localbbbb0001-0001-0001-0001-000000000001
multi-org@test.localcccc0001-0001-0001-0001-000000000001
admin@test.localdddd0001-0001-0001-0001-000000000001

Organizations

OrganizationUUIDCreated ByMembers
Local Test Org Alpha11111111-1111-1111-1111-111111111111alpha-owneralpha-owner (owner), alpha-member (member), multi-org (member)
Local Test Org Beta22222222-2222-2222-2222-222222222222beta-ownerbeta-owner (owner), multi-org (member)
Local Test Org Admin33333333-3333-3333-3333-333333333333adminadmin (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 UserCurrent OrgMCP ServersAgent CardsCan Switch To
alpha-ownerOrg AlphaWeather Service, Calculator APIWeather Assistant Agent
alpha-memberOrg AlphaWeather Service, Calculator APIWeather Assistant Agent
beta-ownerOrg BetaInventory ServiceInventory Manager Agent
multi-org (default)Org AlphaWeather Service, Calculator APIWeather Assistant AgentOrg Beta
multi-org (switched)Org BetaInventory ServiceInventory Manager AgentOrg Alpha
adminOrg 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 adminadmin@test.local gets admin UI access via the A2D_PLATFORM_ADMIN_EMAILS env var, not via RLS

Testing RLS Isolation

To verify isolation is working:

  1. As alpha-owner — See Weather Service + Calculator API. Do NOT see Inventory Service.
  2. As beta-owner — See Inventory Service. Do NOT see Weather Service or Calculator API.
  3. As multi-org — See Alpha data by default. After switching org to Beta, see only Beta data.
  4. As alpha-member — See exact same data as alpha-owner (same org).

Seeded Test Data

MCP Servers

NameOrgTypeCreated By
Weather Service (Mock)Alphamockalpha-owner
Calculator API (OpenAPI)Alphaopenapialpha-owner
Inventory Service (Mock)Betamockbeta-owner

MCP Tools

ToolServerDescriptionMock Scenario
get_weatherWeather ServiceGet current weather for a citySan Francisco: 72F, sunny
get_forecastWeather ServiceGet 5-day forecast for a cityNew York: Mon-Tue forecast
calculateCalculator APIEvaluate a math expression2+2 = 4
check_stockInventory ServiceCheck stock level for a productSKU-001: qty 42, in stock

Agent Cards

NameOrgTypeSkills
Weather Assistant AgentAlphamockCheck Weather, Get Forecast
Inventory Manager AgentBetamock

Environment Variables

The start script generates .env.supabase automatically. Copy it to .env.local for Next.js:

cp .env.supabase .env.local

Generated Variables

VariableValuePurpose
NEXT_PUBLIC_SUPABASE_URLhttp://127.0.0.1:54321Supabase 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_EMAILSadmin@test.localPlatform 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=500

The 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:

VariableDefaultDescription
MAX_PUBLIC_RATE_LIMIT100Global ceiling for public MCP endpoints (/api/platform/[id]/mcp) in requests/minute
PLATFORM_RATE_LIMIT500Rate 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=500

Per-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 dev

Then 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
done

Expected 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:

LevelDescriptionUse Case
debugMost verbose - detailed debugging informationLocal development, troubleshooting
infoGeneral informational messages (default)Production monitoring
warnWarning messages for potentially harmful situationsProduction alerts
errorError messages onlyProduction error tracking

Example:

LOG_LEVEL=debug npm run dev

This enables detailed logging for MCP endpoints, rate limiting, and middleware operations.


Local Endpoints

ServiceURLNotes
Next.js Apphttp://localhost:3000Your application
Supabase Studiohttp://127.0.0.1:54323Database UI, table editor, SQL editor
Supabase APIhttp://127.0.0.1:54321PostgREST API endpoint
Databasepostgresql://postgres:postgres@127.0.0.1:54322/postgresDirect psql access
Inbuckethttp://127.0.0.1:54324Catches 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.sql

Running Tests

supabase test db
 
npm test
 
npm run test:api
 
npm run test:actions

Script Reference

start-local-supabase.sh

FlagDescription
--quickSkip all prompts (no data pull, sample seed, no dev server)
--env ENVAuto-select environment to pull data from (production, staging, dev)
--no-seedSkip database seeding entirely
--no-dev-serverSkip the Next.js dev server prompt
--helpShow help
./scripts/start-local-supabase.sh --quick
 
./scripts/start-local-supabase.sh --env staging
 
./scripts/start-local-supabase.sh --no-seed

stop-local-supabase.sh

FlagDescription
--cleanRemove all data dumps and env files without prompting
--helpShow help
./scripts/stop-local-supabase.sh
 
./scripts/stop-local-supabase.sh --clean

Pulling 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.json

Edit .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 staging

Remote 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 dev

Remember 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 start

Port already in use

lsof -i :54321
 
supabase stop

Login 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.sql

Admin UI not accessible for admin@test.local

Verify the env var is set:

grep A2D_PLATFORM_ADMIN_EMAILS .env.local

If missing, add it and restart the dev server:

echo "A2D_PLATFORM_ADMIN_EMAILS=admin@test.local" >> .env.local

Complete 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 dev

Safety Rules

Never commit secrets or production data. The following files are gitignored for your protection.

FileGitignoredSafe to Commit
.supabase-projects.jsonYesNo — contains project refs
.env.supabase / .env.localYesNo — contains API keys
.local/*.sqlYesNo — may contain production data
scripts/seed-local.sqlNoYes — contains only fabricated test data

For CI/CD deployments, use supabase db push --linked without seeding.


Next Steps


Ready to start? Run ./scripts/start-local-supabase.sh --quick and you are up in under 5 minutes.