Skip to main content
Glama
getDatasourceMetadata.ts6.13 kB
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { Err, Ok } from 'ts-results-es'; import { z } from 'zod'; import { getConfig } from '../../config.js'; import { useRestApi } from '../../restApiInstance.js'; import { GraphQLResponse } from '../../sdks/tableau/apis/metadataApi.js'; import { Server } from '../../server.js'; import { getTableauAuthInfo } from '../../server/oauth/getTableauAuthInfo.js'; import { getVizqlDataServiceDisabledError } from '../getVizqlDataServiceDisabledError.js'; import { resourceAccessChecker } from '../resourceAccessChecker.js'; import { Tool } from '../tool.js'; import { validateDatasourceLuid } from '../validateDatasourceLuid.js'; import { combineFields, FieldsResult, simplifyReadMetadataResult, } from './datasourceMetadataUtils.js'; export const getGraphqlQuery = (datasourceLuid: string): string => ` query datasourceFieldInfo { publishedDatasources(filter: { luid: "${datasourceLuid}" }) { name description owner { name } fields { name isHidden description descriptionInherited { attribute value } fullyQualifiedName __typename ... on AnalyticsField { __typename } ... on ColumnField { dataCategory role dataType defaultFormat semanticRole aggregation aggregationParam } ... on CalculatedField { dataCategory role dataType defaultFormat semanticRole aggregation aggregationParam formula isAutoGenerated hasUserReference } ... on BinField { dataCategory role dataType formula binSize } ... on GroupField { dataCategory role dataType hasOther } ... on CombinedSetField { delimiter combinationType } } } }`; const paramsSchema = { datasourceLuid: z.string().nonempty(), }; export type GetDatasourceMetadataError = | { type: 'feature-disabled'; } | { type: 'datasource-not-allowed'; message: string; }; export const getGetDatasourceMetadataTool = (server: Server): Tool<typeof paramsSchema> => { const getDatasourceMetadataTool = new Tool({ server, name: 'get-datasource-metadata', description: ` This tool retrieves field metadata for a specified datasource by taking the basic, high level, metadata results from Tableau's VizQL Data Service and enriches them with additional context provided by Tableau's Metadata API. The fields provided by this tool will contain properties such as name and dataType, but may also expose richer context such as descriptions, dataCategories, roles, etc. This tool should be used for getting the metadata to ground the use of a tool that queries Tableau published data sources. `, paramsSchema, annotations: { title: 'Get Datasource Metadata', readOnlyHint: true, openWorldHint: false, }, argsValidator: validateDatasourceLuid, callback: async ({ datasourceLuid }, { requestId, authInfo }): Promise<CallToolResult> => { const config = getConfig(); const query = getGraphqlQuery(datasourceLuid); return await getDatasourceMetadataTool.logAndExecute< FieldsResult, GetDatasourceMetadataError >({ requestId, authInfo, args: { datasourceLuid }, callback: async () => { const isDatasourceAllowedResult = await resourceAccessChecker.isDatasourceAllowed({ datasourceLuid, restApiArgs: { config, requestId, server }, }); if (!isDatasourceAllowedResult.allowed) { return new Err({ type: 'datasource-not-allowed', message: isDatasourceAllowedResult.message, }); } return await useRestApi({ config, requestId, server, jwtScopes: ['tableau:content:read', 'tableau:viz_data_service:read'], authInfo: getTableauAuthInfo(authInfo), callback: async (restApi) => { // Fetching metadata from VizQL Data Service API. const readMetadataResult = await restApi.vizqlDataServiceMethods.readMetadata({ datasource: { datasourceLuid, }, }); if (readMetadataResult.isErr()) { return Err({ type: 'feature-disabled' }); } if (config.disableMetadataApiRequests) { // Exit early since requests to the Tableau Metadata API are disabled. return Ok(simplifyReadMetadataResult(readMetadataResult.value)); } let listFieldsResult: GraphQLResponse; try { // Fetching metadata from Tableau Metadata API. // Using try-catch here since requests could fail if the service is not enabled. listFieldsResult = await restApi.metadataMethods.graphql(query); } catch { return Ok(simplifyReadMetadataResult(readMetadataResult.value)); } // Combine the results from the VizQL Data Service API and the Tableau Metadata API. return Ok(combineFields(readMetadataResult.value, listFieldsResult)); }, }); }, constrainSuccessResult: (fields) => { return { type: 'success', result: fields, }; }, getErrorText: (error: GetDatasourceMetadataError) => { switch (error.type) { case 'feature-disabled': return getVizqlDataServiceDisabledError(); case 'datasource-not-allowed': return error.message; } }, }); }, }); return getDatasourceMetadataTool; };

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/datalabs89/tableau-mcp'

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