# Fireworks AI

Call Fireworks AI models from Supabase Edge Functions, and store and search Fireworks embeddings in Postgres with pgvector.

Call [Fireworks AI](https://fireworks.ai) models from Supabase Edge Functions, and store Fireworks embeddings in Postgres for similarity search with pgvector. The Fireworks API is compatible with the OpenAI API, so the OpenAI SDK works with a different base URL and API key.

- [Generate text](#generate-text) calls a Fireworks chat model from an Edge Function.
- [Search with embeddings](#search-with-embeddings) stores Fireworks embeddings in a table and finds the closest matches to a search query.
- [Expose documents to MCP clients](#expose-documents-to-mcp-clients) serves the same table as a remote MCP server.

## Prerequisites

- A Supabase project. To create one, see [Create a project](https://database.new).
- The [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started).
- A Fireworks API key. To create one, open [**API Keys**](https://app.fireworks.ai/settings/users/api-keys) in the Fireworks dashboard.

## Store your API key

Edge Functions read the Fireworks API key from a secret named `FIREWORKS_API_KEY`.

1. For local development, add the key to `supabase/functions/.env`:

   ```bash
   echo "FIREWORKS_API_KEY=<your-fireworks-api-key>" >> supabase/functions/.env
   ```

2. For your hosted project, set the secret with the Supabase CLI:

   ```bash
   supabase secrets set FIREWORKS_API_KEY=<your-fireworks-api-key>
   ```

For more information about secrets in Edge Functions, see [Environment variables](https://supabase.com/docs/guides/functions/secrets).

## Generate text

Create an Edge Function that sends a prompt to a Fireworks chat model and returns the response.

1. Create the function:

   ```bash
   supabase functions new fireworks-chat
   ```

2. Replace the contents of `supabase/functions/fireworks-chat/index.ts` with the following code. It points the OpenAI SDK at the Fireworks base URL and calls the `glm-5p3-flash` model:

   ```ts supabase/functions/fireworks-chat/index.ts
   import { withSupabase } from 'npm:@supabase/server@^1'
   import OpenAI from 'npm:openai@^6'

   const fireworks = new OpenAI({
     apiKey: Deno.env.get('FIREWORKS_API_KEY'),
     baseURL: 'https://api.fireworks.ai/inference/v1',
   })

   export default {
     fetch: withSupabase({ auth: 'user' }, async (req) => {
       const { prompt } = await req.json()

       const completion = await fireworks.chat.completions.create({
         model: 'accounts/fireworks/models/glm-5p3-flash',
         messages: [{ role: 'user', content: prompt }],
       })

       return Response.json({ text: completion.choices[0].message.content })
     }),
   }
   ```

3. Deploy the function:

   ```bash
   supabase functions deploy fireworks-chat
   ```

4. Call the function from your app with a signed-in user:

   ```ts
   const { data, error } = await supabase.functions.invoke('fireworks-chat', {
     body: { prompt: 'Explain Row Level Security in one sentence.' },
   })
   ```

To use a different model, replace the `model` value with any model ID from the [Fireworks model library](https://fireworks.ai/models). For streaming responses, see [Running AI models](https://supabase.com/docs/guides/functions/ai-models).

## Search with embeddings

Store text with its Fireworks embedding in a table, then find the rows closest in meaning to a search query. This section uses the `fireworks/qwen3-embedding-8b` model and requests 1,024-dimension vectors with the `dimensions` parameter. The table column has to match the dimension you request.

### Create the table and search function

Run the following SQL using the [SQL Editor](https://supabase.com/dashboard/project/_/sql) in the Supabase Dashboard or with `psql` over a direct connection. To connect with `psql`, see [Direct connection](https://supabase.com/docs/guides/database/connecting-to-postgres#direct-connection).

```sql
create extension if not exists vector with schema extensions;

create table documents (
  id bigint primary key generated always as identity,
  content text not null,
  embedding extensions.vector(1024) not null
);

alter table documents enable row level security;

create policy "Authenticated users can read documents"
on documents for select
to authenticated
using (true);

create index on documents using hnsw (embedding extensions.vector_cosine_ops);

create or replace function match_documents(
  query_embedding extensions.vector(1024),
  match_count int default 5
)
returns table (id bigint, content text, similarity float)
language sql
stable
set search_path = ''
as $$
  select
    documents.id,
    documents.content,
    1 - (documents.embedding operator(extensions.<=>) query_embedding) as similarity
  from public.documents
  order by documents.embedding operator(extensions.<=>) query_embedding
  limit match_count;
$$;
```

The function orders rows by cosine distance. Qwen3 embeddings aren't normalized, and cosine distance normalizes the vectors for you.

The function runs with the caller's permissions, so the `select` policy decides which rows a user can search.

### Store embeddings

Create an Edge Function that embeds text and inserts it into the `documents` table. Only callers that send your project's secret key can reach it, so call it from a backend job or another Edge Function.

1. Create the function:

   ```bash
   supabase functions new fireworks-embed
   ```

2. In `supabase/config.toml`, turn off the platform JWT check for the function:

   ```toml
   [functions.fireworks-embed]
   verify_jwt = false
   ```

3. Replace the contents of `supabase/functions/fireworks-embed/index.ts` with the following code:

   ```ts supabase/functions/fireworks-embed/index.ts
   import { withSupabase } from 'npm:@supabase/server@^1'
   import OpenAI from 'npm:openai@^6'

   const fireworks = new OpenAI({
     apiKey: Deno.env.get('FIREWORKS_API_KEY'),
     baseURL: 'https://api.fireworks.ai/inference/v1',
   })

   export default {
     fetch: withSupabase({ auth: 'secret' }, async (req, ctx) => {
       const { content } = await req.json()

       const response = await fireworks.embeddings.create({
         model: 'fireworks/qwen3-embedding-8b',
         input: content,
         dimensions: 1024,
       })

       const { error } = await ctx.supabaseAdmin
         .from('documents')
         .insert({ content, embedding: JSON.stringify(response.data[0].embedding) })
       if (error) {
         return Response.json({ error: error.message }, { status: 500 })
       }

       return Response.json({ ok: true })
     }),
   }
   ```

4. Deploy the function:

   ```bash
   supabase functions deploy fireworks-embed
   ```

To call the function with the secret key from a database trigger or a scheduled job, see [Call an Edge Function](https://supabase.com/docs/guides/database/webhooks#call-an-edge-function).

### Search documents

Create an Edge Function that embeds a search query with the same model and dimension, then calls `match_documents`.

1. Create the function:

   ```bash
   supabase functions new fireworks-search
   ```

2. Replace the contents of `supabase/functions/fireworks-search/index.ts` with the following code:

   ```ts supabase/functions/fireworks-search/index.ts
   import { withSupabase } from 'npm:@supabase/server@^1'
   import OpenAI from 'npm:openai@^6'

   const fireworks = new OpenAI({
     apiKey: Deno.env.get('FIREWORKS_API_KEY'),
     baseURL: 'https://api.fireworks.ai/inference/v1',
   })

   export default {
     fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
       const { query } = await req.json()

       const response = await fireworks.embeddings.create({
         model: 'fireworks/qwen3-embedding-8b',
         input: query,
         dimensions: 1024,
       })

       const { data, error } = await ctx.supabase.rpc('match_documents', {
         query_embedding: JSON.stringify(response.data[0].embedding),
         match_count: 5,
       })
       if (error) {
         return Response.json({ error: error.message }, { status: 500 })
       }

       return Response.json({ query, results: data })
     }),
   }
   ```

3. Deploy the function:

   ```bash
   supabase functions deploy fireworks-search
   ```

4. Call the function from your app with a signed-in user:

   ```ts
   const { data, error } = await supabase.functions.invoke('fireworks-search', {
     body: { query: 'How do I secure a table?' },
   })
   ```

The response lists the five closest documents, each with a `similarity` score between -1 and 1. Higher scores mean closer matches.

## Expose documents to MCP clients

Serve the `documents` table as a remote [Model Context Protocol](https://modelcontextprotocol.io/specification/2026-07-28) (MCP) server, so clients such as Claude, Cursor, or VS Code can search it. The server embeds each query with the same Fireworks model as [Search with embeddings](#search-with-embeddings), and every tool call runs as the signed-in user, so the `select` policy on `documents` decides what a client reads.

The function composes the authenticated pipeline from [Deploy MCP servers](https://supabase.com/docs/guides/ai-tools/byo-mcp). `withOAuthProtectedResource()` answers MCP client discovery, and `withSupabase({ auth: 'user' })` verifies the user's token and scopes the Supabase client to them. Configure the OAuth 2.1 server, dynamic client registration, and a consent screen before you start. For those steps, see [Add authentication](https://supabase.com/docs/guides/ai-tools/byo-mcp#add-authentication).

1. Create the function:

   ```bash
   supabase functions new mcp
   ```

2. In `supabase/config.toml`, turn off the platform JWT check for the function. The function verifies tokens itself, and the gateway would otherwise reject the unauthenticated discovery request:

   ```toml supabase/config.toml
   [functions.mcp]
   verify_jwt = false
   ```

3. Replace the contents of `supabase/functions/mcp/index.ts` with the following code. It reads the `FIREWORKS_API_KEY` secret from [Store your API key](#store-your-api-key) and registers one tool to search the table and one to read a single row:

   ```ts supabase/functions/mcp/index.ts
   import { createMcpHandler, McpServer } from 'npm:@modelcontextprotocol/server@^2.0.0'
   import { pipeline } from 'npm:@supabase/middleware@1'
   import { withOAuthProtectedResource, withSupabase } from 'npm:@supabase/server@1'
   import OpenAI from 'npm:openai@^6'
   import { z } from 'npm:zod@^4.3.6'

   const fireworks = new OpenAI({
     apiKey: Deno.env.get('FIREWORKS_API_KEY'),
     baseURL: 'https://api.fireworks.ai/inference/v1',
   })

   Deno.serve(
     pipeline(
       [withOAuthProtectedResource(), withSupabase({ auth: 'user' })],
       async (req, { supabase }) => {
         // A fresh server per request: Edge Functions are stateless
         const handler = createMcpHandler(() => {
           const server = new McpServer({ name: 'documents', version: '0.1.0' })

           server.registerTool(
             'search_document',
             {
               description: 'Search documents by meaning and return the closest matches',
               inputSchema: z.object({
                 query: z.string().min(1),
                 limit: z.number().int().min(1).max(20).default(5),
               }),
               annotations: { readOnlyHint: true },
             },
             async ({ query, limit }) => {
               const response = await fireworks.embeddings.create({
                 model: 'fireworks/qwen3-embedding-8b',
                 input: query,
                 dimensions: 1024,
               })

               const { data, error } = await supabase.rpc('match_documents', {
                 query_embedding: JSON.stringify(response.data[0].embedding),
                 match_count: limit,
               })
               if (error) throw new Error(error.message)
               return { content: [{ type: 'text', text: JSON.stringify(data) }] }
             }
           )

           server.registerTool(
             'fetch_document',
             {
               description: 'Fetch the full content of one document by ID',
               inputSchema: z.object({ id: z.number().int() }),
               annotations: { readOnlyHint: true },
             },
             async ({ id }) => {
               const { data, error } = await supabase
                 .from('documents')
                 .select('id, content')
                 .eq('id', id)
                 .single()
               if (error) throw new Error(error.message)
               return { content: [{ type: 'text', text: JSON.stringify(data) }] }
             }
           )

           return server
         })

         return handler.fetch(req)
       }
     )
   )
   ```

4. Deploy the function:

   ```bash
   supabase functions deploy mcp
   ```

5. Add the server to an MCP client. For Claude Code:

   ```bash
   claude mcp add --transport http documents \
     https://<your-project-ref>.supabase.co/functions/v1/mcp
   ```

Every client takes the same URL and runs the OAuth flow on first connection. For the other clients and where to enter the URL, see [Connect from MCP clients](https://supabase.com/docs/guides/ai-tools/byo-mcp#connect-from-mcp-clients).

## Resources

- [Fireworks OpenAI compatibility](https://docs.fireworks.ai/tools-sdks/openai-compatibility)
- [Fireworks embeddings](https://docs.fireworks.ai/guides/querying-embeddings-models)
- [Vector columns](https://supabase.com/docs/guides/ai/vector-columns)
- [Vector indexes](https://supabase.com/docs/guides/ai/vector-indexes)
- [Securing Edge Functions](https://supabase.com/docs/guides/functions/auth)
