Skip to content

Commit d4c17aa

Browse files
authored
Merge pull request #13 from supabase-community/feat/get-logs
get_logs tool call
2 parents 7f28a82 + e960cb4 commit d4c17aa

File tree

7 files changed

+240
-4
lines changed

7 files changed

+240
-4
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ The following Supabase tools are available to the LLM:
134134
- `list_migrations`: Lists all migrations in the database.
135135
- `apply_migration`: Applies a SQL migration to the database. SQL passed to this tool will be tracked within the database, so LLMs should use this for DDL operations (schema changes).
136136
- `execute_sql`: Executes raw SQL in the database. LLMs should use this for regular queries that don't change the schema.
137+
- `get_logs`: Gets logs for a Supabase project by service type (api, postgres, edge functions, auth, storage, realtime). LLMs can use this to help with debugging and monitoring service performance.
137138

138139
#### Project Configuration
139140

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/mcp-server-supabase/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@gregnr/postgres-meta": "^0.82.0-dev.2",
3131
"@modelcontextprotocol/sdk": "^1.4.1",
3232
"@supabase/mcp-utils": "0.1.1",
33+
"common-tags": "^1.8.2",
3334
"openapi-fetch": "^0.13.4",
3435
"postgres": "^3.4.5",
3536
"zod": "^3.24.1",
@@ -38,6 +39,7 @@
3839
"devDependencies": {
3940
"@electric-sql/pglite": "^0.2.17",
4041
"@total-typescript/tsconfig": "^1.0.4",
42+
"@types/common-tags": "^1.8.4",
4143
"@types/node": "^22.8.6",
4244
"date-fns": "^4.1.0",
4345
"msw": "^2.7.3",
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { stripIndent } from 'common-tags';
2+
3+
export function getLogQuery(
4+
service:
5+
| 'api'
6+
| 'postgres'
7+
| 'edge-function'
8+
| 'auth'
9+
| 'storage'
10+
| 'realtime',
11+
limit: number = 100
12+
) {
13+
switch (service) {
14+
case 'api':
15+
return stripIndent`
16+
select id, identifier, timestamp, event_message, request.method, request.path, response.status_code
17+
from edge_logs
18+
cross join unnest(metadata) as m
19+
cross join unnest(m.request) as request
20+
cross join unnest(m.response) as response
21+
order by timestamp desc
22+
limit ${limit}
23+
`;
24+
case 'postgres':
25+
return stripIndent`
26+
select identifier, postgres_logs.timestamp, id, event_message, parsed.error_severity from postgres_logs
27+
cross join unnest(metadata) as m
28+
cross join unnest(m.parsed) as parsed
29+
order by timestamp desc
30+
limit ${limit}
31+
`;
32+
case 'edge-function':
33+
return stripIndent`
34+
select * from function_logs
35+
order by timestamp desc
36+
limit ${limit}
37+
`;
38+
case 'auth':
39+
return stripIndent`
40+
select id, auth_logs.timestamp, event_message, metadata.level, metadata.status, metadata.path, metadata.msg as msg, metadata.error from auth_logs
41+
cross join unnest(metadata) as metadata
42+
order by timestamp desc
43+
limit ${limit}
44+
`;
45+
case 'storage':
46+
return stripIndent`
47+
select id, storage_logs.timestamp, event_message from storage_logs
48+
order by timestamp desc
49+
limit ${limit}
50+
`;
51+
case 'realtime':
52+
return stripIndent`
53+
select id, realtime_logs.timestamp, event_message from realtime_logs
54+
order by timestamp desc
55+
limit ${limit}
56+
`;
57+
default:
58+
throw new Error(`unsupported log service type: ${service}`);
59+
}
60+
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,47 @@ describe('tools', () => {
451451
);
452452
});
453453

454+
test('get logs for each service type', async () => {
455+
const { callTool } = await setup();
456+
const project = mockProjects.values().next().value!;
457+
const services = [
458+
'api',
459+
'postgres',
460+
'edge-function',
461+
'auth',
462+
'storage',
463+
'realtime',
464+
] as const;
465+
466+
for (const service of services) {
467+
const result = await callTool({
468+
name: 'get_logs',
469+
arguments: {
470+
project_id: project.id,
471+
service,
472+
},
473+
});
474+
475+
expect(result).toEqual([]);
476+
}
477+
});
478+
479+
test('get logs for invalid service type', async () => {
480+
const { callTool } = await setup();
481+
const project = mockProjects.values().next().value!;
482+
const invalidService = 'invalid-service';
483+
const getLogsPromise = callTool({
484+
name: 'get_logs',
485+
arguments: {
486+
project_id: project.id,
487+
service: invalidService,
488+
},
489+
});
490+
await expect(getLogsPromise).rejects.toThrow(
491+
`unsupported log service type: invalid-service`
492+
);
493+
});
494+
454495
test('enable branching', async () => {
455496
const { callTool } = await setup();
456497
const project = mockProjects.values().next().value!;

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
import { createMcpServer, tool } from '@supabase/mcp-utils';
77
import { z } from 'zod';
88
import { version } from '../package.json';
9+
import { getLogQuery } from './logs.js';
910
import {
1011
assertSuccess,
1112
createManagementApiClient,
@@ -328,6 +329,45 @@ export function createSupabaseMcpServer(options: SupabaseMcpServerOptions) {
328329
return await executeSql(project_id, query);
329330
},
330331
}),
332+
get_logs: tool({
333+
description:
334+
'Gets logs for a Supabase project by service type. Use this to help debug problems with your app. This will only return logs within the last minute. If the logs you are looking for are older than 1 minute, re-run your test to reproduce them.',
335+
parameters: z.object({
336+
project_id: z.string(),
337+
service: z
338+
.enum([
339+
'api',
340+
'postgres',
341+
'edge-function',
342+
'auth',
343+
'storage',
344+
'realtime',
345+
])
346+
.describe('The service to fetch logs for'),
347+
}),
348+
execute: async ({ project_id, service }) => {
349+
const now = Date.now();
350+
const response = await managementApiClient.GET(
351+
'/v1/projects/{ref}/analytics/endpoints/logs.all',
352+
{
353+
params: {
354+
path: {
355+
ref: project_id,
356+
},
357+
query: {
358+
// Omitting start and end time defaults to the last minute
359+
sql: getLogQuery(service),
360+
},
361+
},
362+
}
363+
);
364+
365+
assertSuccess(response, 'Failed to fetch logs');
366+
367+
return response.data;
368+
},
369+
}),
370+
331371
get_project_url: tool({
332372
description: 'Gets the API URL for a project.',
333373
parameters: z.object({

0 commit comments

Comments
 (0)