nutrition-mcp/src/config.rs
Anthony Merlo 244e141053 feat: scaffold nutrition-mcp server with all 13 tools
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
2026-08-20 16:52:11 +01:00

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
})
}