Skip to content

Commit 510010d

Browse files
committed
feat: support import map path
1 parent e44af7e commit 510010d

File tree

4 files changed

+348
-63
lines changed

4 files changed

+348
-63
lines changed

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

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import { codeBlock } from 'common-tags';
2+
import { fileURLToPath } from 'url';
3+
import { extractFiles } from './eszip.js';
4+
import {
5+
assertSuccess,
6+
type ManagementApiClient,
7+
} from './management-api/index.js';
28

39
/**
410
* Gets the deployment ID for an Edge Function.
@@ -34,3 +40,90 @@ export const edgeFunctionExample = codeBlock`
3440
});
3541
});
3642
`;
43+
44+
/**
45+
* Fetches a full Edge Function from the Supabase Management API.
46+
47+
* - Includes both function metadata and the contents of each file.
48+
* - Normalizes file paths to be relative to the project root.
49+
*/
50+
export async function getFullEdgeFunction(
51+
managementApiClient: ManagementApiClient,
52+
projectId: string,
53+
functionSlug: string
54+
) {
55+
const functionResponse = await managementApiClient.GET(
56+
'/v1/projects/{ref}/functions/{function_slug}',
57+
{
58+
params: {
59+
path: {
60+
ref: projectId,
61+
function_slug: functionSlug,
62+
},
63+
},
64+
}
65+
);
66+
67+
if (functionResponse.error) {
68+
return {
69+
data: undefined,
70+
error: functionResponse.error as { message: string },
71+
};
72+
}
73+
74+
assertSuccess(functionResponse, 'Failed to fetch Edge Function');
75+
76+
const edgeFunction = functionResponse.data;
77+
78+
const deploymentId = getDeploymentId(
79+
projectId,
80+
edgeFunction.id,
81+
edgeFunction.version
82+
);
83+
84+
const pathPrefix = getPathPrefix(deploymentId);
85+
86+
const entrypoint_path = edgeFunction.entrypoint_path
87+
? fileURLToPath(edgeFunction.entrypoint_path).replace(pathPrefix, '')
88+
: undefined;
89+
90+
const import_map_path = edgeFunction.import_map_path
91+
? fileURLToPath(edgeFunction.import_map_path).replace(pathPrefix, '')
92+
: undefined;
93+
94+
const eszipResponse = await managementApiClient.GET(
95+
'/v1/projects/{ref}/functions/{function_slug}/body',
96+
{
97+
params: {
98+
path: {
99+
ref: projectId,
100+
function_slug: functionSlug,
101+
},
102+
},
103+
parseAs: 'arrayBuffer',
104+
}
105+
);
106+
107+
assertSuccess(eszipResponse, 'Failed to fetch Edge Function eszip bundle');
108+
109+
const extractedFiles = await extractFiles(
110+
new Uint8Array(eszipResponse.data),
111+
pathPrefix
112+
);
113+
114+
const files = await Promise.all(
115+
extractedFiles.map(async (file) => ({
116+
name: file.name,
117+
content: await file.text(),
118+
}))
119+
);
120+
121+
const normalizedFunction = {
122+
...edgeFunction,
123+
entrypoint_path,
124+
import_map_path,
125+
files,
126+
};
127+
128+
return { data: normalizedFunction, error: undefined };
129+
}

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

