Skip to content

Commit a5eaac9

Browse files
committed
add elicitation to create_project
1 parent 80b3287 commit a5eaac9

File tree

4 files changed

+66
-27
lines changed

4 files changed

+66
-27
lines changed

packages/mcp-server-supabase/src/platform/types.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,7 @@ export type DevelopmentOperations = {
234234

235235
export type StorageOperations = {
236236
getStorageConfig(projectId: string): Promise<StorageConfig>;
237-
updateStorageConfig(
238-
projectId: string,
239-
config: StorageConfig
240-
): Promise<void>;
237+
updateStorageConfig(projectId: string, config: StorageConfig): Promise<void>;
241238
listAllBuckets(projectId: string): Promise<StorageBucket[]>;
242239
};
243240

@@ -249,10 +246,7 @@ export type BranchingOperations = {
249246
): Promise<Branch>;
250247
deleteBranch(branchId: string): Promise<void>;
251248
mergeBranch(branchId: string): Promise<void>;
252-
resetBranch(
253-
branchId: string,
254-
options: ResetBranchOptions
255-
): Promise<void>;
249+
resetBranch(branchId: string, options: ResetBranchOptions): Promise<void>;
256250
rebaseBranch(branchId: string): Promise<void>;
257251
};
258252

packages/mcp-server-supabase/src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export function createSupabaseMcpServer(options: SupabaseMcpServerOptions) {
134134
}
135135

136136
if (!projectId && account && enabledFeatures.has('account')) {
137-
Object.assign(tools, getAccountTools({ account, readOnly }));
137+
Object.assign(tools, getAccountTools({ account, readOnly, server }));
138138
}
139139

140140
if (database && enabledFeatures.has('database')) {

packages/mcp-server-supabase/src/tools/account-tools.ts

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { tool } from '@supabase/mcp-utils';
1+
import { tool, type ToolExecuteContext } from '@supabase/mcp-utils';
2+
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
23
import { z } from 'zod';
34
import type { AccountOperations } from '../platform/types.js';
45
import { type Cost, getBranchCost, getNextProjectCost } from '../pricing.js';
@@ -10,9 +11,14 @@ const SUCCESS_RESPONSE = { success: true };
1011
export type AccountToolsOptions = {
1112
account: AccountOperations;
1213
readOnly?: boolean;
14+
server?: Server;
1315
};
1416

15-
export function getAccountTools({ account, readOnly }: AccountToolsOptions) {
17+
export function getAccountTools({
18+
account,
19+
readOnly,
20+
server,
21+
}: AccountToolsOptions) {
1622
return {
1723
list_organizations: tool({
1824
description: 'Lists all organizations that the user is a member of.',
@@ -131,7 +137,7 @@ export function getAccountTools({ account, readOnly }: AccountToolsOptions) {
131137
}),
132138
create_project: tool({
133139
description:
134-
'Creates a new Supabase project. Always ask the user which organization to create the project in. The project can take a few minutes to initialize - use `get_project` to check the status.',
140+
'Creates a new Supabase project. Always ask the user which organization to create the project in. If there is a cost involved, the user will be asked to confirm before creation. The project can take a few minutes to initialize - use `get_project` to check the status.',
135141
annotations: {
136142
title: 'Create project',
137143
readOnlyHint: false,
@@ -145,31 +151,63 @@ export function getAccountTools({ account, readOnly }: AccountToolsOptions) {
145151
.enum(AWS_REGION_CODES)
146152
.describe('The region to create the project in.'),
147153
organization_id: z.string(),
148-
confirm_cost_id: z
149-
.string({
150-
required_error:
151-
'User must confirm understanding of costs before creating a project.',
152-
})
153-
.describe('The cost confirmation ID. Call `confirm_cost` first.'),
154154
}),
155-
execute: async ({ name, region, organization_id, confirm_cost_id }) => {
155+
execute: async ({ name, region, organization_id }, context) => {
156156
if (readOnly) {
157157
throw new Error('Cannot create a project in read-only mode.');
158158
}
159159

160+
// Calculate cost inline
160161
const cost = await getNextProjectCost(account, organization_id);
161-
const costHash = await hashObject(cost);
162-
if (costHash !== confirm_cost_id) {
163-
throw new Error(
164-
'Cost confirmation ID does not match the expected cost of creating a project.'
165-
);
162+
163+
// Only request confirmation if there's a cost AND server supports elicitation
164+
if (cost.amount > 0 && context?.server?.elicitInput) {
165+
const costMessage = `$${cost.amount} per ${cost.recurrence}`;
166+
167+
const result = await context.server.elicitInput({
168+
message: `You are about to create project "${name}" in region ${region}.\n\n💰 Cost: ${costMessage}\n\nDo you want to proceed with this billable project?`,
169+
requestedSchema: {
170+
type: 'object',
171+
properties: {
172+
confirm: {
173+
type: 'boolean',
174+
title: 'Confirm billable project creation',
175+
description: `I understand this will cost ${costMessage} and want to proceed`,
176+
},
177+
},
178+
required: ['confirm'],
179+
},
180+
});
181+
182+
// Handle user response
183+
if (result.action === 'decline' || result.action === 'cancel') {
184+
throw new Error('Project creation cancelled by user.');
185+
}
186+
187+
if (result.action === 'accept' && !result.content?.confirm) {
188+
throw new Error(
189+
'You must confirm understanding of the cost to create a billable project.'
190+
);
191+
}
166192
}
167193

168-
return await account.createProject({
194+
// Create the project (either free or confirmed)
195+
const project = await account.createProject({
169196
name,
170197
region,
171198
organization_id,
172199
});
200+
201+
// Return appropriate message based on cost
202+
const costInfo =
203+
cost.amount > 0
204+
? `Cost: $${cost.amount}/${cost.recurrence}`
205+
: 'Cost: Free';
206+
207+
return {
208+
...project,
209+
message: `Project "${name}" created successfully. ${costInfo}`,
210+
};
173211
},
174212
}),
175213
pause_project: tool({

packages/mcp-utils/src/server.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,21 @@ export type ResourceTemplate<Uri extends string = string, Result = unknown> = {
5050
): Promise<Result>;
5151
};
5252

53+
export type ToolExecuteContext = {
54+
server?: Server;
55+
};
56+
5357
export type Tool<
5458
Params extends z.ZodObject<any> = z.ZodObject<any>,
5559
Result = unknown,
5660
> = {
5761
description: Prop<string>;
5862
annotations?: Annotations;
5963
parameters: Params;
60-
execute(params: z.infer<Params>): Promise<Result>;
64+
execute(
65+
params: z.infer<Params>,
66+
context?: ToolExecuteContext
67+
): Promise<Result>;
6168
};
6269

6370
/**
@@ -484,7 +491,7 @@ export function createMcpServer(options: McpServerOptions) {
484491
const executeWithCallback = async (tool: Tool) => {
485492
// Wrap success or error in a result value
486493
const res = await tool
487-
.execute(args)
494+
.execute(args, { server })
488495
.then((data: unknown) => ({ success: true as const, data }))
489496
.catch((error) => ({ success: false as const, error }));
490497

0 commit comments

Comments
 (0)