Skip to content

MCP Server (AI-Ready Data Layer)

Drizzle Cube includes a built-in MCP server that lets AI agents like Claude, ChatGPT, and n8n query your semantic layer directly. The server follows the Model Context Protocol specification and exposes tools for discovering cubes, validating queries, and executing them.

All framework adapters expose an MCP server at /mcp:

https://your-app.com/mcp

This endpoint implements the full MCP specification including:

  • Tools - Functions the AI can call
  • Prompts - Pre-built prompts for common tasks
  • Server-Sent Events (SSE) - For streaming responses
ToolPurpose
drizzle_cube_discoverFind relevant cubes based on topic or intent
drizzle_cube_validateValidate queries and get auto-corrections
drizzle_cube_loadExecute queries and return results

Add to your claude_desktop_config.json:

{
"mcpServers": {
"your-app-analytics": {
"command": "npx",
"args": ["-y", "@anthropic/mcp-remote", "https://your-app.com/mcp"]
}
}
}

With authentication (recommended for production):

{
"mcpServers": {
"your-app-analytics": {
"command": "npx",
"args": [
"-y", "@anthropic/mcp-remote",
"https://your-app.com/mcp",
"--header", "Authorization: Bearer YOUR_TOKEN"
]
}
}
}
Terminal window
claude mcp add --transport http analytics https://your-app.com/mcp \
--header "Authorization: Bearer YOUR_TOKEN"
  1. Go to Settings → Connectors → Add Connector
  2. Enter your MCP server URL: https://your-app.com/mcp
  3. The tools will be available in your conversations

Claude.ai connectors support OAuth 2.1 — if your MCP endpoint has an OAuth discovery document, Claude.ai will handle the auth flow automatically. You can also pass an authorization_token via the Messages API MCP connector for programmatic access.

  1. Go to Settings → Connectors → Advanced → Developer Mode
  2. Add your MCP server URL: https://your-app.com/mcp
  3. The tools will be available in ChatGPT

Use the MCP Client node:

  1. Add an MCP Client node to your workflow
  2. Set the server URL: https://your-app.com/mcp
  3. Connect it to an AI Agent node

See n8n MCP Client documentation for details.

When an AI agent connects to your MCP server, it typically follows this workflow:

User: "Show me average salary by department"
┌─────────────────────────────────────────────────────┐
│ 1. drizzle_cube_discover │
│ Find cubes related to "salary" and "department" │
│ → Returns: Employees, Departments cubes with │
│ suggested measures and dimensions │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 2. AI builds query using metadata │
│ The AI uses cube metadata to construct a query │
│ → Query: { measures: ['Employees.avgSalary'], │
│ dimensions: ['Departments.name'] } │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 3. drizzle_cube_validate (optional) │
│ Check query validity, get corrections if needed │
│ → Returns: { isValid: true, correctedQuery } │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 4. drizzle_cube_load │
│ Execute query with security context │
│ → Returns: { data: [...], annotation: {...} } │
└─────────────────────────────────────────────────────┘

Find cubes relevant to a topic or intent.

// Parameters
{
"topic": "salary",
"intent": "I want to analyze compensation",
"limit": 5,
"minScore": 0.3
}
// Response
{
"cubes": [
{
"cube": "Employees",
"relevanceScore": 0.85,
"matchedOn": ["measure:avgSalary", "measure:totalSalary"],
"suggestedMeasures": ["Employees.avgSalary", "Employees.totalSalary"],
"suggestedDimensions": ["Employees.department", "Employees.location"]
}
]
}

Validate a query and get helpful corrections.

// Parameters
{
"query": {
"measures": ["Employees.cont"],
"dimensions": ["Departments.nam"]
}
}
// Response
{
"isValid": false,
"errors": [
"Unknown measure 'Employees.cont' - did you mean 'Employees.count'?",
"Unknown dimension 'Departments.nam' - did you mean 'Departments.name'?"
],
"correctedQuery": {
"measures": ["Employees.count"],
"dimensions": ["Departments.name"]
}
}

Execute a query and return results.

