Skip to main content
Glama
IBM

IBM i MCP Server

Official
by IBM
duckDBService.ts6.62 kB
/** * @fileoverview Implements the DuckDB service for interacting with a DuckDB database. * @module services/duck-db/duckDBService */ import * as duckdb from "@duckdb/node-api"; import { JsonRpcErrorCode, McpError } from "@/types-global/errors.js"; import { ErrorHandler, logger, RequestContext, requestContextService, } from "@/utils/index.js"; import { DuckDBConnectionManager } from "./duckDBConnectionManager.js"; import { DuckDBQueryExecutor } from "./duckDBQueryExecutor.js"; import { DuckDBQueryResult, DuckDBServiceConfig, IDuckDBService, } from "./types.js"; export class DuckDBService implements IDuckDBService { private connectionManager: DuckDBConnectionManager; private queryExecutor: DuckDBQueryExecutor | null = null; private isInitialized = false; constructor() { this.connectionManager = new DuckDBConnectionManager(); } async initialize(config?: DuckDBServiceConfig): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.initialize", initialData: config, }); if (this.isInitialized) { logger.warning( context, "DuckDBService already initialized. Close first to re-initialize.", ); return; } return ErrorHandler.tryCatch( async () => { await this.connectionManager.initialize(config); const connection = this.connectionManager.getConnection(); this.queryExecutor = new DuckDBQueryExecutor(connection); this.isInitialized = true; logger.info(context, "DuckDBService initialized successfully."); }, { operation: "DuckDBService.initialize", context, input: config, errorCode: JsonRpcErrorCode.InitializationFailed, critical: true, }, ); } private ensureInitialized(context: RequestContext): void { this.connectionManager.ensureInitialized(context); // Delegate to manager if (!this.queryExecutor) { // This check is mostly for type safety, as connectionManager.ensureInitialized should cover it throw new McpError( JsonRpcErrorCode.ServiceUnavailable, "DuckDBQueryExecutor not available. DuckDBService may not be fully initialized.", context, ); } } private validateParams( params: unknown, context: RequestContext, ): duckdb.DuckDBValue[] | undefined { if (params === undefined) { return undefined; } if (Array.isArray(params)) { return params as duckdb.DuckDBValue[]; } throw new McpError( JsonRpcErrorCode.InvalidParams, "DuckDB service only supports array-style parameters, not named objects.", context, ); } async run(sql: string, params?: unknown[]): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.run", initialData: { sql, params }, }); this.ensureInitialized(context); const validatedParams = this.validateParams(params, context); return this.queryExecutor!.run(sql, validatedParams); } async query<T = Record<string, unknown>>( sql: string, params?: unknown[], ): Promise<DuckDBQueryResult<T>> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.query", initialData: { sql, params }, }); this.ensureInitialized(context); const validatedParams = this.validateParams(params, context); return this.queryExecutor!.query<T>(sql, validatedParams); } async stream(sql: string, params?: unknown[]): Promise<duckdb.DuckDBResult> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.stream", initialData: { sql, params }, }); this.ensureInitialized(context); const validatedParams = this.validateParams(params, context); return this.queryExecutor!.stream(sql, validatedParams); } async prepare(sql: string): Promise<duckdb.DuckDBPreparedStatement> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.prepare", initialData: { sql }, }); this.ensureInitialized(context); return this.queryExecutor!.prepare(sql); } async beginTransaction(): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.beginTransaction", }); this.ensureInitialized(context); return this.queryExecutor!.beginTransaction(); } async commitTransaction(): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.commitTransaction", }); this.ensureInitialized(context); return this.queryExecutor!.commitTransaction(); } async rollbackTransaction(): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.rollbackTransaction", }); this.ensureInitialized(context); return this.queryExecutor!.rollbackTransaction(); } async loadExtension(extensionName: string): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.loadExtension", initialData: { extensionName }, }); // ensureInitialized is implicitly called by connectionManager.loadExtension // if we call ensureInitialized here, it uses the service's context, // but loadExtension in manager will create its own. // It's better to let the manager handle its own initialization checks. return this.connectionManager.loadExtension(extensionName, context); } async close(): Promise<void> { const context = requestContextService.createRequestContext({ operation: "DuckDBService.close", }); // No need to check this.isInitialized here, connectionManager.close() handles it. return ErrorHandler.tryCatch( async () => { await this.connectionManager.close(); this.queryExecutor = null; this.isInitialized = false; logger.info(context, "DuckDBService closed successfully."); }, { operation: "DuckDBService.close", context, errorCode: JsonRpcErrorCode.InternalError, }, ); } getRawConnection(): duckdb.DuckDBConnection | null { if (this.connectionManager.isServiceInitialized) { return this.connectionManager.getConnection(); } return null; } getRawInstance(): duckdb.DuckDBInstance | null { if (this.connectionManager.isServiceInitialized) { return this.connectionManager.getInstance(); } return null; } }

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/IBM/ibmi-mcp-server'

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