Lines changed: 179 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,7 @@ describe('tools', () => {
10671067
name: edgeFunction.name,
10681068
status: edgeFunction.status,
10691069
entrypoint_path: 'index.ts',
1070-
import_map_path: null,
1070+
import_map_path: undefined,
10711071
import_map: false,
10721072
verify_jwt: true,
10731073
created_at: expect.stringMatching(
@@ -1126,7 +1126,7 @@ describe('tools', () => {
11261126
name: functionName,
11271127
status: 'ACTIVE',
11281128
entrypoint_path: expect.stringMatching(/index\.ts$/),
1129-
import_map_path: null,
1129+
import_map_path: undefined,
11301130
import_map: false,
11311131
verify_jwt: true,
11321132
created_at: expect.stringMatching(
@@ -1193,7 +1193,7 @@ describe('tools', () => {
11931193
name: functionName,
11941194
status: 'ACTIVE',
11951195
entrypoint_path: expect.stringMatching(/index\.ts$/),
1196-
import_map_path: null,
1196+
import_map_path: undefined,
11971197
import_map: false,
11981198
verify_jwt: true,
11991199
created_at: expect.stringMatching(
@@ -1210,6 +1210,182 @@ describe('tools', () => {
12101210
);
12111211
});
12121212

1213+
test('custom edge function import map', async () => {
1214+
const { callTool } = await setup();
1215+
1216+
const org = await createOrganization({
1217+
name: 'My Org',
1218+
plan: 'free',
1219+
allowed_release_channels: ['ga'],
1220+
});
1221+
1222+
const project = await createProject({
1223+
name: 'Project 1',
1224+
region: 'us-east-1',
1225+
organization_id: org.id,
1226+
});
1227+
1228+
const functionName = 'hello-world';
1229+
const functionCode = 'console.log("Hello, world!");';
1230+
1231+
const result = await callTool({
1232+
name: 'deploy_edge_function',
1233+
arguments: {
1234+
project_id: project.id,
1235+
name: functionName,
1236+
import_map_path: 'custom-map.json',
1237+
files: [
1238+
{
1239+
name: 'index.ts',
1240+
content: functionCode,
1241+
},
1242+
{
1243+
name: 'custom-map.json',
1244+
content: '{}',
1245+
},
1246+
],
1247+
},
1248+
});
1249+
1250+
expect(result.import_map).toBe(true);
1251+
expect(result.import_map_path).toMatch(/custom-map\.json$/);
1252+
});
1253+
1254+
test('default edge function import map to deno.json', async () => {
1255+
const { callTool } = await setup();
1256+
1257+
const org = await createOrganization({
1258+
name: 'My Org',
1259+
plan: 'free',
1260+
allowed_release_channels: ['ga'],
1261+
});
1262+
1263+
const project = await createProject({
1264+
name: 'Project 1',
1265+
region: 'us-east-1',
1266+
organization_id: org.id,
1267+
});
1268+
1269+
const functionName = 'hello-world';
1270+
const functionCode = 'console.log("Hello, world!");';
1271+
1272+
const result = await callTool({
1273+
name: 'deploy_edge_function',
1274+
arguments: {
1275+
project_id: project.id,
1276+
name: functionName,
1277+
files: [
1278+
{
1279+
name: 'index.ts',
1280+
content: functionCode,
1281+
},
1282+
{
1283+
name: 'deno.json',
1284+
content: '{}',
1285+
},
1286+
],
1287+
},
1288+
});
1289+
1290+
expect(result.import_map).toBe(true);
1291+
expect(result.import_map_path).toMatch(/deno\.json$/);
1292+
});
1293+
1294+
test('default edge function import map to import_map.json', async () => {
1295+
const { callTool } = await setup();
1296+
1297+
const org = await createOrganization({
1298+
name: 'My Org',
1299+
plan: 'free',
1300+
allowed_release_channels: ['ga'],
1301+
});
1302+
1303+
const project = await createProject({
1304+
name: 'Project 1',
1305+
region: 'us-east-1',
1306+
organization_id: org.id,
1307+
});
1308+
1309+
const functionName = 'hello-world';
1310+
const functionCode = 'console.log("Hello, world!");';
1311+
1312+
const result = await callTool({
1313+
name: 'deploy_edge_function',
1314+
arguments: {
1315+
project_id: project.id,
1316+
name: functionName,
1317+
files: [
1318+
{
1319+
name: 'index.ts',
1320+
content: functionCode,
1321+
},
1322+
{
1323+
name: 'import_map.json',
1324+
content: '{}',
1325+
},
1326+
],
1327+
},
1328+
});
1329+
1330+
expect(result.import_map).toBe(true);
1331+
expect(result.import_map_path).toMatch(/import_map\.json$/);
1332+
});
1333+
1334+
test('updating edge function with missing import_map_path defaults to previous value', async () => {
1335+
const { callTool } = await setup();
1336+
const org = await createOrganization({
1337+
name: 'My Org',
1338+
plan: 'free',
1339+
allowed_release_channels: ['ga'],
1340+
});
1341+
1342+
const project = await createProject({
1343+
name: 'Project 1',
1344+
region: 'us-east-1',
1345+
organization_id: org.id,
1346+
});
1347+
project.status = 'ACTIVE_HEALTHY';
1348+
1349+
const functionName = 'hello-world';
1350+
1351+
const edgeFunction = await project.deployEdgeFunction(
1352+
{
1353+
name: functionName,
1354+
entrypoint_path: 'index.ts',
1355+
import_map_path: 'custom-map.json',
1356+
},
1357+
[
1358+
new File(['console.log("Hello, world!");'], 'index.ts', {
1359+
type: 'application/typescript',
1360+
}),
1361+
new File(['{}'], 'custom-map.json', {
1362+
type: 'application/json',
1363+
}),
1364+
]
1365+
);
1366+
1367+
const result = await callTool({
1368+
name: 'deploy_edge_function',
1369+
arguments: {
1370+
project_id: project.id,
1371+
name: functionName,
1372+
files: [
1373+
{
1374+
name: 'index.ts',
1375+
content: 'console.log("Hello, world! v2");',
1376+
},
1377+
{
1378+
name: 'custom-map.json',
1379+
content: '{}',
1380+
},
1381+
],
1382+
},
1383+
});
1384+
1385+
expect(result.import_map).toBe(true);
1386+
expect(result.import_map_path).toMatch(/custom-map\.json$/);
1387+
});
1388+
12131389
test('create branch', async () => {
12141390
const { callTool } = await setup();
12151391

0 commit comments

Comments
 (0)