Skip to content

Commit adcf273

Browse files
committed
Refine configuration, and add a default builtin one.
1 parent 4053bd7 commit adcf273

File tree

7 files changed

+60
-16
lines changed

7 files changed

+60
-16
lines changed

crates/elastic-mcp/src/bin/start_http.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub async fn main() -> anyhow::Result<()> {
2424
println!("Current directory: {:?}", std::env::current_dir()?);
2525

2626
elastic_mcp::run_http(HttpCommand {
27-
config: "elastic-mcp.json5".parse()?,
27+
config: Some("elastic-mcp.json5".parse()?),
2828
address: "127.0.0.1:8080".parse()?,
2929
sse: true,
3030
})

crates/elastic-mcp/src/cli.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ pub enum Command {
3939
#[derive(Debug, Args)]
4040
pub struct HttpCommand {
4141
/// Config file
42-
#[clap(short, long, default_value = "elastic-mcp.json5")]
43-
pub config: PathBuf,
42+
#[clap(short, long)]
43+
pub config: Option<PathBuf>,
4444

4545
/// Address to listen to
4646
#[clap(long, value_name = "IP_ADDRESS:PORT", default_value = "127.0.0.1:8080")]
@@ -55,8 +55,8 @@ pub struct HttpCommand {
5555
#[derive(Debug, Args)]
5656
pub struct StdioCommand {
5757
/// Config file
58-
#[clap(short, long, default_value = "elastic-mcp.json5")]
59-
pub config: PathBuf,
58+
#[clap(short, long)]
59+
pub config: Option<PathBuf>,
6060
}
6161

6262
//---------------------------------------------------------------

crates/elastic-mcp/src/lib.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ use rmcp::transport::stdio;
2828
use rmcp::transport::streamable_http_server::session::never::NeverSessionManager;
2929
use rmcp::{RoleServer, Service, ServiceExt};
3030
use std::io::ErrorKind;
31-
use std::path::Path;
31+
use std::path::PathBuf;
3232
use std::sync::Arc;
3333
use tokio::select;
3434
use tokio_util::sync::CancellationToken;
3535

3636
pub async fn run_stdio(cmd: StdioCommand) -> anyhow::Result<()> {
37-
let (ct, handler) = crate::setup_services(&cmd.config).await?;
37+
let (ct, handler) = setup_services(&cmd.config).await?;
3838
let service = handler.serve(stdio()).await.inspect_err(|e| {
3939
tracing::error!("serving error: {:?}", e);
4040
})?;
@@ -69,15 +69,28 @@ pub async fn run_http(cmd: HttpCommand) -> anyhow::Result<()> {
6969
Ok(())
7070
}
7171

72-
async fn setup_services(config: &Path) -> anyhow::Result<(CancellationToken, impl Service<RoleServer> + Clone)> {
72+
async fn setup_services(config: &Option<PathBuf>) -> anyhow::Result<(CancellationToken, impl Service<RoleServer> + Clone)> {
7373
// Read config file and expand variables, also accepting .env files
7474
match dotenvy::dotenv() {
7575
Err(dotenvy::Error::Io(io_err)) if io_err.kind() == ErrorKind::NotFound => {}
7676
Err(err) => return Err(err)?,
7777
Ok(_) => {}
7878
}
7979

80-
let config = std::fs::read_to_string(config)?;
80+
let config = if let Some(path) = config {
81+
std::fs::read_to_string(path)?
82+
} else {
83+
// Built-in default configuration, based on env variables.
84+
r#"{
85+
"elasticsearch": {
86+
"url": "${ES_URL}",
87+
"api_key": "${ES_API_KEY:}",
88+
"username": "${ES_LOGIN:}",
89+
"password": "${ES_PASSWORD:}",
90+
"ssl_skip_verify": "${ES_SSL_SKIP_VERIFY:false}"
91+
}
92+
}"#.to_string()
93+
};
8194

8295
// Expand environment variables in the config file
8396
let config = interpolator::interpolate_from_env(config)?;

crates/elastic-mcp/src/servers/elasticsearch/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
mod base_tools;
1919
mod query_templates;
2020

21+
use crate::utils::none_if_empty_string;
2122
use crate::servers::IncludeExclude;
2223
use elasticsearch::Elasticsearch;
2324
use elasticsearch::auth::Credentials;
@@ -34,21 +35,30 @@ use serde::{Deserialize, Serialize};
3435
use std::borrow::Cow;
3536
use std::collections::HashMap;
3637
use std::sync::Arc;
38+
use elasticsearch::cert::CertificateValidation;
39+
use serde_aux::field_attributes::deserialize_bool_from_anything;
3740

3841
#[derive(Debug, Serialize, Deserialize)]
3942
pub struct ElasticsearchMcpConfig {
4043
/// Cluster URL
4144
pub url: String,
4245

4346
/// API key
47+
#[serde(default, deserialize_with = "none_if_empty_string")]
4448
pub api_key: Option<String>,
4549

4650
/// Login
51+
#[serde(default, deserialize_with = "none_if_empty_string")]
4752
pub login: Option<String>,
4853

4954
/// Password
55+
#[serde(default, deserialize_with = "none_if_empty_string")]
5056
pub password: Option<String>,
5157

58+
/// Should we skip SSL certificate verification?
59+
#[serde(default, deserialize_with = "deserialize_bool_from_anything")]
60+
pub ssl_skip_verify: bool,
61+
5262
/// Search templates to expose as tools or resources
5363
#[serde(default)]
5464
pub tools: Tools,
@@ -197,6 +207,9 @@ impl ElasticsearchMcp {
197207
if let Some(creds) = creds {
198208
transport = transport.auth(creds);
199209
}
210+
if config.ssl_skip_verify {
211+
transport = transport.cert_validation(CertificateValidation::None)
212+
}
200213
let transport = transport.build()?;
201214
let es_client = Elasticsearch::new(transport);
202215

crates/elastic-mcp/src/utils/interpolator.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use thiserror::Error;
2121
#[derive(Error, Debug)]
2222
#[error("Invalid configuration template: {reason} at {line}:{char}")]
2323
pub struct InterpolationError {
24-
pub reason: &'static str,
24+
pub reason: String,
2525
pub line: usize,
2626
pub char: usize,
2727
}
@@ -49,7 +49,7 @@ pub fn interpolate(s: String, lookup: impl Fn(&str) -> Option<String>) -> Result
4949
}
5050
let mut char_no = 0;
5151

52-
let err = |char_no: usize, msg: &'static str| InterpolationError {
52+
let err = |char_no: usize, msg: String| InterpolationError {
5353
reason: msg,
5454
line: line_no + 1, // editors (and humans) are 1-based
5555
char: char_no,
@@ -67,14 +67,14 @@ pub fn interpolate(s: String, lookup: impl Fn(&str) -> Option<String>) -> Result
6767
let value = if let Some((name, default)) = expr.split_once(':') {
6868
lookup(name).unwrap_or(default.to_string())
6969
} else {
70-
lookup(expr).ok_or_else(|| err(char_no, "unknown variable"))?
70+
lookup(expr).ok_or_else(|| err(char_no, format!("unknown variable '{expr}'")))?
7171
};
7272
result.push_str(&value);
7373

7474
char_no += expr.len() + CLOSE_LEN;
7575
line = &line[expr.len() + CLOSE_LEN..];
7676
} else {
77-
return Err(err(char_no, "missing closing braces"));
77+
return Err(err(char_no, "missing closing braces".to_string()));
7878
}
7979
}
8080
result.push_str(line);

crates/elastic-mcp/src/utils/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,21 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18+
use serde::{Deserialize, Deserializer};
19+
1820
pub mod interpolator;
1921
pub mod rmcp_ext;
22+
23+
24+
/// Deserialize a string, and return `None` if it's empty. Useful for configuration fields like
25+
/// `"foo": "${SOME_ENV_VAR:}"` that uses an env var if present without failing if missing.
26+
pub fn none_if_empty_string<'de, D: Deserializer<'de>>(
27+
deserializer: D,
28+
) -> Result<Option<String>, D::Error> {
29+
30+
let s: Option<String> = Deserialize::deserialize(deserializer)?;
31+
match s {
32+
Some(s) if s.is_empty() => Ok(None),
33+
_ => Ok(s)
34+
}
35+
}

elastic-mcp.json5

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
{
33
// Configure the target Elasticsearch server
44
"elasticsearch": {
5-
"type": "elasticsearch",
6-
"url": "${ELASTICSEARCH_URL:http://localhost:9200}",
7-
"api_key": "${ELASTICSEARCH_API_KEY:}",
5+
"url": "${ES_URL}",
6+
"api_key": "${ES_API_KEY:}",
7+
"username": "${ES_LOGIN:}",
8+
"password": "${ES_PASSWORD:}",
9+
"ssl_skip_verify": "${ES_SSL_SKIP_VERIFY:false}",
810

911
/* WIP
1012
"tools": {

0 commit comments

Comments
 (0)