Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
377cf23
Add cookie-parser
anyoung-tableau Mar 2, 2026
9e92200
Add passthrough middleware
anyoung-tableau Mar 3, 2026
b6834ec
Cache session response
anyoung-tableau Mar 3, 2026
dee5d97
Update restApiInstance tests
anyoung-tableau Mar 3, 2026
d619b80
Add docs
anyoung-tableau Mar 3, 2026
63b9bdc
Add Passthrough auth test
anyoung-tableau Mar 3, 2026
f9d4b64
Add env vars to workflow
anyoung-tableau Mar 3, 2026
53aafcb
Update Access-Control-Expose-Headers header
anyoung-tableau Mar 3, 2026
60c1081
Merge remote-tracking branch 'origin/main' into anyoung/237
anyoung-tableau Mar 13, 2026
1003b80
Add PASSTHROUGH_AUTH_USER_SESSION_CHECK_INTERVAL_IN_MINUTES
anyoung-tableau Mar 13, 2026
667924b
Update tests
anyoung-tableau Mar 13, 2026
d8ab72f
Add comment
anyoung-tableau Mar 13, 2026
e88cb59
Allow for disabling cache, update docs
anyoung-tableau Mar 13, 2026
c023ffe
Add scope comment
anyoung-tableau Mar 13, 2026
3384aea
Allow interval to be 0
anyoung-tableau Mar 13, 2026
b6194f7
Address copilot comments
anyoung-tableau Mar 13, 2026
2f99955
Revert action.yml
anyoung-tableau Mar 13, 2026
3970a0d
Revert ci.yml
anyoung-tableau Mar 13, 2026
7cf8bcf
Merge remote-tracking branch 'origin/main' into anyoung/237
anyoung-tableau Mar 17, 2026
6761c9b
Bump
anyoung-tableau Mar 17, 2026
4b36d56
Guard against accidental usage of passthrough auth middleware
anyoung-tableau Mar 17, 2026
52f133a
Add test to disallow passthrough auth for tools with no API scopes
anyoung-tableau Mar 17, 2026
2aa3492
Fix typo
anyoung-tableau Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ There are a couple different ways to authenticate to Tableau.
2. Use Tableau [Connected Apps](direct-trust.md).
3. Use Tableau [Unified Access Tokens](uat.md).
4. Use Tableau [OAuth](oauth.md).
5. Use [Passthrough Authentication](passthrough.md).
61 changes: 61 additions & 0 deletions docs/docs/configuration/mcp-config/authentication/passthrough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
sidebar_position: 5
---

# Passthrough Authentication

