Supabase
Supabase's auto-generated REST API (PostgREST) pairs naturally with Shredly — every table in your database becomes a set of HTTP endpoints with no backend code required. This guide covers authentication, PostgREST filter syntax, and a complete CRUD example across two related tables.
Authentication
Supabase authenticates REST requests via the apikey header. Use the ApiKey auth type with header: "apikey" so Shredly forwards the caller's token on every request:
{
"authentication": {
"type": "ApiKey",
"header": "apikey"
}
}
Use your service role key for full read/write access bypassing Row Level Security, or your anon key for tools where RLS policies should apply.
Your service role key has unrestricted database access. Never expose it in client-side code or commit it to source control.
PostgREST filter syntax
PostgREST identifies rows using query parameters in the form column=operator.value — for example, ?id=eq.abc-123. Shredly's :param URL templates let you embed the operator prefix directly in the tool URL, so callers only pass a plain value.
PATCH and DELETE use this to target a specific row:
PATCH /rest/v1/projects?id=eq.:id
DELETE /rest/v1/projects?id=eq.:id
The :id token is replaced before the request is sent. For PATCH, the remaining input_schema fields are sent as the JSON body — the filter goes in the query string and the update data goes in the body, exactly as PostgREST expects.
GET list endpoints support optional filters via automatic query parameters. Any input_schema field not consumed by a URL template is appended as a query param. Pass PostgREST operator values directly from the caller:
GET /rest/v1/tasks?status=eq.todo&priority=eq.high
Returning the created or updated row
Add "Prefer": "return=representation" to POST and PATCH headers. This tells PostgREST to return the full row in the response instead of an empty 204 — without it, create and update tools return nothing useful to the agent.
Full example: projects and tasks
Suppose you're building a project management tool backed by Supabase. You have two tables:
projects — top-level containers for work, with fields for name, description, and a status that's either active or archived.
tasks — individual units of work that belong to a project. Each task has a title, description, status (todo, in_progress, done), priority (low, medium, high), an optional assignee, and an optional due date. Deleting a project cascades to its tasks.
Supabase exposes these as REST endpoints automatically:
| Method | Endpoint | Description |
|---|---|---|
GET | /rest/v1/projects | List projects, with optional status filter |
POST | /rest/v1/projects | Create a project |
PATCH | /rest/v1/projects?id=eq.<id> | Update a project by UUID |
DELETE | /rest/v1/projects?id=eq.<id> | Delete a project by UUID |
GET | /rest/v1/tasks | List tasks, with optional project, status, and priority filters |
POST | /rest/v1/tasks | Create a task |
PATCH | /rest/v1/tasks?id=eq.<id> | Update a task by UUID |
DELETE | /rest/v1/tasks?id=eq.<id> | Delete a task by UUID |
To expose all of this through a single Shredly MCP server, you define one tool per endpoint. The :id URL template handles row targeting for PATCH and DELETE, and optional GET filters fall through as query parameters automatically.
Here's the complete server definition:
{
"name": "My App — Project Management",
"slug": "my-app-pm",
"description": "CRUD for projects and tasks via Supabase.",
"authentication": {
"type": "ApiKey",
"header": "apikey"
},
"tools": [
{
"name": "list_projects",
"description": "List all projects. Optionally filter by status - pass eq.active or eq.archived.",
"method": "GET",
"url": "https://<project>.supabase.co/rest/v1/projects",
"headers": { "Accept": "application/json" },
"input_schema": {
"status": {
"type": "string",
"description": "Filter by status. PostgREST format: eq.active or eq.archived.",
"optional": true
}
},
"response_transform": ".",
"timeout_ms": 10000
},
{
"name": "create_project",
"description": "Create a new project.",
"method": "POST",
"url": "https://<project>.supabase.co/rest/v1/projects",
"headers": {
"Content-Type": "application/json",
"Prefer": "return=representation"
},
"input_schema": {
"name": { "type": "string", "description": "Project name." },
"description": { "type": "string", "description": "Project description.", "optional": true },
"status": { "type": "string", "description": "active or archived. Defaults to active.", "optional": true }
},
"response_transform": ".",
"timeout_ms": 10000
},
{
"name": "update_project",
"description": "Update one or more fields on a project by UUID.",
"method": "PATCH",
"url": "https://<project>.supabase.co/rest/v1/projects?id=eq.:id",
"headers": {
"Content-Type": "application/json",
"Prefer": "return=representation"
},
"input_schema": {
"id": { "type": "string", "description": "UUID of the project to update." },
"name": { "type": "string", "description": "New project name.", "optional": true },
"description": { "type": "string", "description": "New project description.", "optional": true },
"status": { "type": "string", "description": "New status: active or archived.", "optional": true }
},
"response_transform": ".",
"timeout_ms": 10000
},
{
"name": "delete_project",
"description": "Delete a project by UUID. Cascades to all associated tasks.",
"method": "DELETE",
"url": "https://<project>.supabase.co/rest/v1/projects?id=eq.:id",
"headers": { "Accept": "application/json" },
"input_schema": {
"id": { "type": "string", "description": "UUID of the project to delete." }
},
"response_transform": ".",
"timeout_ms": 10000
},
{
"name": "list_tasks",
"description": "List tasks. Filter by project_id, status, or priority using PostgREST format (e.g. eq.todo, eq.high).",
"method": "GET",
"url": "https://<project>.supabase.co/rest/v1/tasks",
"headers": { "Accept": "application/json" },
"input_schema": {
"project_id": { "type": "string", "description": "Filter by project UUID. PostgREST format: eq.<uuid>.", "optional": true },
"status": { "type": "string", "description": "Filter by status: eq.todo, eq.in_progress, or eq.done.", "optional": true },
"priority": { "type": "string", "description": "Filter by priority: eq.low, eq.medium, or eq.high.", "optional": true }
},
"response_transform": ".",
"timeout_ms": 10000
},
{
"name": "create_task",
"description": "Create a new task within a project.",
"method": "POST",
"url": "https://<project>.supabase.co/rest/v1/tasks",
"headers": {
"Content-Type": "application/json",
"Prefer": "return=representation"
},
"input_schema": {
"project_id": { "type": "string", "description": "UUID of the parent project." },
"title": { "type": "string", "description": "Task title." },
"description": { "type": "string", "description": "Task description.", "optional": true },
"status": { "type": "string", "description": "todo, in_progress, or done. Defaults to todo.", "optional": true },
"priority": { "type": "string", "description": "low, medium, or high. Defaults to medium.", "optional": true },
"assignee": { "type": "string", "description": "Name or identifier of the assignee.", "optional": true },
"due_date": { "type": "string", "description": "Due date in YYYY-MM-DD format.", "optional": true }
},
"response_transform": ".",
"timeout_ms": 10000
},
{
"name": "update_task",
"description": "Update one or more fields on a task by UUID.",
"method": "PATCH",
"url": "https://<project>.supabase.co/rest/v1/tasks?id=eq.:id",
"headers": {
"Content-Type": "application/json",
"Prefer": "return=representation"
},
"input_schema": {
"id": { "type": "string", "description": "UUID of the task to update." },
"title": { "type": "string", "description": "New task title.", "optional": true },
"description": { "type": "string", "description": "New task description.", "optional": true },
"status": { "type": "string", "description": "New status: todo, in_progress, or done.", "optional": true },
"priority": { "type": "string", "description": "New priority: low, medium, or high.", "optional": true },
"assignee": { "type": "string", "description": "New assignee.", "optional": true },
"due_date": { "type": "string", "description": "New due date in YYYY-MM-DD format.", "optional": true }
},
"response_transform": ".",
"timeout_ms": 10000
},
{
"name": "delete_task",
"description": "Delete a task by UUID.",
"method": "DELETE",
"url": "https://<project>.supabase.co/rest/v1/tasks?id=eq.:id",
"headers": { "Accept": "application/json" },
"input_schema": {
"id": { "type": "string", "description": "UUID of the task to delete." }
},
"response_transform": ".",
"timeout_ms": 10000
}
]
}
Connecting the server
Once created in Shredly, add it to your MCP client using your Supabase service role key as the API key.
Claude Code:
claude mcp add --transport http my-app-pm \
https://mcp.shredly.io/mcp/my-app-pm \
--header "Authorization: Bearer <your-service-role-key>"
Next steps
- MCP Server Schema — full reference for tool definitions, auth options, and URL templates.
- Quick Start — end-to-end walkthrough of creating your first MCP server with an agent.