// Parameters
{
"query": {
"measures": ["Employees.count", "Employees.avgSalary"],
"dimensions": ["Departments.name"]
}
}
// Response
{
"data": [
{ "Departments.name": "Engineering", "Employees.count": 45, "Employees.avgSalary": 125000 },
{ "Departments.name": "Sales", "Employees.count": 32, "Employees.avgSalary": 85000 }
],
"annotation": {
"measures": {
"Employees.count": { "title": "Total Employees", "type": "count" },
"Employees.avgSalary": { "title": "Average Salary", "type": "avg" }
},
"dimensions": {
"Departments.name": { "title": "Department Name", "type": "string" }
}
}
}

If you don’t want the MCP server exposed:

createCubeRouter({
// ... other options
mcp: {
enabled: false
}
})

Expose only specific MCP tools:

createCubeRouter({
// ... other options
mcp: {
enabled: true,
tools: ['discover', 'validate', 'load'] // Only expose these
}
})

Restrict which origins can connect to your MCP server:

createCubeRouter({
// ... other options
mcp: {
enabled: true,
allowedOrigins: ['https://claude.ai', 'https://chat.openai.com']
}
})

Important: The MCP server does not include built-in authentication. You are responsible for adding authentication middleware, just like with the standard Cube API routes.

The MCP endpoint (/mcp) is just an HTTP POST route — standard auth middleware protects it exactly like any other route. The complete flow is:

  1. Middleware authenticates the request (validates token, session, etc.)
  2. extractSecurityContext extracts the user’s identity and permissions
  3. Cube security filters scope all data access to the authenticated user’s tenant

Apply authentication middleware before mounting the cube router. Here are examples for each framework:

import express from 'express'
import { createCubeRouter } from 'drizzle-cube/adapters/express'
const app = express()
// Auth middleware protects ALL routes including /mcp
app.use(async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) return res.status(401).json({ error: 'Unauthorized' })
req.user = await validateToken(token)
next()
})
// Both /cubejs-api/v1/* AND /mcp are now protected
app.use('/', createCubeRouter({
cubes: [employeesCube],
drizzle: db,
schema,
extractSecurityContext: async (req) => ({
organisationId: req.user.orgId,
userId: req.user.id
})
}))
import { Hono } from 'hono'
import { createCubeRoutes } from 'drizzle-cube/adapters/hono'
const app = new Hono()
// Auth middleware protects ALL routes including /mcp
app.use('*', async (c, next) => {
const token = c.req.header('Authorization')?.replace('Bearer ', '')
if (!token) return c.json({ error: 'Unauthorized' }, 401)
const user = await validateToken(token)
c.set('user', user)
await next()
})
// Cube routes (including MCP) are now protected
app.route('/', createCubeRoutes({
cubes: [employeesCube],
drizzle: db,
schema,
extractSecurityContext: async (c) => ({
organisationId: c.get('user').orgId,
userId: c.get('user').id
})
}))
import Fastify from 'fastify'
import { registerCubeRoutes } from 'drizzle-cube/adapters/fastify'
const fastify = Fastify()
// Auth hook protects ALL routes including /mcp
fastify.addHook('onRequest', async (request, reply) => {
const token = request.headers.authorization?.replace('Bearer ', '')
if (!token) return reply.code(401).send({ error: 'Unauthorized' })
request.user = await validateToken(token)
})
await registerCubeRoutes(fastify, {
cubes: [employeesCube],
drizzle: db,
schema,
extractSecurityContext: async (req) => ({
organisationId: req.user.orgId,
userId: req.user.id
})
})
// In Next.js, validate auth inside extractSecurityContext
// since there's no separate middleware layer for API routes
export const cubeHandlers = createCubeHandlers({
cubes: [employeesCube],
drizzle: db,
schema,
extractSecurityContext: async (req) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) throw new Error('Unauthorized')
const user = await validateToken(token)
return {
organisationId: user.orgId,
userId: user.id
}
}
})

All MCP tools respect your security context:

  • drizzle_cube_load executes queries with the authenticated user’s security context
  • drizzle_cube_discover returns cube metadata (schema information) — access is gated by your auth middleware
  • Multi-tenant isolation is enforced on all data access via cube sql filters

The MCP tools work best when your cubes have rich semantic metadata. See Adding Semantic Metadata for how to add:

  • Descriptions for cubes, measures, and dimensions
  • Synonyms for alternate names (“revenue” → “sales”, “income”)
  • Example questions that help AI understand the cube’s purpose

Connect to the demo MCP server to try it out:

https://try.drizzle-cube.dev/mcp