Skip to content

Commit 1330c6a

Browse files
20001020ycxjunhaoliaokirkrodrigues
authored
feat(clp-package): Add MCP Server config interface and Docker Compose orchestration. (#1481)
Co-authored-by: Junhao Liao <[email protected]> Co-authored-by: Junhao Liao <[email protected]> Co-authored-by: kirkrodrigues <[email protected]>
1 parent 6abc934 commit 1330c6a

File tree

9 files changed

+132
-6
lines changed

9 files changed

+132
-6
lines changed

components/clp-mcp-server/clp_mcp_server/clp_mcp_server.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22

33
import ipaddress
44
import logging
5+
import os
56
import socket
67
import sys
78
from pathlib import Path
89

910
import click
10-
from clp_py_utils.clp_config import CLPConfig
11+
from clp_py_utils.clp_config import CLPConfig, MCP_SERVER_COMPONENT_NAME
12+
from clp_py_utils.clp_logging import get_logger, get_logging_formatter, set_logging_level
1113
from clp_py_utils.core import read_yaml_config_file
1214
from pydantic import ValidationError
1315

1416
from .server import create_mcp_server
1517

18+
logger = get_logger(MCP_SERVER_COMPONENT_NAME)
19+
1620

1721
@click.command()
1822
@click.option(
@@ -34,10 +38,12 @@ def main(host: str, port: int, config_path: Path) -> int:
3438
:param config_path: The path to server's configuration file.
3539
:return: Exit code (0 for success, non-zero for failure).
3640
"""
37-
logging.basicConfig(
38-
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
39-
)
40-
logger = logging.getLogger(__name__)
41+
# Setup logging to file
42+
log_file_path = Path(os.getenv("CLP_LOGS_DIR")) / "mcp_server.log"
43+
logging_file_handler = logging.FileHandler(filename=log_file_path, encoding="utf-8")
44+
logging_file_handler.setFormatter(get_logging_formatter())
45+
logger.addHandler(logging_file_handler)
46+
set_logging_level(logger, os.getenv("CLP_LOGGING_LEVEL"))
4147

4248
exit_code = 0
4349

components/clp-mcp-server/clp_mcp_server/server/server.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from clp_py_utils.clp_config import CLPConfig
66
from fastmcp import Context, FastMCP
7+
from starlette.requests import Request
8+
from starlette.responses import PlainTextResponse
79

810
from clp_mcp_server.clp_connector import ClpConnector
911

@@ -161,4 +163,14 @@ async def search_by_kql_with_timestamp_range(
161163

162164
return await _execute_kql_query(ctx.session_id, kql_query, begin_ts, end_ts)
163165

166+
@mcp.custom_route("/health", methods=["GET"])
167+
async def health_check(_request: Request) -> PlainTextResponse:
168+
"""
169+
Health check endpoint.
170+
171+
:param _request: An HTTP request object.
172+
:return: A plain text response indicating server is healthy.
173+
"""
174+
return PlainTextResponse("OK")
175+
164176
return mcp

components/clp-package-utils/clp_package_utils/controller.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
DB_COMPONENT_NAME,
2020
DeploymentType,
2121
GARBAGE_COLLECTOR_COMPONENT_NAME,
22+
MCP_SERVER_COMPONENT_NAME,
2223
QUERY_JOBS_TABLE_NAME,
2324
QUERY_SCHEDULER_COMPONENT_NAME,
2425
QUERY_WORKER_COMPONENT_NAME,
@@ -47,6 +48,7 @@
4748
get_clp_home,
4849
is_retention_period_configured,
4950
validate_db_config,
51+
validate_mcp_server_config,
5052
validate_queue_config,
5153
validate_redis_config,
5254
validate_results_cache_config,
@@ -528,6 +530,37 @@ def _set_up_env_for_webui(self, container_clp_config: CLPConfig) -> EnvVarsDict:
528530

529531
return env_vars
530532

533+
def _set_up_env_for_mcp_server(self) -> EnvVarsDict:
534+
"""
535+
Sets up environment variables and directories for the MCP server component.
536+
537+
:return: Dictionary of environment variables necessary to launch the component.
538+
"""
539+
component_name = MCP_SERVER_COMPONENT_NAME
540+
if self._clp_config.mcp_server is None:
541+
logger.info(f"The MCP Server is not configured, skipping {component_name} creation...")
542+
return EnvVarsDict()
543+
logger.info(f"Setting up environment for {component_name}...")
544+
545+
logs_dir = self._clp_config.logs_directory / component_name
546+
validate_mcp_server_config(self._clp_config, logs_dir)
547+
logs_dir.mkdir(parents=True, exist_ok=True)
548+
549+
env_vars = EnvVarsDict()
550+
551+
# Connection config
552+
env_vars |= {
553+
"CLP_MCP_HOST": _get_ip_from_hostname(self._clp_config.mcp_server.host),
554+
"CLP_MCP_PORT": str(self._clp_config.mcp_server.port),
555+
}
556+
557+
# Logging config
558+
env_vars |= {
559+
"CLP_MCP_LOGGING_LEVEL": self._clp_config.mcp_server.logging_level,
560+
}
561+
562+
return env_vars
563+
531564
def _set_up_env_for_garbage_collector(self) -> EnvVarsDict:
532565
"""
533566
Sets up environment variables for the garbage collector component.
@@ -627,6 +660,8 @@ def start(self) -> None:
627660
cmd = ["docker", "compose", "--project-name", self._project_name]
628661
if deployment_type == DeploymentType.BASE:
629662
cmd += ["--file", "docker-compose.base.yaml"]
663+
if self._clp_config.mcp_server is not None:
664+
cmd += ["--profile", "mcp"]
630665
cmd += ["up", "--detach", "--wait"]
631666
subprocess.run(
632667
cmd,
@@ -750,6 +785,7 @@ def _set_up_env(self) -> None:
750785
env_vars |= self._set_up_env_for_query_worker(num_workers)
751786
env_vars |= self._set_up_env_for_reducer(num_workers)
752787
env_vars |= self._set_up_env_for_webui(container_clp_config)
788+
env_vars |= self._set_up_env_for_mcp_server()
753789
env_vars |= self._set_up_env_for_garbage_collector()
754790

755791
# Write the environment variables to the `.env` file.

components/clp-package-utils/clp_package_utils/general.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
CONTAINER_CLP_HOME,
2121
CONTAINER_INPUT_LOGS_ROOT_DIR,
2222
DB_COMPONENT_NAME,
23+
MCP_SERVER_COMPONENT_NAME,
2324
QueryEngine,
2425
QUEUE_COMPONENT_NAME,
2526
REDIS_COMPONENT_NAME,
@@ -584,6 +585,14 @@ def validate_webui_config(
584585
validate_port(f"{WEBUI_COMPONENT_NAME}.port", clp_config.webui.host, clp_config.webui.port)
585586

586587

588+
def validate_mcp_server_config(clp_config: CLPConfig, logs_dir: pathlib.Path):
589+
_validate_log_directory(logs_dir, MCP_SERVER_COMPONENT_NAME)
590+
591+
validate_port(
592+
f"{MCP_SERVER_COMPONENT_NAME}.port", clp_config.mcp_server.host, clp_config.mcp_server.port
593+
)
594+
595+
587596
def validate_path_for_container_mount(path: pathlib.Path) -> None:
588597
RESTRICTED_PREFIXES: List[pathlib.Path] = [
589598
CONTAINER_AWS_CONFIG_DIRECTORY,

components/clp-py-utils/clp_py_utils/clp_config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
COMPRESSION_WORKER_COMPONENT_NAME = "compression_worker"
3636
QUERY_WORKER_COMPONENT_NAME = "query_worker"
3737
WEBUI_COMPONENT_NAME = "webui"
38+
MCP_SERVER_COMPONENT_NAME = "mcp_server"
3839
GARBAGE_COLLECTOR_COMPONENT_NAME = "garbage_collector"
3940

4041
# Action names
@@ -575,6 +576,14 @@ class SweepInterval(BaseModel):
575576
search_result: PositiveInt = 30
576577

577578

579+
class McpServer(BaseModel):
580+
DEFAULT_PORT: ClassVar[int] = 8000
581+
582+
host: DomainStr = "localhost"
583+
port: Port = DEFAULT_PORT
584+
logging_level: LoggingLevel = "INFO"
585+
586+
578587
class GarbageCollector(BaseModel):
579588
logging_level: LoggingLevel = "INFO"
580589
sweep_interval: SweepInterval = SweepInterval()
@@ -611,6 +620,7 @@ class CLPConfig(BaseModel):
611620
garbage_collector: GarbageCollector = GarbageCollector()
612621
credentials_file_path: SerializablePath = CLP_DEFAULT_CREDENTIALS_FILE_PATH
613622

623+
mcp_server: Optional[McpServer] = None
614624
presto: Optional[Presto] = None
615625

616626
archive_output: ArchiveOutput = ArchiveOutput()

components/package-template/src/etc/clp-config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@
6868
# results_metadata_collection_name: "results-metadata"
6969
# rate_limit: 1000
7070
#
71+
#mcp_server:
72+
# host: "localhost"
73+
# port: 8000
74+
# logging_level: "INFO"
75+
#
7176
## Where archives should be output to
7277
#archive_output:
7378
# storage:

docs/src/dev-docs/design-deployment-orchestration.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ graph LR
4545
query_worker["query-worker"]
4646
reducer["reducer"]
4747
webui["webui"]
48+
mcp_server["mcp-server"]
49+
4850
garbage_collector["garbage-collector"]
4951

5052
%% One-time jobs
@@ -64,6 +66,8 @@ graph LR
6466
results_cache_indices_creator -->|completed_successfully| reducer
6567
db_table_creator -->|completed_successfully| webui
6668
results_cache_indices_creator -->|completed_successfully| webui
69+
db_table_creator -->|completed_successfully| mcp_server
70+
results_cache_indices_creator -->|completed_successfully| mcp_server
6771
db_table_creator -->|completed_successfully| garbage_collector
6872
results_cache_indices_creator -->|completed_successfully| garbage_collector
6973

@@ -94,7 +98,11 @@ graph LR
9498
webui
9599
garbage_collector
96100
end
97-
:::
101+
102+
subgraph AI
103+
mcp_server
104+
end
105+
98106

99107
+++
100108
**Figure 1**: Orchestration architecture of the services in the CLP package.
@@ -118,6 +126,7 @@ graph LR
118126
| query_worker | Worker processes for search/aggregation jobs |
119127
| reducer | Reducers for performing the final stages of aggregation jobs |
120128
| webui | Web server for the UI |
129+
| mcp_server | MCP server for AI agent to access CLP functionalities |
121130
| garbage_collector | Process to manage data retention |
122131

123132
:::

tools/deployment/package/docker-compose.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,41 @@ services:
125125
"--concurrency", "${CLP_REDUCER_CONCURRENCY:-1}",
126126
"--upsert-interval", "${CLP_REDUCER_UPSERT_INTERVAL:-100}"
127127
]
128+
129+
mcp-server:
130+
<<: *service_defaults
131+
hostname: "mcp_server"
132+
profiles: ["mcp"]
133+
environment:
134+
CLP_LOGGING_LEVEL: "${CLP_MCP_LOGGING_LEVEL:-INFO}"
135+
CLP_LOGS_DIR: "/var/log/mcp_server"
136+
CLP_DB_USER: "${CLP_DB_USER}"
137+
CLP_DB_PASS: "${CLP_DB_PASS}"
138+
PYTHONPATH: "/opt/clp/lib/python3/site-packages"
139+
ports:
140+
- host_ip: "${CLP_MCP_HOST:-127.0.0.1}"
141+
published: "${CLP_MCP_PORT:-8000}"
142+
target: 8000
143+
volumes:
144+
- *volume_clp_config_readonly
145+
- *volume_clp_logs
146+
depends_on:
147+
db-table-creator:
148+
condition: "service_completed_successfully"
149+
results-cache-indices-creator:
150+
condition: "service_completed_successfully"
151+
command: [
152+
"python3", "-u",
153+
"-m", "clp_mcp_server.clp_mcp_server",
154+
"--host", "mcp_server",
155+
"--port", "8000",
156+
"--config-path", "/etc/clp-config.yml",
157+
]
158+
healthcheck:
159+
<<: *healthcheck_defaults
160+
test: [
161+
"CMD",
162+
"curl",
163+
"-f",
164+
"http://mcp_server:8000/health"
165+
]

tools/docker-images/clp-package/setup-scripts/install-prebuilt-packages.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ set -o pipefail
66
apt-get update
77
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
88
ca-certificates \
9+
curl \
910
libcurl4 \
1011
libmariadb3 \
1112
python3

0 commit comments

Comments
 (0)