-
Notifications
You must be signed in to change notification settings - Fork 36
Constrain tool results by project/datasource/workbook #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 47 commits
77daa9f
7933d1a
cfbdd9a
3b7525d
3b1b43e
a8940cc
e035e0b
b121f37
6bb8fff
53c4005
2317fa3
5358b74
f68b608
21c9ee7
0d95d2b
3addd0d
2db5593
b2a8fd3
016fc4d
b91d174
9180454
29cfefb
326021a
30812cb
18adabb
d6d39dc
a01434b
ce1f3da
b3a6e18
d84cd8b
156afd6
1a41ac3
7b47e46
48a8c53
9eb34f9
02ed5d2
a24ebee
65d1043
766f2d8
7dd24a5
b6440c6
8497864
8184009
f8c06b5
a0647e4
727070d
22bdbea
7fd8219
6f1169b
9632329
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| --- | ||
| sidebar_position: 6 | ||
| --- | ||
|
|
||
| # Tool Scoping | ||
|
|
||
| The Tableau MCP server can be configured to limit the scope of its tools to a set of data sources, | ||
| workbooks, or projects. | ||
|
|
||
| Enabling tool scoping can cause: | ||
|
|
||
| 1. Tools to return an error if they are called with arguments that are not within the allowed scope, | ||
| and | ||
| 2. Tools to respond with results that have been filtered to only include content from the allowed | ||
| scope. | ||
|
|
||
| ## Examples use-cases | ||
|
|
||
| - Only allow clients to query a single data source with the | ||
| [Query Data Source](../../tools/data-qna/query-datasource.md) tool. A client attempting to query | ||
| any other data source will result in an error. | ||
| - Filter the results of the [List Workbooks](../../tools/workbooks/list-workbooks.md) tool to only | ||
| include workbooks that exist in a single project. Workbooks from other projects will not be | ||
| included in the results. | ||
|
|
||
| ## Environment variables | ||
|
|
||
| The following optional environment variables can be used to configure the tool scoping. | ||
|
|
||
| ### `INCLUDE_PROJECT_IDS` | ||
|
|
||
| A comma-separated list of project IDs by which to constrain tool arguments and results. Only data | ||
| sources and workbooks (or views from those workbooks) that are members of the provided projects can | ||
| be queried or will be included in the results of the tools. | ||
|
|
||
| - When set, cannot be empty. | ||
| - Project IDs can be determined using the | ||
| [Query Projects](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_projects.htm#query_projects) | ||
| REST API or by the [List Data Sources](../../tools/data-qna/list-datasources.md), | ||
| [List Workbooks](../../tools/workbooks/list-workbooks.md), and | ||
| [List Views](../../tools/views/list-views.md) tools (assuming tool scoping is disabled). | ||
| - Has no impact on the results of the Pulse-related tools. | ||
|
|
||
| Example: `d87d843b-4326-4ce3-bc50-a68c1e6c9ca5` | ||
|
|
||
| :::warning | ||
|
|
||
| To constrain the results of the [Search Content](../../tools/content-exploration/search-content.md) | ||
| tool by project, you must also provide the project ID found in the project's URL in the Explore | ||
| section of your Tableau site e.g. `861566` from | ||
| `https://10ax.online.tableau.com/#/site/my-site/projects/861566` | ||
|
|
||
| Example: `d87d843b-4326-4ce3-bc50-a68c1e6c9ca5,861566` | ||
|
|
||
| ::: | ||
|
|
||
| <hr /> | ||
|
|
||
| ### `INCLUDE_DATASOURCE_IDS` | ||
|
|
||
| A comma-separated list of data source IDs by which to constrain tool arguments and results. Only | ||
| data sources or Pulse metrics and definitions derived from those data sources can be queried or will | ||
| be included in the results of the tools. | ||
|
|
||
| - When set, cannot be empty. | ||
| - Data source IDs can be determined using the | ||
| [Query Data Sources](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_data_sources.htm#query_data_sources) | ||
| REST API or the [List Data Sources](../../tools/data-qna/list-datasources.md) tool (assuming tool | ||
| scoping is disabled). | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps note that limiting a datasource does not limit workbooks or views (assuming that's how that works) |
||
|
|
||
| Example: `2d935df8-fe7e-4fd8-bb14-35eb4ba31d4` | ||
|
|
||
| <hr /> | ||
|
|
||
| ### `INCLUDE_WORKBOOK_IDS` | ||
|
|
||
| A comma-separated list of workbook IDs by which to constrain tool arguments and results. Only | ||
| workbooks or views from those workbooks can be queried or will be included in the results of the | ||
| tools. | ||
|
|
||
| - When set, cannot be empty. | ||
| - Workbook IDs can be determined using the | ||
| [Query Workbooks](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref_workbooks_and_views.htm#query_workbooks) | ||
| REST API or the [List Workbooks](../../tools/workbooks/list-workbooks.md) tool (assuming tool | ||
| scoping is disabled). The [List Views](../../tools/views/list-views.md) tools also return workbook | ||
| IDs. | ||
| - Has no impact on the results of the Pulse-related tools. | ||
|
|
||
| Example: `222ea993-9391-4910-a167-56b3d19b4e3b` | ||
|
|
||
| <hr /> | ||
|
|
||
| <hr /> | ||
|
|
||
| [cors]: https://expressjs.com/en/resources/middleware/cors.html#configuration-options | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,12 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url)); | |
| const authTypes = ['pat', 'direct-trust'] as const; | ||
| type AuthType = (typeof authTypes)[number]; | ||
|
|
||
| export type BoundedContext = { | ||
| projectIds: Set<string> | null; | ||
| datasourceIds: Set<string> | null; | ||
| workbookIds: Set<string> | null; | ||
| }; | ||
|
|
||
| export class Config { | ||
| auth: AuthType; | ||
| server: string; | ||
|
|
@@ -37,6 +43,7 @@ export class Config { | |
| disableMetadataApiRequests: boolean; | ||
| enableServerLogging: boolean; | ||
| serverLogDirectory: string; | ||
| boundedContext: BoundedContext; | ||
|
|
||
| constructor() { | ||
| const cleansedVars = removeClaudeMcpBundleUserConfigTemplates(process.env); | ||
|
|
@@ -66,6 +73,9 @@ export class Config { | |
| DISABLE_METADATA_API_REQUESTS: disableMetadataApiRequests, | ||
| ENABLE_SERVER_LOGGING: enableServerLogging, | ||
| SERVER_LOG_DIRECTORY: serverLogDirectory, | ||
| INCLUDE_PROJECT_IDS: includeProjectIds, | ||
| INCLUDE_DATASOURCE_IDS: includeDatasourceIds, | ||
| INCLUDE_WORKBOOK_IDS: includeWorkbookIds, | ||
| } = cleansedVars; | ||
|
|
||
| const defaultPort = 3927; | ||
|
|
@@ -86,6 +96,29 @@ export class Config { | |
| this.disableMetadataApiRequests = disableMetadataApiRequests === 'true'; | ||
| this.enableServerLogging = enableServerLogging === 'true'; | ||
| this.serverLogDirectory = serverLogDirectory || join(__dirname, 'logs'); | ||
| this.boundedContext = { | ||
| projectIds: createSetFromCommaSeparatedString(includeProjectIds), | ||
| datasourceIds: createSetFromCommaSeparatedString(includeDatasourceIds), | ||
| workbookIds: createSetFromCommaSeparatedString(includeWorkbookIds), | ||
| }; | ||
|
|
||
| if (this.boundedContext.projectIds?.size === 0) { | ||
| throw new Error( | ||
| 'When set, the environment variable INCLUDE_PROJECT_IDS must have at least one value', | ||
| ); | ||
| } | ||
|
|
||
| if (this.boundedContext.datasourceIds?.size === 0) { | ||
| throw new Error( | ||
| 'When set, the environment variable INCLUDE_DATASOURCE_IDS must have at least one value', | ||
| ); | ||
| } | ||
|
|
||
| if (this.boundedContext.workbookIds?.size === 0) { | ||
| throw new Error( | ||
| 'When set, the environment variable INCLUDE_WORKBOOK_IDS must have at least one value', | ||
| ); | ||
| } | ||
|
|
||
| const maxResultLimitNumber = maxResultLimit ? parseInt(maxResultLimit) : NaN; | ||
| this.maxResultLimit = | ||
|
|
@@ -181,6 +214,22 @@ function getCorsOriginConfig(corsOriginConfig: string): CorsOptions['origin'] { | |
| } | ||
| } | ||
|
|
||
| // Creates a set from a comma-separated string of values. | ||
| // Returns null if the value is undefined. | ||
| function createSetFromCommaSeparatedString(value: string | undefined): Set<string> | null { | ||
| if (value === undefined) { | ||
| return null; | ||
| } | ||
|
|
||
| return new Set( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we perform validation of these strings? Like check that the string is either all numbers or follows the LUID format? I'm not sure if there's an exact format for LUIDs, but let's assume that there is one. If we had a set of rules such as 32 characters equally separated by 4 dashes. Then we can have an error message in case some one configuring the MCP server accidentally copy pastes 31 characters and then they know why their tools aren't letting them query a datasource or something. |
||
| value | ||
| .trim() | ||
| .split(',') | ||
| .map((id) => id.trim()) | ||
| .filter(Boolean), | ||
| ); | ||
| } | ||
|
|
||
| // When the user does not provide a site name in the Claude MCP Bundle configuration, | ||
| // Claude doesn't replace its value and sets the site name to "${user_config.site_name}". | ||
| function removeClaudeMcpBundleUserConfigTemplates( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this "and" intentional or did you mean to add more to this sentence?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was intentional but I can remove it. The 2nd bullet is the continuation of the sentence.