yourindie-mcp
v0.1.0
Published
Production-oriented MCP server toolkit for Supabase OAuth 2.1, RLS, Next.js, and Supabase Edge Functions.
Maintainers
Readme
yourindie-mcp
Build a production-oriented remote MCP server on top of your existing Supabase users, OAuth 2.1 server, and Row Level Security.
npm install yourindie-mcp
npx yourindie-mcp initThe initializer detects Next.js or a Supabase Edge/Vite project and generates:
- a stateless Streamable HTTP MCP endpoint
- OAuth protected-resource discovery metadata, including a separate Next.js
.well-knownroute - local JWT verification through Supabase JWKS
- a user-scoped Supabase client that preserves RLS
- a consent page/component
- an example authenticated tool
- an optional strict audience and permission Auth Hook migration
- a project-specific setup checklist
Why this package exists
Supabase handles the OAuth 2.1 authorization code flow, PKCE, user login, consent handoff, client registration, access tokens, refresh tokens, and JWKS. This package handles the MCP resource server: protocol transport, token verification, authenticated tools, permissions, RLS integration, rate limiting hooks, and audit hooks.
It never uses your Supabase service-role key. Every tool receives a Supabase client carrying the user's bearer token, so existing RLS policies remain active.
Requirements
- Node.js 20 or newer for Next.js and CLI usage
- Supabase Auth with OAuth 2.1 Server enabled
- an asymmetric Supabase JWT signing key, ES256 or RS256
- HTTPS in production
Supabase OAuth 2.1 Server is currently beta. Pin and test package updates before rolling them into critical production systems.
Quick start
Run this from an existing application:
npx yourindie-mcp initOr provide everything without prompts:
npx yourindie-mcp init \
--framework next \
--name ViewBAIT \
--resource-url https://viewbait.app/api/mcp \
--yesFor a Supabase Edge Function:
npx yourindie-mcp init \
--framework supabase-edge \
--name ViziVibes \
--resource-url https://PROJECT_REF.supabase.co/functions/v1/mcp \
--yesOpen the generated MCP_SETUP.md, complete the Supabase dashboard steps, replace the example tool, and deploy normally.
Define tools
import { defineMcpTool, z } from 'yourindie-mcp';
export const listProjects = defineMcpTool({
name: 'list_projects',
description: "List the authenticated user's projects.",
inputSchema: {
limit: z.number().int().min(1).max(100).default(25),
},
readOnly: true,
requiredPermissions: ['projects:read'],
async handler({ limit }, { supabase, userId }) {
const { data, error } = await supabase
.from('projects')
.select('id, name, updated_at')
.eq('user_id', userId)
.limit(limit);
if (error) throw error;
return { projects: data ?? [] };
},
});Create the MCP app
import { createSupabaseMcpApp } from 'yourindie-mcp';
import { listProjects } from './tools';
const resourceUrl = 'https://viewbait.app/api/mcp';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
export const mcpApp = createSupabaseMcpApp({
name: 'ViewBAIT',
resourceUrl,
issuer: `${supabaseUrl}/auth/v1`,
supabaseUrl,
supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
expectedAudience: resourceUrl,
tools: [listProjects],
resolvePermissions(context) {
const claim = context.claims.mcp_permissions;
return Array.isArray(claim) ? claim.filter((value): value is string => typeof value === 'string') : [];
},
});Next.js adapter
import { createNextRouteHandlers } from 'yourindie-mcp/next';
import { mcpApp } from '@/lib/mcp/server';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const { GET, POST, DELETE, OPTIONS } = createNextRouteHandlers(mcpApp);Supabase Edge adapter
import { createSupabaseMcpApp } from 'npm:[email protected]';
import { createEdgeHandler } from 'npm:[email protected]/edge';
const app = createSupabaseMcpApp({ /* configuration */ });
Deno.serve(createEdgeHandler(app));Permissions are separate from OAuth scopes
Supabase currently supports the standard OIDC scopes openid, email, profile, and phone. Those scopes control identity information, not access to application tables or MCP tools.
Use these layers for application authorization:
- RLS for row and operation access.
client_idfor client-specific database policies.mcp_permissionsor your own database roles for tool-level permissions.requiredPermissions, per-toolauthorize, and globalauthorizeToolchecks.
The initializer generates a Custom Access Token Hook that binds OAuth tokens to the MCP resource URL and adds an mcp_permissions claim. Strict audience validation is enabled by default in generated examples.
Security defaults
- locally verifies JWT signatures against Supabase JWKS
- validates issuer, expiration, role, OAuth
client_id, and optional audience - does not accept normal browser-session tokens by default
- uses the user's access token for Supabase requests
- limits request body size to 1 MB by default
- returns sanitized tool errors by default
- supports host/origin allowlists, rate limiting, per-tool authorization, and audit logging
- uses stateless JSON Streamable HTTP to fit serverless runtimes
- advertises a function-local metadata fallback when Supabase Edge cannot own the origin-level
.well-knownroute
API
createSupabaseMcpApp(options)
Creates a Hono application containing OAuth discovery, health, authentication, and MCP routes.
Important options include:
resourceUrl: exact public MCP URLissuer: Supabase Auth issuer, normally${SUPABASE_URL}/auth/v1expectedAudience: exact audience expected in OAuth access tokensresolvePermissions: maps claims/database state to application permissionsauthorizeTool: global authorization hookrateLimit: global rate-limit hookaudit: audit event hookallowedHostsandallowedOrigins: optional request allowlists
defineMcpTool(tool)
Type-safe helper for declaring tools.
createSupabaseJwtVerifier(options)
Creates the default Supabase JWKS verifier. Supply your own tokenVerifier to integrate another compliant authorization server later.
Consent helpers
The yourindie-mcp/consent export wraps Supabase's OAuth authorization UI methods:
getAuthorizationDetailsapproveAuthorizationdenyAuthorization
CLI
npx yourindie-mcp init [options]
--framework <next|supabase-edge>
--name <application name>
--resource-url <public MCP URL>
--cwd <project directory>
--yes
--force
--dry-runThe CLI refuses to replace existing generated files unless --force is supplied.
Production checklist
Before exposing write tools:
- test every tool against a user who does not own the requested record
- keep RLS enabled and avoid service-role clients in tool handlers
- validate exact resource audience
- require explicit consent and provide revocation controls
- monitor dynamically registered OAuth clients
- add durable audit storage and rate limiting
- keep admin tools in a separate MCP or enforce server-side admin claims
- pin package versions and run integration tests before upgrades
Development
npm install
npm run typecheck
npm test
npm run build
npm run pack:checkLicense
MIT
