Skip to main content
Glama

GetSqlQuery

Execute ABAP SQL SELECT queries on database tables and CDS views for ad-hoc data retrieval, row counts, and filtered queries via SAP ADT Data Preview API.

Instructions

[read-only] Execute ABAP SQL SELECT queries on database tables and CDS views via SAP ADT Data Preview API. Use for ad-hoc data retrieval, row counts, and filtered queries.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sql_queryYesSQL query to execute
row_numberNo[read-only] Maximum number of rows to return

Implementation Reference

  • Main handler function for the GetSqlQuery tool. Uses ADT client to execute SQL queries via SAP ADT Data Preview API, parses XML response, and returns structured results.
    export async function handleGetSqlQuery(context: HandlerContext, args: any) {
      const { connection, logger } = context;
      try {
        logger?.info('handleGetSqlQuery called');
    
        if (!args?.sql_query) {
          throw new McpError(ErrorCode.InvalidParams, 'SQL query is required');
        }
    
        const sqlQuery = args.sql_query;
        const rowNumber = args.row_number || 100; // Default to 100 rows if not specified
    
        logger?.info(`Executing SQL query (rows=${rowNumber})`);
    
        const client = createAdtClient(connection, logger);
        const response = await client
          .getUtils()
          .getSqlQuery({ sql_query: sqlQuery, row_number: rowNumber });
    
        if (response.status === 200 && response.data) {
          logger?.info('SQL query request completed successfully');
    
          // Parse the XML response
          const parsedData = parseSqlQueryXml(
            response.data,
            sqlQuery,
            rowNumber,
            logger,
          );
    
          logger?.debug(
            `Parsed SQL query data: rows=${parsedData.rows.length}/${parsedData.total_rows ?? 0}, columns=${parsedData.columns.length}`,
          );
    
          const result = {
            isError: false,
            content: [
              {
                type: 'text',
                text: JSON.stringify(parsedData, null, 2),
              },
            ],
          };
          if (args.filePath) {
            logger?.debug(`Writing SQL query result to file: ${args.filePath}`);
            writeResultToFile(result, args.filePath);
          }
          return result;
        } else {
          throw new McpError(
            ErrorCode.InternalError,
            `Failed to execute SQL query. Status: ${response.status}`,
          );
        }
      } catch (error) {
        logger?.error('Failed to execute SQL query', error as any);
        // MCP-compliant error response: always return content[] with type "text"
        return {
          isError: true,
          content: [
            {
              type: 'text',
              text: `ADT error: ${String(error)}`,
            },
          ],
        };
      }
    }
  • Tool definition with input schema: requires 'sql_query' (string), optional 'row_number' (number, default 100).
    export const TOOL_DEFINITION = {
      name: 'GetSqlQuery',
      available_in: ['onprem', 'cloud'] as const,
      description:
        '[read-only] Execute ABAP SQL SELECT queries on database tables and CDS views via SAP ADT Data Preview API. Use for ad-hoc data retrieval, row counts, and filtered queries.',
      inputSchema: {
        type: 'object',
        properties: {
          sql_query: {
            type: 'string',
            description: 'SQL query to execute',
          },
          row_number: {
            type: 'number',
            description: '[read-only] Maximum number of rows to return',
            default: 100,
          },
        },
        required: ['sql_query'],
      },
    } as const;
  • Helper function parseSqlQueryXml that parses the SAP ADT XML response from freestyle SQL queries into a structured JSON format (columns, rows, metadata).
    export function parseSqlQueryXml(
      xmlData: string,
      sqlQuery: string,
      rowNumber: number,
      logger?: ILogger,
    ): SqlQueryResponse {
      try {
        // Extract basic information
        const totalRowsMatch = xmlData.match(
          /<dataPreview:totalRows>(\d+)<\/dataPreview:totalRows>/,
        );
        const totalRows = totalRowsMatch ? parseInt(totalRowsMatch[1], 10) : 0;
    
        const queryTimeMatch = xmlData.match(
          /<dataPreview:queryExecutionTime>([\d.]+)<\/dataPreview:queryExecutionTime>/,
        );
        const queryExecutionTime = queryTimeMatch
          ? parseFloat(queryTimeMatch[1])
          : 0;
    
        // Extract column metadata
        const columns: Array<{
          name: string;
          type: string;
          description?: string;
          length?: number;
        }> = [];
        const columnMatches = xmlData.match(/<dataPreview:metadata[^>]*>/g);
    
        if (columnMatches) {
          columnMatches.forEach((match) => {
            const nameMatch = match.match(/dataPreview:name="([^"]+)"/);
            const typeMatch = match.match(/dataPreview:type="([^"]+)"/);
            const descMatch = match.match(/dataPreview:description="([^"]+)"/);
            const lengthMatch = match.match(/dataPreview:length="(\d+)"/);
    
            if (nameMatch) {
              columns.push({
                name: nameMatch[1],
                type: typeMatch ? typeMatch[1] : 'UNKNOWN',
                description: descMatch ? descMatch[1] : '',
                length: lengthMatch ? parseInt(lengthMatch[1], 10) : undefined,
              });
            }
          });
        }
    
        // Extract row data
        const rows: Array<Record<string, any>> = [];
    
        // Find all column sections
        const columnSections = xmlData.match(
          /<dataPreview:columns>.*?<\/dataPreview:columns>/gs,
        );
    
        if (columnSections && columnSections.length > 0) {
          // Extract data for each column
          const columnData: Record<string, (string | null)[]> = {};
    
          columnSections.forEach((section, index) => {
            if (index < columns.length) {
              const columnName = columns[index].name;
              const dataMatches = section.match(
                /<dataPreview:data[^>]*>(.*?)<\/dataPreview:data>/g,
              );
    
              if (dataMatches) {
                columnData[columnName] = dataMatches.map((match) => {
                  const content = match.replace(/<[^>]+>/g, '');
                  return content || null;
                });
              } else {
                columnData[columnName] = [];
              }
            }
          });
    
          // Convert column-based data to row-based data
          const maxRowCount = Math.max(
            ...Object.values(columnData).map((arr) => arr.length),
            0,
          );
    
          for (let rowIndex = 0; rowIndex < maxRowCount; rowIndex++) {
            const row: Record<string, any> = {};
            columns.forEach((column) => {
              const columnValues = columnData[column.name] || [];
              row[column.name] = columnValues[rowIndex] || null;
            });
            rows.push(row);
          }
        }
    
        return {
          sql_query: sqlQuery,
          row_number: rowNumber,
          execution_time: queryExecutionTime,
          total_rows: totalRows,
          columns,
          rows,
        };
      } catch (parseError) {
        logger?.error('Failed to parse SQL query XML:', parseError as any);
    
        // Return basic structure on parse error
        return {
          sql_query: sqlQuery,
          row_number: rowNumber,
          columns: [],
          rows: [],
          error: 'Failed to parse XML response',
        } as any;
      }
    }
  • Registration of GetSqlQuery tool in the SystemHandlersGroup, mapping GetSqlQuery_Tool definition to handleGetSqlQuery handler.
    {
      toolDefinition: GetSqlQuery_Tool,
      handler: (args: any) => handleGetSqlQuery(this.context, args),
    },
  • Import of GetSqlQuery_Tool and handleGetSqlQuery from the handler file into the registration group.
    import {
      TOOL_DEFINITION as GetSession_Tool,
      handleGetSession,
    } from '../../../handlers/system/readonly/handleGetSession';
    import {
      TOOL_DEFINITION as GetSqlQuery_Tool,
      handleGetSqlQuery,
    } from '../../../handlers/system/readonly/handleGetSqlQuery';
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses it is read-only and uses the SAP ADT Data Preview API. Although no annotations are provided, the description clearly indicates no side effects. It lacks details on error handling or query limitations, but the read-only nature is well communicated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, front-loaded sentence that immediately identifies the tool as read-only and explains its core functionality. No extraneous words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description adequately covers purpose and usage, but lacks information about the output format (common for query tools). Given the low complexity and lack of output schema, it is mostly complete but could mention result structure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with both parameters described. The description adds little beyond the schema but reinforces the purpose (e.g., 'filtered queries' for sql_query). Baseline 3 is appropriate as the schema carries the parameter definitions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool executes ABAP SQL SELECT queries on database tables and CDS views, distinguishing it from sibling tools that retrieve specific objects (e.g., GetTable, GetView) or perform mutations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly recommends using it for ad-hoc data retrieval, row counts, and filtered queries. While it does not explicitly list when not to use it, the context of sibling tools implies alternatives for specific object retrieval.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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/fr0ster/mcp-abap-adt'

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