Rust MCP server using rmcp SDK with streamable HTTP transport. - Config from env vars (bind, api_key, db_path, off_user_agent) - SQLite schema: products (FTS5), entries, daily_goal, weight_log - OFF API client: Search-a-licious + v3 barcode + SQLite caching - 13 MCP tools: search_food, get_food_by_barcode, log_food, log_custom_food, delete_entry, daily_summary, history, list_entries, get_goals, set_goals, log_weight, weight_history, bulk_import (stub) - 14 tracked nutrients (gout + hypertension aware) - Bearer token auth middleware - Smoke tested: health, initialize, tools/list, get_goals, daily_summary Closes nutrition-mcp-dfl
74 lines
No EOL
2.2 KiB
Rust
74 lines
No EOL
2.2 KiB
Rust
use std::path::PathBuf;
|
|
use anyhow::{Result, bail};
|
|
|
|
/// Server configuration loaded from environment variables.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Config {
|
|
/// Bind address, e.g. "0.0.0.0:9432"
|
|
pub bind: String,
|
|
/// API key for Bearer auth
|
|
pub api_key: String,
|
|
/// Path to SQLite database file
|
|
pub db_path: PathBuf,
|
|
/// User-Agent header for OpenFood Facts API requests
|
|
pub off_user_agent: String,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn from_env() -> Result<Self> {
|
|
let bind = std::env::var("NUTRITION_MCP_BIND")
|
|
.unwrap_or_else(|_| "0.0.0.0:9432".to_string());
|
|
|
|
let api_key = std::env::var("NUTRITION_MCP_API_KEY")
|
|
.unwrap_or_else(|_| {
|
|
tracing::warn!("NUTRITION_MCP_API_KEY not set — generating a random key");
|
|
uuid::Uuid::new_v4().to_string()
|
|
});
|
|
|
|
let db_path = std::env::var("NUTRITION_MCP_DB_PATH")
|
|
.map(PathBuf::from)
|
|
.unwrap_or_else(|_| {
|
|
let mut p = dirs_data_path();
|
|
p.push("nutrition");
|
|
p.push("nutrition.db");
|
|
p
|
|
});
|
|
|
|
let off_user_agent = std::env::var("NUTRITION_MCP_OFF_USER_AGENT")
|
|
.unwrap_or_else(|_| {
|
|
"NutritionMCP/0.1.0 (personal use)".to_string()
|
|
});
|
|
|
|
if api_key.is_empty() {
|
|
bail!("NUTRITION_MCP_API_KEY must not be empty");
|
|
}
|
|
|
|
Ok(Self {
|
|
bind,
|
|
api_key,
|
|
db_path,
|
|
off_user_agent,
|
|
})
|
|
}
|
|
|
|
/// Create the parent directory of db_path if it doesn't exist.
|
|
pub fn ensure_db_dir(&self) -> Result<()> {
|
|
if let Some(parent) = self.db_path.parent() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn dirs_data_path() -> PathBuf {
|
|
// Prefer XDG_DATA_HOME, fall back to ~/.local/share
|
|
std::env::var("XDG_DATA_HOME")
|
|
.map(PathBuf::from)
|
|
.unwrap_or_else(|_| {
|
|
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
|
|
let mut p = PathBuf::from(home);
|
|
p.push(".local");
|
|
p.push("share");
|
|
p
|
|
})
|
|
} |