From e2df7d11e9feb00b310f00a3ada36973db00d681 Mon Sep 17 00:00:00 2001 From: Sylvain Wallez Date: Tue, 15 Jul 2025 15:51:32 +0200 Subject: [PATCH] Add 2nd Dockerfile for port 8000 --- Cargo.lock | 2 +- Cargo.toml | 4 +-- Dockerfile | 2 +- Dockerfile-8000 | 37 ++++++++++++++++++++++++ README.md | 6 ++-- src/bin/elasticsearch-core-mcp-server.rs | 11 ++++++- src/cli.rs | 4 +-- src/lib.rs | 15 ++++------ 8 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 Dockerfile-8000 diff --git a/Cargo.lock b/Cargo.lock index a6e5a3a..4dcdf6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -458,7 +458,7 @@ dependencies = [ [[package]] name = "elasticsearch-core-mcp-server" -version = "0.4.2" +version = "0.4.3" dependencies = [ "anyhow", "axum", diff --git a/Cargo.toml b/Cargo.toml index 8dc4a19..9331861 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "elasticsearch-core-mcp-server" -version = "0.4.2" +version = "0.4.3" edition = "2024" authors = ["Elastic.co"] license-file = "LICENSE" @@ -22,7 +22,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1" # CLI, config -clap = { version = "4", features = ["derive"] } +clap = { version = "4", features = ["derive", "env"] } dotenvy = "0.15" serde-aux = "4" serde_json5 = "0.2" diff --git a/Dockerfile b/Dockerfile index 25eb997..d4e04c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,5 +27,5 @@ FROM cgr.dev/chainguard/wolfi-base:latest COPY --from=builder /app/target/release/elasticsearch-core-mcp-server /usr/local/bin/elasticsearch-core-mcp-server -EXPOSE 8000/tcp +EXPOSE 8080/tcp ENTRYPOINT ["/usr/local/bin/elasticsearch-core-mcp-server"] diff --git a/Dockerfile-8000 b/Dockerfile-8000 new file mode 100644 index 0000000..10f30f0 --- /dev/null +++ b/Dockerfile-8000 @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and contributors +# SPDX-License-Identifier: Apache-2.0 + +# Custom image to start +# To create a multi-arch image, run: +# docker buildx build --platform linux/amd64,linux/arm64 --tag elasticsearch-core-mcp-server . + +FROM rust:1.88 AS builder + +WORKDIR /app + +COPY Cargo.toml Cargo.lock ./ + +# Cache dependencies +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/app/target \ + mkdir -p ./src/bin && \ + echo "pub fn main() {}" > ./src/bin/elasticsearch-core-mcp-server.rs && \ + cargo build --release + +COPY src ./src/ + +RUN cargo build --release + +#-------------------------------------------------------------------------------------------------- + +FROM debian:stable-slim + +RUN apt-get update && apt install -y libssl-dev && apt clean && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/target/release/elasticsearch-core-mcp-server /usr/local/bin/elasticsearch-core-mcp-server + +ENV HTTP_ADDRESS="0.0.0.0:8000" + +EXPOSE 8000/tcp +ENTRYPOINT ["/usr/local/bin/elasticsearch-core-mcp-server"] +CMD ["http"] diff --git a/README.md b/README.md index ee826c8..3092235 100644 --- a/README.md +++ b/README.md @@ -98,13 +98,13 @@ cluster's API key The MCP server is started in http mode with this command: ```bash -docker run --rm -e ES_URL -p 8000:8000 docker.elastic.co/mcp/elasticsearch http +docker run --rm -e ES_URL -p 8080:8080 docker.elastic.co/mcp/elasticsearch http ``` If for some reason your execution environment doesn't allow passing parameters to the container, they can be passed -using the `CLI_ARGS` environment variable: `docker run --rm -e ES_URL -e CLI_ARGS=http -p 8000:8000...` +using the `CLI_ARGS` environment variable: `docker run --rm -e ES_URL -e CLI_ARGS=http -p 8080:8080...` -The streamable-HTTP endpoint is at `http::8000/mcp`. There's also a health check at `http::8000/ping` +The streamable-HTTP endpoint is at `http::8080/mcp`. There's also a health check at `http::8080/ping` Configuration for Claude Desktop (free edition that only supports the stdio protocol). diff --git a/src/bin/elasticsearch-core-mcp-server.rs b/src/bin/elasticsearch-core-mcp-server.rs index 8312168..6ac06f2 100644 --- a/src/bin/elasticsearch-core-mcp-server.rs +++ b/src/bin/elasticsearch-core-mcp-server.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use std::io::ErrorKind; use clap::Parser; use elasticsearch_core_mcp_server::cli::Cli; use tracing_subscriber::EnvFilter; @@ -22,6 +23,14 @@ use tracing_subscriber::EnvFilter; #[tokio::main] async fn main() -> anyhow::Result<()> { + + // Also accept .env files + match dotenvy::dotenv() { + Err(dotenvy::Error::Io(io_err)) if io_err.kind() == ErrorKind::NotFound => {} + Err(err) => return Err(err)?, + Ok(_) => {} + } + let env_args = std::env::vars().find(|(k, _v)| k == "CLI_ARGS").map(|(_k, v)| v); let cli = if let Some(env_args) = env_args { @@ -43,7 +52,7 @@ async fn main() -> anyhow::Result<()> { .with_ansi(false) .init(); - tracing::info!("Starting MCP server"); + tracing::info!("Elasticsearch MCP server, version {}", env!("CARGO_PKG_VERSION")); cli.run().await } diff --git a/src/cli.rs b/src/cli.rs index 6a45360..7eb0246 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -42,8 +42,8 @@ pub struct HttpCommand { #[clap(short, long)] pub config: Option, - /// Address to listen to [default: 127.0.0.1:8000] - #[clap(long, value_name = "IP_ADDRESS:PORT")] + /// Address to listen to [default: 127.0.0.1:8080] + #[clap(long, value_name = "IP_ADDRESS:PORT", env = "HTTP_ADDRESS")] pub address: Option, /// Also start an SSE server on '/sse' diff --git a/src/lib.rs b/src/lib.rs index b465748..95c811c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,6 @@ use is_container::is_container; use rmcp::transport::stdio; use rmcp::transport::streamable_http_server::session::never::NeverSessionManager; use rmcp::{RoleServer, Service, ServiceExt}; -use std::io::ErrorKind; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; use std::sync::Arc; @@ -45,6 +44,7 @@ impl Cli { } pub async fn run_stdio(cmd: StdioCommand) -> anyhow::Result<()> { + tracing::info!("Starting stdio server"); let handler = setup_services(&cmd.config).await?; let service = handler.serve(stdio()).await.inspect_err(|e| { tracing::error!("serving error: {:?}", e); @@ -64,9 +64,9 @@ pub async fn run_http(cmd: HttpCommand) -> anyhow::Result<()> { let address: SocketAddr = if let Some(addr) = cmd.address { addr } else if is_container() { - SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8000) + SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8080) } else { - SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8000) + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080) }; let ct = HttpProtocol::serve_with_config( @@ -82,18 +82,15 @@ pub async fn run_http(cmd: HttpCommand) -> anyhow::Result<()> { ) .await?; + tracing::info!("Starting http server at address {}", address); + tokio::signal::ctrl_c().await?; ct.cancel(); Ok(()) } pub async fn setup_services(config: &Option) -> anyhow::Result + Clone> { - // Read config file and expand variables, also accepting .env files - match dotenvy::dotenv() { - Err(dotenvy::Error::Io(io_err)) if io_err.kind() == ErrorKind::NotFound => {} - Err(err) => return Err(err)?, - Ok(_) => {} - } + // Read config file and expand variables let config = if let Some(path) = config { std::fs::read_to_string(path)?