From 368ceae178b7b83f0e60601e2a17c6056454f2df Mon Sep 17 00:00:00 2001 From: Andy Young Date: Wed, 22 Apr 2026 15:07:59 -0700 Subject: [PATCH 1/5] Add "combined" build variant --- README.combined.md | 1 + package.json | 1 + src/index.combined.ts | 79 ++++++++++++++++++++++++++++ src/scripts/prepareVariantPackage.ts | 9 ++++ src/scripts/variants.ts | 2 +- src/server.ts | 10 +++- 6 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 README.combined.md create mode 100644 src/index.combined.ts diff --git a/README.combined.md b/README.combined.md new file mode 100644 index 00000000..626d61a3 --- /dev/null +++ b/README.combined.md @@ -0,0 +1 @@ +# Tableau MCP diff --git a/package.json b/package.json index da8bf084..14bdae37 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "scripts": { "build": "tsx src/scripts/build.ts", "build:desktop": "tsx src/scripts/build.ts --variant desktop", + "build:combined": "tsx src/scripts/build.ts --variant combined", "build:dev": "tsx src/scripts/build.ts --dev", "build:docker": "docker build -t tableau-mcp .", ":build:mcpb": "npx -y @anthropic-ai/mcpb pack . tableau-mcp.mcpb", diff --git a/src/index.combined.ts b/src/index.combined.ts new file mode 100644 index 00000000..8033a90d --- /dev/null +++ b/src/index.combined.ts @@ -0,0 +1,79 @@ +#!/usr/bin/env node +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import dotenv from 'dotenv'; + +import { getConfig } from './config.js'; +import { getTableauServerInfo } from './getTableauServerInfo.js'; +import { FileLogger, setFileLogger } from './logging/fileLogger.js'; +import { writeToStderr } from './logging/logger.js'; +import { isNotificationLevel, notifier, setNotificationLevel } from './logging/notification.js'; +import { RestApi } from './sdks/tableau/restApi.js'; +import { DesktopMcpServer } from './server.desktop.js'; +import { serverName, serverVersion } from './server.js'; +import { WebMcpServer } from './server.web.js'; +import { getExceptionMessage } from './utils/getExceptionMessage.js'; + +async function startServer(): Promise { + dotenv.config(); + const config = getConfig(); + + if (config.transport !== 'stdio') { + throw new Error('Transport must be stdio for Desktop server'); + } + + RestApi.host = config.server; + + // Start fetching server info immediately but don't block the port from opening. + // Any failure here is fatal and logged explicitly -- no silent failures. + // For http transport, the port opens first so health checks can succeed, + // then we await this before declaring the server ready. + // For stdio transport, there are no health checks, but we still await before serving. + const serverInfoReady = getTableauServerInfo(config.server).catch((error) => { + writeToStderr(`Fatal error initializing server info: ${getExceptionMessage(error)}`); + process.exit(1); + }); + + const logLevel = isNotificationLevel(config.defaultLogLevel) ? config.defaultLogLevel : 'debug'; + if (config.loggers.has('fileLogger')) { + setFileLogger(new FileLogger({ logDirectory: config.fileLoggerDirectory })); + } + + await serverInfoReady; + + const mcpServer = new McpServer( + { + name: serverName, + version: serverVersion, + }, + { + capabilities: { + logging: {}, + tools: {}, + }, + }, + ); + + const webMcpServer = new WebMcpServer({ mcpServer }); + await webMcpServer.registerTools(); + webMcpServer.registerRequestHandlers(); + + const desktopMcpServer = new DesktopMcpServer({ mcpServer }); + await desktopMcpServer.registerTools(); + desktopMcpServer.registerRequestHandlers(); + + const transport = new StdioServerTransport(); + await mcpServer.connect(transport); + + setNotificationLevel(webMcpServer, logLevel); + notifier.info(webMcpServer, `${webMcpServer.name} v${webMcpServer.version} running on stdio`); + + if (config.disableLogMasking) { + writeToStderr('⚠️ Log masking is disabled!'); + } +} + +startServer().catch((error) => { + writeToStderr(`Fatal error when starting the server: ${getExceptionMessage(error)}`); + process.exit(1); +}); diff --git a/src/scripts/prepareVariantPackage.ts b/src/scripts/prepareVariantPackage.ts index 809632bd..ee564768 100644 --- a/src/scripts/prepareVariantPackage.ts +++ b/src/scripts/prepareVariantPackage.ts @@ -35,6 +35,15 @@ const variantPackageJsonOverrides = { '.': './build/index-desktop.js', }, }, + combined: { + name: '@tableau/combined-mcp-server', + bin: { + 'tableau-combined-mcp-server': './build/index-combined.js', + }, + exports: { + '.': './build/index-combined.js', + }, + }, } satisfies Record>; const packageJsonSchema = z diff --git a/src/scripts/variants.ts b/src/scripts/variants.ts index 5c1e0e6b..e7004ec1 100644 --- a/src/scripts/variants.ts +++ b/src/scripts/variants.ts @@ -1,4 +1,4 @@ -export const variants = ['default', 'desktop'] as const; +export const variants = ['default', 'desktop', 'combined'] as const; export type Variant = (typeof variants)[number]; export function isVariant(value: unknown): value is Variant { return variants.some((variant) => variant === value); diff --git a/src/server.ts b/src/server.ts index d64c4feb..5e602fcc 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,10 +3,16 @@ import { InitializeRequest, SetLevelRequestSchema } from '@modelcontextprotocol/ import pkg from '../package.json'; import { setNotificationLevel } from './logging/notification.js'; +import { Variant } from './scripts/variants'; import { TableauAuthInfo } from './server/oauth/schemas.js'; -export const serverName = - import.meta.env.BUILD_VARIANT === 'desktop' ? 'tableau-desktop-mcp' : 'tableau-mcp'; +const serverNames: Record = { + desktop: 'tableau-desktop-mcp', + combined: 'tableau-combined-mcp', + default: 'tableau-mcp', +}; + +export const serverName = serverNames[import.meta.env.BUILD_VARIANT as Variant]; export const serverVersion = pkg.version; export const userAgent = `${serverName}/${serverVersion}`; From b48786fb199cbcf0a9fb034fed67c75bc5ade1bd Mon Sep 17 00:00:00 2001 From: Andy Young Date: Fri, 24 Apr 2026 12:29:31 -0700 Subject: [PATCH 2/5] Remove server.registerRequestHandlers --- src/index.combined.ts | 12 ++++++--- src/index.desktop.ts | 10 ++++--- src/index.ts | 10 ++++--- src/logging/notification.test.ts | 26 +++++++++---------- src/logging/notification.ts | 16 +++++------- src/logging/secretMask.test.ts | 8 +++--- src/restApiInstance.test.ts | 12 ++++----- src/restApiInstance.ts | 10 +++---- src/server.ts | 10 +------ src/server.web.test.ts | 8 ------ src/server/express.ts | 13 +++++++--- src/tools/tool.ts | 2 +- .../validators/validateFilterValues.ts | 2 +- 13 files changed, 70 insertions(+), 69 deletions(-) diff --git a/src/index.combined.ts b/src/index.combined.ts index 8033a90d..0eb987a7 100644 --- a/src/index.combined.ts +++ b/src/index.combined.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import { getConfig } from './config.js'; @@ -56,17 +57,20 @@ async function startServer(): Promise { const webMcpServer = new WebMcpServer({ mcpServer }); await webMcpServer.registerTools(); - webMcpServer.registerRequestHandlers(); const desktopMcpServer = new DesktopMcpServer({ mcpServer }); await desktopMcpServer.registerTools(); - desktopMcpServer.registerRequestHandlers(); + + mcpServer.server.setRequestHandler(SetLevelRequestSchema, async (request) => { + setNotificationLevel(desktopMcpServer.mcpServer, request.params.level); + return {}; + }); const transport = new StdioServerTransport(); await mcpServer.connect(transport); - setNotificationLevel(webMcpServer, logLevel); - notifier.info(webMcpServer, `${webMcpServer.name} v${webMcpServer.version} running on stdio`); + setNotificationLevel(mcpServer, logLevel); + notifier.info(mcpServer, `${serverName} v${serverVersion} running on stdio`); if (config.disableLogMasking) { writeToStderr('⚠️ Log masking is disabled!'); diff --git a/src/index.desktop.ts b/src/index.desktop.ts index d6f33f05..dcabe1ae 100644 --- a/src/index.desktop.ts +++ b/src/index.desktop.ts @@ -1,4 +1,5 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import { getDesktopConfig } from './config.desktop.js'; @@ -23,13 +24,16 @@ async function startServer(): Promise { const server = new DesktopMcpServer(); await server.registerTools(); - server.registerRequestHandlers(); + server.mcpServer.server.setRequestHandler(SetLevelRequestSchema, async (request) => { + setNotificationLevel(server.mcpServer, request.params.level); + return {}; + }); const transport = new StdioServerTransport(); await server.mcpServer.connect(transport); - setNotificationLevel(server, logLevel); - notifier.info(server, `${server.name} v${server.version} running on stdio`); + setNotificationLevel(server.mcpServer, logLevel); + notifier.info(server.mcpServer, `${server.name} v${server.version} running on stdio`); } startServer().catch((error) => { diff --git a/src/index.ts b/src/index.ts index 96d3c717..411896fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import { getConfig } from './config.js'; @@ -40,13 +41,16 @@ async function startServer(): Promise { const server = new WebMcpServer(); await server.registerTools(); - server.registerRequestHandlers(); + server.mcpServer.server.setRequestHandler(SetLevelRequestSchema, async (request) => { + setNotificationLevel(server.mcpServer, request.params.level); + return {}; + }); const transport = new StdioServerTransport(); await server.mcpServer.connect(transport); - setNotificationLevel(server, logLevel); - notifier.info(server, `${server.name} v${server.version} running on stdio`); + setNotificationLevel(server.mcpServer, logLevel); + notifier.info(server.mcpServer, `${server.name} v${server.version} running on stdio`); break; } case 'http': { diff --git a/src/logging/notification.test.ts b/src/logging/notification.test.ts index 9f9c1516..c4a9152c 100644 --- a/src/logging/notification.test.ts +++ b/src/logging/notification.test.ts @@ -35,22 +35,22 @@ describe('notification', () => { describe('setLogLevel', () => { it('should set the log level', () => { - setNotificationLevel(new WebMcpServer(), 'error', { silent: true }); + setNotificationLevel(new WebMcpServer().mcpServer, 'error', { silent: true }); expect(shouldNotifyWhenLevelIsAtLeast('error')).toBe(true); expect(shouldNotifyWhenLevelIsAtLeast('debug')).toBe(false); }); it('should not change level if it is the same', () => { const server = new WebMcpServer(); - setNotificationLevel(server, 'debug', { silent: true }); - setNotificationLevel(server, 'debug', { silent: true }); + setNotificationLevel(server.mcpServer, 'debug', { silent: true }); + setNotificationLevel(server.mcpServer, 'debug', { silent: true }); expect(server.mcpServer.server.notification).not.toHaveBeenCalled(); }); }); describe('shouldLogWhenLevelIsAtLeast', () => { it('should return true for levels at or above current level', () => { - setNotificationLevel(new WebMcpServer(), 'warning', { silent: true }); + setNotificationLevel(new WebMcpServer().mcpServer, 'warning', { silent: true }); expect(shouldNotifyWhenLevelIsAtLeast('warning')).toBe(true); expect(shouldNotifyWhenLevelIsAtLeast('error')).toBe(true); expect(shouldNotifyWhenLevelIsAtLeast('info')).toBe(false); @@ -116,9 +116,9 @@ describe('notification', () => { describe('log functions', () => { it('should send logging message when level is appropriate', async () => { const server = new WebMcpServer(); - setNotificationLevel(server, 'info', { silent: true }); + setNotificationLevel(server.mcpServer, 'info', { silent: true }); - await notifier.info(server, 'test message', { notifier: 'test-logger' }); + await notifier.info(server.mcpServer, 'test message', { notifier: 'test-logger' }); expect(server.mcpServer.server.notification).toHaveBeenCalledWith( { @@ -137,18 +137,18 @@ describe('notification', () => { it('should not send logging message when level is below current level', async () => { const server = new WebMcpServer(); - setNotificationLevel(server, 'warning', { silent: true }); + setNotificationLevel(server.mcpServer, 'warning', { silent: true }); - await notifier.debug(server, 'test message', { notifier: 'test-logger' }); + await notifier.debug(server.mcpServer, 'test message', { notifier: 'test-logger' }); expect(server.mcpServer.server.notification).not.toHaveBeenCalled(); }); - it('should use server name as default logger', async () => { + it('should use tableau-mcp as default logger', async () => { const server = new WebMcpServer(); - setNotificationLevel(server, 'info', { silent: true }); + setNotificationLevel(server.mcpServer, 'info', { silent: true }); - await notifier.info(server, 'test message'); + await notifier.info(server.mcpServer, 'test message'); expect(server.mcpServer.server.notification).toHaveBeenCalledWith( { @@ -167,14 +167,14 @@ describe('notification', () => { it('should handle LogMessage objects', async () => { const server = new WebMcpServer(); - setNotificationLevel(server, 'info', { silent: true }); + setNotificationLevel(server.mcpServer, 'info', { silent: true }); const logMessage = { type: 'request', method: 'GET', path: '/test', } as const; - await notifier.info(server, logMessage, { notifier: 'test-logger' }); + await notifier.info(server.mcpServer, logMessage, { notifier: 'test-logger' }); expect(server.mcpServer.server.notification).toHaveBeenCalledWith( { diff --git a/src/logging/notification.ts b/src/logging/notification.ts index dbe80082..7f95d51a 100644 --- a/src/logging/notification.ts +++ b/src/logging/notification.ts @@ -1,6 +1,6 @@ +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { LoggingLevel, RequestId } from '@modelcontextprotocol/sdk/types.js'; -import { Server } from '../server.js'; import { ToolName } from '../tools/toolName.js'; import { getFileLogger } from './fileLogger.js'; @@ -29,7 +29,7 @@ export function isNotificationLevel(level: unknown): level is LoggingLevel { } export const setNotificationLevel = ( - server: Server, + mcpServer: McpServer, level: LoggingLevel, { silent = false }: { silent?: boolean } = {}, ): void => { @@ -40,7 +40,7 @@ export const setNotificationLevel = ( currentNotificationLevel = level; if (!silent) { - notifier.notice(server, `Logging level set to: ${level}`); + notifier.notice(mcpServer, `Logging level set to: ${level}`); } }; @@ -57,7 +57,7 @@ export const notifier = { emergency: getSendNotificationMessageFn('emergency'), } satisfies { [level in LoggingLevel]: ( - server: Server, + mcpServer: McpServer, message: string | NotificationMessage, { notifier, requestId }: NotificationMethodOptions, ) => Promise; @@ -91,11 +91,9 @@ export const getNotificationMessageForTool = ({ function getSendNotificationMessageFn(level: LoggingLevel) { return async ( - server: Server, + mcpServer: McpServer, message: string | NotificationMessage, - { notifier: notifier, requestId }: NotificationMethodOptions = { - notifier: server.name, - }, + { notifier, requestId }: NotificationMethodOptions = { notifier: 'tableau-mcp' }, ) => { getFileLogger()?.log({ message, level, logger: notifier }); @@ -105,7 +103,7 @@ function getSendNotificationMessageFn(level: LoggingLevel) { // server.sendNotification doesn't provide a way to provide the relatedRequestId // so we're using server.notification directly. - return server.mcpServer.server.notification( + return mcpServer.server.notification( { method: 'notifications/message', params: { diff --git a/src/logging/secretMask.test.ts b/src/logging/secretMask.test.ts index 4adccab4..a3383f4f 100644 --- a/src/logging/secretMask.test.ts +++ b/src/logging/secretMask.test.ts @@ -8,7 +8,7 @@ import { maskRequest, maskResponse } from './secretMask.js'; describe('secretMask', () => { beforeEach(() => { - setNotificationLevel(new WebMcpServer(), 'debug', { silent: true }); + setNotificationLevel(new WebMcpServer().mcpServer, 'debug', { silent: true }); }); it('should mask secrets in requests', () => { @@ -111,7 +111,7 @@ describe('secretMask', () => { }); it('should not include headers and data in the request if the log level is not debug', () => { - setNotificationLevel(new WebMcpServer(), 'info', { silent: true }); + setNotificationLevel(new WebMcpServer().mcpServer, 'info', { silent: true }); const maskedRequest = maskRequest({ method: 'POST', @@ -134,7 +134,7 @@ describe('secretMask', () => { }); it('should not include headers and data in the response if the log level is not debug', () => { - setNotificationLevel(new WebMcpServer(), 'info', { silent: true }); + setNotificationLevel(new WebMcpServer().mcpServer, 'info', { silent: true }); const maskedResponse = maskResponse({ status: 200, @@ -214,7 +214,7 @@ describe('secretMask', () => { }); it('should not include params in the request if the log level is not debug', () => { - setNotificationLevel(new WebMcpServer(), 'info', { silent: true }); + setNotificationLevel(new WebMcpServer().mcpServer, 'info', { silent: true }); const maskedRequest = maskRequest({ method: 'POST', diff --git a/src/restApiInstance.test.ts b/src/restApiInstance.test.ts index d6a35047..de1832ac 100644 --- a/src/restApiInstance.test.ts +++ b/src/restApiInstance.test.ts @@ -231,7 +231,7 @@ describe('restApiInstance', () => { expect(mockRequest.headers['User-Agent']).toBe(userAgent); expect(notifier.info).toHaveBeenCalledWith( - server, + server.mcpServer, expect.objectContaining({ type: 'request', requestId: mockRequestId, @@ -263,7 +263,7 @@ describe('restApiInstance', () => { expect(result).toBe(mockResponse); expect(notifier.info).toHaveBeenCalledWith( - server, + server.mcpServer, expect.objectContaining({ type: 'response', requestId: mockRequestId, @@ -294,7 +294,7 @@ describe('restApiInstance', () => { errorInterceptor(mockError, mockHost); expect(notifier.error).toHaveBeenCalledWith( - server, + server.mcpServer, `Request ${mockRequestId} failed with error: ${JSON.stringify(mockError)}`, expect.objectContaining({ notifier: 'rest-api', @@ -321,7 +321,7 @@ describe('restApiInstance', () => { expect(notifier.info).toHaveBeenCalled(); expect(notifier.info).toHaveBeenCalledWith( - server, + server.mcpServer, expect.objectContaining({ type: 'request', requestId: mockRequestId, @@ -351,7 +351,7 @@ describe('restApiInstance', () => { errorInterceptor(mockError, mockHost); expect(notifier.error).toHaveBeenCalledWith( - server, + server.mcpServer, `Response from request ${mockRequestId} failed with error: ${JSON.stringify(mockError)}`, expect.objectContaining({ notifier: 'rest-api', @@ -378,7 +378,7 @@ describe('restApiInstance', () => { errorInterceptor(mockError, mockHost); expect(notifier.info).toHaveBeenCalledWith( - server, + server.mcpServer, expect.objectContaining({ type: 'response', requestId: mockRequestId, diff --git a/src/restApiInstance.ts b/src/restApiInstance.ts index 14a9adc6..c5d84bee 100644 --- a/src/restApiInstance.ts +++ b/src/restApiInstance.ts @@ -68,7 +68,7 @@ const getNewRestApiInstanceAsync = async ( 'abort', () => { notifier.info( - server, + server.mcpServer, { type: 'request-cancelled', requestId, @@ -204,7 +204,7 @@ export const getRequestErrorInterceptor = (error, baseUrl) => { if (!isAxiosError(error) || !error.request) { notifier.error( - server, + server.mcpServer, `Request ${requestId} failed with error: ${getExceptionMessage(error)}`, { notifier: 'rest-api', @@ -237,7 +237,7 @@ export const getResponseErrorInterceptor = (error, baseUrl) => { if (!isAxiosError(error) || !error.response) { notifier.error( - server, + server.mcpServer, `Response from request ${requestId} failed with error: ${getExceptionMessage(error)}`, { notifier: 'rest-api', requestId }, ); @@ -278,7 +278,7 @@ function logRequest(server: Server, request: RequestInterceptorConfig, requestId }), } as const; - notifier.info(server, messageObj, { notifier: 'rest-api', requestId }); + notifier.info(server.mcpServer, messageObj, { notifier: 'rest-api', requestId }); } function logResponse( @@ -305,7 +305,7 @@ function logResponse( }), } as const; - notifier.info(server, messageObj, { notifier: 'rest-api', requestId }); + notifier.info(server.mcpServer, messageObj, { notifier: 'rest-api', requestId }); } function getUserAgent(server: Server): string { diff --git a/src/server.ts b/src/server.ts index 5e602fcc..a7c4b014 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,8 +1,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { InitializeRequest, SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import { InitializeRequest } from '@modelcontextprotocol/sdk/types.js'; import pkg from '../package.json'; -import { setNotificationLevel } from './logging/notification.js'; import { Variant } from './scripts/variants'; import { TableauAuthInfo } from './server/oauth/schemas.js'; @@ -59,11 +58,4 @@ export abstract class Server { } abstract registerTools: (tableauAuthInfo?: TableauAuthInfo) => Promise; - - registerRequestHandlers = (): void => { - this.mcpServer.server.setRequestHandler(SetLevelRequestSchema, async (request) => { - setNotificationLevel(this, request.params.level); - return {}; - }); - }; } diff --git a/src/server.web.test.ts b/src/server.web.test.ts index 32da7912..8a607f32 100644 --- a/src/server.web.test.ts +++ b/src/server.web.test.ts @@ -123,14 +123,6 @@ describe('WebMcpServer', () => { await expect(server.registerTools).rejects.toThrow(sentence); } }); - - it('should register request handlers', async () => { - const server = getServer(); - server.mcpServer.server.setRequestHandler = vi.fn(); - server.registerRequestHandlers(); - - expect(server.mcpServer.server.setRequestHandler).toHaveBeenCalled(); - }); }); function getServer(): WebMcpServer { diff --git a/src/server/express.ts b/src/server/express.ts index 4f29dfcf..a70a1500 100644 --- a/src/server/express.ts +++ b/src/server/express.ts @@ -1,5 +1,9 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { isInitializeRequest, LoggingLevel } from '@modelcontextprotocol/sdk/types.js'; +import { + isInitializeRequest, + LoggingLevel, + SetLevelRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; import cookieParser from 'cookie-parser'; import cors from 'cors'; import express, { Request, RequestHandler, Response } from 'express'; @@ -182,10 +186,13 @@ async function connect( authInfo: TableauAuthInfo | undefined, ): Promise { await server.registerTools(authInfo); - server.registerRequestHandlers(); + server.mcpServer.server.setRequestHandler(SetLevelRequestSchema, async (request) => { + setNotificationLevel(server.mcpServer, request.params.level); + return {}; + }); await server.mcpServer.connect(transport); - setNotificationLevel(server, logLevel); + setNotificationLevel(server.mcpServer, logLevel); } async function methodNotAllowed(_req: Request, res: Response): Promise { diff --git a/src/tools/tool.ts b/src/tools/tool.ts index 31662ac6..67e065c7 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -118,7 +118,7 @@ export abstract class Tool< username?: string; }): void { notifier.debug( - this.server, + this.server.mcpServer, getNotificationMessageForTool({ requestId, toolName: this.name, diff --git a/src/tools/web/queryDatasource/validators/validateFilterValues.ts b/src/tools/web/queryDatasource/validators/validateFilterValues.ts index 2c52eae9..a23b044f 100644 --- a/src/tools/web/queryDatasource/validators/validateFilterValues.ts +++ b/src/tools/web/queryDatasource/validators/validateFilterValues.ts @@ -70,7 +70,7 @@ export async function validateFilterValues( } } catch (error) { notifier.warning( - server, + server.mcpServer, `Filter value validation failed for field ${fieldCaption}: ${error}`, ); } From eaf351da1da83bfb1322944d9f829105442cb897 Mon Sep 17 00:00:00 2001 From: Andy Young Date: Fri, 24 Apr 2026 13:51:29 -0700 Subject: [PATCH 3/5] Fix package overrides --- src/scripts/prepareVariantPackage.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/scripts/prepareVariantPackage.ts b/src/scripts/prepareVariantPackage.ts index ee564768..196358db 100644 --- a/src/scripts/prepareVariantPackage.ts +++ b/src/scripts/prepareVariantPackage.ts @@ -29,19 +29,19 @@ const variantPackageJsonOverrides = { description: 'MCP server for Tableau Desktop Agent API - enables AI agents to interact with Tableau workbooks', bin: { - 'tableau-desktop-mcp-server': './build/index-desktop.js', + 'tableau-desktop-mcp-server': './build/index.desktop.js', }, exports: { - '.': './build/index-desktop.js', + '.': './build/index.desktop.js', }, }, combined: { name: '@tableau/combined-mcp-server', bin: { - 'tableau-combined-mcp-server': './build/index-combined.js', + 'tableau-combined-mcp-server': './build/index.combined.js', }, exports: { - '.': './build/index-combined.js', + '.': './build/index.combined.js', }, }, } satisfies Record>; From 8ff0f0da939e3a8fb3986d87901636389a5f9080 Mon Sep 17 00:00:00 2001 From: Andy Young Date: Fri, 24 Apr 2026 13:56:39 -0700 Subject: [PATCH 4/5] Add debug config --- .vscode/launch.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8a72e952..344b071d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach to Node", + "port": 9229, + "restart": true, + "skipFiles": ["/**"] + }, { "type": "node", "request": "launch", From 7ba1461a4400e82e472976c5ea41883f3f235b78 Mon Sep 17 00:00:00 2001 From: Andy Young Date: Fri, 24 Apr 2026 15:41:46 -0700 Subject: [PATCH 5/5] Remove reference to BUILD_VARIANT --- src/index.combined.ts | 5 ++++- src/server.ts | 6 ------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/index.combined.ts b/src/index.combined.ts index 0eb987a7..4b5bb772 100644 --- a/src/index.combined.ts +++ b/src/index.combined.ts @@ -4,6 +4,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; +import pkg from '../package.json'; import { getConfig } from './config.js'; import { getTableauServerInfo } from './getTableauServerInfo.js'; import { FileLogger, setFileLogger } from './logging/fileLogger.js'; @@ -11,10 +12,12 @@ import { writeToStderr } from './logging/logger.js'; import { isNotificationLevel, notifier, setNotificationLevel } from './logging/notification.js'; import { RestApi } from './sdks/tableau/restApi.js'; import { DesktopMcpServer } from './server.desktop.js'; -import { serverName, serverVersion } from './server.js'; import { WebMcpServer } from './server.web.js'; import { getExceptionMessage } from './utils/getExceptionMessage.js'; +const serverName = 'tableau-combined-mcp'; +const serverVersion = pkg.version; + async function startServer(): Promise { dotenv.config(); const config = getConfig(); diff --git a/src/server.ts b/src/server.ts index 0f64a4d0..8e10619f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,14 +1,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { InitializeRequest } from '@modelcontextprotocol/sdk/types.js'; -import { Variant } from './scripts/variants'; import { TableauAuthInfo } from './server/oauth/schemas.js'; - combined: 'tableau-combined-mcp', - default: 'tableau-mcp', -}; - -export const serverName = serverNames[import.meta.env.BUILD_VARIANT as Variant]; export type ClientInfo = InitializeRequest['params']['clientInfo']; export abstract class Server {