From f9ce52a5d5f15e482522c626a16b86df06f3f018 Mon Sep 17 00:00:00 2001 From: mibe Date: Sun, 24 Aug 2025 09:59:24 +0100 Subject: [PATCH 01/18] #43: Enhanced `find_schemas` and `find_tables` --- doc/changes/unreleased.md | 6 + exasol/ai/mcp/server/main.py | 24 +- exasol/ai/mcp/server/mcp_server.py | 213 +++------ exasol/ai/mcp/server/meta_query.py | 318 +++++++++++++ exasol/ai/mcp/server/server_settings.py | 7 +- test/integration/conftest.py | 2 +- test/integration/test_mcp_server.py | 70 ++- ...est_verify_query.py => test_mcp_server.py} | 23 +- test/unit/test_meta_query.py | 445 ++++++++++++++++++ test/utils/text_utils.py | 9 + 10 files changed, 939 insertions(+), 178 deletions(-) create mode 100644 exasol/ai/mcp/server/meta_query.py rename test/unit/{test_verify_query.py => test_mcp_server.py} (84%) create mode 100644 test/unit/test_meta_query.py create mode 100644 test/utils/text_utils.py diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index f6d117b..1adb40d 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1,5 +1,11 @@ # Unreleased +## Features + +* #43: Modified `find_schemas` and `find_tables`, adding extra information about child objects. +* #45: Added object schema to the output of meta queries. + ## Refactoring * #41: Moved the `main` and `_register_tools` function to a separate file. +* #44: Extracted meta queries into a separated class with added unit tests. diff --git a/exasol/ai/mcp/server/main.py b/exasol/ai/mcp/server/main.py index 89a2a36..79fa9fe 100644 --- a/exasol/ai/mcp/server/main.py +++ b/exasol/ai/mcp/server/main.py @@ -43,7 +43,7 @@ def _register_list_tables(mcp_server: ExasolMCPServer) -> None: description=( "The tool lists tables and views in the specified schema of the " "the Exasol Database. For each table and view, it provides the " - "name and an optional comment." + "name, the schema, and an optional comment." ), ) @@ -54,8 +54,8 @@ def _register_find_tables(mcp_server: ExasolMCPServer) -> None: description=( "The tool finds tables and views in the Exasol Database by looking " "for the specified keywords in their names and comments. " - "For each table or view it finds, it provides the name and an " - "optional comment. An optional `schema_name` argument allows " + "For each table or view it finds, it provides the name, the schema, " + "and an optional comment. An optional `schema_name` argument allows " "restricting the search to tables and views in the specified schema." ), ) @@ -66,8 +66,8 @@ def _register_list_functions(mcp_server: ExasolMCPServer) -> None: mcp_server.list_functions, description=( "The tool lists functions in the specified schema of the Exasol " - "Database. For each function, it provides the name and an optional " - "comment." + "Database. For each function, it provides the name, the schema, " + "and an optional comment." ), ) @@ -78,9 +78,9 @@ def _register_find_functions(mcp_server: ExasolMCPServer) -> None: description=( "The tool finds functions in the Exasol Database by looking for " "the specified keywords in their names and comments. For each " - "function it finds, it provides the name and an optional comment. " - "An optional `schema_name` argument allows restricting the search " - "to functions in the specified schema." + "function it finds, it provides the name, the schema, and an optional " + "comment. An optional `schema_name` argument allows restricting the " + "search to functions in the specified schema." ), ) @@ -90,8 +90,8 @@ def _register_list_scripts(mcp_server: ExasolMCPServer) -> None: mcp_server.list_scripts, description=( "The tool lists the user defined functions (UDF) in the specified " - "schema of the Exasol Database. For each UDF, it provides the name " - "and an optional comment." + "schema of the Exasol Database. For each UDF, it provides the name, " + "the schema, and an optional comment." ), ) @@ -102,8 +102,8 @@ def _register_find_scripts(mcp_server: ExasolMCPServer) -> None: description=( "The tool finds the user defined functions (UDF) in the Exasol " "Database by looking for the specified keywords in their names and " - "comments. For each UDF it finds, it provides the name and an " - "optional comment. An optional `schema_name` argument allows " + "comments. For each UDF it finds, it provides the name, the schema, " + "and an optional comment. An optional `schema_name` argument allows " "restricting the search to UDFs in the specified schema." ), ) diff --git a/exasol/ai/mcp/server/mcp_server.py b/exasol/ai/mcp/server/mcp_server.py index 8f675bf..ea5f54c 100644 --- a/exasol/ai/mcp/server/mcp_server.py +++ b/exasol/ai/mcp/server/mcp_server.py @@ -1,6 +1,5 @@ import re from functools import cache -from textwrap import dedent from typing import ( Annotated, Any, @@ -15,6 +14,11 @@ ) from sqlglot.errors import ParseError +from exasol.ai.mcp.server.meta_query import ( + INFO_COLUMN, + ExasolMetaQuery, + MetaType, +) from exasol.ai.mcp.server.parameter_parser import ( FuncParameterParser, ScriptParameterParser, @@ -26,12 +30,8 @@ from exasol.ai.mcp.server.server_settings import ( ExaDbResult, McpServerSettings, - MetaListSettings, -) -from exasol.ai.mcp.server.utils import ( - keyword_filter, - sql_text_value, ) +from exasol.ai.mcp.server.utils import keyword_filter TABLE_USAGE = ( "In an SQL query, the names of database objects, such as schemas, " @@ -55,13 +55,6 @@ SCRIPT_NAME_TYPE = Annotated[str, "name of the script"] -def _where_clause(*predicates) -> str: - condition = " AND ".join(filter(bool, predicates)) - if condition: - return f"WHERE {condition}" - return "" - - @cache def _get_emits_pattern() -> re.Pattern: pattern = rf"EMITS\s*\({parameter_list_pattern}\)" @@ -88,6 +81,16 @@ def verify_query(query: str) -> bool: return False +def remove_info_column(result: list[dict[str, Any]]) -> list[dict[str, Any]]: + """ + Removes the column with extra information, collected for data filtering. + """ + for row in result: + if INFO_COLUMN in row: + row.pop(INFO_COLUMN) + return result + + class ExasolMCPServer(FastMCP): """ An Exasol MCP server based on FastMCP. @@ -106,52 +109,11 @@ def __init__( super().__init__(name="exasol-mcp") self.connection = connection self.connection.options["fetch_dict"] = True - self.config = config + self.meta_query = ExasolMetaQuery(config) - def _build_meta_query( - self, meta_name: str, conf: MetaListSettings, schema_name: str, *predicates - ) -> str: - """ - Builds a metadata query. - - Args: - meta_name: - Must be one of "SCHEMA", "TABLE", "VIEW", "FUNCTION", or "SCRIPT". - conf: - Metadata type settings, which is a part of the server configuration. - schema_name: - An optional schema name provided in the call to the tool. Ignored if - meta_name=='SCHEMA'. In all other cases, if the name is specified, it - will be included in the WHERE clause. Otherwise, the query will return - objects from all visible schemas. - predicates: - Any additional predicates to be used in the WHERE clause. - """ - predicates = [conf.select_predicate, *predicates] - if meta_name != "SCHEMA": - schema_column = f"{meta_name}_SCHEMA" - if schema_name: - predicates.append(f"{schema_column} = {sql_text_value(schema_name)}") - else: - # Adds the schema restriction if specified in the settings. - schema_conf = self.config.schemas - if schema_conf.like_pattern: - predicates.append( - f"{schema_column} LIKE " - f"{sql_text_value(schema_conf.like_pattern)}" - ) - if schema_conf.regexp_pattern: - predicates.append( - f"{schema_column} REGEXP_LIKE " - f"{sql_text_value(schema_conf.regexp_pattern)}" - ) - return dedent( - f""" - SELECT {meta_name}_NAME AS "{conf.name_field}", {meta_name}_COMMENT AS "{conf.comment_field}" - FROM SYS.EXA_ALL_{meta_name}S - {_where_clause(*predicates)} - """ - ) + @property + def config(self) -> McpServerSettings: + return self.meta_query.config def _execute_meta_query( self, query: str, keywords: list[str] | None = None @@ -159,86 +121,84 @@ def _execute_meta_query( """ Executes a metadata query and returns the result as a list of dictionaries. Applies the keyword fitter if provided. + Removes the column with extra information that could be added to the result + to assist filtering. This is necessary to avoid polluting the LLM's context + with data it doesn't need at the current stage. """ result = self.connection.meta.execute_snapshot(query=query).fetchall() if keywords: result = keyword_filter(result, keywords) - return ExaDbResult(result) + return ExaDbResult(remove_info_column(result)) - def _find_schemas(self, keywords: list[str] | None = None) -> ExaDbResult: - conf = self.config.schemas - if not conf.enable: + def list_schemas(self) -> ExaDbResult: + if not self.config.schemas.enable: raise RuntimeError("The schema listing is disabled.") - query = self._build_meta_query("SCHEMA", conf, "") - return self._execute_meta_query(query, keywords) + query = self.meta_query.get_metadata(MetaType.SCHEMA) + return self._execute_meta_query(query) def find_schemas(self, keywords: KEYWORDS_TYPE) -> ExaDbResult: - return self._find_schemas(keywords) + if not self.config.schemas.enable: + raise RuntimeError("The schema listing is disabled.") - def list_schemas(self) -> ExaDbResult: - return self._find_schemas() + query = self.meta_query.find_schemas() + return self._execute_meta_query(query, keywords) - def _find_tables( - self, keywords: list[str] | None = None, schema_name: str | None = None - ) -> ExaDbResult: + def list_tables(self, schema_name: SCHEMA_NAME_TYPE) -> ExaDbResult: table_conf = self.config.tables view_conf = self.config.views if (not table_conf.enable) and (not view_conf.enable): raise RuntimeError("Both the table and the view listings are disabled.") query = "\nUNION\n".join( - self._build_meta_query(meta_name, conf, schema_name) - for meta_name, conf in zip(["TABLE", "VIEW"], [table_conf, view_conf]) + self.meta_query.get_metadata(meta_type, schema_name) + for meta_type, conf in zip( + [MetaType.TABLE, MetaType.VIEW], [table_conf, view_conf] + ) if conf.enable ) - return self._execute_meta_query(query, keywords) + return self._execute_meta_query(query) def find_tables( self, keywords: KEYWORDS_TYPE, schema_name: OPTIONAL_SCHEMA_NAME_TYPE ) -> ExaDbResult: - return self._find_tables(keywords, schema_name) + if (not self.config.tables.enable) and (not self.config.views.enable): + raise RuntimeError("Both the table and the view listings are disabled.") - def list_tables(self, schema_name: SCHEMA_NAME_TYPE) -> ExaDbResult: - return self._find_tables(schema_name=schema_name) + query = self.meta_query.find_tables(schema_name) + return self._execute_meta_query(query, keywords) - def _find_functions( - self, keywords: list[str] | None = None, schema_name: str | None = None - ) -> ExaDbResult: - conf = self.config.functions - if not conf.enable: + def list_functions(self, schema_name: SCHEMA_NAME_TYPE) -> ExaDbResult: + if not self.config.functions.enable: raise RuntimeError("The function listing is disabled.") - query = self._build_meta_query("FUNCTION", conf, schema_name) - return self._execute_meta_query(query, keywords) + query = self.meta_query.get_metadata(MetaType.FUNCTION, schema_name) + return self._execute_meta_query(query) def find_functions( self, keywords: KEYWORDS_TYPE, schema_name: OPTIONAL_SCHEMA_NAME_TYPE ) -> ExaDbResult: - return self._find_functions(keywords, schema_name) + if not self.config.functions.enable: + raise RuntimeError("The function listing is disabled.") - def list_functions(self, schema_name: SCHEMA_NAME_TYPE) -> ExaDbResult: - return self._find_functions(schema_name=schema_name) + query = self.meta_query.get_metadata(MetaType.FUNCTION, schema_name) + return self._execute_meta_query(query, keywords) - def _find_scripts( - self, keywords: list[str] | None = None, schema_name: str | None = None - ) -> ExaDbResult: - conf = self.config.scripts - if not conf.enable: + def list_scripts(self, schema_name: SCHEMA_NAME_TYPE) -> ExaDbResult: + if not self.config.scripts.enable: raise RuntimeError("The script listing is disabled.") - query = self._build_meta_query( - "SCRIPT", conf, schema_name, "SCRIPT_TYPE = 'UDF'" - ) - return self._execute_meta_query(query, keywords) + query = self.meta_query.get_metadata(MetaType.SCRIPT, schema_name) + return self._execute_meta_query(query) def find_scripts( self, keywords: KEYWORDS_TYPE, schema_name: OPTIONAL_SCHEMA_NAME_TYPE ) -> ExaDbResult: - return self._find_scripts(keywords, schema_name) + if not self.config.scripts.enable: + raise RuntimeError("The script listing is disabled.") - def list_scripts(self, schema_name: SCHEMA_NAME_TYPE) -> ExaDbResult: - return self._find_scripts(schema_name=schema_name) + query = self.meta_query.get_metadata(MetaType.SCRIPT, schema_name) + return self._execute_meta_query(query, keywords) def describe_columns( self, schema_name: SCHEMA_NAME_TYPE, table_name: TABLE_NAME_TYPE @@ -247,22 +207,10 @@ def describe_columns( Returns the list of columns in the given table. Currently, this is a part of the `describe_table` tool, but it can be used independently in the future. """ - conf = self.config.columns - if not conf.enable: + if not self.config.columns.enable: raise RuntimeError("The column listing is disabled.") - query = dedent( - f""" - SELECT - COLUMN_NAME AS "{conf.name_field}", - COLUMN_TYPE AS "{conf.type_field}", - COLUMN_COMMENT AS "{conf.comment_field}" - FROM SYS.EXA_ALL_COLUMNS - WHERE - COLUMN_SCHEMA = {sql_text_value(schema_name)} AND - COLUMN_TABLE = {sql_text_value(table_name)} - """ - ) + query = self.meta_query.describe_columns(schema_name, table_name) return self._execute_meta_query(query) def describe_constraints( @@ -272,49 +220,14 @@ def describe_constraints( Returns the list of constraints in the given table. Currently, this is a part of the `describe_table` tool, but it can be used independently in the future. """ - conf = self.config.columns - if not conf.enable: + if not self.config.columns.enable: raise RuntimeError("The constraint listing is disabled.") - query = dedent( - f""" - SELECT - FIRST_VALUE(CONSTRAINT_TYPE) AS "{conf.constraint_type_field}", - CASE LEFT(CONSTRAINT_NAME, 4) WHEN 'SYS_' THEN NULL - ELSE CONSTRAINT_NAME END AS "{conf.constraint_name_field}", - GROUP_CONCAT(DISTINCT COLUMN_NAME ORDER BY ORDINAL_POSITION) - AS "{conf.constraint_columns_field}", - FIRST_VALUE(REFERENCED_SCHEMA) AS "{conf.referenced_schema_field}", - FIRST_VALUE(REFERENCED_TABLE) AS "{conf.referenced_table_field}", - GROUP_CONCAT(DISTINCT REFERENCED_COLUMN ORDER BY ORDINAL_POSITION) - AS "{conf.referenced_columns_field}" - FROM SYS.EXA_ALL_CONSTRAINT_COLUMNS - WHERE - CONSTRAINT_SCHEMA = {sql_text_value(schema_name)} AND - CONSTRAINT_TABLE = {sql_text_value(table_name)} - GROUP BY CONSTRAINT_NAME - """ - ) + query = self.meta_query.describe_constraints(schema_name, table_name) return self._execute_meta_query(query) def get_table_comment(self, schema_name: str, table_name: str) -> str | None: - # `table_name` can be the name of a table or a view. - # This query tries both possibilities. The UNION clause collapses - # the result into a single non-NULL distinct value. - query = dedent( - f""" - SELECT TABLE_COMMENT AS COMMENT FROM SYS.EXA_ALL_TABLES - WHERE - TABLE_SCHEMA = {sql_text_value(schema_name)} AND - TABLE_NAME = {sql_text_value(table_name)} - UNION - SELECT VIEW_COMMENT AS COMMENT FROM SYS.EXA_ALL_VIEWS - WHERE - VIEW_SCHEMA = {sql_text_value(schema_name)} AND - VIEW_NAME = {sql_text_value(table_name)} - LIMIT 1; - """ - ) + query = self.meta_query.get_table_comment(schema_name, table_name) comment_row = self.connection.meta.execute_snapshot(query=query).fetchone() if comment_row is None: return None diff --git a/exasol/ai/mcp/server/meta_query.py b/exasol/ai/mcp/server/meta_query.py new file mode 100644 index 0000000..2679047 --- /dev/null +++ b/exasol/ai/mcp/server/meta_query.py @@ -0,0 +1,318 @@ +from enum import Enum +from functools import cache +from textwrap import dedent +from typing import Any + +from exasol.ai.mcp.server.server_settings import ( + McpServerSettings, + MetaListSettings, +) +from exasol.ai.mcp.server.utils import sql_text_value + +INFO_COLUMN = "SUPPORT_INFO" +""" +Column with additional information, to be used for filtering. +""" + + +class MetaType(Enum): + SCHEMA = "SCHEMA" + TABLE = "TABLE" + VIEW = "VIEW" + FUNCTION = "FUNCTION" + SCRIPT = "SCRIPT" + COLUMN = "COLUMN" + + +def _where_clause(*predicates) -> str: + condition = " AND ".join(filter(bool, predicates)) + if condition: + return f"WHERE {condition}" + return "" + + +def _get_meta_predicates(column: str, conf: MetaListSettings) -> list[str]: + """ + Constructs predicates for the provided column using the LIKE and REGEXP_LIKE + patterns in the provided configuration. + """ + predicates: list[str] = [] + if conf.like_pattern: + predicates.append(f'"{column}" LIKE {sql_text_value(conf.like_pattern)}') + if conf.regexp_pattern: + predicates.append( + f'"{column}" REGEXP_LIKE {sql_text_value(conf.regexp_pattern)}' + ) + return predicates + + +def _inner_meta_query( + meta_type: MetaType, output_types: list[MetaType], *predicates +) -> str: + """ + Builds a query collecting names and comments for the given type of meta and putting + them in a json string. The comment element is omitted if NULL. + + Args: + meta_type: + Metadata type to collect the data for. + output_types: + List of metadata types to go in the SELECT list. This should correspond to + the `meta_type`. For example, the output type TABLE can be used only with + the meta type COLUMN, but 'SCHEMA' can be used with any meta type. + predicates: + WHERE clause predicates. + + Example output for `meta_name` = 'TABLE', output_types = ['SCHEMA']. + { + 'SCHEMA_NAME': '', + 'OBJ_INFO': '{"TABLE": "", "COMMENT": ""}' + } + """ + meta_name = meta_type.value + select_list = ", ".join( + f'"{meta_name}_{out_type.value}" AS "{out_type.value}"' + for out_type in output_types + ) + return dedent( + f""" + SELECT + {select_list}, + CONCAT( + '{{"{meta_name}": "', "{meta_name}_NAME", + NVL2("{meta_name}_COMMENT", CONCAT('", "COMMENT": "', "{meta_name}_COMMENT"), ''), + '"}}' + ) AS "OBJ_INFO" + FROM SYS."EXA_ALL_{meta_name}S" + {_where_clause(*predicates)} + """ + ) + + +class ExasolMetaQuery: + """ + A query builder class, constructing metadata queries based on the provided server + configuration. + """ + + def __init__(self, config: McpServerSettings): + self._config = config + self._meta_conf = { + MetaType.SCHEMA: config.schemas, + MetaType.TABLE: config.tables, + MetaType.VIEW: config.views, + MetaType.FUNCTION: config.functions, + MetaType.SCRIPT: config.scripts, + } + + @property + def config(self) -> McpServerSettings: + return self._config + + def get_metadata(self, meta_type: MetaType, schema_name: str | None = None) -> str: + """ + A generic metadata query. Collects the DB object name, schema and comment + for a given object type. Applies visibility restrictions for the type of + metadata in question and for the schema. Optionally, limits the output to + the objects in one particular schema. + + Args: + meta_type: + Metadata type to collect the data for. + schema_name: + An optional schema name provided in the call to the tool. Ignored if + meta_name=='SCHEMA'. In all other cases, if the name is specified, it + will be included in the WHERE clause. Otherwise, the query will return + objects from all visible schemas. + """ + conf = self._meta_conf[meta_type] + meta_name = meta_type.value + select_list = [ + f'"{meta_name}_NAME" AS "{conf.name_field}"', + f'"{meta_name}_COMMENT" AS "{conf.comment_field}"', + ] + predicates = [conf.select_predicate] + if meta_type == MetaType.SCRIPT: + predicates.append(""""SCRIPT_TYPE" = 'UDF'""") + if meta_type != MetaType.SCHEMA: + schema_column = f"{meta_name}_SCHEMA" + if schema_name: + predicates.append(f'"{schema_column}" = {sql_text_value(schema_name)}') + else: + # Adds the schema restriction if specified in the settings. + predicates.extend( + _get_meta_predicates(schema_column, self.config.schemas) + ) + select_list.append(f'"{schema_column}" AS "{conf.schema_field}"') + return dedent( + f""" + SELECT {', '.join(select_list)} + FROM SYS."EXA_ALL_{meta_name}S" + {_where_clause(*predicates)} + """ + ) + + @cache + def find_schemas(self) -> str: + """ + Collects names, comments and the support information for schemas within + the defined schema visibility. + + The support information includes the names and comments of the database + objects within the schema. The visibility rules defined for each type of + metadata is applied when the support information is collected. + The support information is formated as json. For an example see the + `_inner_meta_query`. + """ + inner_queries = "UNION".join( + _inner_meta_query( + meta_type, + [MetaType.SCHEMA], + predicate, + *_get_meta_predicates( + f"{meta_type.value}_NAME", self._meta_conf[meta_type] + ), + ) + for meta_type, predicate in [ + (MetaType.TABLE, ""), + (MetaType.VIEW, ""), + (MetaType.FUNCTION, ""), + (MetaType.SCRIPT, """"SCRIPT_TYPE"='UDF'"""), + ] + ) + predicate = self._config.schemas.select_predicate + return dedent( + f""" + SELECT + S."SCHEMA_NAME" AS "{self._config.schemas.name_field}", + S."SCHEMA_COMMENT" AS "{self._config.schemas.comment_field}", + O."{INFO_COLUMN}" + FROM SYS.EXA_ALL_SCHEMAS S + JOIN ( + SELECT + "SCHEMA", + CONCAT('[', GROUP_CONCAT(DISTINCT "OBJ_INFO" SEPARATOR ', '), ']') AS "{INFO_COLUMN}" + FROM ({inner_queries}) + GROUP BY "SCHEMA" + ) O ON S."SCHEMA_NAME" = O."SCHEMA" + {_where_clause(predicate)} + """ + ) + + def find_tables(self, schema_name: str | None) -> str: + """ + Collects names, comments and support information for tables and/or views, + depending on what is enabled. The visibility rules defined for the schemas, + tables and views are applied. An optional `schema_name`, if provided, restricts + the listing of the tables and views to this particular schema. + + The support information includes the names and comments of the columns. + It is formated as json. For an example see the `_inner_meta_query`. + """ + if schema_name: + predicates = [f'"COLUMN_SCHEMA" = {sql_text_value(schema_name)}'] + else: + predicates = _get_meta_predicates("COLUMN_SCHEMA", self.config.schemas) + inner_query = _inner_meta_query( + MetaType.COLUMN, [MetaType.SCHEMA, MetaType.TABLE], *predicates + ) + main_query = "UNION".join( + dedent( + f""" + SELECT + T."{meta_name}_NAME" AS "{conf.name_field}", + T."{meta_name}_COMMENT" AS "{conf.comment_field}", + T."{meta_name}_SCHEMA" AS "{conf.schema_field}", + C."{INFO_COLUMN}" + FROM SYS.EXA_ALL_{meta_name}S T + JOIN C ON + T."{meta_name}_SCHEMA" = C."SCHEMA" AND + T."{meta_name}_NAME" = C."TABLE" + {_where_clause(conf.select_predicate)} + """ + ) + for meta_name, conf in [ + ("TABLE", self._config.tables), + ("VIEW", self._config.views), + ] + if conf.enable + ) + return dedent( + f""" + WITH C AS ( + SELECT + "SCHEMA", + "TABLE", + CONCAT('[', GROUP_CONCAT(DISTINCT "OBJ_INFO" SEPARATOR ', '), ']') AS "{INFO_COLUMN}" + FROM ({inner_query}) + GROUP BY "SCHEMA", "TABLE" + ) + {main_query} + """ + ) + + def describe_columns(self, schema_name: str, table_name: str) -> str: + """ + Gathers a list of columns in a given table. + """ + conf = self._config.columns + return dedent( + f""" + SELECT + COLUMN_NAME AS "{conf.name_field}", + COLUMN_TYPE AS "{conf.type_field}", + COLUMN_COMMENT AS "{conf.comment_field}" + FROM SYS.EXA_ALL_COLUMNS + WHERE + COLUMN_SCHEMA = {sql_text_value(schema_name)} AND + COLUMN_TABLE = {sql_text_value(table_name)} + """ + ) + + def describe_constraints(self, schema_name: str, table_name: str) -> str: + """ + Gathers a list of constraints for a given table. + """ + conf = self._config.columns + return dedent( + f""" + SELECT + FIRST_VALUE(CONSTRAINT_TYPE) AS "{conf.constraint_type_field}", + CASE LEFT(CONSTRAINT_NAME, 4) WHEN 'SYS_' THEN NULL + ELSE CONSTRAINT_NAME END AS "{conf.constraint_name_field}", + GROUP_CONCAT(DISTINCT COLUMN_NAME ORDER BY ORDINAL_POSITION) + AS "{conf.constraint_columns_field}", + FIRST_VALUE(REFERENCED_SCHEMA) AS "{conf.referenced_schema_field}", + FIRST_VALUE(REFERENCED_TABLE) AS "{conf.referenced_table_field}", + GROUP_CONCAT(DISTINCT REFERENCED_COLUMN ORDER BY ORDINAL_POSITION) + AS "{conf.referenced_columns_field}" + FROM SYS.EXA_ALL_CONSTRAINT_COLUMNS + WHERE + CONSTRAINT_SCHEMA = {sql_text_value(schema_name)} AND + CONSTRAINT_TABLE = {sql_text_value(table_name)} + GROUP BY CONSTRAINT_NAME + """ + ) + + @staticmethod + def get_table_comment(schema_name: str, table_name: str) -> str | None: + """ + The query returns a single row with a comment for a given table or view. + """ + # `table_name` can be the name of a table or a view. + # This query tries both possibilities. The UNION clause collapses + # the result into a single non-NULL distinct value. + return dedent( + f""" + SELECT TABLE_COMMENT AS COMMENT FROM SYS.EXA_ALL_TABLES + WHERE + TABLE_SCHEMA = {sql_text_value(schema_name)} AND + TABLE_NAME = {sql_text_value(table_name)} + UNION + SELECT VIEW_COMMENT AS COMMENT FROM SYS.EXA_ALL_VIEWS + WHERE + VIEW_SCHEMA = {sql_text_value(schema_name)} AND + VIEW_NAME = {sql_text_value(table_name)} + LIMIT 1; + """ + ) diff --git a/exasol/ai/mcp/server/server_settings.py b/exasol/ai/mcp/server/server_settings.py index d14c62b..ff6af96 100644 --- a/exasol/ai/mcp/server/server_settings.py +++ b/exasol/ai/mcp/server/server_settings.py @@ -21,7 +21,7 @@ def check_no_double_quotes(v: str) -> str: @dataclass class ExaDbResult: - result: list[str:Any] + result: list[dict[str, Any]] class MetaSettings(BaseModel): @@ -34,6 +34,11 @@ class MetaSettings(BaseModel): Allows to disable the listing of a particular type of metadata. """ + schema_field: NoDoubleQuotesStr = "schema" + """ + The name of the output field that contains the object schema, e.g. "table_schema". + """ + name_field: NoDoubleQuotesStr = "name" """ The name of the output field that contains the object name, e.g. "table_name". diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 0ca376f..e66ef78 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -105,7 +105,7 @@ def db_tables() -> list[ExaTable]: ref_columns=["resort_id"], ), ], - keywords=["sky run", "detailed information"], + keywords=["ski run", "detailed information"], rows=[ (1000, "Christine", "Blue", 1200), (1000, "Allamande", "Red", 950), diff --git a/test/integration/test_mcp_server.py b/test/integration/test_mcp_server.py index 431ce6f..59bb234 100644 --- a/test/integration/test_mcp_server.py +++ b/test/integration/test_mcp_server.py @@ -81,12 +81,24 @@ def _get_list_result_json(result) -> ExaDbResult: return unsorted +def _get_expected_json( + db_obj: ExaDbObject, conf: MetaListSettings, schema_name: str | None +) -> dict[str, Any]: + expected_json = {conf.name_field: db_obj.name, conf.comment_field: db_obj.comment} + if schema_name: + expected_json[conf.schema_field] = schema_name + return expected_json + + def _get_expected_list_json( - db_objects: list[ExaDbObject], name_part: str, conf: MetaListSettings + db_objects: list[ExaDbObject], + name_part: str, + conf: MetaListSettings, + schema_name: str | None = None, ) -> ExaDbResult: no_pattern = not (conf.like_pattern or conf.regexp_pattern) expected_json = [ - {conf.name_field: db_obj.name, conf.comment_field: db_obj.comment} + _get_expected_json(db_obj, conf, schema_name) for db_obj in db_objects if no_pattern or (name_part in db_obj.name) ] @@ -309,10 +321,14 @@ def test_list_tables( result_json = _get_list_result_json(result) expected_result: list[dict[str, Any]] = [] if enable_tables: - expected_json = _get_expected_list_json(db_tables, "resort", config.tables) + expected_json = _get_expected_list_json( + db_tables, "resort", config.tables, schema.name + ) expected_result.extend(expected_json.result) if enable_views: - expected_json = _get_expected_list_json(db_views, "run", config.views) + expected_json = _get_expected_list_json( + db_views, "run", config.views, schema.name + ) expected_result.extend(expected_json.result) expected_json = ExaDbResult(sorted(expected_result, key=_result_sort_func)) assert result_json == expected_json @@ -342,10 +358,16 @@ def test_find_tables( regexp_pattern=schema.name if use_regexp else "", ), tables=MetaListSettings( - enable=True, name_field="name", comment_field="comment" + enable=True, + name_field="name", + comment_field="comment", + schema_field="schema", ), views=MetaListSettings( - enable=True, name_field="name", comment_field="comment" + enable=True, + name_field="name", + comment_field="comment", + schema_field="schema", ), ) # If the schema visibility is restricted to one schema we will not @@ -361,7 +383,11 @@ def test_find_tables( ) result_json = _get_result_json(result)["result"][0] - expected_json = {"name": table.name, "comment": table.comment} + expected_json = { + "name": table.name, + "comment": table.comment, + "schema": schema.name, + } assert result_json == expected_json @@ -394,7 +420,9 @@ def test_list_functions( schema_name=schema.name, ) result_json = _get_list_result_json(result) - expected_json = _get_expected_list_json(db_functions, "cut", config.functions) + expected_json = _get_expected_list_json( + db_functions, "cut", config.functions, schema.name + ) assert result_json == expected_json @@ -416,7 +444,10 @@ def test_find_functions( regexp_pattern=schema.name if use_regexp else "", ), functions=MetaListSettings( - enable=True, name_field="name", comment_field="comment" + enable=True, + name_field="name", + comment_field="comment", + schema_field="schema", ), ) # If the schema visibility is restricted to one schema we will not @@ -431,7 +462,11 @@ def test_find_functions( schema_name=schema_name, ) result_json = _get_result_json(result)["result"][0] - expected_json = {"name": func.name, "comment": func.comment} + expected_json = { + "name": func.name, + "comment": func.comment, + "schema": schema.name, + } assert result_json == expected_json @@ -464,7 +499,9 @@ def test_list_scripts( schema_name=schema.name, ) result_json = _get_list_result_json(result) - expected_json = _get_expected_list_json(db_scripts, "fibo", config.scripts) + expected_json = _get_expected_list_json( + db_scripts, "fibo", config.scripts, schema.name + ) assert result_json == expected_json @@ -486,7 +523,10 @@ def test_find_scripts( regexp_pattern=schema.name if use_regexp else "", ), scripts=MetaListSettings( - enable=True, name_field="name", comment_field="comment" + enable=True, + name_field="name", + comment_field="comment", + schema_field="schema", ), ) # If the schema visibility is restricted to one schema we will not @@ -501,7 +541,11 @@ def test_find_scripts( schema_name=schema_name, ) result_json = _get_result_json(result)["result"][0] - expected_json = {"name": script.name, "comment": script.comment} + expected_json = { + "name": script.name, + "comment": script.comment, + "schema": schema.name, + } assert result_json == expected_json diff --git a/test/unit/test_verify_query.py b/test/unit/test_mcp_server.py similarity index 84% rename from test/unit/test_verify_query.py rename to test/unit/test_mcp_server.py index 2ffd035..ff3fb9c 100644 --- a/test/unit/test_verify_query.py +++ b/test/unit/test_mcp_server.py @@ -2,7 +2,11 @@ import pytest -from exasol.ai.mcp.server.mcp_server import verify_query +from exasol.ai.mcp.server.mcp_server import ( + remove_info_column, + verify_query, +) +from exasol.ai.mcp.server.meta_query import INFO_COLUMN def sample_select_query() -> str: @@ -165,3 +169,20 @@ def test_verify_query(query, expected_result): in this case is that such queries are not recognised as valid SQL statements. """ assert verify_query(query) == expected_result + + +def test_remove_info_column(): + input_data = [ + {"name": "db_object1", "comment": "this is my first db object"}, + { + "name": "db_object2", + "comment": "this is my second db object", + INFO_COLUMN: "this column should be removed", + }, + ] + output_data = remove_info_column(input_data) + expected_output_data = [ + {"name": "db_object1", "comment": "this is my first db object"}, + {"name": "db_object2", "comment": "this is my second db object"}, + ] + assert output_data == expected_output_data diff --git a/test/unit/test_meta_query.py b/test/unit/test_meta_query.py new file mode 100644 index 0000000..50b34e4 --- /dev/null +++ b/test/unit/test_meta_query.py @@ -0,0 +1,445 @@ +from test.utils.text_utils import collapse_spaces + +import pytest + +from exasol.ai.mcp.server.meta_query import ( + INFO_COLUMN, + ExasolMetaQuery, + MetaType, +) +from exasol.ai.mcp.server.server_settings import ( + McpServerSettings, + MetaListSettings, +) + + +@pytest.mark.parametrize( + [ + "schema_name", + "schema_pattern", + "schema_pattern_type", + "table_pattern", + "table_pattern_type", + "expected_where_clause", + ], + [ + ("", "", "", "", "", ""), + ("EXA_TOOLBOX", "", "", "", "", """WHERE "TABLE_SCHEMA" = 'EXA_TOOLBOX'"""), + ("", "", "", "PUB", "REGEXP_LIKE", """WHERE local."name" REGEXP_LIKE 'PUB'"""), + ( + "EXA_TOOLBOX", + "EXA%", + "LIKE", + "PUB%", + "LIKE", + """WHERE local."name" LIKE 'PUB%' AND "TABLE_SCHEMA" = 'EXA_TOOLBOX'""", + ), + ( + "", + "EXA", + "REGEXP_LIKE", + "PUB%", + "LIKE", + """WHERE local."name" LIKE 'PUB%' AND "TABLE_SCHEMA" REGEXP_LIKE 'EXA'""", + ), + ], + ids=[ + "all-tables", + "exact-schema", + "table-pattern", + "exact-schema-table-pattern", + "schema-and-table-patterns", + ], +) +def test_get_metadata( + schema_name, + schema_pattern, + schema_pattern_type, + table_pattern, + table_pattern_type, + expected_where_clause, +): + config = McpServerSettings( + schemas=MetaListSettings( + like_pattern=schema_pattern if schema_pattern_type == "LIKE" else "", + regexp_pattern=( + schema_pattern if schema_pattern_type == "REGEXP_LIKE" else "" + ), + ), + tables=MetaListSettings( + enable=True, + like_pattern=table_pattern if table_pattern_type == "LIKE" else "", + regexp_pattern=table_pattern if table_pattern_type == "REGEXP_LIKE" else "", + name_field="name", + comment_field="comment", + schema_field="schema", + ), + ) + meta_query = ExasolMetaQuery(config) + query = collapse_spaces(meta_query.get_metadata(MetaType.TABLE, schema_name)) + expected_query = collapse_spaces( + f""" + SELECT + "TABLE_NAME" AS "name", + "TABLE_COMMENT" AS "comment", + "TABLE_SCHEMA" AS "schema" + FROM SYS."EXA_ALL_TABLES" + {expected_where_clause} + """ + ) + assert query == expected_query + + +@pytest.mark.parametrize( + ["schema_name", "schema_pattern", "script_pattern", "expected_where_clause"], + [ + ("", "", "", """WHERE "SCRIPT_TYPE" = 'UDF'"""), + ( + "EXA_TOOLBOX", + "", + "", + """WHERE "SCRIPT_TYPE" = 'UDF' AND "SCRIPT_SCHEMA" = 'EXA_TOOLBOX'""", + ), + ( + "", + "", + "BUCKETFS%", + """WHERE local."name" LIKE 'BUCKETFS%' AND "SCRIPT_TYPE" = 'UDF'""", + ), + ( + "EXA_TOOLBOX", + "EXA%", + "BUCKETFS%", + """WHERE local."name" LIKE 'BUCKETFS%' AND "SCRIPT_TYPE" = 'UDF' AND "SCRIPT_SCHEMA" = 'EXA_TOOLBOX'""", + ), + ( + "", + "EXA%", + "BUCKETFS%", + """WHERE local."name" LIKE 'BUCKETFS%' AND "SCRIPT_TYPE" = 'UDF' AND "SCRIPT_SCHEMA" LIKE 'EXA%'""", + ), + ], + ids=[ + "all-tables", + "exact-schema", + "table-pattern", + "exact-schema-table-pattern", + "schema-and-table-patterns", + ], +) +def test_get_script_metadata( + schema_name, schema_pattern, script_pattern, expected_where_clause +): + config = McpServerSettings( + schemas=MetaListSettings(like_pattern=schema_pattern), + scripts=MetaListSettings( + enable=True, + like_pattern=script_pattern, + name_field="name", + comment_field="comment", + schema_field="schema", + ), + ) + meta_query = ExasolMetaQuery(config) + query = collapse_spaces(meta_query.get_metadata(MetaType.SCRIPT, schema_name)) + expected_query = collapse_spaces( + f""" + SELECT + "SCRIPT_NAME" AS "name", + "SCRIPT_COMMENT" AS "comment", + "SCRIPT_SCHEMA" AS "schema" + FROM SYS."EXA_ALL_SCRIPTS" + {expected_where_clause} + """ + ) + assert query == expected_query + + +@pytest.mark.parametrize( + ["pattern", "pattern_type"], + [("", ""), ("EXASOL%", "LIKE"), ("EXASOL", "REGEXP_LIKE")], + ids=["no-pattern", "like", "regexp"], +) +def test_get_schema_metadata(pattern, pattern_type): + config = McpServerSettings( + schemas=MetaListSettings( + enable=True, + like_pattern=pattern if pattern_type == "LIKE" else "", + regexp_pattern=pattern if pattern_type == "REGEXP_LIKE" else "", + name_field="name", + comment_field="comment", + ) + ) + meta_query = ExasolMetaQuery(config) + query = collapse_spaces(meta_query.get_metadata(MetaType.SCHEMA, "to be ignored")) + expected_where_clause = ( + f"""WHERE local."name" {pattern_type} '{pattern}'""" if pattern else "" + ) + expected_query = collapse_spaces( + f""" + SELECT + "SCHEMA_NAME" AS "name", + "SCHEMA_COMMENT" AS "comment" + FROM SYS."EXA_ALL_SCHEMAS" + {expected_where_clause} + """ + ) + assert query == expected_query + + +@pytest.mark.parametrize( + ["pattern", "pattern_type"], + [("", ""), ("EXASOL%", "LIKE"), ("EXASOL", "REGEXP_LIKE")], + ids=["no-pattern", "like", "regexp"], +) +def test_find_schemas(pattern, pattern_type) -> None: + config = McpServerSettings( + schemas=MetaListSettings( + enable=True, + like_pattern=pattern if pattern_type == "LIKE" else "", + regexp_pattern=pattern if pattern_type == "REGEXP_LIKE" else "", + name_field="name", + comment_field="comment", + ) + ) + meta_query = ExasolMetaQuery(config) + query = collapse_spaces(meta_query.find_schemas()) + expected_where_clause = ( + f"""WHERE local."name" {pattern_type} '{pattern}'""" if pattern else "" + ) + expected_query = collapse_spaces( + f""" + SELECT + S."SCHEMA_NAME" AS "name", + S."SCHEMA_COMMENT" AS "comment", + O."{INFO_COLUMN}" + FROM SYS.EXA_ALL_SCHEMAS S + JOIN ( + SELECT + "SCHEMA", + CONCAT('[', GROUP_CONCAT(DISTINCT "OBJ_INFO" SEPARATOR ', '), ']') AS "{INFO_COLUMN}" + FROM ( + SELECT + "TABLE_SCHEMA" AS "SCHEMA", + CONCAT( + '{{"TABLE": "', "TABLE_NAME", + NVL2("TABLE_COMMENT", CONCAT('", "COMMENT": "', "TABLE_COMMENT"), ''), + '"}}' + ) AS "OBJ_INFO" + FROM SYS."EXA_ALL_TABLES" + UNION + SELECT + "VIEW_SCHEMA" AS "SCHEMA", + CONCAT( + '{{"VIEW": "', "VIEW_NAME", + NVL2("VIEW_COMMENT", CONCAT('", "COMMENT": "', "VIEW_COMMENT"), ''), + '"}}' + ) AS "OBJ_INFO" + FROM SYS."EXA_ALL_VIEWS" + UNION + SELECT + "FUNCTION_SCHEMA" AS "SCHEMA", + CONCAT( + '{{"FUNCTION": "', "FUNCTION_NAME", + NVL2("FUNCTION_COMMENT", CONCAT('", "COMMENT": "', "FUNCTION_COMMENT"), ''), + '"}}' + ) AS "OBJ_INFO" + FROM SYS."EXA_ALL_FUNCTIONS" + UNION + SELECT + "SCRIPT_SCHEMA" AS "SCHEMA", + CONCAT( + '{{"SCRIPT": "', "SCRIPT_NAME", + NVL2("SCRIPT_COMMENT", CONCAT('", "COMMENT": "', "SCRIPT_COMMENT"), ''), + '"}}' + ) AS "OBJ_INFO" + FROM SYS."EXA_ALL_SCRIPTS" + WHERE "SCRIPT_TYPE"='UDF' + ) + GROUP BY "SCHEMA" + ) O ON S."SCHEMA_NAME" = O."SCHEMA" + {expected_where_clause} + """ + ) + assert query == expected_query + + +@pytest.mark.parametrize( + ["schema_name", "schema_regex_pattern", "table_regex_pattern"], + [ + ("", "", ""), + ("EXA_TOOLBOX", "EXA", ""), + ("", "EXA", ""), + ("", "", "PUB"), + ("", "EXA%", "PUB%"), + ], + ids=[ + "no-predicates", + "exact-schema", + "schema-pattern", + "table-pattern", + "all-patterns", + ], +) +def test_find_tables(schema_name, schema_regex_pattern, table_regex_pattern) -> None: + config = McpServerSettings( + schemas=MetaListSettings(regexp_pattern=schema_regex_pattern), + tables=MetaListSettings( + enable=True, + regexp_pattern=table_regex_pattern, + name_field="name", + comment_field="comment", + ), + views=MetaListSettings(enable=False), + ) + meta_query = ExasolMetaQuery(config) + query = collapse_spaces(meta_query.find_tables(schema_name)) + if schema_name: + expected_column_where_clause = f"""WHERE "COLUMN_SCHEMA" = '{schema_name}'""" + elif schema_regex_pattern: + expected_column_where_clause = ( + f"""WHERE "COLUMN_SCHEMA" REGEXP_LIKE '{schema_regex_pattern}'""" + ) + else: + expected_column_where_clause = "" + expected_table_where_clause = ( + f"""WHERE local."name" REGEXP_LIKE '{table_regex_pattern}'""" + if table_regex_pattern + else "" + ) + expected_query = collapse_spaces( + f""" + WITH C AS ( + SELECT + "SCHEMA", + "TABLE", + CONCAT('[', GROUP_CONCAT(DISTINCT "OBJ_INFO" SEPARATOR ', '), ']') AS "{INFO_COLUMN}" + FROM ( + SELECT + "COLUMN_SCHEMA" AS "SCHEMA", "COLUMN_TABLE" AS "TABLE", + CONCAT( + '{{"COLUMN": "', "COLUMN_NAME", + NVL2("COLUMN_COMMENT", CONCAT('", "COMMENT": "', "COLUMN_COMMENT"), ''), + '"}}' + ) AS "OBJ_INFO" + FROM SYS."EXA_ALL_COLUMNS" + {expected_column_where_clause} + ) + GROUP BY "SCHEMA", "TABLE" + ) + SELECT + T."TABLE_NAME" AS "name", + T."TABLE_COMMENT" AS "comment", + T."TABLE_SCHEMA" AS "schema", + C."{INFO_COLUMN}" + FROM SYS.EXA_ALL_TABLES T + JOIN C ON + T."TABLE_SCHEMA" = C."SCHEMA" AND + T."TABLE_NAME" = C."TABLE" + {expected_table_where_clause} + """ + ) + assert query == expected_query + + +@pytest.mark.parametrize( + ["schema_name", "schema_like_pattern", "table_like_pattern", "view_like_pattern"], + [ + ("", "", "", ""), + ("EXA_TOOLBOX", "EXA%", "", ""), + ("", "EXA%", "", ""), + ("", "", "PUB%", ""), + ("", "", "", "AUDITING%"), + ("", "EXA%", "PUB%", "AUDITING%"), + ], + ids=[ + "no-predicates", + "exact-schema", + "schema-pattern", + "table-pattern", + "view-pattern", + "all-patterns", + ], +) +def test_find_tables_and_views( + schema_name, schema_like_pattern, table_like_pattern, view_like_pattern +) -> None: + config = McpServerSettings( + schemas=MetaListSettings(like_pattern=schema_like_pattern), + tables=MetaListSettings( + enable=True, + like_pattern=table_like_pattern, + name_field="name", + comment_field="comment", + ), + views=MetaListSettings( + enable=True, + like_pattern=view_like_pattern, + name_field="name", + comment_field="comment", + ), + ) + meta_query = ExasolMetaQuery(config) + query = collapse_spaces(meta_query.find_tables(schema_name)) + if schema_name: + expected_column_where_clause = f"""WHERE "COLUMN_SCHEMA" = '{schema_name}'""" + elif schema_like_pattern: + expected_column_where_clause = ( + f"""WHERE "COLUMN_SCHEMA" LIKE '{schema_like_pattern}'""" + ) + else: + expected_column_where_clause = "" + expected_table_where_clause = ( + f"""WHERE local."name" LIKE '{table_like_pattern}'""" + if table_like_pattern + else "" + ) + expected_view_where_clause = ( + f"""WHERE local."name" LIKE '{view_like_pattern}'""" + if view_like_pattern + else "" + ) + expected_query = collapse_spaces( + f""" + WITH C AS ( + SELECT + "SCHEMA", + "TABLE", + CONCAT('[', GROUP_CONCAT(DISTINCT "OBJ_INFO" SEPARATOR ', '), ']') AS "{INFO_COLUMN}" + FROM ( + SELECT + "COLUMN_SCHEMA" AS "SCHEMA", "COLUMN_TABLE" AS "TABLE", + CONCAT( + '{{"COLUMN": "', "COLUMN_NAME", + NVL2("COLUMN_COMMENT", CONCAT('", "COMMENT": "', "COLUMN_COMMENT"), ''), + '"}}' + ) AS "OBJ_INFO" + FROM SYS."EXA_ALL_COLUMNS" + {expected_column_where_clause} + ) + GROUP BY "SCHEMA", "TABLE" + ) + SELECT + T."TABLE_NAME" AS "name", + T."TABLE_COMMENT" AS "comment", + T."TABLE_SCHEMA" AS "schema", + C."{INFO_COLUMN}" + FROM SYS.EXA_ALL_TABLES T + JOIN C ON + T."TABLE_SCHEMA" = C."SCHEMA" AND + T."TABLE_NAME" = C."TABLE" + {expected_table_where_clause} + UNION + SELECT + T."VIEW_NAME" AS "name", + T."VIEW_COMMENT" AS "comment", + T."VIEW_SCHEMA" AS "schema", + C."{INFO_COLUMN}" + FROM SYS.EXA_ALL_VIEWS T + JOIN C ON + T."VIEW_SCHEMA" = C."SCHEMA" AND + T."VIEW_NAME" = C."TABLE" + {expected_view_where_clause} + """ + ) + assert query == expected_query diff --git a/test/utils/text_utils.py b/test/utils/text_utils.py new file mode 100644 index 0000000..a6aff89 --- /dev/null +++ b/test/utils/text_utils.py @@ -0,0 +1,9 @@ +import re + + +def collapse_spaces(text: str) -> str: + text = re.sub(r"\s+", " ", text) + # Remove leading and trailing spaces in brackets. + text = re.sub(r"\(\s+", "(", text) + text = re.sub(r"\s+\)", ")", text) + return text.strip() From 946f7bc76a59d179e57fb8a2737b5530c8c0340c Mon Sep 17 00:00:00 2001 From: mibe Date: Mon, 20 Oct 2025 10:09:31 +0100 Subject: [PATCH 02/18] #70: Started the User Guide --- doc/user_guide/open_id_setup.md | 1 + doc/user_guide/tool_setup.md | 147 ++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 doc/user_guide/open_id_setup.md create mode 100644 doc/user_guide/tool_setup.md diff --git a/doc/user_guide/open_id_setup.md b/doc/user_guide/open_id_setup.md new file mode 100644 index 0000000..8c65a45 --- /dev/null +++ b/doc/user_guide/open_id_setup.md @@ -0,0 +1 @@ +# OpenID Setup diff --git a/doc/user_guide/tool_setup.md b/doc/user_guide/tool_setup.md new file mode 100644 index 0000000..11b6f58 --- /dev/null +++ b/doc/user_guide/tool_setup.md @@ -0,0 +1,147 @@ +# Tool setup: + +This guide provides detailed information on how to configure Exasol MCP Server tools +to address the requirements of a specific use case. + +## Enable SQL queries + +Most importantly, the server configuration specifies if reading the data using SQL +queries is enabled. Note that reading is disabled by default. To enable the data +reading, set the `enable_read_query` property to true: +```json +{ + "enable_read_query": true +} +``` + +## Set DB object listing filters + +The server configuration settings can also be used to enable/disable or filter the +listing of a particular type of database objects. Similar settings are defined for +the following object types: +``` +schemas, +tables, +views, +functions, +scripts +``` +The settings include the following properties: +- `enable`: a boolean flag that enables or disables the listing. +- `like_pattern`: filters the output by applying the specified SQL LIKE condition to +the object name. +- `regexp_pattern`: filters the output by matching the object name with the specified +regular expression. + +In the following example, the listing of schemas is limited to only one schema, +the listings of functions and scripts are disabled and the visibility of tables is +limited to tables with certain name pattern. + +```json +{ + "schemas": { + "like_pattern": "MY_SCHEMA" + }, + "tables": { + "like_pattern": "MY_TABLE%" + }, + "functions": { + "enable": false + }, + "scripts": { + "enable": false + } +} +``` + +## Set the language + +The language, if specified, can help the tools execute more precise search of requested +database object. This should be the language of communication with the LLM and also the +language used for naming and documenting the database objects. The language must be set +to its english name, e.g. "spanish", not "español". +Below is an example of configuration settings that sets the language to English. + +```json +{ + "language": "english" +} +``` + +## Set the case-sensitive search option + +By default, the database objects are searched in case-insensitive way, i.e. it is assumed +that the names "My_Table" and "MY_TABLE" refer to the same table. If this is undesirable, +the configuration setting `case_sensitive` should be set to true, as in the example below. + +```json +{ + "case_sensitive": true +} +``` + +## Add the server configuration to the MCP Client configuration + +The customised settings can be specified directly in the MCP Client configuration file +using another environment variable - `EXA_MCP_SETTINGS`: +```json +{ + "env": { + "EXA_DSN": "my-dsn", + "EXA_USER": "my-user-name", + "EXA_PASSWORD": "my-password", + "EXA_MCP_SETTINGS": "{\"schemas\": {\"like_pattern\": \"MY_SCHEMA\"}" + } +} +``` +Note that double quotes in the json text must be escaped, otherwise the environment +variable value will be interpreted, not as a text, but as a part of the outer json. + +Alternatively, the settings can be written in a json file. In this case, the +`EXA_MCP_SETTINGS` should contain the path to this file, e.g. +```json +{ + "env": { + "EXA_DSN": "my-dsn", + "EXA_USER": "my-user-name", + "EXA_PASSWORD": "my-password", + "EXA_MCP_SETTINGS": "path_to_settings.json" + } +} +``` + +## Default server settings + +The following json shows the default settings. +```json +{ + "schemas": { + "enable": true, + "like_pattern": "", + "regexp_pattern": "" + }, + "tables": { + "enable": true, + "like_pattern": "", + "regexp_pattern": "" + }, + "views": { + "enable": false, + "like_pattern": "", + "regexp_pattern": "" + }, + "functions": { + "enable": true, + "like_pattern": "", + "regexp_pattern": "" + }, + "scripts": { + "enable": true, + "like_pattern": "", + "regexp_pattern": "" + }, + "enable_read_query": false, + "language": "" +} +``` +The default values do not need to be repeated in the customised settings. From b638baa1c46e3634f07d97fbf86d9b85d59deb00 Mon Sep 17 00:00:00 2001 From: mibe Date: Thu, 23 Oct 2025 12:16:02 +0100 Subject: [PATCH 03/18] #70: doc checkpoint 1 --- README.md | 150 +------------------------- doc/user_guide/db_connection_setup.md | 0 doc/user_guide/open_id_setup.md | 35 ++++++ doc/user_guide/server_setup.md | 60 +++++++++++ 4 files changed, 99 insertions(+), 146 deletions(-) create mode 100644 doc/user_guide/db_connection_setup.md create mode 100644 doc/user_guide/server_setup.md diff --git a/README.md b/README.md index a57455b..d189bec 100644 --- a/README.md +++ b/README.md @@ -102,152 +102,10 @@ after restarting Claude Desktop. ## Configuration settings: -In the above example the server is configured to run using default settings. -The way the server runs can be fine-tuned by providing customised settings in -json format. - -### Enable SQL queries - -Most importantly, the server configuration specifies if reading the data using SQL -queries is enabled. Note that reading is disabled by default. To enable the data -reading, set the `enable_read_query` property to true: -```json -{ - "enable_read_query": true -} -``` - -### Set DB object listing filters - -The server configuration settings can also be used to enable/disable or filter the -listing of a particular type of database objects. Similar settings are defined for -the following object types: -``` -schemas, -tables, -views, -functions, -scripts -``` -The settings include the following properties: -- `enable`: a boolean flag that enables or disables the listing. -- `like_pattern`: filters the output by applying the specified SQL LIKE condition to -the object name. -- `regexp_pattern`: filters the output by matching the object name with the specified -regular expression. - -In the following example, the listing of schemas is limited to only one schema, -the listings of functions and scripts are disabled and the visibility of tables is -limited to tables with certain name pattern. - -```json -{ - "schemas": { - "like_pattern": "MY_SCHEMA" - }, - "tables": { - "like_pattern": "MY_TABLE%" - }, - "functions": { - "enable": false - }, - "scripts": { - "enable": false - } -} -``` - -### Set the language - -The language, if specified, can help the tools execute more precise search of requested -database object. This should be the language of communication with the LLM and also the -language used for naming and documenting the database objects. The language must be set -to its english name, e.g. "spanish", not "español". -Below is an example of configuration settings that sets the language to English. - -```json -{ - "language": "english" -} -``` - -### Set the case-sensitive search option - -By default, the database objects are searched in case-insensitive way, i.e. it is assumed -that the names "My_Table" and "MY_TABLE" refer to the same table. If this is undesirable, -the configuration setting `case_sensitive` should be set to true, as in the example below. - -```json -{ - "case_sensitive": true -} -``` - -### Add the server configuration to the MCP Client configuration - -The customised settings can be specified directly in the MCP Client configuration file -using another environment variable - `EXA_MCP_SETTINGS`: -```json -{ - "env": { - "EXA_DSN": "my-dsn", - "EXA_USER": "my-user-name", - "EXA_PASSWORD": "my-password", - "EXA_MCP_SETTINGS": "{\"schemas\": {\"like_pattern\": \"MY_SCHEMA\"}" - } -} -``` -Note that double quotes in the json text must be escaped, otherwise the environment -variable value will be interpreted, not as a text, but as a part of the outer json. - -Alternatively, the settings can be written in a json file. In this case, the -`EXA_MCP_SETTINGS` should contain the path to this file, e.g. -```json -{ - "env": { - "EXA_DSN": "my-dsn", - "EXA_USER": "my-user-name", - "EXA_PASSWORD": "my-password", - "EXA_MCP_SETTINGS": "path_to_settings.json" - } -} -``` - -### Default server settings - -The following json shows the default settings. -```json -{ - "schemas": { - "enable": true, - "like_pattern": "", - "regexp_pattern": "" - }, - "tables": { - "enable": true, - "like_pattern": "", - "regexp_pattern": "" - }, - "views": { - "enable": false, - "like_pattern": "", - "regexp_pattern": "" - }, - "functions": { - "enable": true, - "like_pattern": "", - "regexp_pattern": "" - }, - "scripts": { - "enable": true, - "like_pattern": "", - "regexp_pattern": "" - }, - "enable_read_query": false, - "language": "" -} -``` -The default values do not need to be repeated in the customised settings. +The sever is configured using environment variables and optionally a json file. In the +above example the server is provided with the database connection parameters, all other +settings left to default. For the information on how to customise server settings +please see the User Guide. ## License diff --git a/doc/user_guide/db_connection_setup.md b/doc/user_guide/db_connection_setup.md new file mode 100644 index 0000000..e69de29 diff --git a/doc/user_guide/open_id_setup.md b/doc/user_guide/open_id_setup.md index 8c65a45..f8d7bf0 100644 --- a/doc/user_guide/open_id_setup.md +++ b/doc/user_guide/open_id_setup.md @@ -1 +1,36 @@ # OpenID Setup + +A production installation of the MCP server may require proper security configuration. +This is certainly the case if the server is deployed as an HTTP server. Then its tools +must be protected with an authorisation mechanism. This section of the User Guide +provides details on how Exasol MCP server can be configured in the remote deployment +scenario, i.e. as an HTTP server. + +The MCP supports and recommends OAuth2 as the authorization framework for protecting +the tools and resources from unauthorized access. OAuth2 and OpenID Connect are modern +and widely used specifications for resource protection. Exasol MCP server supports +OAuth2-based authorization to control access to its own tools, as well as the Exasol +database. The authentication options for the database connection are described in +the [Database Connection Setup](db_connection_setup.md) guide. This section focuses +on the configuring MCP Server authorization. + +It is assumed that the reader has some familiarity with the basic concepts of OAuth2. +Without going into details on how the OAuth2 works, lets us recap what roles exist in +the specification and how they map onto MCP interaction. + +The OAuth2 defines four actors: +- The resource with a certain API. +- The client, usually an application that wants to access the resource. +- The resource owner, usually a human, that can authorize the client to access the resource. +- The identity server, a service used to facilitate the authorization. + +In the MCP case, the resource API are the server tools, and the client is the MCP client +application, e.g. Claude Desktop. + +The implementation of Exasol MCP Server is based on FastMCP package, hence its +authentication layer is based on [FastMCP Authentication](https://gofastmcp.com/servers/auth/authentication). +Exasol MCP Server supports all authentication mechanisms provided by FastMCP at the +moment of the release. However, at the time of writing, FastMCP authentication is in +active development. Exasol MCP Server will inevitably fall behind at certain point. + +The FastMCP provides t diff --git a/doc/user_guide/server_setup.md b/doc/user_guide/server_setup.md new file mode 100644 index 0000000..6774912 --- /dev/null +++ b/doc/user_guide/server_setup.md @@ -0,0 +1,60 @@ +# MCP Server Setup + +Essentially, the sever is configured using environment variables. + +In case the MCP Sever is running locally, under control of an MCP client application, +the latter usually provides a convenient way of creating an environment from some kind +of configuration file. Below is an example of Claude Desktop configuration file that +references the Exasol MCP Server. + +```json +{ + "mcpServers": { + "exasol_db": { + "command": "uvx", + "args": ["exasol-mcp-server@latest"], + "env": { + "EXA_DSN": "my-dsn, e.g. demodb.exasol.com:8563", + "EXA_USER": "my-user-name", + "EXA_PASSWORD": "my-password", + "EXA_MCP_SETTINGS": "{\"schemas\": {\"like_pattern\": \"MY_SCHEMA\"}" + } + }, + "other_server": {} + } +} +``` + +The `env` section of this file lists the environment variables that will be created in +the environment where the MCP Server is going to run. + +The environment variables can be divided into three groups. + +- [OpenID settings](open_id_setup.md). +- [Database connection settings](db_connection_setup.md) +- [Tool settings](tool_setup.md) + +The settings are described in details in resective sections of the User Guide. + +## Tool settings + +The tool settings are stored in a single variable - `EXA_MCP_SETTINGS`. The settings +are written in the json format. The json string can be set directly in the environment +variable, as shown in the above example. Note that double quotes in the json string must +be escaped, otherwise the environment variable value will be interpreted, not as a text, +but as a part of the outer json. + +Alternatively, the settings can be written in a json file. In this case, the `EXA_MCP_SETTINGS` +should contain the path to this file, e.g. + +```json +{ + "env": { + "other": "variables", + "EXA_MCP_SETTINGS": "path_to_settings.json" + } +} +``` + +Please see the [Tool Setup](tool_setup.md) for details on how the MCP Server tools +can be customised. From 00446b8d180be6795fadaead9df70a5144dc3f93 Mon Sep 17 00:00:00 2001 From: mibe Date: Fri, 24 Oct 2025 15:00:51 +0100 Subject: [PATCH 04/18] #70: checkpoint 2 --- doc/user_guide/open_id_setup.md | 51 +++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/doc/user_guide/open_id_setup.md b/doc/user_guide/open_id_setup.md index f8d7bf0..836bc79 100644 --- a/doc/user_guide/open_id_setup.md +++ b/doc/user_guide/open_id_setup.md @@ -16,7 +16,7 @@ on the configuring MCP Server authorization. It is assumed that the reader has some familiarity with the basic concepts of OAuth2. Without going into details on how the OAuth2 works, lets us recap what roles exist in -the specification and how they map onto MCP interaction. +this specification and how they map onto MCP interaction. The OAuth2 defines four actors: - The resource with a certain API. @@ -31,6 +31,51 @@ The implementation of Exasol MCP Server is based on FastMCP package, hence its authentication layer is based on [FastMCP Authentication](https://gofastmcp.com/servers/auth/authentication). Exasol MCP Server supports all authentication mechanisms provided by FastMCP at the moment of the release. However, at the time of writing, FastMCP authentication is in -active development. Exasol MCP Server will inevitably fall behind at certain point. +active development. Exasol MCP Server will likely be behind at certain points. -The FastMCP provides t +The FastMCP provides three generic ways to configure the OAuth2 authentication. +- [Remote OAuth](https://gofastmcp.com/servers/auth/remote-oauth), to work with identity +servers supporting Dynamic Client Registration (DCR). +- [OAuth Proxy](https://gofastmcp.com/servers/auth/oauth-proxy), to work with identity +servers that do not support DCR. +- [Token Verification](https://gofastmcp.com/servers/auth/token-verification), for the +case when the MCP server only verifies a bearer token, not being concerned how this +token is acquired. + +The FastMCP also provides an integration with a number of popular OAuth2 providers. +At the time of writing, the following providers are supported: +- [Auth0](https://gofastmcp.com/integrations/auth0) +- [AuthKit](https://gofastmcp.com/integrations/authkit) +- [AWS Cognito](https://gofastmcp.com/integrations/aws-cognito) +- [Azure](https://gofastmcp.com/integrations/azure) +- [Descope](https://gofastmcp.com/integrations/descope) +- [GitHub](https://gofastmcp.com/integrations/github) +- [Scalekit](https://gofastmcp.com/integrations/scalekit) +- [Google](https://gofastmcp.com/integrations/google) +- [WorkOS](https://gofastmcp.com/integrations/workos) + +To configure the MCP authentication with any of these providers please set the +environment variables, as described in documentation for a particular provider. +As an example, the following environment variables shall be set when working +with AuthKit: +```bash +export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.AuthKitProvider +export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN=https://your-project.authkit.app +export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL=https://your-server.com +``` +Note that the `FASTMCP_SERVER_AUTH` should always be set to the path of the +provider's class. + +If the chosen identity provider is not in the list, it should still be possible to +configure the MCP server authentication using one of the generic providers. Please use +[Remote OAuth](https://gofastmcp.com/servers/auth/remote-oauth) if the provider supports +DCR, and [OAuth Proxy](https://gofastmcp.com/servers/auth/oauth-proxy) if it doesn't. +Currently, FastMCP does not define environment variables for generic providers. Exasol +MCP Server fills the gap by providing its own set of variables, in a similar fashion. + +| Name | Required | Default | Meaning | +|---------------|:--------:|---------|---------| +| authorization_servers | yes | | +| base_url | yes | | +| resource_name | no | $1 | +| resource_documentation | no | $1 | From e5c98bb534f5b3a734271764efccc1ad4ad0c3ce Mon Sep 17 00:00:00 2001 From: mibe Date: Tue, 28 Oct 2025 18:31:47 +0000 Subject: [PATCH 05/18] #70: finished the guide --- README.md | 31 ++++++- doc/changes/unreleased.md | 1 + doc/user_guide/db_connection_setup.md | 98 +++++++++++++++++++ doc/user_guide/open_id_setup.md | 129 ++++++++++++++++++++------ doc/user_guide/server_setup.md | 6 +- 5 files changed, 230 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index d189bec..8c19c49 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ example. "command": "uvx", "args": ["exasol-mcp-server@latest"], "env": { - "EXA_DSN": "my-dsn, e.g. demodb.exasol.com:8563", + "EXA_DSN": "my-dsn", "EXA_USER": "my-user-name", "EXA_PASSWORD": "my-password" } @@ -100,12 +100,35 @@ If the server is installed, the Claude configuration file should look like this: Please note that any changes to the Claude configuration file will only take effect after restarting Claude Desktop. -## Configuration settings: +## Running modes + +The MCP server can be deployed either locally, as described above, or as a remote HTTP +server. To run the server as a Direct HTTP Server execute the command: + +```bash +exasol-mcp-server-http --host --port +``` +The `host` defaults to 0.0.0.0. + +This command provides a simple way to verify the setup for a remote MCP Server deployment. +For the production environment, one might consider using an ASGI server like Unicorn. The +most flexible approach is implementing a wrapper for the Exasol MCP server that will +provide the desired control options. For further information and ideas, please check the +[HTTP Deployment](https://gofastmcp.com/deployment/http) in the FastMCP documentation. + +Here is an example code creating the Exasol MCP server from a wrapper. +```python +from exasol.ai.mcp.server import mcp_server + +exasol_mcp = mcp_server() +``` + +## Configuration settings The sever is configured using environment variables and optionally a json file. In the -above example the server is provided with the database connection parameters, all other +above example, the server is provided with the database connection parameters, all other settings left to default. For the information on how to customise server settings -please see the User Guide. +please see the [Server Setup User Guide](doc/user_guide/server_setup.md). ## License diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 236ca61..071957d 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -9,3 +9,4 @@ * #76: Implemented extraction of the username from the MCP context, where possible. Queries are executed under the extracted username. * #78: Added an OIDC integration test for the basic connection option. * #79: Extracted the server builder into a separate function and added HTTP server CLI. +* #70: Added User Guide. diff --git a/doc/user_guide/db_connection_setup.md b/doc/user_guide/db_connection_setup.md index e69de29..4deeb84 100644 --- a/doc/user_guide/db_connection_setup.md +++ b/doc/user_guide/db_connection_setup.md @@ -0,0 +1,98 @@ +# Database Connection Setup + +The MCP server supports two user authentication methods available in Exasol database - +password and an OpenID token. + +The MCP server can be deployed in two ways: locally or as a remote HTTP server. +In the latter case, the server works in the multiple-user mode, and its tools must be +protected with OAuth2 authorization. Please refer to [OpenID Setup](open_id_setup.md) +guide for details on the OAuth configuration. The choice of the database connection +parameters will likely depend on the MCP server deployment mode. This section of the +User Guide explains possible deployment options in the context of the database +connection and lists the expected environment variables in each of these cases. + +## Local MCP Server + +In this mode, the server should be configured to use particular database credentials +(username and either password or an OpenID token). Presumably, these will be +Exasol credentials given to the user of an AI Application hosting the MCP server. +The application is running on the user's machine. + +| Variable Name | Required | +|----------------------------------------|:--------:| +| EXA_DSN (e.g. demodb.exasol.com:8563") | yes | +| EXA_USER | yes | +| EXA_PASSWORD | no | +| EXA_ACCESS_TOKEN | no | +| EXA_REFRESH_TOKEN | no | + +One of the EXA_PASSWORD, EXA_ACCESS_TOKEN, EXA_REFRESH_TOKEN must be provided, +depending on how the EXA_USER is identified. + +## HTTP MCP Server + +This is the multiuser setup. The MCP Server may or may not be able to identify the +user, depending on how server authorization is configured. The username can be stored +in one of the claims in the access token. Most identity providers allow setting a custom +claim or offer a choice of standard claims that can be used for that. The server needs +to know the name of this claim. + +Being able to identify the user gives two possibilities for making the database +connection user special. + +### Passthrough Access Token + +Under certain conditions, the access token can be extracted from the MCP Auth context +and used to open the database connection on behalf of the user calling an MCP tool. +The following two additional conditions must be met: + +- All MCP server users are identified by an access token in the Exasol database, +- The database verifies the token with the same identity provider as the MCP server. + In which case the subject, the user is identified with in the database, should + match the subject field in the access token issued to this user. + +Note that the user has to be identified in the database by an access token, not a +refresh token. Currently, the refresh token identification is not supported. This +doesn't mean the identity provider cannot use a refresh token. If it does, the MCP client +will request access token regeneration when its validity period expires. + +| Variable Name | Required | +|----------------------------------------|:--------:| +| EXA_DSN (e.g. demodb.exasol.com:8563") | yes | +| EXA_USERNAME_CLAIM | yes | +| EXA_POOL_SIZE | no | + +EXA_USERNAME_CLAIM is where the name of the username claim should be provided. +EXA_POOL_SIZE is the maximum size of the connection pool, defaults to 5. + +### User Impersonation + +If the users are not identified by access tokens in the database, but their names can be +made visible through a claim, it is possible to make the connection using a separate MCP +Server credentials, with subsequent impersonation of the user. The database queries +executed by the server tools when serving this user's requests will be subject to this +user permissions. For this to work, the server's own credentials must have the +"IMPERSONATE ANY USER" or "IMPERSONATION ON " privileges. + +| Variable Name | Required | +|----------------------------------------|:--------:| +| EXA_DSN (e.g. demodb.exasol.com:8563") | yes | +| EXA_USER | yes | +| EXA_PASSWORD | no | +| EXA_ACCESS_TOKEN | no | +| EXA_REFRESH_TOKEN | no | +| EXA_USERNAME_CLAIM | yes | +| EXA_POOL_SIZE | no | + +Here, EXA_USER is the server's own username. One of the EXA_PASSWORD, EXA_ACCESS_TOKEN, +EXA_REFRESH_TOKEN must be provided, depending on how the server's username is identified. + +### Delegated database access + +This is a backup option for the case when the user cannot be identified or the server's +username cannot be granted the impersonation privilege. In principle, it is identical to +the local deployment case. The server tools should still be protected with OAuth +authorization, but as far as the database connection is concerned, this is irrelevant. +To keep the database access integrity, the server's username must have the permission +that is the least common denominator of the permissions of the users allowed to access +the MCP server. diff --git a/doc/user_guide/open_id_setup.md b/doc/user_guide/open_id_setup.md index 836bc79..de468f3 100644 --- a/doc/user_guide/open_id_setup.md +++ b/doc/user_guide/open_id_setup.md @@ -4,7 +4,7 @@ A production installation of the MCP server may require proper security configur This is certainly the case if the server is deployed as an HTTP server. Then its tools must be protected with an authorisation mechanism. This section of the User Guide provides details on how Exasol MCP server can be configured in the remote deployment -scenario, i.e. as an HTTP server. +scenario, i.e., as an HTTP server. The MCP supports and recommends OAuth2 as the authorization framework for protecting the tools and resources from unauthorized access. OAuth2 and OpenID Connect are modern @@ -12,37 +12,32 @@ and widely used specifications for resource protection. Exasol MCP server suppor OAuth2-based authorization to control access to its own tools, as well as the Exasol database. The authentication options for the database connection are described in the [Database Connection Setup](db_connection_setup.md) guide. This section focuses -on the configuring MCP Server authorization. +on the configuration of the MCP Server authorization. It is assumed that the reader has some familiarity with the basic concepts of OAuth2. -Without going into details on how the OAuth2 works, lets us recap what roles exist in +Without going into details on how OAuth2 works, let us recap what roles exist in this specification and how they map onto MCP interaction. The OAuth2 defines four actors: - The resource with a certain API. - The client, usually an application that wants to access the resource. -- The resource owner, usually a human, that can authorize the client to access the resource. +- The resource owner, usually a human, who can authorize the client to access the resource. - The identity server, a service used to facilitate the authorization. -In the MCP case, the resource API are the server tools, and the client is the MCP client +In the MCP case, the resource API is the server tools, and the client is the MCP client application, e.g. Claude Desktop. +## OAuth in FastMCP + The implementation of Exasol MCP Server is based on FastMCP package, hence its authentication layer is based on [FastMCP Authentication](https://gofastmcp.com/servers/auth/authentication). Exasol MCP Server supports all authentication mechanisms provided by FastMCP at the -moment of the release. However, at the time of writing, FastMCP authentication is in -active development. Exasol MCP Server will likely be behind at certain points. +moment of the release. However, the FastMCP authentication is currently in active +development. Exasol MCP Server will likely fall behind at certain points. -The FastMCP provides three generic ways to configure the OAuth2 authentication. -- [Remote OAuth](https://gofastmcp.com/servers/auth/remote-oauth), to work with identity -servers supporting Dynamic Client Registration (DCR). -- [OAuth Proxy](https://gofastmcp.com/servers/auth/oauth-proxy), to work with identity -servers that do not support DCR. -- [Token Verification](https://gofastmcp.com/servers/auth/token-verification), for the -case when the MCP server only verifies a bearer token, not being concerned how this -token is acquired. +### FastMCP Integration with Identity Providers -The FastMCP also provides an integration with a number of popular OAuth2 providers. +The FastMCP provides an integration with several popular OAuth providers. At the time of writing, the following providers are supported: - [Auth0](https://gofastmcp.com/integrations/auth0) - [AuthKit](https://gofastmcp.com/integrations/authkit) @@ -54,8 +49,8 @@ At the time of writing, the following providers are supported: - [Google](https://gofastmcp.com/integrations/google) - [WorkOS](https://gofastmcp.com/integrations/workos) -To configure the MCP authentication with any of these providers please set the -environment variables, as described in documentation for a particular provider. +To configure the MCP authentication with any of these providers, please set the +environment variables, as described in the documentation for a particular provider. As an example, the following environment variables shall be set when working with AuthKit: ```bash @@ -66,16 +61,94 @@ export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL=https://your-server.com Note that the `FASTMCP_SERVER_AUTH` should always be set to the path of the provider's class. -If the chosen identity provider is not in the list, it should still be possible to -configure the MCP server authentication using one of the generic providers. Please use +### FastMCP Generic OAuth Providers + +The FastMCP also provides three generic ways to configure the OAuth2 authentication. +- [Remote OAuth](https://gofastmcp.com/servers/auth/remote-oauth), to work with identity +servers supporting Dynamic Client Registration (DCR). +- [OAuth Proxy](https://gofastmcp.com/servers/auth/oauth-proxy), to work with identity +servers that do not support DCR. +- [Token Verification](https://gofastmcp.com/servers/auth/token-verification), for the +case when the MCP server only verifies a bearer token, not being concerned about how this +token is acquired. + +If the chosen identity provider is not in the above list, it should still be possible to +configure the MCP server authentication using one of those generic providers. Please use [Remote OAuth](https://gofastmcp.com/servers/auth/remote-oauth) if the provider supports DCR, and [OAuth Proxy](https://gofastmcp.com/servers/auth/oauth-proxy) if it doesn't. + Currently, FastMCP does not define environment variables for generic providers. Exasol -MCP Server fills the gap by providing its own set of variables, in a similar fashion. - -| Name | Required | Default | Meaning | -|---------------|:--------:|---------|---------| -| authorization_servers | yes | | -| base_url | yes | | -| resource_name | no | $1 | -| resource_documentation | no | $1 | +MCP Server fills the gap by providing its own set of variables in a similar fashion. +As with specific providers, the variable `FASTMCP_SERVER_AUTH` should be set to the path +of the chosen generic auth provider class. The required value for this variable can be +found in one of the tables below. + +Normally, two sets of variables must be configured - one for a Token Verifier and another +one for the provider - Remote OAuth or OAuth Proxy. + +The tables below list all possible variables in each set. Note that the column `Required` +means whether the value is mandatory or not in the corresponding FastMCP module. An +optional (for FastMCP) variable may be required by a particular identity provider. One +need to check the provider's specification to find out what information should be supplied. + +The rest of this section provides information about the environment variables defined by +Exasol MCP Server. + +### Token Verification + +Both `Remote OAuth` and `OAuthProxy` require a [Token Verifier](https://gofastmcp.com/servers/auth/token-verification). +The choice of the token verifier depends on the token verification model supported by a +particular identity provider. + +At the time of writing, the latest release of the FastMCP - 2.12.5 - only offers the +JWT Token Verification. The Opaque Token Verification should be added soon. + +A token verifier provider may be the only module needed if the MCP Authentication uses +an externally supplied bearer token. However, this guide doesn't give any details on how +such a system could be configured. Also, in this scenario, we recommend using FastMCP's +[environment variables](https://gofastmcp.com/servers/auth/token-verification), not the Exasol ones. + +#### JWT Token Verification. + +| Variable Name | Required | Value | +|--------------------------|:--------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| EXA_AUTH_PUBLIC_KEY | no | For asymmetric algorithms (RS256, ES256, etc.): PEM-encoded public key.
For symmetric algorithms (HS256, HS384, HS512): The shared secret string. | +| EXA_AUTH_JWKS_URI | no | URI to fetch JSON Web Key Set (only for asymmetric algorithms). | +| EXA_AUTH_ISSUER | no | Expected issuer claim. | +| EXA_AUTH_AUDIENCE | no | Expected audience claim(s). | +| EXA_AUTH_ALGORITHM | no | JWT signing algorithm. Supported algorithms:
- Asymmetric: RS256/384/512, ES256/384/512, PS256/384/512 (default: RS256).
- Symmetric: HS256, HS384, HS512. | +| EXA_AUTH_REQUIRED_SCOPES | no | Required scopes for all tokens. | +| EXA_AUTH_BASE_URL | no | Base URL for TokenVerifier protocol. | + +Either of `EXA_AUTH_PUBLIC_KEY` or `EXA_AUTH_JWKS_URI` must be set. + +### Remote OAuth + +| Variable Name | Required | Value | +|---------------------------------|:--------:|---------------------------------------------------| +| FASTMCP_SERVER_AUTH | yes | fastmcp.server.auth.auth.RemoteAuthProvider. | +| EXA_AUTH_AUTHORIZATION_SERVERS | yes | List of identity servers that issue valid tokens. | +| EXA_AUTH_BASE_URL | yes | Base URL of the MCP server. | +| EXA_AUTH_RESOURCE_NAME | no | Name for the protected resource. | +| EXA_AUTH_RESOURCE_DOCUMENTATION | no | Documentation URL for the protected resource. | + +### OAuth Proxy + +| Variable Name | Required | Value | +|------------------------------------------|:--------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| FASTMCP_SERVER_AUTH | yes | fastmcp.server.auth.oauth_proxy.OAuthProxy | +| EXA_AUTH_UPSTREAM_AUTHORIZATION_ENDPOINT | yes | URL of upstream authorization endpoint. | +| EXA_AUTH_UPSTREAM_TOKEN_ENDPOINT | yes | URL of upstream token endpoint. | +| EXA_AUTH_UPSTREAM_CLIENT_ID | yes | Client ID registered with upstream server. | +| EXA_AUTH_UPSTREAM_CLIENT_SECRET | yes | Client secret for upstream server. | +| EXA_AUTH_UPSTREAM_REVOCATION_ENDPOINT | no | Optional upstream revocation endpoint. | +| EXA_AUTH_BASE_URL | yes | Public URL of the MCP server.
Redirect path is relative to this URL. | +| EXA_AUTH_REDIRECT_PATH | no | Redirect path configured in upstream OAuth app.
Defaults to "/auth/callback". | +| EXA_AUTH_ISSUER_URL | no | Issuer URL for OAuth metadata. Defaults to EXA_AUTH_BASE_URL. | +| EXA_AUTH_SERVICE_DOCUMENTATION_URL | no | Optional service documentation URL. | +| EXA_AUTH_ALLOWED_CLIENT_REDIRECT_URIS | no | List of allowed redirect URI patterns for MCP clients.
Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
If not set, only localhost redirect URIs are allowed.
These are for MCP clients performing loopback redirects,
NOT for the upstream OAuth app. | +| EXA_AUTH_VALID_SCOPES | no | List of all the possible valid scopes for a client. These are
advertised to clients through the `/.well-known` endpoints.
If not set, defaults to EXA_AUTH_REQUIRED_SCOPES. | +| EXA_AUTH_FORWARD_PKCE | no | Whether to forward PKCE to upstream server. Defaults to True.
Enable for providers that support/require PKCE (Google, Azure, AWS, etc.).
Disable only if upstream provider doesn't support PKCE. | +| EXA_AUTH_TOKEN_ENDPOINT_AUTH_METHOD | no | Token endpoint authentication method for upstream server.
Common values: "client_secret_basic","client_secret_post", "none".
If not set, the provider will user default (typically "client_secret_basic"). | +| EXA_AUTH_EXTRA_AUTHORIZE_PARAMS | no | Additional parameters to forward to the upstream authorization
endpoint. Useful for provider-specific parameters like Auth0's "audience".
Example: {"audience": "https://api.example.com"} | +| EXA_AUTH_EXTRA_TOKEN_PARAMS | no | Additional parameters to forward to the upstream token endpoint.
Useful for provider-specific parameters during token exchange. | diff --git a/doc/user_guide/server_setup.md b/doc/user_guide/server_setup.md index 6774912..ed61886 100644 --- a/doc/user_guide/server_setup.md +++ b/doc/user_guide/server_setup.md @@ -2,7 +2,7 @@ Essentially, the sever is configured using environment variables. -In case the MCP Sever is running locally, under control of an MCP client application, +In case of the MCP Sever running locally, under the control of an MCP client application, the latter usually provides a convenient way of creating an environment from some kind of configuration file. Below is an example of Claude Desktop configuration file that references the Exasol MCP Server. @@ -34,14 +34,14 @@ The environment variables can be divided into three groups. - [Database connection settings](db_connection_setup.md) - [Tool settings](tool_setup.md) -The settings are described in details in resective sections of the User Guide. +All settings are described in details in respective sections of the User Guide. ## Tool settings The tool settings are stored in a single variable - `EXA_MCP_SETTINGS`. The settings are written in the json format. The json string can be set directly in the environment variable, as shown in the above example. Note that double quotes in the json string must -be escaped, otherwise the environment variable value will be interpreted, not as a text, +be escaped, otherwise the environment variable value will be interpreted, not as text, but as a part of the outer json. Alternatively, the settings can be written in a json file. In this case, the `EXA_MCP_SETTINGS` From 6b244429e74f4876f47e49cf18eaddc0f482017e Mon Sep 17 00:00:00 2001 From: mibe Date: Wed, 29 Oct 2025 08:20:17 +0000 Subject: [PATCH 06/18] #70: removed doc build --- .github/workflows/checks.yml | 4 ---- doc/api.rst | 5 ----- doc/developer_guide.rst | 5 ----- doc/faq.rst | 6 ------ doc/index.rst | 35 ----------------------------------- doc/user_guide.rst | 5 ----- 6 files changed, 60 deletions(-) delete mode 100644 doc/api.rst delete mode 100644 doc/developer_guide.rst delete mode 100644 doc/faq.rst delete mode 100644 doc/index.rst delete mode 100644 doc/user_guide.rst diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index d4ae5cd..5f3a09a 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -34,10 +34,6 @@ jobs: - name: Setup Python & Poetry Environment uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 - - name: Build Documentation - run: | - poetry run -- nox -s docs:build - - name: Link Check run: | poetry run -- nox -s links:check diff --git a/doc/api.rst b/doc/api.rst deleted file mode 100644 index 246b507..0000000 --- a/doc/api.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. _api: - -:octicon:`cpu` API Reference -============================= - diff --git a/doc/developer_guide.rst b/doc/developer_guide.rst deleted file mode 100644 index 80ef261..0000000 --- a/doc/developer_guide.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. _developer_guide: - -:octicon:`tools` Developer Guide -================================ - diff --git a/doc/faq.rst b/doc/faq.rst deleted file mode 100644 index c912364..0000000 --- a/doc/faq.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _faq: - -:octicon:`question` FAQ -======================= - - diff --git a/doc/index.rst b/doc/index.rst deleted file mode 100644 index a2dcce7..0000000 --- a/doc/index.rst +++ /dev/null @@ -1,35 +0,0 @@ -Documentation of Exasol MCP Server ------------------------------------ - -.. grid:: 1 1 3 2 - :gutter: 2 - :padding: 0 - :class-container: surface - - .. grid-item-card:: :octicon:`person` User Guide - :link: user_guide - :link-type: ref - - Resource for users to understand how to utilize this project and its features. - - .. grid-item-card:: :octicon:`tools` Developer Guide - :link: developer_guide - :link-type: ref - - Instructions and best practices to help developers contribute to the project and set up their development environment. - - .. grid-item-card:: :octicon:`question` FAQ - :link: faq - :link-type: ref - - Frequently asked questsions. - -.. toctree:: - :maxdepth: 1 - :hidden: - - user_guide - developer_guide - api - faq - changes/changelog diff --git a/doc/user_guide.rst b/doc/user_guide.rst deleted file mode 100644 index 801de87..0000000 --- a/doc/user_guide.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. _user_guide: - -:octicon:`person` User Guide -============================ - From a89d74dcfc03465c7b3c28b3b15eb4026d94835a Mon Sep 17 00:00:00 2001 From: mibe Date: Wed, 29 Oct 2025 08:29:30 +0000 Subject: [PATCH 07/18] #70: removed doc build --- .github/workflows/checks.yml | 17 ------- doc/conf.py | 91 ------------------------------------ 2 files changed, 108 deletions(-) delete mode 100644 doc/conf.py diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 5f3a09a..f86c673 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -21,23 +21,6 @@ jobs: - name: Check Version(s) run: poetry run -- nox -s version:check - Documentation: - name: Docs - needs: [ Version-Check ] - runs-on: ubuntu-24.04 - permissions: - contents: read - steps: - - name: SCM Checkout - uses: actions/checkout@v4 - - - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 - - - name: Link Check - run: | - poetry run -- nox -s links:check - build-matrix: name: Generate Build Matrix uses: ./.github/workflows/matrix-python.yml diff --git a/doc/conf.py b/doc/conf.py deleted file mode 100644 index e7c8a3c..0000000 --- a/doc/conf.py +++ /dev/null @@ -1,91 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - -sys.path.insert(0, os.path.abspath("../exasol-mcp-server/")) - -# -- Project information ----------------------------------------------------- - -project = "Exasol MCP Server" -copyright = "2025, Exasol" # pylint: disable=redefined-builtin -author = "Exasol" - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.todo", - "sphinx.ext.autodoc", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.intersphinx", - "sphinx.ext.autosummary", - "sphinx_copybutton", - "myst_parser", - "sphinx_design", - "exasol.toolbox.sphinx.multiversion", -] - -intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} - -# Make sure the target is unique -source_suffix = { - ".rst": "restructuredtext", - ".txt": "markdown", - ".md": "markdown", -} - -todo_include_todos = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".build-docu"] - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "shibuya" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] -html_title = "Exasol MCP Server" -html_theme_options = { - "light_logo": "_static/light-exasol-logo.svg", - "dark_logo": "_static/dark-exasol-logo.svg", - "github_url": "https://github.com/exasol/exasol-mcp-server", - "accent_color": "grass", -} - -# -- Configure link checking behavior ---------------------------------------- -linkcheck_rate_limit_timeout = 60 -linkcheck_timeout = 15 -linkcheck_delay = 30 -linkcheck_retries = 2 -linkcheck_anchors = False -linkcheck_ignore: list[str] = [] -linkcheck_allowed_redirects = { - # All HTTP redirections from the source URI to - # the canonical URI will be treated as "working". - r"https://github\.com/.*": r"https://github\.com/login*" -} From 204202d37afc01f2665a7795ec9e3be579b104c0 Mon Sep 17 00:00:00 2001 From: mibe Date: Wed, 29 Oct 2025 08:33:25 +0000 Subject: [PATCH 08/18] kick off CI From cf1193d00899614ce5f0a6edc619bf5475a5c64e Mon Sep 17 00:00:00 2001 From: mibe Date: Wed, 29 Oct 2025 08:37:06 +0000 Subject: [PATCH 09/18] #70: removed doc build --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f86c673..2ce8c02 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -141,7 +141,7 @@ jobs: Tests: name: Unit-Tests (Python-${{ matrix.python-version }}) - needs: [ Documentation, Lint, Type-Check, Security, Format, build-matrix ] + needs: [ Lint, Type-Check, Security, Format, build-matrix ] runs-on: ubuntu-24.04 permissions: contents: read From 60c49ae56353d92f295074951dde1d4cbf6c1e58 Mon Sep 17 00:00:00 2001 From: mibe Date: Wed, 29 Oct 2025 09:54:11 +0000 Subject: [PATCH 10/18] #70: small change in db conn user guide --- doc/user_guide/db_connection_setup.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/user_guide/db_connection_setup.md b/doc/user_guide/db_connection_setup.md index 4deeb84..acca351 100644 --- a/doc/user_guide/db_connection_setup.md +++ b/doc/user_guide/db_connection_setup.md @@ -31,11 +31,11 @@ depending on how the EXA_USER is identified. ## HTTP MCP Server -This is the multiuser setup. The MCP Server may or may not be able to identify the -user, depending on how server authorization is configured. The username can be stored -in one of the claims in the access token. Most identity providers allow setting a custom -claim or offer a choice of standard claims that can be used for that. The server needs -to know the name of this claim. +This is the multiuser setup. The MCP Server may be able to identify the user, if the +server authorization is configured in a certain way. The username can be stored in one +of the claims in the access token. Most identity providers allow setting a custom claim +or offer a choice of standard claims that can be used for that. The server needs to know +the name of this claim. Being able to identify the user gives two possibilities for making the database connection user special. @@ -44,11 +44,11 @@ connection user special. Under certain conditions, the access token can be extracted from the MCP Auth context and used to open the database connection on behalf of the user calling an MCP tool. -The following two additional conditions must be met: +This can be enabled if the following two additional requirements are met: - All MCP server users are identified by an access token in the Exasol database, - The database verifies the token with the same identity provider as the MCP server. - In which case the subject, the user is identified with in the database, should + In that case the subject, the user is identified with in the database, should match the subject field in the access token issued to this user. Note that the user has to be identified in the database by an access token, not a From e9df8206c3d18b15e066de7e27793ffc43630d34 Mon Sep 17 00:00:00 2001 From: mibe Date: Wed, 29 Oct 2025 13:14:32 +0000 Subject: [PATCH 11/18] #70: addressed review comments --- .github/workflows/checks.yml | 23 ++++++++++++++ README.md | 4 +-- doc/user_guide/db_connection_setup.md | 16 +++++----- doc/user_guide/open_id_setup.md | 44 +++++++++++++-------------- doc/user_guide/server_setup.md | 3 +- 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 2ce8c02..4ed59d8 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -21,6 +21,29 @@ jobs: - name: Check Version(s) run: poetry run -- nox -s version:check + # Will be re-enabled once the User Guide is moved to sphinx + # + # Documentation: + # name: Docs + # needs: [ Version-Check ] + # runs-on: ubuntu-24.04 + # permissions: + # contents: read + # steps: + # - name: SCM Checkout + # uses: actions/checkout@v4 + # + # - name: Setup Python & Poetry Environment + # uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + # + # - name: Build Documentation + # run: | + # poetry run -- nox -s docs:build + # + # - name: Link Check + # run: | + # poetry run -- nox -s links:check + build-matrix: name: Generate Build Matrix uses: ./.github/workflows/matrix-python.yml diff --git a/README.md b/README.md index 8c19c49..5ebdb74 100644 --- a/README.md +++ b/README.md @@ -125,9 +125,9 @@ exasol_mcp = mcp_server() ## Configuration settings -The sever is configured using environment variables and optionally a json file. In the +The server is configured using environment variables and optionally a json file. In the above example, the server is provided with the database connection parameters, all other -settings left to default. For the information on how to customise server settings +settings left to default. For the information on how to customize server settings please see the [Server Setup User Guide](doc/user_guide/server_setup.md). ## License diff --git a/doc/user_guide/db_connection_setup.md b/doc/user_guide/db_connection_setup.md index acca351..7d707f2 100644 --- a/doc/user_guide/db_connection_setup.md +++ b/doc/user_guide/db_connection_setup.md @@ -7,9 +7,9 @@ The MCP server can be deployed in two ways: locally or as a remote HTTP server. In the latter case, the server works in the multiple-user mode, and its tools must be protected with OAuth2 authorization. Please refer to [OpenID Setup](open_id_setup.md) guide for details on the OAuth configuration. The choice of the database connection -parameters will likely depend on the MCP server deployment mode. This section of the -User Guide explains possible deployment options in the context of the database -connection and lists the expected environment variables in each of these cases. +parameters depends on the MCP server deployment mode. This section of the User Guide +explains possible deployment options in the context of the database connection and +lists the expected environment variables in each of these cases. ## Local MCP Server @@ -42,9 +42,9 @@ connection user special. ### Passthrough Access Token -Under certain conditions, the access token can be extracted from the MCP Auth context -and used to open the database connection on behalf of the user calling an MCP tool. -This can be enabled if the following two additional requirements are met: +Under certain conditions, the access token can be extracted from the MCP Authentication +context and used to open the database connection on behalf of the user calling an MCP +tool. This can be enabled if the following two additional requirements are met: - All MCP server users are identified by an access token in the Exasol database, - The database verifies the token with the same identity provider as the MCP server. @@ -70,8 +70,8 @@ EXA_POOL_SIZE is the maximum size of the connection pool, defaults to 5. If the users are not identified by access tokens in the database, but their names can be made visible through a claim, it is possible to make the connection using a separate MCP Server credentials, with subsequent impersonation of the user. The database queries -executed by the server tools when serving this user's requests will be subject to this -user permissions. For this to work, the server's own credentials must have the +executed by the server tools will be subject to permissions of the user who initiated +the request. For this to work, the server's own credentials must have the "IMPERSONATE ANY USER" or "IMPERSONATION ON " privileges. | Variable Name | Required | diff --git a/doc/user_guide/open_id_setup.md b/doc/user_guide/open_id_setup.md index de468f3..35f0b02 100644 --- a/doc/user_guide/open_id_setup.md +++ b/doc/user_guide/open_id_setup.md @@ -33,7 +33,7 @@ The implementation of Exasol MCP Server is based on FastMCP package, hence its authentication layer is based on [FastMCP Authentication](https://gofastmcp.com/servers/auth/authentication). Exasol MCP Server supports all authentication mechanisms provided by FastMCP at the moment of the release. However, the FastMCP authentication is currently in active -development. Exasol MCP Server will likely fall behind at certain points. +development. ### FastMCP Integration with Identity Providers @@ -50,15 +50,15 @@ At the time of writing, the following providers are supported: - [WorkOS](https://gofastmcp.com/integrations/workos) To configure the MCP authentication with any of these providers, please set the -environment variables, as described in the documentation for a particular provider. -As an example, the following environment variables shall be set when working -with AuthKit: +environment variables, as described in the FastMCP documentation for a particular +provider. As an example, the following environment variables shall be set when +working with AuthKit: ```bash export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.AuthKitProvider export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN=https://your-project.authkit.app export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL=https://your-server.com ``` -Note that the `FASTMCP_SERVER_AUTH` should always be set to the path of the +Note that the `FASTMCP_SERVER_AUTH` should always be set to the module path of the provider's class. ### FastMCP Generic OAuth Providers @@ -134,21 +134,21 @@ Either of `EXA_AUTH_PUBLIC_KEY` or `EXA_AUTH_JWKS_URI` must be set. ### OAuth Proxy -| Variable Name | Required | Value | -|------------------------------------------|:--------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| FASTMCP_SERVER_AUTH | yes | fastmcp.server.auth.oauth_proxy.OAuthProxy | -| EXA_AUTH_UPSTREAM_AUTHORIZATION_ENDPOINT | yes | URL of upstream authorization endpoint. | -| EXA_AUTH_UPSTREAM_TOKEN_ENDPOINT | yes | URL of upstream token endpoint. | -| EXA_AUTH_UPSTREAM_CLIENT_ID | yes | Client ID registered with upstream server. | -| EXA_AUTH_UPSTREAM_CLIENT_SECRET | yes | Client secret for upstream server. | -| EXA_AUTH_UPSTREAM_REVOCATION_ENDPOINT | no | Optional upstream revocation endpoint. | -| EXA_AUTH_BASE_URL | yes | Public URL of the MCP server.
Redirect path is relative to this URL. | -| EXA_AUTH_REDIRECT_PATH | no | Redirect path configured in upstream OAuth app.
Defaults to "/auth/callback". | -| EXA_AUTH_ISSUER_URL | no | Issuer URL for OAuth metadata. Defaults to EXA_AUTH_BASE_URL. | -| EXA_AUTH_SERVICE_DOCUMENTATION_URL | no | Optional service documentation URL. | +| Variable Name | Required | Value | +|------------------------------------------|:--------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| FASTMCP_SERVER_AUTH | yes | fastmcp.server.auth.oauth_proxy.OAuthProxy | +| EXA_AUTH_UPSTREAM_AUTHORIZATION_ENDPOINT | yes | URL of upstream authorization endpoint. | +| EXA_AUTH_UPSTREAM_TOKEN_ENDPOINT | yes | URL of upstream token endpoint. | +| EXA_AUTH_UPSTREAM_CLIENT_ID | yes | Client ID registered with upstream server. | +| EXA_AUTH_UPSTREAM_CLIENT_SECRET | yes | Client secret for upstream server. | +| EXA_AUTH_UPSTREAM_REVOCATION_ENDPOINT | no | Optional upstream revocation endpoint. | +| EXA_AUTH_BASE_URL | yes | Public URL of the MCP server.
Redirect path is relative to this URL. | +| EXA_AUTH_REDIRECT_PATH | no | Redirect path configured in upstream OAuth app.
Defaults to "/auth/callback". | +| EXA_AUTH_ISSUER_URL | no | Issuer URL for OAuth metadata. Defaults to EXA_AUTH_BASE_URL. | +| EXA_AUTH_SERVICE_DOCUMENTATION_URL | no | Optional service documentation URL. | | EXA_AUTH_ALLOWED_CLIENT_REDIRECT_URIS | no | List of allowed redirect URI patterns for MCP clients.
Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
If not set, only localhost redirect URIs are allowed.
These are for MCP clients performing loopback redirects,
NOT for the upstream OAuth app. | -| EXA_AUTH_VALID_SCOPES | no | List of all the possible valid scopes for a client. These are
advertised to clients through the `/.well-known` endpoints.
If not set, defaults to EXA_AUTH_REQUIRED_SCOPES. | -| EXA_AUTH_FORWARD_PKCE | no | Whether to forward PKCE to upstream server. Defaults to True.
Enable for providers that support/require PKCE (Google, Azure, AWS, etc.).
Disable only if upstream provider doesn't support PKCE. | -| EXA_AUTH_TOKEN_ENDPOINT_AUTH_METHOD | no | Token endpoint authentication method for upstream server.
Common values: "client_secret_basic","client_secret_post", "none".
If not set, the provider will user default (typically "client_secret_basic"). | -| EXA_AUTH_EXTRA_AUTHORIZE_PARAMS | no | Additional parameters to forward to the upstream authorization
endpoint. Useful for provider-specific parameters like Auth0's "audience".
Example: {"audience": "https://api.example.com"} | -| EXA_AUTH_EXTRA_TOKEN_PARAMS | no | Additional parameters to forward to the upstream token endpoint.
Useful for provider-specific parameters during token exchange. | +| EXA_AUTH_VALID_SCOPES | no | List of all the possible valid scopes for a client. These are
advertised to clients through the `/.well-known` endpoints.
If not set, defaults to EXA_AUTH_REQUIRED_SCOPES. | +| EXA_AUTH_FORWARD_PKCE | no | Whether to forward PKCE to upstream server. Defaults to True.
Enable for providers that support/require PKCE (Google, Azure, AWS, etc.).
Disable only if upstream provider doesn't support PKCE. | +| EXA_AUTH_TOKEN_ENDPOINT_AUTH_METHOD | no | Token endpoint authentication method for upstream server.
Common values: "client_secret_basic","client_secret_post", "none".
If not set, the provider will use default (typically "client_secret_basic"). | +| EXA_AUTH_EXTRA_AUTHORIZE_PARAMS | no | Additional parameters to forward to the upstream authorization
endpoint. Useful for provider-specific parameters like Auth0's "audience".
Example: {"audience": "https://api.example.com"} | +| EXA_AUTH_EXTRA_TOKEN_PARAMS | no | Additional parameters to forward to the upstream token endpoint.
Useful for provider-specific parameters during token exchange. | diff --git a/doc/user_guide/server_setup.md b/doc/user_guide/server_setup.md index ed61886..b44f3ce 100644 --- a/doc/user_guide/server_setup.md +++ b/doc/user_guide/server_setup.md @@ -19,8 +19,7 @@ references the Exasol MCP Server. "EXA_PASSWORD": "my-password", "EXA_MCP_SETTINGS": "{\"schemas\": {\"like_pattern\": \"MY_SCHEMA\"}" } - }, - "other_server": {} + } } } ``` From b7c5fd221e233ca12757194a5f6575babbcb44ab Mon Sep 17 00:00:00 2001 From: mibe Date: Fri, 14 Nov 2025 14:51:15 +0000 Subject: [PATCH 12/18] #94: converted md to rst --- .github/workflows/checks.yml | 42 ++- README.md => README.rst | 192 +++++++------ doc/changes/unreleased.md | 4 + doc/conf.py | 91 +++++++ doc/index.rst | 27 ++ ...ction_setup.md => db_connection_setup.rst} | 90 +++++-- doc/user_guide/open_id_setup.md | 169 ------------ doc/user_guide/open_id_setup.rst | 254 ++++++++++++++++++ doc/user_guide/server_setup.md | 59 ---- doc/user_guide/server_setup.rst | 61 +++++ doc/user_guide/tool_setup.md | 155 ----------- doc/user_guide/tool_setup.rst | 167 ++++++++++++ doc/user_guide/user_guide.rst | 12 + 13 files changed, 815 insertions(+), 508 deletions(-) rename README.md => README.rst (54%) create mode 100644 doc/conf.py create mode 100644 doc/index.rst rename doc/user_guide/{db_connection_setup.md => db_connection_setup.rst} (63%) delete mode 100644 doc/user_guide/open_id_setup.md create mode 100644 doc/user_guide/open_id_setup.rst delete mode 100644 doc/user_guide/server_setup.md create mode 100644 doc/user_guide/server_setup.rst delete mode 100644 doc/user_guide/tool_setup.md create mode 100644 doc/user_guide/tool_setup.rst create mode 100644 doc/user_guide/user_guide.rst diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 4ed59d8..2160b20 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -21,28 +21,26 @@ jobs: - name: Check Version(s) run: poetry run -- nox -s version:check - # Will be re-enabled once the User Guide is moved to sphinx - # - # Documentation: - # name: Docs - # needs: [ Version-Check ] - # runs-on: ubuntu-24.04 - # permissions: - # contents: read - # steps: - # - name: SCM Checkout - # uses: actions/checkout@v4 - # - # - name: Setup Python & Poetry Environment - # uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 - # - # - name: Build Documentation - # run: | - # poetry run -- nox -s docs:build - # - # - name: Link Check - # run: | - # poetry run -- nox -s links:check + Documentation: + name: Docs + needs: [ Version-Check ] + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: SCM Checkout + uses: actions/checkout@v4 + + - name: Setup Python & Poetry Environment + uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + + - name: Build Documentation + run: | + poetry run -- nox -s docs:build + + - name: Link Check + run: | + poetry run -- nox -s links:check build-matrix: name: Generate Build Matrix diff --git a/README.md b/README.rst similarity index 54% rename from README.md rename to README.rst index 5ebdb74..15f5bfd 100644 --- a/README.md +++ b/README.rst @@ -1,54 +1,65 @@ -# Exasol MCP Server - -

- - - License - - - Downloads - - - Supported Python Versions - - - PyPi Package - -

+Exasol MCP Server +================= Provides an LLM access to the Exasol database via MCP tools. Includes the tools for reading the database metadata and executing data reading queries. -## Features +.. image:: https://img.shields.io/pypi/l/exasol-mcp-server + :target: https://opensource.org/licenses/MIT + :alt: License + +.. image:: https://img.shields.io/pypi/dm/exasol-mcp-server + :target: https://pypi.org/project/exasol-mcp-server/ + :alt: Downloads + +.. image:: https://img.shields.io/pypi/pyversions/exasol-mcp-server + :target: https://pypi.org/project/exasol-mcp-server/ + :alt: Supported Python Versions + +.. image:: https://img.shields.io/pypi/v/exasol-mcp-server + :target: https://pypi.org/project/exasol-mcp-server/ + :alt: PyPi Package + +🚀 Features +----------- + +* Collects the metadata. -- Collects the metadata. * Enumerates the existing database objects, including schemas, tables, views, functions and UDF scripts. * Provides a filtering mechanisms to use with object enumeration. * Describes the database objects: for tables returns the list of columns and constraints; for functions and scripts - the list of input and output parameters. -- Executes provided data reading SQL query. Disallows any other type of query. -## Prerequisites +* Executes provided data reading SQL query. Disallows any other type of query. + +🔌️ Prerequisites +----------------- + +* `Python `__ >= 3.10 +* MCP Client application, e.g. `Claude Desktop `__ -- [Python](https://www.python.org/) >= 3.10. -- MCP Client application, e.g. [Claude Desktop](https://claude.ai/download). +💾 Installation +--------------- -## Installation +Ensure the *uv* package is installed. If uncertain call -Ensure the `uv` package is installed. If uncertain call -```bash -uv --version -``` -To install `uv` on macOS please use `brew`, i.e. -```bash -brew install uv -``` -For other operating systems, please follow [the instructions](https://docs.astral.sh/uv/getting-started/installation/) -in the `uv` official documentation. +.. code-block:: shell -## Using the server with the Claude Desktop. + uv --version + +To install *uv* on macOS please use *brew*, i.e. + +.. code-block:: shell + + brew install uv + +For other operating systems, please follow `the instructions `__ +in the *uv* official documentation. + +📝 Using the server with the Claude Desktop +------------------------------------------- To enable the Claude Desktop using the Exasol MCP server, the latter must be listed -in the configuration file `claude_desktop_config.json`. A similar configuration file +in the configuration file *claude_desktop_config.json*. A similar configuration file would exist for most other MCP Client applications. To find the Claude Desktop configuration file, click on the Settings and navigate to the @@ -58,83 +69,95 @@ the editor of your choice. Add the Exasol MCP server to the list of MCP servers as shown in this configuration example. -```json -{ - "mcpServers": { - "exasol_db": { - "command": "uvx", - "args": ["exasol-mcp-server@latest"], - "env": { - "EXA_DSN": "my-dsn", - "EXA_USER": "my-user-name", - "EXA_PASSWORD": "my-password" - } - }, - "other_server": {} - } -} -``` - -With these settings, `uv` will execute the latest version of the `exasol-mcp-server` + +.. code-block:: json + + { + "mcpServers": { + "exasol_db": { + "command": "uvx", + "args": ["exasol-mcp-server@latest"], + "env": { + "EXA_DSN": "my-dsn", + "EXA_USER": "my-user-name", + "EXA_PASSWORD": "my-password" + } + } + } + } + + +With these settings, *uv* will execute the latest version of the `exasol-mcp-server` in an ephemeral environment, without installing it. Alternatively, the `exasol-mcp-server` can be installed using the command: -```bash -uv tool install exasol-mcp-server@latest -``` -For further details on installing and upgrading the server using `uv` see the -[uv Tools](https://docs.astral.sh/uv/concepts/tools/) documentation. + +.. code-block:: shell + + uv tool install exasol-mcp-server@latest + +For further details on installing and upgrading the server using *uv* see the +`uv Tools `__ documentation. If the server is installed, the Claude configuration file should look like this: -```json -{ - "mcpServers": { - "exasol_db": { - "command": "exasol-mcp-server", - "env": "same as above" + +.. code-block:: json + + { + "mcpServers": { + "exasol_db": { + "command": "exasol-mcp-server", + "env": "same as above" + } + } } - } -} -``` Please note that any changes to the Claude configuration file will only take effect after restarting Claude Desktop. -## Running modes +🟠 🟢 Running modes +------------------- The MCP server can be deployed either locally, as described above, or as a remote HTTP server. To run the server as a Direct HTTP Server execute the command: -```bash -exasol-mcp-server-http --host --port -``` -The `host` defaults to 0.0.0.0. +.. code-block:: shell + + exasol-mcp-server-http --host --port + +The *host* defaults to 0.0.0.0. This command provides a simple way to verify the setup for a remote MCP Server deployment. For the production environment, one might consider using an ASGI server like Unicorn. The most flexible approach is implementing a wrapper for the Exasol MCP server that will provide the desired control options. For further information and ideas, please check the -[HTTP Deployment](https://gofastmcp.com/deployment/http) in the FastMCP documentation. +`HTTP Deployment `__ in the FastMCP documentation. Here is an example code creating the Exasol MCP server from a wrapper. -```python -from exasol.ai.mcp.server import mcp_server -exasol_mcp = mcp_server() -``` +.. code-block:: python + + from exasol.ai.mcp.server import mcp_server + + exasol_mcp = mcp_server() -## Configuration settings + +🔧 Configuration settings +------------------------- The server is configured using environment variables and optionally a json file. In the above example, the server is provided with the database connection parameters, all other settings left to default. For the information on how to customize server settings -please see the [Server Setup User Guide](doc/user_guide/server_setup.md). +please see the `Server Setup `_ +in the User Guide. -## License +📜 License +---------- This project is licensed under the MIT License - see the LICENSE file for details. -### Safe Harbor Statement: Exasol MCP Server & AI Solutions +Safe Harbor Statement: Exasol MCP Server & AI Solutions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Exasol’s AI solutions (including MCP Server) are designed to enable intelligent, autonomous, and highly performant access to data through AI and LLM-powered agents. @@ -153,3 +176,8 @@ and system owner, assume full responsibility for managing these solutions within environment. This includes establishing appropriate governance, authorization controls, sandboxing mechanisms, and operational guardrails to mitigate risks to your organization, your customers, and their data. + +📚 Documentation +---------------- + +For further details, check out the latest `documentation `_. diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index e106470..f5b9408 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -9,3 +9,7 @@ ## Refactoring * #90: Disabled OIDC tests with OAuthProxy. + +## Documentation + +* # 94: Moved the documentation for md to rst diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..d266224 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,91 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath("../")) + +# -- Project information ----------------------------------------------------- + +project = "Exasol MCP Server" +copyright = "2025, Exasol" # pylint: disable=redefined-builtin +author = "Exasol" + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.todo", + "sphinx.ext.autodoc", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.intersphinx", + "sphinx.ext.autosummary", + "sphinx_copybutton", + "myst_parser", + "sphinx_design", + "exasol.toolbox.sphinx.multiversion", +] + +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} + +# Make sure the target is unique +source_suffix = { + ".rst": "restructuredtext", + ".txt": "markdown", + ".md": "markdown", +} + +todo_include_todos = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".build-docu"] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "shibuya" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] +html_title = "Exasol MCP Server" +html_theme_options = { + "light_logo": "_static/light-exasol-logo.svg", + "dark_logo": "_static/dark-exasol-logo.svg", + "github_url": "https://github.com/exasol/mcp-server", + "accent_color": "grass", +} + +# -- Configure link checking behavior ---------------------------------------- +linkcheck_rate_limit_timeout = 60 +linkcheck_timeout = 15 +linkcheck_delay = 30 +linkcheck_retries = 2 +linkcheck_anchors = False +linkcheck_ignore: list[str] = [] +linkcheck_allowed_redirects = { + # All HTTP redirections from the source URI to + # the canonical URI will be treated as "working". + r"https://github\.com/.*": r"https://github\.com/login*" +} diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..0dec8d4 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,27 @@ +Documentation of Exasol MCP Server +----------------------------------- + +.. grid:: 1 1 3 2 + :gutter: 2 + :padding: 0 + :class-container: surface + + .. grid-item-card:: :octicon:`person` User Guide + :link: user_guide + :link-type: ref + + Resource for users to understand how to utilize this project and its features. + + .. grid-item-card:: :octicon:`tools` Developer Guide + :link: developer_guide + :link-type: ref + + Instructions and best practices to help developers contribute to the project and set up their development environment. + +.. toctree:: + :maxdepth: 4 + :hidden: + + user_guide/user_guide + developer_guide/developer_guide + changes/changelog diff --git a/doc/user_guide/db_connection_setup.md b/doc/user_guide/db_connection_setup.rst similarity index 63% rename from doc/user_guide/db_connection_setup.md rename to doc/user_guide/db_connection_setup.rst index ce41477..6cb9132 100644 --- a/doc/user_guide/db_connection_setup.md +++ b/doc/user_guide/db_connection_setup.rst @@ -1,4 +1,5 @@ -# Database Connection Setup +Database Connection Setup +========================= The MCP server supports two user authentication methods available in Exasol database - password and an OpenID token. @@ -11,39 +12,55 @@ parameters depends on the MCP server deployment mode. This section of the User G explains possible deployment options in the context of the database connection and lists the expected environment variables in each of these cases. -## Local MCP Server +Local MCP Server +---------------- In this mode, the server should be configured to use particular database credentials, e.g. username and password (On-Prem). Presumably, these will be Exasol credentials given to the user of an AI Application hosting the MCP server. The application is running on the user's machine. -### On-Prem Backend +On-Prem Backend +^^^^^^^^^^^^^^^ ++----------------------------------------+----------+ | Variable Name | Required | -|----------------------------------------|:--------:| ++========================================+==========+ | EXA_DSN (e.g. demodb.exasol.com:8563") | yes | ++----------------------------------------+----------+ | EXA_USER | yes | ++----------------------------------------+----------+ | EXA_PASSWORD | no | ++----------------------------------------+----------+ | EXA_ACCESS_TOKEN | no | ++----------------------------------------+----------+ | EXA_REFRESH_TOKEN | no | ++----------------------------------------+----------+ One of the EXA_PASSWORD, EXA_ACCESS_TOKEN, EXA_REFRESH_TOKEN must be provided, depending on how the EXA_USER is identified. -### SaaS Backend +SaaS Backend +^^^^^^^^^^^^ ++------------------------------------------------------+----------+ | Variable Name | Required | -|------------------------------------------------------|:--------:| ++======================================================+==========+ | EXA_SAAS_HOST (defaults to https://cloud.exasol.com) | no | ++------------------------------------------------------+----------+ | EXA_SAAS_ACCOUNT_ID | yes | ++------------------------------------------------------+----------+ | EXA_SAAS_PAT (Personal Access Token) | yes | ++------------------------------------------------------+----------+ | EXA_SAAS_DATABASE_ID | no | ++------------------------------------------------------+----------+ | EXA_SAAS_DATABASE_NAME | no | ++------------------------------------------------------+----------+ Either EXA_SAAS_DATABASE_ID or EXA_SAAS_DATABASE_NAME must be provided, -## HTTP MCP Server +HTTP MCP Server +--------------- This is the multiuser setup. The MCP Server may be able to identify the user, if the server authorization is configured in a certain way. The username can be stored in one @@ -54,7 +71,8 @@ the name of this claim. Being able to identify the user gives two possibilities for making the database connection user special. -### Passthrough Access Token (On-Prem) +Passthrough Access Token (On-Prem) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Under certain conditions, the access token can be extracted from the MCP Authentication context and used to open the database connection on behalf of the user calling an MCP @@ -70,16 +88,21 @@ refresh token. Currently, the refresh token identification is not supported. Thi doesn't mean the identity provider cannot use a refresh token. If it does, the MCP client will request access token regeneration when its validity period expires. ++----------------------------------------+----------+ | Variable Name | Required | -|----------------------------------------|:--------:| ++========================================+==========+ | EXA_DSN (e.g. demodb.exasol.com:8563") | yes | ++----------------------------------------+----------+ | EXA_USERNAME_CLAIM | yes | ++----------------------------------------+----------+ | EXA_POOL_SIZE | no | ++----------------------------------------+----------+ EXA_USERNAME_CLAIM is where the name of the username claim should be provided. EXA_POOL_SIZE is the maximum size of the connection pool, defaults to 5. -### User Impersonation (On-Prem) +User Impersonation (On-Prem) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the users are not identified by access tokens in the database, but their names can be made visible through a claim, it is possible to make the connection using a separate MCP @@ -88,37 +111,53 @@ executed by the server tools will be subject to permissions of the user who init the request. For this to work, the server's own credentials must have the "IMPERSONATE ANY USER" or "IMPERSONATION ON " privileges. ++----------------------------------------+----------+ | Variable Name | Required | -|----------------------------------------|:--------:| ++========================================+==========+ | EXA_DSN (e.g. demodb.exasol.com:8563") | yes | ++----------------------------------------+----------+ | EXA_USER | yes | ++----------------------------------------+----------+ | EXA_PASSWORD | no | ++----------------------------------------+----------+ | EXA_ACCESS_TOKEN | no | ++----------------------------------------+----------+ | EXA_REFRESH_TOKEN | no | ++----------------------------------------+----------+ | EXA_USERNAME_CLAIM | yes | ++----------------------------------------+----------+ | EXA_POOL_SIZE | no | ++----------------------------------------+----------+ Here, EXA_USER is the server's own username. One of the EXA_PASSWORD, EXA_ACCESS_TOKEN, EXA_REFRESH_TOKEN must be provided, depending on how the server's username is identified. -### Passthrough PAT (SaaS) +Passthrough PAT (SaaS) +^^^^^^^^^^^^^^^^^^^^^^ For this option, the MCP Client should be configured to pass the user's PAT in the HTTP headers, e.g. as X-API-KEY. The exact name of the header doesn't matter, so long as the MCP Server knows it. Apart from that, the configuration of the SaaS backend connection is similar to the local deployment case. ++------------------------------------------------------+----------+ | Variable Name | Required | -|------------------------------------------------------|:--------:| ++======================================================+==========+ | EXA_SAAS_HOST (defaults to https://cloud.exasol.com) | no | ++------------------------------------------------------+----------+ | EXA_SAAS_ACCOUNT_ID | yes | ++------------------------------------------------------+----------+ | EXA_SAAS_PAT_HEADER | yes | ++------------------------------------------------------+----------+ | EXA_SAAS_DATABASE_ID | no | ++------------------------------------------------------+----------+ | EXA_SAAS_DATABASE_NAME | no | ++------------------------------------------------------+----------+ Either EXA_SAAS_DATABASE_ID or EXA_SAAS_DATABASE_NAME must be provided, -### Delegated database access +Delegated database access +^^^^^^^^^^^^^^^^^^^^^^^^^ This is a backup option for the case when the user cannot be identified or the server's username cannot be granted the impersonation privilege. In principle, it is identical to @@ -128,15 +167,24 @@ is concerned, this is irrelevant. To keep the database access integrity, the ser username must have the permission that is the least common denominator of the permissions of the users allowed to access the MCP server. -## SSL Options +SSL Options +----------- Exasol MCP Server supports configuration of the Transport Layer Security (TLS) or Secure Sockets Layer (SSL) protocols. Below is a list of TLS/SSL options that can be specified. These options are applicable to all connection modes described above. -| Variable Name | Description | -|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| -| EXA_SSL_CERT_VALIDATION (defaults to true) | Other peers’ certificate verification (true/false).
Can be set to false for testing purpose, but never in production. | -| EXA_SSL_TRUSTED_CA | Custom path to a directory with Certification Authority (CA) certificates,
or a single CA file. | -| EXA_SSL_CLIENT_CERT | Custom path to the own certificate file in PEM format. | -| EXA_SSL_PRIVATE_KEY | Path to the private key file, if separate from the certificate.
This SSL feature is currently not supported by the Exasol database. | ++--------------------------------------------+---------------------------------------------------------------------+ +| Variable Name | Description | ++============================================+=====================================================================+ +| EXA_SSL_CERT_VALIDATION (defaults to true) | Other peers’ certificate verification (true/false). | +| | Can be set to false for testing purpose, but never in production. | ++--------------------------------------------+---------------------------------------------------------------------+ +| EXA_SSL_TRUSTED_CA | Custom path to a directory with Certification Authority (CA) | +| | certificates, or a single CA file. | ++--------------------------------------------+---------------------------------------------------------------------+ +| EXA_SSL_CLIENT_CERT | Custom path to the own certificate file in PEM format. | ++--------------------------------------------+---------------------------------------------------------------------+ +| EXA_SSL_PRIVATE_KEY | Path to the private key file, if separate from the certificate. | +| | This SSL feature is currently not supported by the Exasol database. | ++--------------------------------------------+---------------------------------------------------------------------+ diff --git a/doc/user_guide/open_id_setup.md b/doc/user_guide/open_id_setup.md deleted file mode 100644 index 68402ac..0000000 --- a/doc/user_guide/open_id_setup.md +++ /dev/null @@ -1,169 +0,0 @@ -# OpenID Setup - -A production installation of the MCP server may require proper security configuration. -This is certainly the case if the server is deployed as an HTTP server. Then its tools -must be protected with an authorisation mechanism. This section of the User Guide -provides details on how Exasol MCP server can be configured in the remote deployment -scenario, i.e., as an HTTP server. - -The MCP supports and recommends OAuth2 as the authorization framework for protecting -the tools and resources from unauthorized access. OAuth2 and OpenID Connect are modern -and widely used specifications for resource protection. Exasol MCP server supports -OAuth2-based authorization to control access to its own tools, as well as the Exasol -database. The authentication options for the database connection are described in -the [Database Connection Setup](db_connection_setup.md) guide. This section focuses -on the configuration of the MCP Server authorization. - -It is assumed that the reader has some familiarity with the basic concepts of OAuth2. -Without going into details on how OAuth2 works, let us recap what roles exist in -this specification and how they map onto MCP interaction. - -The OAuth2 defines four actors: -- The resource with a certain API. -- The client, usually an application that wants to access the resource. -- The resource owner, usually a human, who can authorize the client to access the resource. -- The identity server, a service used to facilitate the authorization. - -In the MCP case, the resource API is the server tools, and the client is the MCP client -application, e.g. Claude Desktop. - -## OAuth in FastMCP - -The implementation of Exasol MCP Server is based on FastMCP package, hence its -authentication layer is based on [FastMCP Authentication](https://gofastmcp.com/servers/auth/authentication). -Exasol MCP Server supports all authentication mechanisms provided by FastMCP at the -moment of the release. However, the FastMCP authentication is currently in active -development. - -### FastMCP Integration with Identity Providers - -The FastMCP provides an integration with several popular OAuth providers. -At the time of writing, the following providers are supported: -- [Auth0](https://gofastmcp.com/integrations/auth0) -- [AuthKit](https://gofastmcp.com/integrations/authkit) -- [AWS Cognito](https://gofastmcp.com/integrations/aws-cognito) -- [Azure](https://gofastmcp.com/integrations/azure) -- [Descope](https://gofastmcp.com/integrations/descope) -- [GitHub](https://gofastmcp.com/integrations/github) -- [Scalekit](https://gofastmcp.com/integrations/scalekit) -- [Google](https://gofastmcp.com/integrations/google) -- [WorkOS](https://gofastmcp.com/integrations/workos) - -To configure the MCP authentication with any of these providers, please set the -environment variables, as described in the FastMCP documentation for a particular -provider. As an example, the following environment variables shall be set when -working with AuthKit: -```bash -export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.AuthKitProvider -export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN=https://your-project.authkit.app -export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL=https://your-server.com -``` -Note that the `FASTMCP_SERVER_AUTH` should always be set to the module path of the -provider's class. - -### FastMCP Generic OAuth Providers - -The FastMCP also provides three generic ways to configure the OAuth2 authentication. -- [Remote OAuth](https://gofastmcp.com/servers/auth/remote-oauth), to work with identity -servers supporting Dynamic Client Registration (DCR). -- [OAuth Proxy](https://gofastmcp.com/servers/auth/oauth-proxy), to work with identity -servers that do not support DCR. -- [Token Verification](https://gofastmcp.com/servers/auth/token-verification), for the -case when the MCP server only verifies a bearer token, not being concerned about how this -token is acquired. - -If the chosen identity provider is not in the above list, it should still be possible to -configure the MCP server authentication using one of those generic providers. Please use -[Remote OAuth](https://gofastmcp.com/servers/auth/remote-oauth) if the provider supports -DCR, and [OAuth Proxy](https://gofastmcp.com/servers/auth/oauth-proxy) if it doesn't. - -Currently, FastMCP does not define environment variables for generic providers. Exasol -MCP Server fills the gap by providing its own set of variables in a similar fashion. -As with specific providers, the variable `FASTMCP_SERVER_AUTH` should be set to the path -of the chosen generic auth provider class. The required value for this variable can be -found in one of the tables below. - -Normally, two sets of variables must be configured - one for a Token Verifier and another -one for the provider - Remote OAuth or OAuth Proxy. - -The tables below list all possible variables in each set. Note that the column `Required` -means whether the value is mandatory or not in the corresponding FastMCP module. An -optional (for FastMCP) variable may be required by a particular identity provider. One -need to check the provider's specification to find out what information should be supplied. - -The rest of this section provides information about the environment variables defined by -Exasol MCP Server. - -### Token Verification - -Both `Remote OAuth` and `OAuthProxy` require a [Token Verifier](https://gofastmcp.com/servers/auth/token-verification). -The choice of the token verifier depends on the token verification model supported by a -particular identity provider. - -At the time of writing, the latest release of the FastMCP - 2.12.5 - only offers the -JWT Token Verification. The Opaque Token Verification should be added soon. - -A token verifier provider may be the only module needed if the MCP Authentication uses -an externally supplied bearer token. However, this guide doesn't give any details on how -such a system could be configured. Also, in this scenario, we recommend using FastMCP's -[environment variables](https://gofastmcp.com/servers/auth/token-verification), not the Exasol ones. - -#### JWT Token Verification. - -| Variable Name | Required | Value | -|--------------------------|:--------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| EXA_AUTH_PUBLIC_KEY | no | For asymmetric algorithms (RS256, ES256, etc.): PEM-encoded public key.
For symmetric algorithms (HS256, HS384, HS512): The shared secret string. | -| EXA_AUTH_JWKS_URI | no | URI to fetch JSON Web Key Set (only for asymmetric algorithms). | -| EXA_AUTH_ISSUER | no | Expected issuer claim. | -| EXA_AUTH_AUDIENCE | no | Expected audience claim(s). | -| EXA_AUTH_ALGORITHM | no | JWT signing algorithm. Supported algorithms:
- Asymmetric: RS256/384/512, ES256/384/512, PS256/384/512 (default: RS256).
- Symmetric: HS256, HS384, HS512. | -| EXA_AUTH_REQUIRED_SCOPES | no | Required scopes for all tokens. | -| EXA_AUTH_BASE_URL | no | Base URL for TokenVerifier protocol. | - -Either of `EXA_AUTH_PUBLIC_KEY` or `EXA_AUTH_JWKS_URI` must be set. - -### Remote OAuth - -| Variable Name | Required | Value | -|---------------------------------|:--------:|---------------------------------------------------| -| FASTMCP_SERVER_AUTH | yes | fastmcp.server.auth.auth.RemoteAuthProvider. | -| EXA_AUTH_AUTHORIZATION_SERVERS | yes | List of identity servers that issue valid tokens. | -| EXA_AUTH_BASE_URL | yes | Base URL of the MCP server. | -| EXA_AUTH_RESOURCE_NAME | no | Name for the protected resource. | -| EXA_AUTH_RESOURCE_DOCUMENTATION | no | Documentation URL for the protected resource. | - -### OAuth Proxy - -| Variable Name | Required | Value | -|------------------------------------------|:--------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| FASTMCP_SERVER_AUTH | yes | fastmcp.server.auth.oauth_proxy.OAuthProxy | -| EXA_AUTH_UPSTREAM_AUTHORIZATION_ENDPOINT | yes | URL of upstream authorization endpoint. | -| EXA_AUTH_UPSTREAM_TOKEN_ENDPOINT | yes | URL of upstream token endpoint. | -| EXA_AUTH_UPSTREAM_CLIENT_ID | yes | Client ID registered with upstream server. | -| EXA_AUTH_UPSTREAM_CLIENT_SECRET | yes | Client secret for upstream server. | -| EXA_AUTH_UPSTREAM_REVOCATION_ENDPOINT | no | Optional upstream revocation endpoint. | -| EXA_AUTH_BASE_URL | yes | Public URL of the MCP server.
Redirect path is relative to this URL. | -| EXA_AUTH_REDIRECT_PATH | no | Redirect path configured in upstream OAuth app.
Defaults to "/auth/callback". | -| EXA_AUTH_ISSUER_URL | no | Issuer URL for OAuth metadata. Defaults to EXA_AUTH_BASE_URL. | -| EXA_AUTH_SERVICE_DOCUMENTATION_URL | no | Optional service documentation URL. | -| EXA_AUTH_ALLOWED_CLIENT_REDIRECT_URIS | no | List of allowed redirect URI patterns for MCP clients.
Patterns support wildcards (e.g., "http://localhost:*", "https://*.example.com/*").
If not set, only localhost redirect URIs are allowed.
These are for MCP clients performing loopback redirects,
NOT for the upstream OAuth app. | -| EXA_AUTH_VALID_SCOPES | no | List of all the possible valid scopes for a client. These are
advertised to clients through the `/.well-known` endpoints.
If not set, defaults to EXA_AUTH_REQUIRED_SCOPES. | -| EXA_AUTH_FORWARD_PKCE | no | Whether to forward PKCE to upstream server. Defaults to True.
Enable for providers that support/require PKCE (Google, Azure, AWS, etc.).
Disable only if upstream provider doesn't support PKCE. | -| EXA_AUTH_TOKEN_ENDPOINT_AUTH_METHOD | no | Token endpoint authentication method for upstream server.
Common values: "client_secret_basic","client_secret_post", "none".
If not set, the provider will use default (typically "client_secret_basic"). | -| EXA_AUTH_EXTRA_AUTHORIZE_PARAMS | no | Additional parameters to forward to the upstream authorization
endpoint. Useful for provider-specific parameters like Auth0's "audience".
Example: {"audience": "https://api.example.com"} | -| EXA_AUTH_EXTRA_TOKEN_PARAMS | no | Additional parameters to forward to the upstream token endpoint.
Useful for provider-specific parameters during token exchange. | - -## OpenID with SaaS Backend - -The information in this guide is mostly relevant to the On-Prem backend. The -authentication in SaaS backend is based on OpenID Connect (OIDC), which is an -authentication layer built on top of OAuth. For details please refer to the Exasol -SaaS [Access Management](https://docs.exasol.com/saas/administration/access_mngt/access_management.htm) -documentation, and in particular to the [Personal Access Token (PAT)](https://docs.exasol.com/saas/administration/access_mngt/access_token.htm) -section. - -Essentially, an Exasol SaaS user is issued a PAT. They need to pass this token to the -MCP Server. This can be done using an HTTP header, as described in the [Database Connection Setup](db_connection_setup.md) guide. - -Besides that, the MCP server tools can also be protected using one of the authorization -schemas described earlier in this guide, but in case of SaaS backend this is optional. diff --git a/doc/user_guide/open_id_setup.rst b/doc/user_guide/open_id_setup.rst new file mode 100644 index 0000000..9d21320 --- /dev/null +++ b/doc/user_guide/open_id_setup.rst @@ -0,0 +1,254 @@ +OpenID Setup +============ + +A production installation of the MCP server may require proper security configuration. +This is certainly the case if the server is deployed as an HTTP server. Then its tools +must be protected with an authorisation mechanism. This section of the User Guide +provides details on how Exasol MCP server can be configured in the remote deployment +scenario, i.e., as an HTTP server. + +The MCP supports and recommends OAuth2 as the authorization framework for protecting +the tools and resources from unauthorized access. OAuth2 and OpenID Connect are modern +and widely used specifications for resource protection. Exasol MCP server supports +OAuth2-based authorization to control access to its own tools, as well as the Exasol +database. The authentication options for the database connection are described in +the `Database Connection Setup `__ guide. This section focuses +on the configuration of the MCP Server authorization. + +It is assumed that the reader has some familiarity with the basic concepts of OAuth2. +Without going into details on how OAuth2 works, let us recap what roles exist in +this specification and how they map onto MCP interaction. + +The OAuth2 defines four actors: +- The resource with a certain API. +- The client, usually an application that wants to access the resource. +- The resource owner, usually a human, who can authorize the client to access the resource. +- The identity server, a service used to facilitate the authorization. + +In the MCP case, the resource API is the server tools, and the client is the MCP client +application, e.g. Claude Desktop. + +OAuth in FastMCP +---------------- + +The implementation of Exasol MCP Server is based on FastMCP package, hence its +authentication layer is based on `FastMCP Authentication `__. +Exasol MCP Server supports all authentication mechanisms provided by FastMCP at the +moment of the release. However, the FastMCP authentication is currently in active +development. + +FastMCP Integration with Identity Providers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The FastMCP provides an integration with several popular OAuth providers. +At the time of writing, the following providers are supported: + +* `Auth0 `__ +* `AuthKit `__ +* `AWS Cognito `__ +* `Azure `__ +* `Descope `__ +* `GitHub `__ +* `Scalekit `__ +* `Google `__ +* `WorkOS `__ + +To configure the MCP authentication with any of these providers, please set the +environment variables, as described in the FastMCP documentation for a particular +provider. As an example, the following environment variables shall be set when +working with AuthKit: + +.. code-block:: shell + + export FASTMCP_SERVER_AUTH=fastmcp.server.auth.providers.workos.AuthKitProvider + export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN=https://your-project.authkit.app + export FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_BASE_URL=https://your-server.com + +Note that the ``FASTMCP_SERVER_AUTH`` should always be set to the module path of the +provider's class. + +FastMCP Generic OAuth Providers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The FastMCP also provides three generic ways to configure the OAuth2 authentication. + +* `Remote OAuth `__, + to work with identity servers supporting Dynamic Client Registration (DCR). +* `OAuth Proxy `__, + to work with identity servers that do not support DCR. +* `Token Verification `__, + for the case when the MCP server only verifies a bearer token, not being concerned about + how this token is acquired. + +If the chosen identity provider is not in the above list, it should still be possible to +configure the MCP server authentication using one of those generic providers. Please use +`Remote OAuth `__ if the provider supports +DCR, and `OAuth Proxy `__ if it doesn't. + +Currently, FastMCP does not define environment variables for generic providers. Exasol +MCP Server fills the gap by providing its own set of variables in a similar fashion. +As with specific providers, the variable ``FASTMCP_SERVER_AUTH`` should be set to the path +of the chosen generic auth provider class. The required value for this variable can be +found in one of t +he tables below. + +Normally, two sets of variables must be configured - one for a Token Verifier and another +one for the provider - Remote OAuth or OAuth Proxy. + +The tables below list all possible variables in each set. Note that the column *Required* +means whether the value is mandatory or not in the corresponding FastMCP module. An +optional (for FastMCP) variable may be required by a particular identity provider. One +need to check the provider's specification to find out what information should be supplied. + +The rest of this section provides information about the environment variables defined by +Exasol MCP Server. + +Token Verification +^^^^^^^^^^^^^^^^^^ + +Both *Remote OAuth* and *OAuthProxy* require a `Token Verifier `__. +The choice of the token verifier depends on the token verification model supported by a +particular identity provider. + +At the time of writing, the latest release of the FastMCP - 2.13.0 - offers two types of +verification - `JWT Token Verification `__ +and `Opaque Token Verification `__. +The variation of the former is `Static Public Key Verification `__. + +A token verifier provider may be the only module needed if the MCP Authentication uses +an externally supplied bearer token. However, this guide doesn't give any details on how +such a system could be configured. Also, in this scenario, we recommend using FastMCP's +`environment variables `__, not the Exasol ones. + +JWT Token Verification +++++++++++++++++++++++ + ++--------------------------+----------+-----------------------------------------------------------------------------+ +| Variable Name | Required | Value | ++==========================+==========+=============================================================================+ +| EXA_AUTH_PUBLIC_KEY | no | For asymmetric algorithms (RS256, ES256, etc.): PEM-encoded public key. | +| | | For symmetric algorithms (HS256, HS384, HS512): The shared secret string. | ++--------------------------+----------+-----------------------------------------------------------------------------+ +| EXA_AUTH_JWKS_URI | no | URI to fetch JSON Web Key Set (only for asymmetric algorithms). | ++--------------------------+----------+-----------------------------------------------------------------------------+ +| EXA_AUTH_ISSUER | no | Expected issuer claim. | ++--------------------------+----------+-----------------------------------------------------------------------------+ +| EXA_AUTH_AUDIENCE | no | Expected audience claim(s). | ++--------------------------+----------+-----------------------------------------------------------------------------+ +| EXA_AUTH_ALGORITHM | no | JWT signing algorithm. Supported algorithms: | +| | | - Asymmetric: RS256/384/512, ES256/384/512, PS256/384/512 (default: RS256). | +| | | - Symmetric: HS256, HS384, HS512. | ++--------------------------+----------+-----------------------------------------------------------------------------+ +| EXA_AUTH_REQUIRED_SCOPES | no | Required scopes for all tokens. | ++--------------------------+----------+-----------------------------------------------------------------------------+ +| EXA_AUTH_BASE_URL | yes | Base URL for TokenVerifier protocol. | ++--------------------------+----------+-----------------------------------------------------------------------------+ + +Either of ``EXA_AUTH_PUBLIC_KEY`` or ``EXA_AUTH_JWKS_URI`` must be set. + +Opaque Token Verification (Token Introspection) ++++++++++++++++++++++++++++++++++++++++++++++++ + ++----------------------------+----------+-----------------------------------------------------------------------+ +| Variable Name | Required | Value | ++============================+==========+=======================================================================+ +| EXA_AUTH_INTROSPECTION_URL | yes | URL of the OAuth token introspection endpoint. | ++----------------------------+----------+-----------------------------------------------------------------------+ +| EXA_AUTH_CLIENT_ID | yes | OAuth client ID for authenticating to the introspection endpoint. | ++----------------------------+----------+-----------------------------------------------------------------------+ +| EXA_AUTH_CLIENT_SECRET | yes | OAuth client secret for authenticating to the introspection endpoint. | ++----------------------------+----------+-----------------------------------------------------------------------+ +| EXA_AUTH_TIMEOUT_SECONDS | no | HTTP request timeout in seconds (default: 10). | ++----------------------------+----------+-----------------------------------------------------------------------+ +| EXA_AUTH_REQUIRED_SCOPES | no | Required scopes for all tokens. | ++----------------------------+----------+-----------------------------------------------------------------------+ +| EXA_AUTH_BASE_URL | yes | Base URL for TokenVerifier protocol. | ++----------------------------+----------+-----------------------------------------------------------------------+ + +Remote OAuth +^^^^^^^^^^^^ + ++---------------------------------+----------+---------------------------------------------------+ +| Variable Name | Required | Value | ++=================================+==========+===================================================+ +| FASTMCP_SERVER_AUTH | yes | fastmcp.server.auth.auth.RemoteAuthProvider. | ++---------------------------------+----------+---------------------------------------------------+ +| EXA_AUTH_AUTHORIZATION_SERVERS | yes | List of identity servers that issue valid tokens. | ++---------------------------------+----------+---------------------------------------------------+ +| EXA_AUTH_BASE_URL | yes | Base URL of the MCP server. | ++---------------------------------+----------+---------------------------------------------------+ +| EXA_AUTH_RESOURCE_NAME | no | Name for the protected resource. | ++---------------------------------+----------+---------------------------------------------------+ +| EXA_AUTH_RESOURCE_DOCUMENTATION | no | Documentation URL for the protected resource. | ++---------------------------------+----------+---------------------------------------------------+ + +OAuth Proxy +^^^^^^^^^^^ + ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| Variable Name | Required | Value | ++==========================================+==========+==============================================================================+ +| FASTMCP_SERVER_AUTH | yes | fastmcp.server.auth.oauth_proxy.OAuthProxy | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_UPSTREAM_AUTHORIZATION_ENDPOINT | yes | URL of upstream authorization endpoint. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_UPSTREAM_TOKEN_ENDPOINT | yes | URL of upstream token endpoint. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_UPSTREAM_CLIENT_ID | yes | Client ID registered with upstream server. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_UPSTREAM_CLIENT_SECRET | yes | Client secret for upstream server. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_UPSTREAM_REVOCATION_ENDPOINT | no | Optional upstream revocation endpoint. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_BASE_URL | yes | Public URL of the MCP server. | +| | | Redirect path is relative to this URL. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_REDIRECT_PATH | no | Redirect path configured in upstream OAuth app. | +| | | Defaults to ``/auth/callback``. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_ISSUER_URL | no | Issuer URL for OAuth metadata. Defaults to EXA_AUTH_BASE_URL. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_SERVICE_DOCUMENTATION_URL | no | Optional service documentation URL. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_ALLOWED_CLIENT_REDIRECT_URIS | no | List of allowed redirect URI patterns for MCP clients. | +| | | Patterns support wildcards | +| | | (e.g., ``http://localhost:*``, ``https://*.example.com/*``). | +| | | If not set, only localhost redirect URIs are allowed. | +| | | These are for MCP clients performing loopback redirects, | +| | | NOT for the upstream OAuth app. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_VALID_SCOPES | no | List of all the possible valid scopes for a client. These are | +| | | advertised to clients through the ``/.well-known`` endpoints. | +| | | If not set, defaults to EXA_AUTH_REQUIRED_SCOPES. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_FORWARD_PKCE | no | Whether to forward PKCE to upstream server. Defaults to True. | +| | | Enable for providers that support/require PKCE (Google, Azure, AWS, etc.). | +| | | Disable only if upstream provider doesn't support PKCE. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_TOKEN_ENDPOINT_AUTH_METHOD | no | Token endpoint authentication method for upstream server. | +| | | Common values: "client_secret_basic","client_secret_post", "none". | +| | | If not set, the provider will use default (typically "client_secret_basic"). | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_EXTRA_AUTHORIZE_PARAMS | no | Additional parameters to forward to the upstream authorization endpoint. | +| | | Useful for provider-specific parameters like Auth0's "audience". | +| | | Example: {"audience": ``"https://api.example.com"``} | ++------------------------------------------+----------+------------------------------------------------------------------------------+ +| EXA_AUTH_EXTRA_TOKEN_PARAMS | no | Additional parameters to forward to the upstream token endpoint. | +| | | Useful for provider-specific parameters during token exchange. | ++------------------------------------------+----------+------------------------------------------------------------------------------+ + +OpenID with SaaS Backend +------------------------ + +The information in this guide is mostly relevant to the On-Prem backend. The +authentication in SaaS backend is based on OpenID Connect (OIDC), which is an +authentication layer built on top of OAuth. For details please refer to the Exasol +SaaS `Access Management `__ +documentation, and in particular to the `Personal Access Token (PAT) `__ +section. + +Essentially, an Exasol SaaS user is issued a PAT. They need to pass this token to the +MCP Server. This can be done using an HTTP header, as described in the [Database Connection Setup](db_connection_setup.md) guide. + +Besides that, the MCP server tools can also be protected using one of the authorization +schemas described earlier in this guide, but in case of SaaS backend this is optional. diff --git a/doc/user_guide/server_setup.md b/doc/user_guide/server_setup.md deleted file mode 100644 index b44f3ce..0000000 --- a/doc/user_guide/server_setup.md +++ /dev/null @@ -1,59 +0,0 @@ -# MCP Server Setup - -Essentially, the sever is configured using environment variables. - -In case of the MCP Sever running locally, under the control of an MCP client application, -the latter usually provides a convenient way of creating an environment from some kind -of configuration file. Below is an example of Claude Desktop configuration file that -references the Exasol MCP Server. - -```json -{ - "mcpServers": { - "exasol_db": { - "command": "uvx", - "args": ["exasol-mcp-server@latest"], - "env": { - "EXA_DSN": "my-dsn, e.g. demodb.exasol.com:8563", - "EXA_USER": "my-user-name", - "EXA_PASSWORD": "my-password", - "EXA_MCP_SETTINGS": "{\"schemas\": {\"like_pattern\": \"MY_SCHEMA\"}" - } - } - } -} -``` - -The `env` section of this file lists the environment variables that will be created in -the environment where the MCP Server is going to run. - -The environment variables can be divided into three groups. - -- [OpenID settings](open_id_setup.md). -- [Database connection settings](db_connection_setup.md) -- [Tool settings](tool_setup.md) - -All settings are described in details in respective sections of the User Guide. - -## Tool settings - -The tool settings are stored in a single variable - `EXA_MCP_SETTINGS`. The settings -are written in the json format. The json string can be set directly in the environment -variable, as shown in the above example. Note that double quotes in the json string must -be escaped, otherwise the environment variable value will be interpreted, not as text, -but as a part of the outer json. - -Alternatively, the settings can be written in a json file. In this case, the `EXA_MCP_SETTINGS` -should contain the path to this file, e.g. - -```json -{ - "env": { - "other": "variables", - "EXA_MCP_SETTINGS": "path_to_settings.json" - } -} -``` - -Please see the [Tool Setup](tool_setup.md) for details on how the MCP Server tools -can be customised. diff --git a/doc/user_guide/server_setup.rst b/doc/user_guide/server_setup.rst new file mode 100644 index 0000000..d506c42 --- /dev/null +++ b/doc/user_guide/server_setup.rst @@ -0,0 +1,61 @@ +MCP Server Setup +================ + +Essentially, the sever is configured using environment variables. + +In case of the MCP Sever running locally, under the control of an MCP client application, +the latter usually provides a convenient way of creating an environment from some kind +of configuration file. Below is an example of Claude Desktop configuration file that +references the Exasol MCP Server. + +.. code-block:: json + + { + "mcpServers": { + "exasol_db": { + "command": "uvx", + "args": ["exasol-mcp-server@latest"], + "env": { + "EXA_DSN": "my-dsn, e.g. demodb.exasol.com:8563", + "EXA_USER": "my-user-name", + "EXA_PASSWORD": "my-password", + "EXA_MCP_SETTINGS": "{\"schemas\": {\"like_pattern\": \"MY_SCHEMA\"}" + } + } + } + } + +The ``env`` section of this file lists the environment variables that will be created in +the environment where the MCP Server is going to run. + +The environment variables can be divided into three groups. + +* `OpenID settings `__ +* `Database connection settings `__ +* `Tool settings `__ + +All settings are described in details in respective sections of the User Guide. + +Tool settings +------------- + +The tool settings are stored in a single variable - ``EXA_MCP_SETTINGS``. The settings +are written in the json format. The json string can be set directly in the environment +variable, as shown in the above example. Note that double quotes in the json string must +be escaped, otherwise the environment variable value will be interpreted, not as text, +but as a part of the outer json. + +Alternatively, the settings can be written in a json file. In this case, the ``EXA_MCP_SETTINGS`` +should contain the path to this file, e.g. + +.. code-block:: json + + { + "env": { + "other": "variables", + "EXA_MCP_SETTINGS": "path_to_settings.json" + } + } + +Please see the `Tool Setup `__ for details on how the MCP Server tools +can be customised. diff --git a/doc/user_guide/tool_setup.md b/doc/user_guide/tool_setup.md deleted file mode 100644 index 4f90442..0000000 --- a/doc/user_guide/tool_setup.md +++ /dev/null @@ -1,155 +0,0 @@ -# Tool setup: - -This guide provides detailed information on how to configure Exasol MCP Server tools -to address the requirements of a specific use case. - -## Enable SQL queries - -Most importantly, the server configuration specifies if reading and/or writing the data -using SQL queries is enabled. Note that both options are disabled by default. To enable -the data reading, set the `enable_read_query` property to true, as shown below. - -To enable Data Modification and Data Definition queries set `enable_write_query` property -to true. This option is not recommended since it can cause unintended loss of data. -Before the query is executed the user will be asked to review the query and accept its -execution. This is done through the elicitation mechanism. If the client application -does not support elicitation, the tool will return an error. - -```json -{ - "enable_read_query": true, - "enable_write_query": true -} -``` - -## Set DB object listing filters - -The server configuration settings can also be used to enable/disable or filter the -listing of a particular type of database objects. Similar settings are defined for -the following object types: -``` -schemas, -tables, -views, -functions, -scripts -``` -The settings include the following properties: -- `enable`: a boolean flag that enables or disables the listing. -- `like_pattern`: filters the output by applying the specified SQL LIKE condition to -the object name. -- `regexp_pattern`: filters the output by matching the object name with the specified -regular expression. - -In the following example, the listing of schemas is limited to only one schema, -the listings of functions and scripts are disabled and the visibility of tables is -limited to tables with certain name pattern. - -```json -{ - "schemas": { - "like_pattern": "MY_SCHEMA" - }, - "tables": { - "like_pattern": "MY_TABLE%" - }, - "functions": { - "enable": false - }, - "scripts": { - "enable": false - } -} -``` - -## Set the language - -The language, if specified, can help the tools execute more precise search of requested -database object. This should be the language of communication with the LLM and also the -language used for naming and documenting the database objects. The language must be set -to its english name, e.g. "spanish", not "español". -Below is an example of configuration settings that sets the language to English. - -```json -{ - "language": "english" -} -``` - -## Set the case-sensitive search option - -By default, the database objects are searched in case-insensitive way, i.e. it is assumed -that the names "My_Table" and "MY_TABLE" refer to the same table. If this is undesirable, -the configuration setting `case_sensitive` should be set to true, as in the example below. - -```json -{ - "case_sensitive": true -} -``` - -## Add the server configuration to the MCP Client configuration - -The customised settings can be specified directly in the MCP Client configuration file -using another environment variable - `EXA_MCP_SETTINGS`: -```json -{ - "env": { - "EXA_DSN": "my-dsn", - "EXA_USER": "my-user-name", - "EXA_PASSWORD": "my-password", - "EXA_MCP_SETTINGS": "{\"schemas\": {\"like_pattern\": \"MY_SCHEMA\"}" - } -} -``` -Note that double quotes in the json text must be escaped, otherwise the environment -variable value will be interpreted, not as a text, but as a part of the outer json. - -Alternatively, the settings can be written in a json file. In this case, the -`EXA_MCP_SETTINGS` should contain the path to this file, e.g. -```json -{ - "env": { - "EXA_DSN": "my-dsn", - "EXA_USER": "my-user-name", - "EXA_PASSWORD": "my-password", - "EXA_MCP_SETTINGS": "path_to_settings.json" - } -} -``` - -## Default server settings - -The following json shows the default settings. -```json -{ - "schemas": { - "enable": true, - "like_pattern": "", - "regexp_pattern": "" - }, - "tables": { - "enable": true, - "like_pattern": "", - "regexp_pattern": "" - }, - "views": { - "enable": false, - "like_pattern": "", - "regexp_pattern": "" - }, - "functions": { - "enable": true, - "like_pattern": "", - "regexp_pattern": "" - }, - "scripts": { - "enable": true, - "like_pattern": "", - "regexp_pattern": "" - }, - "enable_read_query": false, - "language": "" -} -``` -The default values do not need to be repeated in the customised settings. diff --git a/doc/user_guide/tool_setup.rst b/doc/user_guide/tool_setup.rst new file mode 100644 index 0000000..bee99f2 --- /dev/null +++ b/doc/user_guide/tool_setup.rst @@ -0,0 +1,167 @@ +Tool setup +========== + +This guide provides detailed information on how to configure Exasol MCP Server tools +to address the requirements of a specific use case. + +Enable SQL queries +------------------ + +Most importantly, the server configuration specifies if reading and/or writing the data +using SQL queries is enabled. Note that both options are disabled by default. To enable +the data reading, set the ``enable_read_query`` property to true, as shown below. + +To enable Data Modification and Data Definition queries set ``enable_write_query`` property +to true. This option is not recommended since it can cause unintended loss of data. +Before the query is executed the user will be asked to review the query and accept its +execution. This is done through the elicitation mechanism. If the client application +does not support elicitation, the tool will return an error. + +.. code-block:: json + + { + "enable_read_query": true, + "enable_write_query": true + } + +Set DB object listing filters +----------------------------- + +The server configuration settings can also be used to enable/disable or filter the +listing of a particular type of database objects. Similar settings are defined for +the following object types: + +* ``schemas`` +* ``tables`` +* ``views`` +* ``functions`` +* ``scripts`` + +The settings include the following properties: + +* ``enable``: a boolean flag that enables or disables the listing. +* ``like_pattern``: filters the output by applying the specified SQL LIKE condition to the object name. +* ``regexp_pattern``: filters the output by matching the object name with the specified regular expression. + +In the following example, the listing of schemas is limited to only one schema, +the listings of functions and scripts are disabled and the visibility of tables is +limited to tables with certain name pattern. + +.. code-block:: json + + { + "schemas": { + "like_pattern": "MY_SCHEMA" + }, + "tables": { + "like_pattern": "MY_TABLE%" + }, + "functions": { + "enable": false + }, + "scripts": { + "enable": false + } + } + +Set the language +---------------- + +The language, if specified, can help the tools execute more precise search of requested +database object. This should be the language of communication with the LLM and also the +language used for naming and documenting the database objects. The language must be set +to its english name, e.g. "spanish", not "español". +Below is an example of configuration settings that sets the language to English. + +.. code-block:: json + + { + "language": "english" + } + +Set the case-sensitive search option +------------------------------------ + +By default, the database objects are searched in case-insensitive way, i.e. it is assumed +that the names "My_Table" and "MY_TABLE" refer to the same table. If this is undesirable, +the configuration setting ``case_sensitive`` should be set to true, as in the example below. + +.. code-block:: json + + { + "case_sensitive": true + } + +Add the server configuration to the MCP Client configuration +------------------------------------------------------------ + +The customised settings can be specified directly in the MCP Client configuration file +using another environment variable - ``EXA_MCP_SETTINGS``: + +.. code-block:: json + + { + "env": { + "EXA_DSN": "my-dsn", + "EXA_USER": "my-user-name", + "EXA_PASSWORD": "my-password", + "EXA_MCP_SETTINGS": "{\"schemas\": {\"like_pattern\": \"MY_SCHEMA\"}" + } + } + +Note that double quotes in the json text must be escaped, otherwise the environment +variable value will be interpreted, not as a text, but as a part of the outer json. + +Alternatively, the settings can be written in a json file. In this case, the +``EXA_MCP_SETTINGS`` should contain the path to this file, e.g. + +.. code-block:: json + + { + "env": { + "EXA_DSN": "my-dsn", + "EXA_USER": "my-user-name", + "EXA_PASSWORD": "my-password", + "EXA_MCP_SETTINGS": "path_to_settings.json" + } + } + +Default server settings +----------------------- + +The following json shows the default settings. + +.. code-block:: json + + { + "schemas": { + "enable": true, + "like_pattern": "", + "regexp_pattern": "" + }, + "tables": { + "enable": true, + "like_pattern": "", + "regexp_pattern": "" + }, + "views": { + "enable": false, + "like_pattern": "", + "regexp_pattern": "" + }, + "functions": { + "enable": true, + "like_pattern": "", + "regexp_pattern": "" + }, + "scripts": { + "enable": true, + "like_pattern": "", + "regexp_pattern": "" + }, + "enable_read_query": false, + "enable_write_query": false, + "language": "" + } + +The default values do not need to be repeated in the customised settings. diff --git a/doc/user_guide/user_guide.rst b/doc/user_guide/user_guide.rst new file mode 100644 index 0000000..dcab125 --- /dev/null +++ b/doc/user_guide/user_guide.rst @@ -0,0 +1,12 @@ +.. _user_guide: + +:octicon:`person` User Guide +============================ + +.. toctree:: + :maxdepth: 2 + + server_setup + tool_setup + db_connection_setup + open_id_setup From e765b4874eceec4ea2b30656bf255feae539cb1a Mon Sep 17 00:00:00 2001 From: mibe Date: Fri, 14 Nov 2025 14:53:40 +0000 Subject: [PATCH 13/18] #94: fixed a type in the changelog --- doc/changes/unreleased.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index f5b9408..e82b3d2 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -12,4 +12,4 @@ ## Documentation -* # 94: Moved the documentation for md to rst +* #94: Moved the documentation from md to rst From 794f58038228b1fdd0238957b8622f6359486bd5 Mon Sep 17 00:00:00 2001 From: mibe Date: Fri, 14 Nov 2025 14:59:57 +0000 Subject: [PATCH 14/18] #94: changed readme in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 71004d3..4c688d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ description = "Exasol MCP Server" authors = [ {name = "Mikhail Beck", email = "mikhail.beck@exasol.com"}, ] -readme = "README.md" +readme = "README.rst" license = "MIT" keywords = ['exasol', 'MCP server'] dynamic = ["dependencies"] From ecf260119203fcf4cbebd6964ecb8a8fdb94a2c9 Mon Sep 17 00:00:00 2001 From: mibe Date: Fri, 14 Nov 2025 15:03:45 +0000 Subject: [PATCH 15/18] #94: added developer_guide.rst --- doc/developer_guide/developer_guide.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 doc/developer_guide/developer_guide.rst diff --git a/doc/developer_guide/developer_guide.rst b/doc/developer_guide/developer_guide.rst new file mode 100644 index 0000000..c8a3c48 --- /dev/null +++ b/doc/developer_guide/developer_guide.rst @@ -0,0 +1,6 @@ +.. _developer_guide: + +:octicon:`tools` Developer Guide +================================ + +To be provided. From 1f43367bd9ffcfaaffc41e5cb5d70f5abf0d91d0 Mon Sep 17 00:00:00 2001 From: mibe Date: Fri, 14 Nov 2025 15:43:00 +0000 Subject: [PATCH 16/18] #94: made a small change to README.rst --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 15f5bfd..af0b0c6 100644 --- a/README.rst +++ b/README.rst @@ -28,8 +28,9 @@ tools for reading the database metadata and executing data reading queries. * Enumerates the existing database objects, including schemas, tables, views, functions and UDF scripts. * Provides a filtering mechanisms to use with object enumeration. * Describes the database objects: for tables returns the list of columns and constraints; for functions and scripts - the list of input and output parameters. + * Enables keyword search of database objects. -* Executes provided data reading SQL query. Disallows any other type of query. +* Executes provided SQL query. 🔌️ Prerequisites ----------------- @@ -55,7 +56,7 @@ To install *uv* on macOS please use *brew*, i.e. For other operating systems, please follow `the instructions `__ in the *uv* official documentation. -📝 Using the server with the Claude Desktop +🧠 Using the server with the Claude Desktop ------------------------------------------- To enable the Claude Desktop using the Exasol MCP server, the latter must be listed From f3f8da930498ba39f6985a9b6f79a05acabd2d27 Mon Sep 17 00:00:00 2001 From: mibe Date: Mon, 17 Nov 2025 09:40:57 +0000 Subject: [PATCH 17/18] #94: fixed the links and addressed other review comments --- .github/workflows/build-and-publish.yml | 4 +- .github/workflows/cd.yml | 3 +- .github/workflows/check-release-tag.yml | 4 +- .github/workflows/checks.yml | 58 +++++++++++++++---------- .github/workflows/gh-pages.yml | 4 +- .github/workflows/matrix-all.yml | 4 +- .github/workflows/matrix-exasol.yml | 4 +- .github/workflows/matrix-python.yml | 4 +- .github/workflows/report.yml | 8 ++-- .github/workflows/slow-checks-itde.yml | 4 +- .github/workflows/slow-checks-saas.yml | 4 +- doc/index.rst | 2 +- doc/user_guide/open_id_setup.rst | 4 +- doc/user_guide/server_setup.rst | 8 ++-- doc/user_guide/tool_setup.rst | 2 +- 15 files changed, 66 insertions(+), 51 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 4210532..0887c5e 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -15,10 +15,10 @@ jobs: contents: write steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Build Artifacts run: poetry build diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 835095d..1fb0e2e 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -4,6 +4,7 @@ on: push: tags: - '**' + - '!v*' jobs: @@ -14,6 +15,7 @@ jobs: contents: read cd-job: + needs: [ check-tag-version-job ] name: Continuous Delivery uses: ./.github/workflows/build-and-publish.yml permissions: @@ -29,4 +31,3 @@ jobs: contents: read pages: write id-token: write - diff --git a/.github/workflows/check-release-tag.yml b/.github/workflows/check-release-tag.yml index 8f69a2f..fc8c8ae 100644 --- a/.github/workflows/check-release-tag.yml +++ b/.github/workflows/check-release-tag.yml @@ -12,10 +12,10 @@ jobs: contents: read steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Check Tag Version # make sure the pushed/created tag matched the project version diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 2160b20..7c4fe83 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -11,12 +11,12 @@ jobs: contents: read steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Check Version(s) run: poetry run -- nox -s version:check @@ -29,10 +29,10 @@ jobs: contents: read steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Build Documentation run: | @@ -56,10 +56,10 @@ jobs: if: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' }} steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Run changelog update check run: poetry run -- nox -s changelog:updated @@ -75,10 +75,10 @@ jobs: matrix: ${{ fromJson(needs.build-matrix.outputs.matrix) }} steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 with: python-version: ${{ matrix.python-version }} @@ -86,7 +86,7 @@ jobs: run: poetry run -- nox -s lint:code - name: Upload Artifacts - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v5 with: name: lint-python${{ matrix.python-version }} path: | @@ -106,10 +106,10 @@ jobs: steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 with: python-version: ${{ matrix.python-version }} @@ -128,10 +128,10 @@ jobs: steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 with: python-version: ${{ matrix.python-version }} @@ -139,7 +139,7 @@ jobs: run: poetry run -- nox -s lint:security - name: Upload Artifacts - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v5 with: name: security-python${{ matrix.python-version }} path: .security.json @@ -152,32 +152,46 @@ jobs: contents: read steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Run format check run: poetry run -- nox -s project:format + Build-Packages: + name: Build Package Check + needs: [ Documentation, Lint, Type-Check, Security, Format ] + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: SCM Checkout + uses: actions/checkout@v5 + + - name: Setup Python & Poetry Environment + uses: exasol/python-toolbox/.github/actions/python-environment@v1 + + - name: Run Distribution Check + run: poetry run -- nox -s package:check + Tests: name: Unit-Tests (Python-${{ matrix.python-version }}) - needs: [ Lint, Type-Check, Security, Format, build-matrix ] + needs: [ Build-Packages, build-matrix ] runs-on: ubuntu-24.04 permissions: contents: read - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} strategy: fail-fast: false matrix: ${{ fromJson(needs.build-matrix.outputs.matrix) }} steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 with: python-version: ${{ matrix.python-version }} @@ -185,7 +199,7 @@ jobs: run: poetry run -- nox -s test:unit -- --coverage - name: Upload Artifacts - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v5 with: name: coverage-python${{ matrix.python-version }}-fast path: .coverage diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 305dbe6..b2d9ee5 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -12,12 +12,12 @@ jobs: contents: read steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Build Documentation run: | diff --git a/.github/workflows/matrix-all.yml b/.github/workflows/matrix-all.yml index 1e51af9..8cdb5b1 100644 --- a/.github/workflows/matrix-all.yml +++ b/.github/workflows/matrix-all.yml @@ -14,10 +14,10 @@ jobs: contents: read steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Generate matrix run: poetry run -- nox -s matrix:all diff --git a/.github/workflows/matrix-exasol.yml b/.github/workflows/matrix-exasol.yml index 577910b..fd3225b 100644 --- a/.github/workflows/matrix-exasol.yml +++ b/.github/workflows/matrix-exasol.yml @@ -14,10 +14,10 @@ jobs: contents: read steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Generate matrix run: poetry run -- nox -s matrix:exasol diff --git a/.github/workflows/matrix-python.yml b/.github/workflows/matrix-python.yml index 6c26e57..404e246 100644 --- a/.github/workflows/matrix-python.yml +++ b/.github/workflows/matrix-python.yml @@ -14,10 +14,10 @@ jobs: contents: read steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Generate matrix run: poetry run -- nox -s matrix:python diff --git a/.github/workflows/report.yml b/.github/workflows/report.yml index 7442494..3222943 100644 --- a/.github/workflows/report.yml +++ b/.github/workflows/report.yml @@ -14,15 +14,15 @@ jobs: steps: - name: SCM Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 - name: Download Artifacts - uses: actions/download-artifact@v4.2.1 + uses: actions/download-artifact@v6 with: path: ./artifacts @@ -41,7 +41,7 @@ jobs: run: poetry run -- nox -s project:report -- --format json | tee metrics.json - name: Upload Artifacts - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v5 with: name: metrics.json path: metrics.json diff --git a/.github/workflows/slow-checks-itde.yml b/.github/workflows/slow-checks-itde.yml index 301638b..78f2122 100644 --- a/.github/workflows/slow-checks-itde.yml +++ b/.github/workflows/slow-checks-itde.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 with: python-version: ${{ matrix.python-version }} @@ -44,7 +44,7 @@ jobs: poetry run -- nox -s test:integration -- -s --coverage --db-version ${{ matrix.exasol-version }} --backend=onprem --itde-additional-db-parameter="$JWK_URL" --itde-additional-db-parameter="$TOK_AUD" --itde-additional-db-parameter="$TOK_ISS" - name: Upload Artifacts - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v5 with: name: coverage-python${{ matrix.python-version }}-itde path: .coverage diff --git a/.github/workflows/slow-checks-saas.yml b/.github/workflows/slow-checks-saas.yml index c05fe3a..f2595ba 100644 --- a/.github/workflows/slow-checks-saas.yml +++ b/.github/workflows/slow-checks-saas.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Python & Poetry Environment - uses: exasol/python-toolbox/.github/actions/python-environment@1.6.1 + uses: exasol/python-toolbox/.github/actions/python-environment@v1 with: python-version: ${{ matrix.python-version }} @@ -44,7 +44,7 @@ jobs: run: poetry run -- nox -s test:integration -- -s --coverage --db-version ${{ matrix.exasol-version }} --backend=saas - name: Upload Artifacts - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v5 with: name: coverage-python${{ matrix.python-version }}-saas path: .coverage diff --git a/doc/index.rst b/doc/index.rst index 0dec8d4..2784d3a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,4 +1,4 @@ -Documentation of Exasol MCP Server +Documentation of Exasol MCP Server ----------------------------------- .. grid:: 1 1 3 2 diff --git a/doc/user_guide/open_id_setup.rst b/doc/user_guide/open_id_setup.rst index 9d21320..8bd33b8 100644 --- a/doc/user_guide/open_id_setup.rst +++ b/doc/user_guide/open_id_setup.rst @@ -12,7 +12,7 @@ the tools and resources from unauthorized access. OAuth2 and OpenID Connect are and widely used specifications for resource protection. Exasol MCP server supports OAuth2-based authorization to control access to its own tools, as well as the Exasol database. The authentication options for the database connection are described in -the `Database Connection Setup `__ guide. This section focuses +the :doc:`db_connection_setup` guide. This section focuses on the configuration of the MCP Server authorization. It is assumed that the reader has some familiarity with the basic concepts of OAuth2. @@ -248,7 +248,7 @@ documentation, and in particular to the `Personal Access Token (PAT) `__ -* `Database connection settings `__ -* `Tool settings `__ +* :doc:`open_id_setup` +* :doc:`db_connection_setup` +* :doc:`tool_setup` All settings are described in details in respective sections of the User Guide. @@ -57,5 +57,5 @@ should contain the path to this file, e.g. } } -Please see the `Tool Setup `__ for details on how the MCP Server tools +Please see the :doc:`tool_setup` for details on how the MCP Server tools can be customised. diff --git a/doc/user_guide/tool_setup.rst b/doc/user_guide/tool_setup.rst index bee99f2..f0bd1f1 100644 --- a/doc/user_guide/tool_setup.rst +++ b/doc/user_guide/tool_setup.rst @@ -1,4 +1,4 @@ -Tool setup +Tool Setup ========== This guide provides detailed information on how to configure Exasol MCP Server tools From 26bcce725d9e20df2cb1117effef5e3b4b2b4fe5 Mon Sep 17 00:00:00 2001 From: mibe Date: Mon, 17 Nov 2025 09:58:48 +0000 Subject: [PATCH 18/18] #94: fixed another link --- doc/user_guide/db_connection_setup.rst | 2 +- test/integration/conftest.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/user_guide/db_connection_setup.rst b/doc/user_guide/db_connection_setup.rst index 6cb9132..8d66186 100644 --- a/doc/user_guide/db_connection_setup.rst +++ b/doc/user_guide/db_connection_setup.rst @@ -6,7 +6,7 @@ password and an OpenID token. The MCP server can be deployed in two ways: locally or as a remote HTTP server. In the latter case, the server works in the multiple-user mode, and its tools must be -protected with OAuth2 authorization. Please refer to [OpenID Setup](open_id_setup.md) +protected with OAuth2 authorization. Please refer to :doc:`open_id_setup` guide for details on the OAuth configuration. The choice of the database connection parameters depends on the MCP server deployment mode. This section of the User Guide explains possible deployment options in the context of the database connection and diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 4fee67e..8a719e3 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -359,6 +359,7 @@ def setup_database( ) pyexasol_connection.execute(query=query) + pyexasol_connection.execute(query="COMMIT") yield finally: