This is an automated email from the ASF dual-hosted git repository.
freeoneplus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git
The following commit(s) were added to refs/heads/master by this push:
new a125a2f [fix]Fixed five known issues, including token authentication
and multi-worker operation. (#63)
a125a2f is described below
commit a125a2f5f831595ebfb2814f9fd7519200d90829
Author: Yijia Su <[email protected]>
AuthorDate: Tue Nov 4 14:45:38 2025 +0800
[fix]Fixed five known issues, including token authentication and
multi-worker operation. (#63)
* 0.6.1Version
* fix 0.5.1 schema async bug
* fix security bug
* fix security bug
* Add complete Token, JWT, OAuth authentication system
* Add complete Token, JWT, OAuth authentication system
* Add complete Token, JWT, OAuth authentication system
* Add complete Token, JWT, OAuth authentication system
* Add a controllable MCP Server DB Pool permission authentication system,
connect it with the Doris permission system, and provide it to enterprise-level
applications concurrently with the multi-Worker mode.
* Add Tokens Management
* change version
* fix stdio start bug
* fix stdio start bug
* fix stdio start bug
---
doris_mcp_server/main.py | 16 +++-
doris_mcp_server/utils/db.py | 59 ++++++++++---
doris_mcp_server/utils/query_executor.py | 91 ++++++++++++--------
doris_mcp_server/utils/schema_extractor.py | 69 +++++++++++++--
doris_mcp_server/utils/security.py | 133 ++++++++++++++++++++++++-----
start_server.sh | 15 ++--
6 files changed, 295 insertions(+), 88 deletions(-)
diff --git a/doris_mcp_server/main.py b/doris_mcp_server/main.py
index b28cce7..2630aeb 100644
--- a/doris_mcp_server/main.py
+++ b/doris_mcp_server/main.py
@@ -634,14 +634,24 @@ class DorisServer:
try:
# Extract authentication information
auth_info = await
self._extract_auth_info_from_scope(scope, headers)
-
+
# Authenticate the request
auth_context = await
self.security_manager.authenticate_request(auth_info)
self.logger.info(f"MCP request authenticated:
token_id={auth_context.token_id}, client_ip={auth_context.client_ip}")
-
+
# Store auth context in scope for potential
use by tools/resources
scope["auth_context"] = auth_context
-
+
+ # FIX for Issue #62 Bug 1: Set auth_context in
context variable
+ # This allows tools to access token
information for token-bound database configuration
+ try:
+ from contextvars import ContextVar
+ auth_context_var: ContextVar =
ContextVar('mcp_auth_context', default=None)
+ auth_context_var.set(auth_context)
+ self.logger.debug(f"Set auth_context in
context variable with token: {bool(hasattr(auth_context, 'token') and
auth_context.token)}")
+ except Exception as ctx_error:
+ self.logger.warning(f"Failed to set
auth_context in context variable: {ctx_error}")
+
except Exception as auth_error:
self.logger.error(f"MCP authentication failed:
{auth_error}")
# Return 401 Unauthorized
diff --git a/doris_mcp_server/utils/db.py b/doris_mcp_server/utils/db.py
index 69c769b..7d2acb4 100644
--- a/doris_mcp_server/utils/db.py
+++ b/doris_mcp_server/utils/db.py
@@ -95,12 +95,14 @@ class DorisConnection:
await cursor.execute(sql, params)
# Check if it's a query statement (statement that returns
result set)
+ # FIX for Issue #62 Bug 5: Added WITH support for Common Table
Expressions (CTE)
sql_upper = sql.strip().upper()
- if (sql_upper.startswith("SELECT") or
- sql_upper.startswith("SHOW") or
- sql_upper.startswith("DESCRIBE") or
- sql_upper.startswith("DESC") or
- sql_upper.startswith("EXPLAIN")):
+ if (sql_upper.startswith("SELECT") or
+ sql_upper.startswith("SHOW") or
+ sql_upper.startswith("DESCRIBE") or
+ sql_upper.startswith("DESC") or
+ sql_upper.startswith("EXPLAIN") or
+ sql_upper.startswith("WITH")): # FIX: Support CTE queries
data = await cursor.fetchall()
row_count = len(data)
else:
@@ -250,7 +252,16 @@ class DorisConnectionManager:
self.logger = get_logger(__name__)
self.security_manager = security_manager
self.token_manager = token_manager # Token manager for token-bound DB
config
- self.session_cache = DorisSessionCache(self)
+
+ # FIX for Issue #58 Problem 1: Disable session caching to prevent
connection sharing
+ # Session caching causes multiple threads to share the same MySQL
connection,
+ # leading to race conditions and deadlocks in multi-threaded
environments
+ # By disabling caching, each request gets a fresh connection from the
pool
+ self.session_cache = DorisSessionCache(
+ self,
+ cache_system_session=False, # Disabled to prevent multi-thread
issues
+ cache_user_session=False # Disabled to prevent multi-thread
issues
+ )
# Store original database config for fallback
self.original_db_config = {
@@ -1258,17 +1269,43 @@ class DorisConnectionManager:
async def execute_query(
self, session_id: str, sql: str, params: tuple | None = None,
auth_context=None
) -> QueryResult:
- """Execute query - Simplified Strategy with automatic connection
management"""
+ """Execute query - Simplified Strategy with automatic connection
management
+
+ FIX for Issue #62 Bug 1: Configure token-bound database before query
execution
+ """
connection = None
try:
- # Always get fresh connection from pool
+ # FIX: Configure database for token BEFORE getting connection
+ # This ensures token-bound database configuration is used instead
of global config
+ if auth_context and hasattr(auth_context, 'token') and
auth_context.token:
+ try:
+ success, config_source = await
self.configure_for_token(auth_context.token)
+ if success:
+ self.logger.info(f"Session {session_id}: Using
{config_source} database configuration")
+ else:
+ self.logger.warning(f"Session {session_id}: Token
configuration failed, may use global config")
+ except Exception as token_config_error:
+ # SECURITY: If token should have config but configuration
fails, don't fallback
+ # This prevents privilege escalation (using high-privilege
default user)
+ if self.token_manager:
+ self.logger.error(f"Session {session_id}: Token
database configuration failed: {token_config_error}")
+ raise RuntimeError(
+ f"Failed to configure database for authenticated
token. "
+ f"This is a security measure to prevent using
default high-privilege credentials. "
+ f"Error: {token_config_error}"
+ )
+ else:
+ # No token manager, can use global config
+ self.logger.warning(f"Session {session_id}: No token
manager, using global config")
+
+ # Always get fresh connection from pool (with configured database)
connection = await self.get_connection(session_id)
-
+
# Execute query
result = await connection.execute(sql, params, auth_context)
-
+
return result
-
+
except Exception as e:
self.logger.error(f"Query execution failed for session
{session_id}: {e}")
raise
diff --git a/doris_mcp_server/utils/query_executor.py
b/doris_mcp_server/utils/query_executor.py
index 3d76677..e342a95 100644
--- a/doris_mcp_server/utils/query_executor.py
+++ b/doris_mcp_server/utils/query_executor.py
@@ -541,17 +541,21 @@ class DorisQueryExecutor:
await self.query_cache.clear_all()
async def execute_sql_for_mcp(
- self,
- sql: str,
- limit: int = 1000,
+ self,
+ sql: str,
+ limit: int = 1000,
timeout: int = 30,
session_id: str = "mcp_session",
- user_id: str = "mcp_user"
+ user_id: str = "mcp_user",
+ auth_context = None # FIX for Issue #62 Bug 1: Accept auth_context
with token
) -> Dict[str, Any]:
- """Execute SQL query for MCP interface - unified method"""
+ """Execute SQL query for MCP interface - unified method
+
+ FIX for Issue #62 Bug 1: Now accepts auth_context parameter to support
token-bound database configuration
+ """
max_retries = 2
retry_count = 0
-
+
while retry_count <= max_retries:
try:
if not sql:
@@ -564,14 +568,20 @@ class DorisQueryExecutor:
# Import required security modules
from .security import DorisSecurityManager, AuthContext,
SecurityLevel
- # Create proper auth context with read-only permissions
- auth_context = AuthContext(
- user_id=user_id,
- roles=["read_only_user"], # Restrictive role for MCP
interface
- permissions=["read_data"], # Only read permissions
- session_id=session_id,
- security_level=SecurityLevel.INTERNAL
- )
+ # FIX: Use provided auth_context if available (contains token
for DB config)
+ # Otherwise create default auth context for backward
compatibility
+ if auth_context is None:
+ auth_context = AuthContext(
+ user_id=user_id,
+ roles=["read_only_user"], # Restrictive role for MCP
interface
+ permissions=["read_data"], # Only read permissions
+ session_id=session_id,
+ security_level=SecurityLevel.INTERNAL,
+ token="" # No token in default context
+ )
+ else:
+ # Use provided auth_context (may contain token for
database configuration)
+ self.logger.debug(f"Using provided auth_context with
token: {bool(hasattr(auth_context, 'token') and auth_context.token)}")
# Perform SQL security validation if enabled
if hasattr(self.connection_manager, 'config') and
hasattr(self.connection_manager.config, 'security'):
@@ -579,7 +589,7 @@ class DorisQueryExecutor:
try:
security_manager =
DorisSecurityManager(self.connection_manager.config)
validation_result = await
security_manager.validate_sql_security(sql, auth_context)
-
+
if not validation_result.is_valid:
self.logger.warning(f"SQL security validation
failed for query: {sql[:100]}...")
return {
@@ -877,33 +887,42 @@ class QueryPerformanceMonitor:
# Unified convenience function for MCP integration
async def execute_sql_query(sql: str, connection_manager:
DorisConnectionManager, **kwargs) -> Dict[str, Any]:
"""Execute SQL query - unified convenience function for MCP tools
-
+
This function now includes security validation to ensure safe query
execution.
All queries are validated against the configured security policies before
execution.
+
+ FIX for Issue #62 Bug 1: Now supports auth_context parameter for
token-bound database configuration
+ FIX for Issue #58 Problem 2: Removed executor.close() to prevent
ClosedResourceError in multi-worker mode
"""
try:
# Create query executor with the connection manager's configuration
executor = DorisQueryExecutor(connection_manager)
-
- try:
- # Extract parameters from kwargs or use defaults
- limit = kwargs.get("limit", 1000)
- timeout = kwargs.get("timeout", 30)
- session_id = kwargs.get("session_id", "mcp_session")
- user_id = kwargs.get("user_id", "mcp_user")
-
- # The execute_sql_for_mcp method now includes security validation
- result = await executor.execute_sql_for_mcp(
- sql=sql,
- limit=limit,
- timeout=timeout,
- session_id=session_id,
- user_id=user_id
- )
- return result
- finally:
- await executor.close()
-
+
+ # Extract parameters from kwargs or use defaults
+ limit = kwargs.get("limit", 1000)
+ timeout = kwargs.get("timeout", 30)
+ session_id = kwargs.get("session_id", "mcp_session")
+ user_id = kwargs.get("user_id", "mcp_user")
+ auth_context = kwargs.get("auth_context", None) # FIX: Extract
auth_context
+
+ # The execute_sql_for_mcp method now includes security validation
+ result = await executor.execute_sql_for_mcp(
+ sql=sql,
+ limit=limit,
+ timeout=timeout,
+ session_id=session_id,
+ user_id=user_id,
+ auth_context=auth_context # FIX: Pass auth_context with token
+ )
+
+ # FIX for Issue #58 Problem 2: Do NOT close executor here
+ # In multi-worker mode, closing here causes ClosedResourceError
+ # The executor's resources (cache, background tasks) will be managed
+ # by the connection_manager lifecycle and Python's garbage collection
+ # This prevents premature cleanup while MCP session manager is still
processing
+
+ return result
+
except Exception as e:
return {
"success": False,
diff --git a/doris_mcp_server/utils/schema_extractor.py
b/doris_mcp_server/utils/schema_extractor.py
index 2f0fb79..78eb524 100644
--- a/doris_mcp_server/utils/schema_extractor.py
+++ b/doris_mcp_server/utils/schema_extractor.py
@@ -1454,32 +1454,83 @@ class MetadataExtractor:
return response_data
async def exec_query_for_mcp(
- self,
- sql: str,
- db_name: str = None,
- catalog_name: str = None,
- max_rows: int = 100,
+ self,
+ sql: str,
+ db_name: str = None,
+ catalog_name: str = None,
+ max_rows: int = 100,
timeout: int = 30
) -> Dict[str, Any]:
"""
Execute SQL query and return results, supports catalog federation
queries
Unified interface for MCP tools
+
+ FIX for Issue #62 Bug 1: Now retrieves auth_context from context
variable to support token-bound database configuration
+ FIX for Issue #62 Bug 3: Now uses db_name and catalog_name parameters
to switch database context
"""
logger.info(f"Executing SQL query: {sql}, DB: {db_name}, Catalog:
{catalog_name}, MaxRows: {max_rows}, Timeout: {timeout}")
-
+
try:
if not sql:
return self._format_response(success=False, error="No SQL
statement provided", message="Please provide SQL statement to execute")
+ # FIX for Issue #62 Bug 3: Build context switching SQL if db_name
or catalog_name is specified
+ final_sql = sql
+ if catalog_name or db_name:
+ context_statements = []
+
+ if catalog_name:
+ # Switch to specified catalog
+ context_statements.append(f"USE CATALOG `{catalog_name}`")
+ logger.debug(f"Switching to catalog: {catalog_name}")
+
+ if db_name:
+ # Switch to specified database
+ if catalog_name:
+ context_statements.append(f"USE
`{catalog_name}`.`{db_name}`")
+ else:
+ context_statements.append(f"USE `{db_name}`")
+ logger.debug(f"Switching to database: {db_name}")
+
+ # Combine context switching with original SQL
+ if context_statements:
+ # Remove trailing semicolon from context statements if
present
+ context_sql = "; ".join(context_statements)
+ # Ensure original SQL doesn't start with semicolon
+ sql_clean = sql.lstrip(";").strip()
+ final_sql = f"{context_sql}; {sql_clean}"
+ logger.debug(f"Modified SQL with context switching:
{final_sql[:200]}...")
+
+ # FIX: Try to get auth_context from context variable (set by HTTP
middleware)
+ # This allows token-bound database configuration to work
+ auth_context = None
+ try:
+ from contextvars import ContextVar
+ from .security import AuthContext
+
+ # Try to get auth_context from context variable
+ # This will be set by the HTTP request handler in main.py
+ auth_context_var: ContextVar = ContextVar('mcp_auth_context',
default=None)
+ auth_context = auth_context_var.get()
+
+ if auth_context:
+ logger.debug(f"Retrieved auth_context from context
variable with token: {bool(hasattr(auth_context, 'token') and
auth_context.token)}")
+ else:
+ logger.debug("No auth_context found in context variable,
using default")
+ except Exception as ctx_error:
+ logger.debug(f"Could not retrieve auth_context from context
variable: {ctx_error}")
+ auth_context = None
+
# Import query executor
from .query_executor import execute_sql_query
- # Call execute_sql_query to execute query
+ # Call execute_sql_query to execute query with auth_context
exec_result = await execute_sql_query(
- sql=sql,
+ sql=final_sql, # Use modified SQL with context switching
connection_manager=self.connection_manager,
limit=max_rows,
- timeout=timeout
+ timeout=timeout,
+ auth_context=auth_context # FIX: Pass auth_context with token
)
return exec_result
diff --git a/doris_mcp_server/utils/security.py
b/doris_mcp_server/utils/security.py
index 1497db2..95471e1 100644
--- a/doris_mcp_server/utils/security.py
+++ b/doris_mcp_server/utils/security.py
@@ -939,28 +939,69 @@ class SQLSecurityValidator:
async def _check_sql_injection(
self, sql: str, parsed: Statement
) -> ValidationResult:
- """Check SQL injection risks"""
- # Check common SQL injection patterns
+ """Check SQL injection risks with improved pattern detection
+
+ FIX for Issue #62 Bug 2: Improved patterns to reduce false positives
+ Now better distinguishes between legitimate SQL (like BETWEEN...AND)
and injection attempts
+ """
+ # Improved injection patterns that are more specific and less prone to
false positives
injection_patterns = [
-
r"(?i)(?<![A-Za-z0-9_])(union|select|insert|update|delete|drop|create|alter)(?![A-Za-z0-9_])\s+[\s\S]*?\s+(?<![A-Za-z0-9_])(union|select|insert|update|delete|drop|create|alter)(?![A-Za-z0-9_])",
- r"(\s|^)(or|and)\s+\d+\s*=\s*\d+",
- r"(\s|^)(or|and)\s+['\"].*['\"]",
- r";\s*(drop|delete|truncate|alter|create)",
- r"(exec|execute|sp_|xp_)",
- r"(script|javascript|vbscript)",
- r"(char|ascii|substring|concat)\s*\(",
+ # Stacked queries with dangerous operations (true injection risk)
+ r";\s*(DROP|DELETE|TRUNCATE|ALTER|CREATE|INSERT|UPDATE)\s+",
+
+ # UNION-based injection (but allow legitimate UNION queries)
+ # Only flag if UNION is followed by suspicious patterns like
SELECT with WHERE 1=1
+ r"UNION\s+(ALL\s+)?SELECT\s+.*\s+(WHERE|AND|OR)\s+\d+\s*=\s*\d+",
+
+ # Boolean-based blind injection with comments (true injection
pattern)
+ r"(WHERE|AND|OR)\s+\d+\s*=\s*\d+\s*(--|#|/\*)",
+
+ # Quote-based injection attempts (but not in legitimate strings)
+ r"(WHERE|AND|OR)\s+(['\"])[^\2]*\2\s*=\s*\2[^\2]*\2",
+
+ # Time-based blind injection
+ r"(SLEEP|WAITFOR|BENCHMARK)\s*\(",
+
+ # System stored procedure injection
+ r"(EXEC|EXECUTE|SP_|XP_)\s*\(",
+
+ # Script injection attempts
+ r"<\s*(SCRIPT|JAVASCRIPT|VBSCRIPT)",
]
- sql_lower = sql.lower()
+ # FIX: Don't flag legitimate SQL functions and keywords
+ # These patterns are too broad and cause false positives:
+ # - REMOVED: r"(char|ascii|substring|concat)\s*\(" - These are
legitimate SQL functions
+ # - REMOVED: r"(\s|^)(or|and)\s+\d+\s*=\s*\d+" - This flags
BETWEEN...AND constructs
+ # - REMOVED: r"(\s|^)(or|and)\s+['\"].*['\"]" - This is too broad
+
+ sql_upper = sql.upper()
+
+ # Special case: Allow BETWEEN...AND which is legitimate SQL
+ # This prevents false positives like "WHERE dt BETWEEN '2025-01-01'
AND '2025-01-31'"
+ if "BETWEEN" in sql_upper and "AND" in sql_upper:
+ # This is likely a BETWEEN clause, not injection
+ # Check if AND appears in a BETWEEN context
+ between_pattern = r"BETWEEN\s+[^\s]+\s+AND\s+[^\s]+"
+ if re.search(between_pattern, sql_upper, re.IGNORECASE):
+ # Remove BETWEEN clauses before checking other patterns
+ sql_cleaned = re.sub(between_pattern, "BETWEEN_CLAUSE",
sql_upper, flags=re.IGNORECASE)
+ sql_to_check = sql_cleaned
+ else:
+ sql_to_check = sql_upper
+ else:
+ sql_to_check = sql_upper
+
for pattern in injection_patterns:
- if re.search(pattern, sql_lower, re.IGNORECASE):
+ if re.search(pattern, sql_to_check, re.IGNORECASE):
+ self.logger.warning(f"Potential SQL injection pattern
detected: {pattern}")
return ValidationResult(
is_valid=False,
error_message="Potential SQL injection risk detected",
risk_level="high",
)
- # Check suspicious quotes and comments
+ # Check suspicious quotes and comments (with improved detection)
if self._has_suspicious_quotes_or_comments(sql):
return ValidationResult(
is_valid=False,
@@ -971,19 +1012,67 @@ class SQLSecurityValidator:
return ValidationResult(is_valid=True)
def _has_suspicious_quotes_or_comments(self, sql: str) -> bool:
- """Check suspicious quote and comment patterns"""
- # Check unmatched quotes
- single_quotes = sql.count("'")
- double_quotes = sql.count('"')
+ """Check suspicious quote and comment patterns with improved detection
- if single_quotes % 2 != 0 or double_quotes % 2 != 0:
- return True
+ FIX for Issue #62 Bug 2: Improved detection to reduce false positives
+ Now distinguishes between legitimate comments/strings and injection
attempts
+ """
+ try:
+ # Use sqlparse to parse the SQL and distinguish between code and
comments/strings
+ import sqlparse
+ from sqlparse.tokens import Comment, String
+
+ # Parse the SQL
+ parsed = sqlparse.parse(sql)
+ if not parsed:
+ # If parsing fails, be conservative
+ return True
- # Check SQL comments
- if "--" in sql or "/*" in sql:
- return True
+ statement = parsed[0]
+
+ # Check for unmatched quotes ONLY in non-string tokens
+ # This prevents false positives from legitimate string content
+ non_string_content = []
+ has_string_tokens = False
+
+ for token in statement.flatten():
+ if token.ttype in (String.Single, String.Double):
+ has_string_tokens = True
+ # Skip string content - quotes inside strings are
legitimate
+ continue
+ elif token.ttype in (Comment.Single, Comment.Multi):
+ # Comments are generally OK, but check for suspicious
injection patterns
+ comment_value = str(token).lower()
+ # Check if comment contains dangerous SQL keywords
+ dangerous_in_comments = ['drop', 'delete', 'insert',
'update', 'union', 'exec', 'execute']
+ if any(keyword in comment_value for keyword in
dangerous_in_comments):
+ self.logger.warning(f"Suspicious SQL keyword in
comment: {token}")
+ return True
+ # Normal comments are OK
+ continue
+ else:
+ # Accumulate non-string, non-comment content
+ non_string_content.append(str(token))
- return False
+ # Check for unmatched quotes in non-string content
+ non_string_text = ''.join(non_string_content)
+ single_quotes = non_string_text.count("'")
+ double_quotes = non_string_text.count('"')
+
+ # Only flag if there are unmatched quotes in actual SQL code (not
in strings)
+ if single_quotes % 2 != 0 or double_quotes % 2 != 0:
+ return True
+
+ # FIX: Don't flag legitimate SQL comments
+ # Comments are OK as long as they don't contain dangerous patterns
(already checked above)
+
+ return False
+
+ except Exception as e:
+ self.logger.debug(f"SQL parsing error in quote/comment check: {e}")
+ # On parsing error, fall back to conservative check
+ # But be more lenient than before
+ return False # Don't flag on parse errors to reduce false
positives
async def _check_blocked_keywords(self, parsed: Statement) ->
ValidationResult:
"""Check blocked keywords"""
diff --git a/start_server.sh b/start_server.sh
index 18284af..56290aa 100755
--- a/start_server.sh
+++ b/start_server.sh
@@ -64,9 +64,10 @@ else
fi
# Set HTTP-specific environment variables
+# FIX for Issue #62 Bug 4: Use SERVER_PORT instead of MCP_PORT for consistency
with code
export MCP_TRANSPORT_TYPE="http"
export MCP_HOST="${MCP_HOST:-0.0.0.0}"
-export MCP_PORT="${MCP_PORT:-3000}"
+export SERVER_PORT="${SERVER_PORT:-3000}" # Changed from MCP_PORT to
SERVER_PORT
export WORKERS="${WORKERS:-1}"
export ALLOWED_ORIGINS="${ALLOWED_ORIGINS:-*}"
export LOG_LEVEL="${LOG_LEVEL:-info}"
@@ -77,15 +78,15 @@ export MCP_DEBUG_ADAPTER="true"
export PYTHONPATH="$(pwd):$PYTHONPATH"
echo -e "${GREEN}Starting MCP server (Streamable HTTP mode)...${NC}"
-echo -e "${YELLOW}Service will run on http://${MCP_HOST}:${MCP_PORT}/mcp${NC}"
-echo -e "${YELLOW}Health Check: http://${MCP_HOST}:${MCP_PORT}/health${NC}"
-echo -e "${YELLOW}MCP Endpoint: http://${MCP_HOST}:${MCP_PORT}/mcp${NC}"
-echo -e "${YELLOW}Local access: http://localhost:${MCP_PORT}/mcp${NC}"
+echo -e "${YELLOW}Service will run on
http://${MCP_HOST}:${SERVER_PORT}/mcp${NC}"
+echo -e "${YELLOW}Health Check: http://${MCP_HOST}:${SERVER_PORT}/health${NC}"
+echo -e "${YELLOW}MCP Endpoint: http://${MCP_HOST}:${SERVER_PORT}/mcp${NC}"
+echo -e "${YELLOW}Local access: http://localhost:${SERVER_PORT}/mcp${NC}"
echo -e "${YELLOW}Workers: ${WORKERS}${NC}"
echo -e "${YELLOW}Use Ctrl+C to stop the service${NC}"
# Start the server in HTTP mode (Streamable HTTP)
-python -m doris_mcp_server.main --transport http --host ${MCP_HOST} --port
${MCP_PORT} --workers ${WORKERS}
+python -m doris_mcp_server.main --transport http --host ${MCP_HOST} --port
${SERVER_PORT} --workers ${WORKERS}
# Check exit status
if [ $? -ne 0 ]; then
@@ -97,4 +98,4 @@ fi
echo -e "${YELLOW}Tip: If the page displays abnormally, please clear your
browser cache or use incognito mode${NC}"
echo -e "${YELLOW}Chrome browser clear cache shortcut: Ctrl+Shift+Del
(Windows) or Cmd+Shift+Del (Mac)${NC}"
echo -e "${CYAN}For testing HTTP endpoints, you can use:${NC}"
-echo -e "${CYAN} curl -X POST http://localhost:${MCP_PORT}/mcp -H
'Content-Type: application/json' -d '{\"method\":\"tools/list\"}'${NC}"
\ No newline at end of file
+echo -e "${CYAN} curl -X POST http://localhost:${SERVER_PORT}/mcp -H
'Content-Type: application/json' -d '{\"method\":\"tools/list\"}'${NC}"
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]