Skip to main content
Glama
evtapps

Ditto MCP Server

by evtapps

execute_dql

Execute parameterized DQL queries to retrieve data from Ditto databases with configurable timeouts and transaction consistency.

Instructions

Runs a query in Ditto using a parameterized DQL statement

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
statementYesDQL statement to execute (single statement; no trailing ';')
argsNoNamed parameters for the DQL
transactionIdNoOptional X-DITTO-TXN-ID for consistency
apiKeyNoDitto API key (prefer env over passing here)
baseUrlNoOverrides DITTO_BASE_URL for this call
timeoutMsNoPer-call timeout override (ms)

Implementation Reference

  • Core handler function that executes the DQL query by making an authenticated POST request to Ditto's /api/v4/store/execute endpoint. Handles timeouts, errors, and returns structured DittoResponse.
    export async function executeDql(
      req: ExecuteRequest,
      opts: ExecuteOptions,
    ): Promise<DittoResponse> {
      const url = buildUrl(opts.baseUrl);
      const controller = new AbortController();
      const timeoutMs = opts.timeoutMs ?? 20000;
      const id = setTimeout(() => controller.abort(), timeoutMs);
    
      try {
        const headers: Record<string, string> = {
          Authorization: `Bearer ${opts.apiKey}`,
          'Content-Type': 'application/json',
          'User-Agent': `ditto-mcp-server/${SERVER_VERSION}`,
        };
        if (opts.transactionId !== undefined) {
          headers['X-DITTO-TXN-ID'] = String(opts.transactionId);
        }
    
        const res = await fetch(url, {
          method: 'POST',
          headers,
          body: JSON.stringify(req),
          signal: controller.signal,
        });
    
        const text = await res.text();
        let json: DittoResponse | undefined;
    
        try {
          json = text ? (JSON.parse(text) as DittoResponse) : undefined;
        } catch {
          // Fallback plain-text error
          if (!res.ok) {
            throw Object.assign(new Error(text || `HTTP ${res.status}`), {
              status: res.status,
            });
          }
          throw new Error('Ditto response was not valid JSON');
        }
    
        if (!res.ok) {
          const errMsg = json?.error?.description || `HTTP ${res.status}`;
          class HttpStatusError extends Error {
            status?: number;
            constructor(message: string, status?: number) {
              super(message);
              this.status = status;
            }
          }
          throw new HttpStatusError(errMsg, res.status);
        }
    
        return json!;
      } catch (err: unknown) {
        let message = 'Request failed';
        if (typeof err === 'object' && err !== null) {
          const maybe = err as { message?: string };
          if (typeof maybe.message === 'string') message = maybe.message;
        } else if (typeof err === 'string') {
          message = err;
        }
        logger.error(`[ditto] request failed: ${message}`);
        // Return a Ditto-like error envelope to the tool handler
        const errorResponse: DittoResponse = {
          queryType: 'unknown',
          items: [],
          mutatedDocumentIds: [],
          totalWarningsCount: 0,
          warnings: [],
          error: { description: message },
          transactionId: undefined,
        };
        return errorResponse;
      } finally {
        clearTimeout(id);
      }
    }
  • Registers the 'execute_dql' MCP tool, including input schema, description, and the wrapper handler function that applies guards, resolves config, calls executeDql, and formats the MCP response.
    server.registerTool(
      'execute_dql',
      {
        title: 'Ditto: Execute DQL query',
        description: 'Runs a query in Ditto using a parameterized DQL statement',
        inputSchema: executeInput,
      },
      async ({
        statement,
        args,
        transactionId,
        apiKey,
        baseUrl,
        timeoutMs,
      }: ExecuteToolInput) => {
        // Guard the statement & allowed operations
        const cleaned = ensureSingleStatement(statement);
        const op = detectOperation(cleaned);
        if (op === 'UNKNOWN') {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: 'Statement must start with SELECT/INSERT/UPDATE/DELETE/EVICT.',
              },
            ],
          };
        }
        try {
          assertAllowedOperation(op, cfg.allowedOps);
          assertAllowPatternsIfAny(cleaned, cfg.queryAllowPatterns);
        } catch (e) {
          const message = e instanceof GuardError ? e.message : String(e);
          return { isError: true, content: [{ type: 'text', text: message }] };
        }
    
        // Resolve base URL & key
        const url = baseUrl ?? cfg.dittoBaseUrl;
        if (!url) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: 'DITTO_BASE_URL not set. Pass baseUrl or set env DITTO_BASE_URL.',
              },
            ],
          };
        }
        const envKey = process.env[cfg.apiKeyEnvVar];
        const key = apiKey || envKey;
        if (!key) {
          return {
            isError: true,
            content: [
              {
                type: 'text',
                text: `No API key. Set env ${cfg.apiKeyEnvVar} or pass apiKey.`,
              },
            ],
          };
        }
    
        // Call Ditto
        const res = await executeDql(
          { statement: cleaned, args: args ?? undefined },
          {
            baseUrl: url,
            apiKey: key,
            transactionId,
            timeoutMs: timeoutMs ?? cfg.timeoutMs,
          },
        );
    
        if (res.error?.description) {
          return {
            isError: true,
            content: [{ type: 'text', text: `Ditto error: ${res.error.description}` }],
          };
        }
    
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(
                {
                  transactionId: res.transactionId,
                  queryType: res.queryType,
                  items: res.items,
                  mutatedDocumentIds: res.mutatedDocumentIds,
                  warnings: res.warnings,
                  totalWarningsCount: res.totalWarningsCount,
                },
                null,
                2,
              ),
            },
          ],
        };
      },
    );
  • Zod schema defining the input parameters for the 'execute_dql' tool.
    const executeInput = {
      statement: z
        .string()
        .min(1)
        .describe("DQL statement to execute (single statement; no trailing ';')"),
      args: z.record(z.unknown()).optional().describe('Named parameters for the DQL'),
      transactionId: z
        .number()
        .int()
        .nonnegative()
        .optional()
        .describe('Optional X-DITTO-TXN-ID for consistency'),
      apiKey: z
        .string()
        .min(1)
        .optional()
        .describe('Ditto API key (prefer env over passing here)'),
      baseUrl: z
        .string()
        .url()
        .optional()
        .describe('Overrides DITTO_BASE_URL for this call'),
      timeoutMs: z
        .number()
        .int()
        .positive()
        .max(60000)
        .optional()
        .describe('Per-call timeout override (ms)'),
    };
  • TypeScript type definitions for ExecuteRequest, ExecuteOptions, and DittoResponse used by the executeDql handler.
    export type DittoQueryArgs = Record<string, unknown>;
    
    export type ExecuteRequest = {
      statement: string;
      args?: DittoQueryArgs;
    };
    
    export type DittoResponse = {
      transactionId?: number;
      queryType: string; // "select" | "insert" | "update" | ...
      items: unknown[];
      mutatedDocumentIds: unknown[];
      warnings: { _id?: unknown; description: string }[];
      totalWarningsCount: number;
      error?: { description?: string };
    };
    
    export type ExecuteOptions = {
      baseUrl: string; // canonical root, e.g. https://APP.cloud.ditto.live
      apiKey: string; // Ditto API key (Authorization: Bearer ...)
      timeoutMs?: number;
      transactionId?: number; // optional X-DITTO-TXN-ID
    };
  • Helper functions for DQL statement validation and guarding: single-statement enforcement, operation detection, allowed ops/patterns checks. Used in the tool handler.
    import type { DqlOp } from '../config.js';
    
    export class GuardError extends Error {
      status = 400;
      constructor(message: string, status = 400) {
        super(message);
        this.status = status;
      }
    }
    
    /** Remove trailing semicolons and ensure a single statement. */
    export function ensureSingleStatement(statement: string): string {
      const cleaned = statement.replace(/;+\s*$/, '').trim();
      if (!cleaned) throw new GuardError('DQL statement cannot be empty');
      if (cleaned.includes(';'))
        throw new GuardError('Multiple DQL statements are not allowed');
      return cleaned;
    }
    
    /** Very small lexer: strip block/line comments, then return first token uppercased. */
    export function detectOperation(statement: string): DqlOp | 'UNKNOWN' {
      const noBlock = statement.replace(/\/\*[\s\S]*?\*\//g, ' ');
      const noLine = noBlock.replace(/--.*$/gm, ' ').replace(/#[^\n]*$/gm, ' ');
      const firstToken = noLine.trim().split(/\s+/)[0]?.toUpperCase();
      const ops = new Set(['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'EVICT']);
      return firstToken && ops.has(firstToken) ? (firstToken as DqlOp) : 'UNKNOWN';
    }
    
    export function assertAllowedOperation(op: DqlOp, allowed: Set<DqlOp>) {
      if (!allowed.has(op)) {
        const allowedList = [...allowed].join(', ');
        throw new GuardError(
          `Operation "${op}" is disabled. Allowed operations: ${allowedList}`,
          403,
        );
      }
    }
    
    export function assertAllowPatternsIfAny(statement: string, allowPatterns: RegExp[]) {
      if (allowPatterns.length === 0) return;
      const ok = allowPatterns.some((rx) => rx.test(statement));
      if (!ok) {
        throw new GuardError('Statement does not match any allowed pattern (policy).', 403);
      }
    }
Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/evtapps/ditto-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server