With passthrough authentication enabled, authentication to the MCP server acts similarly to the
Tableau REST APIs. The same
[`X-Tableau-Auth` header](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_concepts_auth.htm#using_auth_token)
used to authenticate to the Tableau REST APIs can also be used to authenticate to the MCP server.

When a request is made to the MCP server, the `X-Tableau-Auth` header is read.

- When the header is present, the value will be "passed through" and re-used during MCP tool calls
when they authenticate to the Tableau REST APIs.
- When absent, normal authentication will resume as defined by the [`AUTH`](../env-vars.md#auth)
environment variable. This allows clients that do not provide the `X-Tableau-Auth` header to still
authenticate to the MCP server.

:::warning

When using passthrough authentication, the calling application is responsible for creating the
credential for the `X-Tableau-Auth` header and managing its lifecycle. The MCP server will not
automatically terminate the Tableau session associated with the credential after its use nor will it
refresh it after it expires. Providing an invalid or expired credential will result in downstream
authentication failures.

Additionally, if [`OAuth`](oauth.md) is enabled, all requests to the MCP server must include the
`X-Tableau-Auth` header, otherwise the client will be considered unauthorized and will be forced to
authenticate using OAuth. This even includes MCP lifecycle requests like the
[Initialization request](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization),
even though it does not make any downstream Tableau REST API calls.

:::

:::danger

Do not use a Personal Access Token (PAT) to generate the `X-Tableau-Auth` credential when using
passthrough authentication since PATs cannot be used concurrently. Signing in multiple times with
the same PAT at the same time will terminate any prior session and will result in an authentication
error. See
[Understand personal access tokens](https://help.tableau.com/current/server/en-us/security_personal_access_tokens.htm#understand-personal-access-tokens)
for more details.

:::

## ENABLE_PASSTHROUGH_AUTH

- Default: `false`
- When `true`, passthrough authentication is enabled.
- Only applies when [`TRANSPORT`](../env-vars.md#transport) is `http`.

<hr />

## PASSTHROUGH_AUTH_USER_SESSION_CHECK_INTERVAL_IN_MINUTES

- Default: `10` minutes
- How often the server re-checks that a passthrough auth token is still valid. Between checks,
recently validated tokens are trusted without re-verification. Downstream requests to the Tableau
REST APIs could potentially fail if the token was invalidated during this interval.
- Valid range: `0` to `1440` (24 hours). Use `0` to verify the token on every request.
35 changes: 33 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@tableau/mcp-server",
"description": "Helping agents see and understand data.",
"version": "1.17.15",
"version": "1.17.16",
"repository": {
"type": "git",
"url": "git+https://github.com/tableau/tableau-mcp.git"
Expand Down Expand Up @@ -54,6 +54,7 @@
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0",
"@zodios/core": "^10.9.6",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^5.1.0",
Expand All @@ -69,6 +70,7 @@
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.25.1",
"@openai/agents": "^0.1.9",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/eslint__js": "^8.42.3",
"@types/express": "^5.0.3",
Expand Down
24 changes: 24 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@ describe('Config', () => {
expect(config.tableauServerVersionCheckIntervalInHours).toBe(2);
});

it('should set passthroughAuthUserSessionCheckIntervalInMinutes to default when not specified', () => {
const config = new Config();
expect(config.passthroughAuthUserSessionCheckIntervalInMinutes).toBe(10);
});

it('should set passthroughAuthUserSessionCheckIntervalInMinutes to the specified value when specified', () => {
vi.stubEnv('PASSTHROUGH_AUTH_USER_SESSION_CHECK_INTERVAL_IN_MINUTES', '2');

const config = new Config();
expect(config.passthroughAuthUserSessionCheckIntervalInMinutes).toBe(2);
});

it('should set mcpSiteSettingsCheckIntervalInMinutes to default when not specified', () => {
const config = new Config();
expect(config.mcpSiteSettingsCheckIntervalInMinutes).toBe(10);
Expand All @@ -187,6 +199,18 @@ describe('Config', () => {
expect(config.enableMcpSiteSettings).toBe(true);
});

it('should set enablePassthroughAuth to false by default', () => {
const config = new Config();
expect(config.enablePassthroughAuth).toBe(false);
});

it('should set enablePassthroughAuth to true when specified', () => {
vi.stubEnv('ENABLE_PASSTHROUGH_AUTH', 'true');

const config = new Config();
expect(config.enablePassthroughAuth).toBe(true);
});

describe('HTTP server config parsing', () => {
it('should set sslKey to default when SSL_KEY is not set', () => {
const config = new Config();
Expand Down
15 changes: 15 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ export class Config {
enableServerLogging: boolean;
serverLogDirectory: string;
tableauServerVersionCheckIntervalInHours: number;
passthroughAuthUserSessionCheckIntervalInMinutes: number;
mcpSiteSettingsCheckIntervalInMinutes: number;
enableMcpSiteSettings: boolean;
enablePassthroughAuth: boolean;
oauth: {
enabled: boolean;
embeddedAuthzServer: boolean;
Expand Down Expand Up @@ -111,8 +113,11 @@ export class Config {
ENABLE_SERVER_LOGGING: enableServerLogging,
SERVER_LOG_DIRECTORY: serverLogDirectory,
TABLEAU_SERVER_VERSION_CHECK_INTERVAL_IN_HOURS: tableauServerVersionCheckIntervalInHours,
PASSTHROUGH_AUTH_USER_SESSION_CHECK_INTERVAL_IN_MINUTES:
passthroughAuthUserSessionCheckIntervalInMinutes,
MCP_SITE_SETTINGS_CHECK_INTERVAL_IN_MINUTES: mcpSiteSettingsCheckIntervalInMinutes,
ENABLE_MCP_SITE_SETTINGS: enableMcpSiteSettings,
ENABLE_PASSTHROUGH_AUTH: enablePassthroughAuth,
DANGEROUSLY_DISABLE_OAUTH: disableOauth,
OAUTH_EMBEDDED_AUTHZ_SERVER: oauthEmbeddedAuthzServer,
OAUTH_ISSUER: oauthIssuer,
Expand Down Expand Up @@ -165,6 +170,15 @@ export class Config {
},
);

this.passthroughAuthUserSessionCheckIntervalInMinutes = parseNumber(
passthroughAuthUserSessionCheckIntervalInMinutes,
{
defaultValue: 10,
minValue: 0,
maxValue: 60 * 24, // 24 hours
},
);

this.mcpSiteSettingsCheckIntervalInMinutes = parseNumber(
mcpSiteSettingsCheckIntervalInMinutes,
{
Expand All @@ -175,6 +189,7 @@ export class Config {
);

this.enableMcpSiteSettings = enableMcpSiteSettings === 'true';
this.enablePassthroughAuth = enablePassthroughAuth === 'true';
const disableOauthOverride = disableOauth === 'true';
const disableScopes = oauthDisableScopes === 'true';
const enforceScopes = !disableScopes;
Expand Down
Loading
Loading