1use async_trait::async_trait;
4use serde_json::{json, Value};
5
6use crate::protocol::CallToolResult;
7use crate::tool::{Context, Tool};
8
9pub struct GetConfig;
10
11#[async_trait]
12impl Tool for GetConfig {
13 fn name(&self) -> &'static str {
14 "get-config"
15 }
16 fn description(&self) -> &'static str {
17 "Read named application config values: `app.*`, `session.*`, `mail.*`, `queue.*`, `db.*`. Omit `key` to dump all known config sections (secrets are redacted)."
18 }
19 fn input_schema(&self) -> Value {
20 json!({
21 "type": "object",
22 "properties": {
23 "key": { "type": "string", "description": "Dot-path like `app.env`. Optional." }
24 }
25 })
26 }
27
28 async fn call(&self, ctx: &Context, args: Value) -> CallToolResult {
29 let key = args.get("key").and_then(|v| v.as_str()).unwrap_or("");
30 let full = dump_all(ctx);
31 if key.is_empty() {
32 return CallToolResult::json(&full);
33 }
34 let mut value = &full;
35 for segment in key.split('.') {
36 match value.get(segment) {
37 Some(v) => value = v,
38 None => {
39 return CallToolResult::error(format!("unknown config key: {key}"));
40 }
41 }
42 }
43 CallToolResult::json(value)
44 }
45}
46
47fn dump_all(ctx: &Context) -> Value {
48 let inner = &ctx.container;
49 let app = inner.app();
50 json!({
51 "app": {
52 "name": app.name,
53 "env": app.env,
54 "url": app.url,
55 "debug": app.debug,
56 "key": redact(&app.key),
57 },
58 "database": {
59 "driver": format!("{:?}", inner.driver()),
60 },
61 })
62}
63
64fn redact(s: &str) -> Value {
65 if s.is_empty() {
66 json!("(unset)")
67 } else {
68 json!(format!("(set, {} chars)", s.len()))
69 }
70}