Files
Sauci/apps/mcp/src/tools/codes.ts
T
jjbrunton 4e40b92b16 Add MCP server and enhance AI content tools
Implement self-hosted MCP server for admin actions, update AI generation tools in admin dashboard, and refine mobile app stores and components.
2026-01-10 19:24:06 +00:00

154 lines
4.2 KiB
TypeScript

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { supabase } from '../lib/supabase.js';
import { logAudit } from '../utils/audit.js';
export function registerCodeTools(server: McpServer) {
server.tool(
'list_redemption_codes',
'List redemption codes',
{
page: z.number().default(1),
limit: z.number().default(20),
search: z.string().optional()
},
async ({ page, limit, search }) => {
const offset = (page - 1) * limit;
let query = supabase
.from('redemption_codes')
.select('*', { count: 'exact' });
if (search) {
query = query.or(`code.ilike.%${search}%,description.ilike.%${search}%`);
}
const { data, error, count } = await query
.range(offset, offset + limit - 1)
.order('created_at', { ascending: false });
if (error) throw new Error(`Failed to list codes: ${error.message}`);
return {
content: [{ type: 'text', text: JSON.stringify({ data, count }, null, 2) }]
};
}
);
server.tool(
'generate_code',
'Create a new redemption code',
{
code: z.string().optional(), // If not provided, should generate one
description: z.string().optional(),
max_uses: z.number().default(1),
expires_at: z.string().optional(), // ISO date string
is_active: z.boolean().default(true)
},
async ({ code, description, max_uses, expires_at, is_active }) => {
const finalCode = code || Math.random().toString(36).substring(2, 10).toUpperCase();
const { data, error } = await supabase
.from('redemption_codes')
.insert({
code: finalCode,
description,
max_uses,
expires_at,
is_active,
current_uses: 0
})
.select()
.single();
if (error) throw new Error(`Failed to create code: ${error.message}`);
await logAudit({
action: 'INSERT',
table_name: 'redemption_codes',
record_id: data.id,
new_values: data
});
return {
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }]
};
}
);
server.tool(
'toggle_code_active',
'Enable or disable a redemption code',
{ id: z.string(), is_active: z.boolean() },
async ({ id, is_active }) => {
const { data: oldData } = await supabase.from('redemption_codes').select('*').eq('id', id).single();
const { data, error } = await supabase
.from('redemption_codes')
.update({ is_active })
.eq('id', id)
.select()
.single();
if (error) throw new Error(`Failed to update code: ${error.message}`);
await logAudit({
action: 'UPDATE',
table_name: 'redemption_codes',
record_id: id,
old_values: oldData,
new_values: data
});
return {
content: [{ type: 'text', text: `Code ${data.code} is now ${is_active ? 'active' : 'inactive'}` }]
};
}
);
server.tool(
'delete_code',
'Delete a redemption code',
{ id: z.string() },
async ({ id }) => {
const { data: oldData } = await supabase.from('redemption_codes').select('*').eq('id', id).single();
const { error } = await supabase.from('redemption_codes').delete().eq('id', id);
if (error) throw new Error(`Failed to delete code: ${error.message}`);
await logAudit({
action: 'DELETE',
table_name: 'redemption_codes',
record_id: id,
old_values: oldData
});
return {
content: [{ type: 'text', text: `Code ${id} deleted successfully` }]
};
}
);
server.tool(
'list_code_redemptions',
'See who redeemed a code',
{ code_id: z.string() },
async ({ code_id }) => {
const { data, error } = await supabase
.from('code_redemptions')
.select(`
*,
user:user_id (name, email)
`)
.eq('code_id', code_id)
.order('redeemed_at', { ascending: false });
if (error) throw new Error(`Failed to list redemptions: ${error.message}`);
return {
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }]
};
}
);
}