Lightdash MCP Server
by syucream
- src
import {
ListToolsRequestSchema,
CallToolRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { createLightdashClient } from 'lightdash-client-typescript-fetch';
import { zodToJsonSchema } from 'zod-to-json-schema';
import {
ListProjectsSchema,
GetProjectSchema,
ListSpacesSchema,
ListChartsSchema,
ListDashboardsSchema,
GetCustomMetricsSchema,
GetCatalogSchema,
GetMetricsCatalogSchema,
GetChartsAsCodeSchema,
GetDashboardsAsCodeSchema,
GetMetadataSchema,
GetAnalyticsSchema,
GetUserAttributesSchema,
} from './schemas.js';
const lightdashClient = createLightdashClient(
process.env.LIGHTDASH_API_URL || 'https://app.lightdash.cloud',
{
headers: {
Authorization: `ApiKey ${process.env.LIGHTDASH_API_KEY}`,
},
}
);
const server = new Server(
{
name: 'lightdash-mcp-server',
version: '0.0.1',
},
{
capabilities: {
tools: {},
},
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'list_projects',
description: 'List all projects in the Lightdash organization',
inputSchema: zodToJsonSchema(ListProjectsSchema),
},
{
name: 'get_project',
description: 'Get details of a specific project',
inputSchema: zodToJsonSchema(GetProjectSchema),
},
{
name: 'list_spaces',
description: 'List all spaces in a project',
inputSchema: zodToJsonSchema(ListSpacesSchema),
},
{
name: 'list_charts',
description: 'List all charts in a project',
inputSchema: zodToJsonSchema(ListChartsSchema),
},
{
name: 'list_dashboards',
description: 'List all dashboards in a project',
inputSchema: zodToJsonSchema(ListDashboardsSchema),
},
{
name: 'get_custom_metrics',
description: 'Get custom metrics for a project',
inputSchema: zodToJsonSchema(GetCustomMetricsSchema),
},
{
name: 'get_catalog',
description: 'Get catalog for a project',
inputSchema: zodToJsonSchema(GetCatalogSchema),
},
{
name: 'get_metrics_catalog',
description: 'Get metrics catalog for a project',
inputSchema: zodToJsonSchema(GetMetricsCatalogSchema),
},
{
name: 'get_charts_as_code',
description: 'Get charts as code for a project',
inputSchema: zodToJsonSchema(GetChartsAsCodeSchema),
},
{
name: 'get_dashboards_as_code',
description: 'Get dashboards as code for a project',
inputSchema: zodToJsonSchema(GetDashboardsAsCodeSchema),
},
{
name: 'get_metadata',
description: 'Get metadata for a specific table in the data catalog',
inputSchema: zodToJsonSchema(GetMetadataSchema),
},
{
name: 'get_analytics',
description: 'Get analytics for a specific table in the data catalog',
inputSchema: zodToJsonSchema(GetAnalyticsSchema),
},
{
name: 'get_user_attributes',
description: 'Get organization user attributes',
inputSchema: zodToJsonSchema(GetUserAttributesSchema),
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
if (!request.params.arguments) {
throw new Error('Arguments are required');
}
switch (request.params.name) {
case 'list_projects': {
const { data, error } = await lightdashClient.GET(
'/api/v1/org/projects',
{}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_project': {
const args = GetProjectSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'list_spaces': {
const args = ListSpacesSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/spaces',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'list_charts': {
const args = ListChartsSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/charts',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'list_dashboards': {
const args = ListDashboardsSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/dashboards',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_custom_metrics': {
const args = GetCustomMetricsSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/custom-metrics',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_catalog': {
const args = GetCatalogSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/dataCatalog',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_metrics_catalog': {
const args = GetMetricsCatalogSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/dataCatalog/metrics',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_charts_as_code': {
const args = GetChartsAsCodeSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/charts/code',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_dashboards_as_code': {
const args = GetDashboardsAsCodeSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/dashboards/code',
{
params: { path: { projectUuid: args.projectUuid } },
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_metadata': {
const args = GetMetadataSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/dataCatalog/{table}/metadata',
{
params: {
path: {
projectUuid: args.projectUuid,
table: args.table,
},
},
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_analytics': {
const args = GetAnalyticsSchema.parse(request.params.arguments);
const { data, error } = await lightdashClient.GET(
'/api/v1/projects/{projectUuid}/dataCatalog/{table}/analytics',
{
params: {
path: {
projectUuid: args.projectUuid,
table: args.table,
},
},
}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
case 'get_user_attributes': {
const { data, error } = await lightdashClient.GET(
'/api/v1/org/attributes',
{}
);
if (error) {
throw new Error(
`Lightdash API error: ${error.error.name}, ${error.error.message ?? 'no message'}`
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(data.results, null, 2),
},
],
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error) {
console.error('Error handling request:', error);
const errorMessage =
error instanceof Error ? error.message : 'Unknown error occurred';
throw new Error(errorMessage);
}
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Lightdash MCP Server running on stdio');
}
runServer().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});