Skip to content

Conversation

@bhauman
Copy link
Owner

@bhauman bhauman commented Sep 14, 2025

Summary

This PR enhances the configuration schema validation by adding comprehensive field descriptions and cleaning up unused code.

Changes

1. Added Field Descriptions to ResourceEntry Schema

  • Added descriptions for all fields: :description, :file-path, :url, :mime-type
  • Provides self-documenting schema for better developer experience
  • Follows the pattern established by AgentConfig

2. Added Field Descriptions to PromptEntry and PromptArg Schemas

  • PromptArg fields: Added descriptions for :name, :description, :required?
  • PromptEntry fields: Added descriptions for :description, :content, :file-path, :args
  • Clarifies the purpose of each field and their relationships (e.g., use :content OR :file-path)

3. Removed Unused coerce-config Function

  • Removed the coerce-config function from schema.clj which was just a pass-through
  • Removed associated test coercion-test from schema_test.clj
  • Cleaned up orphaned comment sections

Testing

  • All tests pass: 374 tests, 2768 assertions
  • Validated that existing configurations work correctly
  • Documentation verified to match schema field names

Impact

These changes make the configuration schemas more consistent and self-documenting, improving the developer experience when working with ClojureMCP configurations.

Summary by CodeRabbit

  • New Features

    • Built-in configuration validation with clearer, user-friendly error reports before merging.
  • Documentation

    • Tool IDs renamed to underscore format across guides and examples (e.g., read_file, glob_files).
    • New agent option track_file_changes (default true) to control display of file diffs.
  • Chores

    • Added Malli validation dependency.
    • Removed legacy spec-based validation and deprecated include-mentioned-files agent option.
  • Tests

    • Added Malli-backed schema tests; removed old spec-based model validation tests.

Bruce Hauman added 6 commits September 13, 2025 11:42
- Add metosin/malli dependency for data-driven schema validation
- Create comprehensive config schemas in clojure-mcp.config.schema
- Support all configuration options including models, agents, resources, prompts
- Provide automatic spell-checking for typos in configuration keys
- Include human-readable error messages with documentation references
- Add comprehensive test suite for validation scenarios
- Support environment variable references [:env "VAR_NAME"]
- Validate nested structures like thinking config, agent config, etc.
- Use closed map schema to detect unknown/misspelled keys
@coderabbitai
Copy link

coderabbitai bot commented Sep 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a Malli-based configuration schema and validators, wires pre-merge validation into config loading with canonical paths and improved error reporting, updates docs and example agent tool IDs from hyphens to underscores, introduces :track-file-changes, removes an agent option, adds tests, adds malli dependency, and replaces clojure.spec model validation with the new schema.

Changes

Cohort / File(s) Summary
Dependencies
deps.edn
Added metosin/malli 0.19.1 for configuration validation.
Documentation
doc/configuring-agents.md
Documented new :track-file-changes; migrated hyphenated tool IDs to underscored forms across text, examples, migration guidance, and tool lists.
Agent configs
resources/clojure-mcp/agents/clojure_edit_agent.edn
Removed :include-mentioned-files true; other keys unchanged.
Example agent configs
resources/configs/example-agents.edn
Migrated tool IDs in :enable-tools / :disable-tools to underscore variants only.
Config loading
src/clojure_mcp/config.clj
Added validate-configs and pre-merge validation using canonical file paths; calls schema/explain-config on errors.
Core integration
src/clojure_mcp/core.clj
Added load-config-handling-validation-errors and replaced direct load-config calls in fetch-config to surface friendly validation reports.
New Malli schema & validators
src/clojure_mcp/config/schema.clj
New comprehensive Malli Config schema, helper validators (explain-config, valid?, etc.), provider/model/agent/resource/prompt schemas, and runtime EnvRef toggle.
Tests added
test/clojure_mcp/config/schema_test.clj
Added extensive tests for valid/invalid configs, typo detection, resources/prompts, example files, edge cases, and error-message quality.
Model validation updates
src/clojure_mcp/agent/langchain/model.clj
Switched validation requires/calls from removed model-spec to clojure-mcp.config.schema (e.g., validate-model-key, validate-config-for-provider).
Removed model-spec & tests
src/clojure_mcp/agent/langchain/model_spec.clj, test/clojure_mcp/agent/langchain/model_spec_test.clj
Deleted clojure.spec-based model_spec namespace and its test suite; corresponding spec-based validators and tests removed.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Startup as Startup
  participant Core as core/fetch-config
  participant Loader as config/load-config
  participant Schema as config.schema

  Note over Startup,Core: Startup requests configuration load
  Startup->>Core: fetch-config
  Core->>Loader: load-config-handling-validation-errors(home, project)
  Loader->>Loader: build list [{:config <map> :file-path <canonical-path>} ...]
  Loader->>Schema: validate-configs(list)
  alt any invalid
    Schema-->>Loader: explain-config errors
    Loader-->>Core: throw ex-info ::schema-error with {:errors :model :config :file-path}
    Core->>Startup: print validation report and rethrow/exit
  else all valid
    Schema-->>Loader: valid
    Loader->>Loader: merge validated configs
    Loader-->>Core: merged-config
    Core->>Startup: continue startup with merged config
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • Config improvements #99 — Modifies load-config/merge behavior; closely related to the pre-merge validation and config-loading changes in this PR.

Poem

I nibbled through schemas late at night,
Malli stitched configs tidy and bright.
Underscores hopped in, neat and quick,
Errors found, then fixed up slick.
Rabbit cheers — validation feels right! 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Config validation" is concise, a single short phrase, and accurately reflects the PR's primary purpose of adding schema-backed configuration validation and related schema/documentation changes, so it correctly summarizes the main change for someone scanning history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98d4464 and 913323d.

📒 Files selected for processing (1)
  • src/clojure_mcp/config/schema.clj (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
test/clojure_mcp/config/schema_test.clj (2)

44-47: Tool ID style in tests differs from docs (hyphens vs underscores).

Docs migrated to underscore IDs; this test still uses hyphens. Consider updating for consistency or explicitly covering both forms.

-   :enable-tools [:clojure-eval :read-file]
+   :enable-tools [:clojure_eval :read_file]

Do example configs under resources/ also use underscores consistently?


137-142: Add tests for PromptEntry XOR rule once schema enforces it.

Cover both “both provided” and “neither provided” cases.

+ (deftest prompt-content-xor-file-path-test
+   (testing "Both :content and :file-path should be invalid"
+     (let [config {:prompts {"p" {:description "d" :content "x" :file-path "p.mustache"}}}]
+       (is (some? (schema/explain-config config)))))
+   (testing "Neither :content nor :file-path should be invalid"
+     (let [config {:prompts {"p" {:description "d"}}}]
+       (is (some? (schema/explain-config config))))))

Also applies to: 198-203

doc/configuring-agents.md (3)

76-85: Fix MD058: add blank lines around tables.

Surround each table with a blank line to satisfy markdownlint.

-### Read-Only Tools
-| Tool ID | Description |
+### Read-Only Tools
+
+| Tool ID | Description |
 |---------|-------------|
 | `:LS` | Directory tree view |
 | `:read_file` | Read file contents with pattern matching |
 | `:grep` | Search file contents |
 | `:glob_files` | Find files by pattern |
 | `:think` | Reasoning tool |
 | `:clojure_inspect_project` | Project structure analysis |
+ 
-### Evaluation Tools
-| Tool ID | Description |
+### Evaluation Tools
+
+| Tool ID | Description |
 |---------|-------------|
 | `:clojure_eval` | Execute Clojure code in REPL |
 | `:bash` | Execute shell commands |
-### File Editing Tools
-| Tool ID | Description |
+ 
+### File Editing Tools
+
+| Tool ID | Description |
 |---------|-------------|
 | `:file_write` | Create or overwrite files |
 | `:file_edit` | Edit files by text replacement |
 | `:clojure_edit` | Structure-aware Clojure editing |
 | `:clojure_edit_replace_sexp` | S-expression replacement |
-### Agent Tools
-| Tool ID | Description |
+ 
+### Agent Tools
+
+| Tool ID | Description |
 |---------|-------------|
 | `:dispatch_agent` | Launch sub-agents |
 | `:architect` | Technical planning assistant |
 | `:scratch_pad` | Persistent data storage |
 | `:code_critique` | Code review feedback |

Also applies to: 86-94, 94-101, 100-107


301-306: Tool ID typo: use underscore form.

Use clojure_inspect_project to match the rest of the doc and examples.

-- Current project structure from `clojure-inspect-project`
+- Current project structure from `clojure_inspect_project`

510-514: Tighten guidance: explicitly call out underscore tool IDs.

Small doc tweak to minimize confusion migrating from hyphens.

- - Check tool IDs match exactly (use underscores: `:read_file` not `:read-file`)
+ - Check tool IDs match exactly (underscore IDs): `:read_file` (not `:read-file`)
src/clojure_mcp/core.clj (1)

262-267: Add info log after loading config; fix README doc link

Add an info log after loading the config to record config-file and user-dir. Repo contains CONFIG.md at repo root, but README.md links to doc/CONFIG.md — update README or move CONFIG.md.

-    (let [config (load-config-handling-validation-errors config-file user-dir)
+    (let [config (load-config-handling-validation-errors config-file user-dir)
+          _ (log/info "Loaded configuration" {:file config-file :user-dir user-dir})
           final-env-type (or cli-env-type
                              (if (contains? config :nrepl-env-type)
                                (:nrepl-env-type config)
                                env-type))]
       (assoc nrepl-client-map ::config/config (assoc config :nrepl-env-type final-env-type))))

Found: CONFIG.md (repo root). References: README.md:1005 -> doc/CONFIG.md; src/clojure_mcp/core.clj prints "See CONFIG.md" (~line 250).

src/clojure_mcp/config.clj (1)

130-144: Pre-merge validation flow is correct; consider not swallowing EDN parse errors.

Current load-config-file logs and returns {} on read/parse failure, which then skips validation here and silently ignores a broken file. Consider promoting read errors to an exception (with :type ::read-error and :file-path) so users aren’t surprised by ignored configs.

If you want, I can draft a minimal change to load-config-file that preserves existing happy-paths but throws on parse errors.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79dda5f and ba7224a.

📒 Files selected for processing (8)
  • deps.edn (1 hunks)
  • doc/configuring-agents.md (15 hunks)
  • resources/clojure-mcp/agents/clojure_edit_agent.edn (0 hunks)
  • resources/configs/example-agents.edn (3 hunks)
  • src/clojure_mcp/config.clj (4 hunks)
  • src/clojure_mcp/config/schema.clj (1 hunks)
  • src/clojure_mcp/core.clj (1 hunks)
  • test/clojure_mcp/config/schema_test.clj (1 hunks)
💤 Files with no reviewable changes (1)
  • resources/clojure-mcp/agents/clojure_edit_agent.edn
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{clj,cljc}: Use :require with ns aliases for imports (e.g., [clojure.string :as string])
Include clear tool :description for LLM guidance
Validate inputs and provide helpful error messages in MCP tools
Return structured data with both result and error status in MCP tools
Maintain atom-based state for consistent service access in MCP tools

Files:

  • src/clojure_mcp/core.clj
  • src/clojure_mcp/config/schema.clj
  • src/clojure_mcp/config.clj
**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{clj,cljc}: Use kebab-case for vars/functions; end predicates with ? (e.g., is-top-level-form?)
Use try/catch with specific exception handling; use atom for tracking errors
Use 2-space indentation and maintain whitespace in edited forms
Align namespaces with directory structure (e.g., clojure-mcp.repl-tools)

Files:

  • src/clojure_mcp/core.clj
  • src/clojure_mcp/config/schema.clj
  • test/clojure_mcp/config/schema_test.clj
  • src/clojure_mcp/config.clj
test/**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

Use deftest with descriptive names; testing for subsections; is for assertions in tests

Files:

  • test/clojure_mcp/config/schema_test.clj
🧠 Learnings (3)
📚 Learning: 2025-08-02T20:23:28.499Z
Learnt from: CR
PR: bhauman/clojure-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-02T20:23:28.499Z
Learning: Applies to src/**/*.{clj,cljc} : Validate inputs and provide helpful error messages in MCP tools

Applied to files:

  • src/clojure_mcp/core.clj
  • src/clojure_mcp/config/schema.clj
  • test/clojure_mcp/config/schema_test.clj
  • src/clojure_mcp/config.clj
📚 Learning: 2025-08-02T20:23:28.499Z
Learnt from: CR
PR: bhauman/clojure-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-02T20:23:28.499Z
Learning: Applies to src/**/*.{clj,cljc} : Return structured data with both result and error status in MCP tools

Applied to files:

  • src/clojure_mcp/config.clj
📚 Learning: 2025-08-02T20:23:28.499Z
Learnt from: CR
PR: bhauman/clojure-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-02T20:23:28.499Z
Learning: Applies to src/**/*.{clj,cljc} : Include clear tool `:description` for LLM guidance

Applied to files:

  • doc/configuring-agents.md
🪛 markdownlint-cli2 (0.17.2)
doc/configuring-agents.md

87-87: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


93-93: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


101-101: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🔇 Additional comments (10)
deps.edn (1)

11-13: Approve — metosin/malli 0.19.1 is current.
0.19.1 (released June 9, 2025) is the latest stable release — no changes required to deps.edn.

src/clojure_mcp/config/schema.clj (1)

151-158: Align agent-level tool ID types with top-level config (keywords vs. strings)

Top-level :enable-tools/:disable-tools accept keywords or strings; agent-level schema in src/clojure_mcp/config/schema.clj (lines ~151–158) only allows keywords — either loosen the agent schema to allow strings for parity or document that agent-level IDs must be keywords.

Suggested parity diff (apply if you want agent-level === top-level):

-  [:enable-tools {:optional true
-                   :description "Tools the agent can access: :all, specific list, or nil (no tools)"}
-    [:or [:= :all] [:sequential :keyword]]]
+  [:enable-tools {:optional true
+                   :description "Tools the agent can access: :all, specific list, or nil (no tools)"}
+    [:or [:= :all] [:sequential [:or :keyword :string]]]]

-  [:disable-tools {:optional true
-                    :description "Tools to exclude even if enabled (applied after enable-tools)"}
-    [:maybe [:sequential :keyword]]]
+  [:disable-tools {:optional true
+                    :description "Tools to exclude even if enabled (applied after enable-tools)"}
+    [:maybe [:sequential [:or :keyword :string]]]]

Would config/tool-id-enabled? and related filters accept string IDs at the agent level today?

src/clojure_mcp/config.clj (2)

6-6: Good: schema ns alias added and aligned with file layout.

Namespace aliasing follows project conventions and matches src/clojure_mcp/config/schema.clj.


118-118: Docstring matches behavior.

Now accurately states “Validates both configs before merging.”

resources/configs/example-agents.edn (6)

17-17: Underscored tool IDs: research-agent entry looks consistent.

:enable-tools updated to [:grep :glob_files :read_file :clojure_inspect_project]. Matches new naming.


25-26: Underscored tool IDs: refactor_assistant entry is consistent.


33-35: Underscored tool IDs: test_explorer enable/disable lists are consistent.


42-42: Underscored tool IDs: doc_reader entry is consistent.


51-52: Underscored tool IDs: code_writer entry is consistent (long token preserved).


62-62: full-access-agent updated correctly — manual repo verification required

rg returned "No files were searched" so I couldn't confirm absence of deprecated hyphenated tool IDs. Re-run locally:

# Search with ripgrep (wide search, ignore node_modules/target)
rg -n --hidden --no-ignore -S -E 'read-file|glob-files|clojure-inspect-project|dispatch-agent' --glob '!*node_modules/**' --glob '!*target/**' || true
# If rg still skips files, run with --debug:
rg --debug -n --hidden --no-ignore -S -E 'read-file|glob-files|clojure-inspect-project|dispatch-agent' || true
# Fallback using find+grep:
find . \( -path './node_modules' -o -path './target' -o -path './.git' \) -prune -o -type f -print0 | xargs -0 -r grep -nE 'read-file|glob-files|clojure-inspect-project|dispatch-agent' || true

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/clojure_mcp/config/schema.clj (1)

206-227: Resolved: enforce exactly one of :content or :file-path.

The XOR check via [:and ... [:fn ...]] is correct and addresses prior feedback.

🧹 Nitpick comments (6)
src/clojure_mcp/config/schema.clj (6)

13-16: Disallow empty paths.

Paths shouldn’t be empty strings. Recommend length check.

Apply:

-(def Path
-  "Schema for file system paths"
-  :string)
+(def Path
+  "Schema for file system paths"
+  [:string {:min 1}])

17-21: Tighten EnvRef var name to be non-empty.

Prevents [:env ""] footguns.

Apply:

 (def EnvRef
   "Schema for environment variable references like [:env \"VAR_NAME\"]"
   [:tuple {:description "Environment variable reference"}
    [:= :env]
-   :string])
+   [:string {:min 1}]])

40-41: Provider enum is missing :gemini.

You expose a :gemini submap below; allow it in :provider too or clarify docs.

Apply:

-   [:provider {:optional true} [:enum :openai :anthropic :google]]
+   [:provider {:optional true} [:enum :openai :anthropic :google :gemini]]

96-97: Constrain OpenAI logit-bias to common range.

OpenAI typically enforces [-100, 100]. Bound the ints to reduce invalid configs.

Apply:

-     [:logit-bias {:optional true} [:map-of :string :int]]
+     [:logit-bias {:optional true} [:map-of :string [:int {:min -100 :max 100}]]]

If you’ve seen different limits recently, keep as-is and update the bounds accordingly.


143-146: Description mentions default but schema doesn’t set one.

Either declare a default or drop “(default)” from the description.

Apply one:

Option A (add default):

-   [:context {:optional true
-              :description "Context to provide: true (default), false (none), or file paths list"}
+   [:context {:optional true
+              :description "Context to provide: true (default), false (none), or file paths list"
+              :default true}
     [:or :boolean [:sequential :string]]]

Option B (doc fix):

-   [:context {:optional true
-              :description "Context to provide: true (default), false (none), or file paths list"}
+   [:context {:optional true
+              :description "Context to provide: true/false, or file paths list"}
     [:or :boolean [:sequential :string]]]

162-164: Set default true for :track-file-changes or adjust wording.

Doc says “(default: true)” but schema has no default.

Apply:

-   [:track-file-changes {:optional true
-                         :description "Whether to track and display file diffs (default: true)"}
+   [:track-file-changes {:optional true
+                         :description "Whether to track and display file diffs (default: true)"
+                         :default true}
     :boolean]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba7224a and 3e029a2.

📒 Files selected for processing (3)
  • src/clojure_mcp/config.clj (4 hunks)
  • src/clojure_mcp/config/schema.clj (1 hunks)
  • src/clojure_mcp/core.clj (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/clojure_mcp/config.clj
  • src/clojure_mcp/core.clj
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{clj,cljc}: Use :require with ns aliases for imports (e.g., [clojure.string :as string])
Include clear tool :description for LLM guidance
Validate inputs and provide helpful error messages in MCP tools
Return structured data with both result and error status in MCP tools
Maintain atom-based state for consistent service access in MCP tools

Files:

  • src/clojure_mcp/config/schema.clj
**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{clj,cljc}: Use kebab-case for vars/functions; end predicates with ? (e.g., is-top-level-form?)
Use try/catch with specific exception handling; use atom for tracking errors
Use 2-space indentation and maintain whitespace in edited forms
Align namespaces with directory structure (e.g., clojure-mcp.repl-tools)

Files:

  • src/clojure_mcp/config/schema.clj
🧠 Learnings (1)
📚 Learning: 2025-08-02T20:23:28.499Z
Learnt from: CR
PR: bhauman/clojure-mcp#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-02T20:23:28.499Z
Learning: Applies to src/**/*.{clj,cljc} : Validate inputs and provide helpful error messages in MCP tools

Applied to files:

  • src/clojure_mcp/config/schema.clj
🔇 Additional comments (4)
src/clojure_mcp/config/schema.clj (4)

1-7: Good namespace and aliases.

Uses :require with aliases per guidelines; concise docstring.


281-289: Nice humanized error reporting with spell-checking.

Good UX for config authors.


299-311: Schema introspection helpers look good.

Handy for docs; no issues spotted.


233-276: Approve: top-level Config keys look good; no stale coerce-config references

Closed map and types are sensible; provider/tool filters consistent. ripgrep found no matches for coerce-config and no occurrences of :provider :gemini — “gemini” only appears in model names, tests, and provider-specific handlers (e.g. src/clojure_mcp/config/schema.clj, src/clojure_mcp/agent/langchain.clj, src/clojure_mcp/agent/langchain/model.clj).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (10)
src/clojure_mcp/config/schema.clj (10)

11-15: Defaulting validate-env-vars to true is surprising; default to false

Prevents configs with env refs from failing in dev/CI unless explicitly enabled.

Apply:

-(def ^:dynamic *validate-env-vars*
-  "When true, validates that environment variables actually exist and aren't blank.
-   Can be bound to false for testing."
-  true)
+(def ^:dynamic *validate-env-vars*
+  "When true, validates that environment variables actually exist and aren't blank.
+   Default false to avoid surprising failures; bind to true in strict environments."
+  false)

30-41: Tighten EnvRef: validate ENV name format

Avoid obviously bad names; keep runtime existence check as-is.

Apply:

 (def EnvRef
   "Schema for environment variable references like [:env \"VAR_NAME\"]"
   [:and
    [:tuple {:description "Environment variable reference"}
     [:= :env]
     NonBlankString]
+   [:fn {:error/message "ENV var name must match [A-Z_][A-Z0-9_]*"}
+    (fn [[_ env-var]]
+      (boolean (re-matches #"[A-Z_][A-Z0-9_]*" env-var)))]
    [:fn {:error/message "Environment variable can't be empty"}
     (fn [[_ env-var]]
       (or (not *validate-env-vars*)
           (let [val (System/getenv env-var)]
             (and val
                  (not (string/blank? val))))))]])

86-91: Require :schema when :response-format :type is :json

Prevents runtime failures when asking for JSON without a schema.

Apply:

-  [:response-format {:optional true}
-   [:map {:closed true}
-    [:type [:enum :json :text]]
-    [:schema {:optional true} :map]]]
+  [:response-format {:optional true}
+   [:and
+    [:map {:closed true}
+     [:type [:enum :json :text]]
+     [:schema {:optional true} :map]]
+    [:fn {:error/message "When :response-format :type is :json, provide :schema"}
+     (fn [{:keys [type schema]}]
+       (if (= type :json) (some? schema) true))]]]

110-117: Constrain OpenAI :logit-bias values to API range

OpenAI expects -100..100; catch out-of-range earlier.

Apply:

-     [:logit-bias {:optional true} [:map-of NonBlankString :int]]
+     [:logit-bias {:optional true} [:map-of NonBlankString [:int {:min -100 :max 100]]]]

154-157: Use Path for file path list in :context

Description says “file paths list”; enforce Path.

Apply:

-   [:context {:optional true
-              :description "Context to provide: true (default), false (none), or file paths list"}
-    [:or :boolean [:sequential NonBlankString]]]
+   [:context {:optional true
+              :description "Context to provide: true (default), false (none), or file paths list"}
+    [:or :boolean [:sequential Path]]]

159-166: Allow explicit nil for :enable-tools to match doc

Doc says “:all, specific list, or nil”; permit nil when key is present.

Apply:

-   [:enable-tools {:optional true
-                   :description "Tools the agent can access: :all, specific list, or nil (no tools)"}
-    [:or [:= :all] [:sequential :keyword]]]
+   [:enable-tools {:optional true
+                   :description "Tools the agent can access: :all, specific list, or nil (no tools)"}
+    [:maybe [:or [:= :all] [:sequential :keyword]]]]

168-171: Allow explicit nil for :memory-size per description

Keeps schema aligned with “false/nil/<10 stateless”.

Apply:

-   [:memory-size {:optional true
-                  :description "Memory behavior: false/nil/<10 = stateless, >=10 = persistent window"}
-    [:or [:= false] [:int {:min 0}]]]
+   [:memory-size {:optional true
+                  :description "Memory behavior: false/nil/<10 = stateless, >=10 = persistent window"}
+    [:maybe [:or [:= false] [:int {:min 0}]]]]

191-197: Drop [:maybe ...] on optional keys (:url, :mime-type)

Key is already optional; allowing explicit nil adds little and can confuse.

Apply:

-   [:url {:optional true
-          :description "Custom URL for resource (defaults to custom://kebab-case-name)"}
-    [:maybe NonBlankString]]
+   [:url {:optional true
+          :description "Custom URL for resource (defaults to custom://kebab-case-name)"}
+    NonBlankString]
@@
-   [:mime-type {:optional true
-                :description "MIME type (auto-detected from file extension if not specified)"}
-    [:maybe NonBlankString]]])
+   [:mime-type {:optional true
+                :description "MIME type (auto-detected from file extension if not specified)"}
+    NonBlankString]])

257-259: Use Path for :scratch-pad-file

This is a file path; enforce with Path like elsewhere.

Apply:

-   [:scratch-pad-file {:optional true} NonBlankString]
+   [:scratch-pad-file {:optional true} Path]

261-286: Add referential integrity and uniqueness checks (agents/models)

Catch config mistakes early: duplicate agent ids and unknown model refs.

Apply:

-   [:agents {:optional true} [:sequential AgentConfig]]
+   [:agents {:optional true} [:sequential AgentConfig]]
+   ;; Cross-field validations
+   [:fn {:error/message "Agent :id values must be unique"}
+    (fn [{:keys [agents]}]
+      (let [ids (map :id agents)]
+        (= (count ids) (count (distinct ids)))))]
+   [:fn {:error/message "Each agent's :model must reference a key under :models"}
+    (fn [{:keys [agents models]}]
+      (let [model-keys (set (keys models))]
+        (every? (fn [{:keys [model]}]
+                  (or (nil? model) (contains? model-keys model)))
+                agents)))]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4992c08 and 0ce3c1d.

📒 Files selected for processing (2)
  • src/clojure_mcp/config/schema.clj (1 hunks)
  • test/clojure_mcp/config/schema_test.clj (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/clojure_mcp/config/schema_test.clj
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{clj,cljc}: Use :require with ns aliases for imports (e.g., [clojure.string :as string])
Include clear tool :description for LLM guidance
Validate inputs and provide helpful error messages in MCP tools
Return structured data with both result and error status in MCP tools
Maintain atom-based state for consistent service access in MCP tools

Files:

  • src/clojure_mcp/config/schema.clj
**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{clj,cljc}: Use kebab-case for vars/functions; end predicates with ? (e.g., is-top-level-form?)
Use try/catch with specific exception handling; use atom for tracking errors
Use 2-space indentation and maintain whitespace in edited forms
Align namespaces with directory structure (e.g., clojure-mcp.repl-tools)

Files:

  • src/clojure_mcp/config/schema.clj
🔇 Additional comments (6)
src/clojure_mcp/config/schema.clj (6)

1-9: Namespace and requires look good

Alias usage and ns-path alignment are correct.


20-25: NonBlankString helper is solid

Covers empty and whitespace-only strings.


203-216: PromptArg schema reads well

Clear, minimal, and fits usage.


217-239: Nice: XOR between :content and :file-path

Prevents the common footgun. Good addition.


292-305: Validation helpers LGTM

explain-config + valid? are straightforward.


310-322: Schema introspection helpers LGTM

Handy for docs; no issues.

@bhauman bhauman changed the title Enhance config schema with field descriptions and remove unused coerce-config Config validation Sep 16, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/clojure_mcp/agent/langchain/model.clj (1)

314-323: Bug: Uses :gemini key but schema defines provider-specific config under :google.

This makes Gemini options unreachable from validated configs.

-         (get-in config [:gemini :allow-code-execution])
-         (.allowCodeExecution (get-in config [:gemini :allow-code-execution]))
-         (get-in config [:gemini :include-code-execution-output])
-         (.includeCodeExecutionOutput (get-in config [:gemini :include-code-execution-output]))
-         (get-in config [:gemini :response-logprobs])
-         (.responseLogprobs (get-in config [:gemini :response-logprobs]))
-         (get-in config [:gemini :enable-enhanced-civic-answers])
-         (.enableEnhancedCivicAnswers (get-in config [:gemini :enable-enhanced-civic-answers]))
-         (get-in config [:gemini :logprobs])
-         (.logprobs (int (get-in config [:gemini :logprobs])))))))
+         (get-in config [:google :allow-code-execution])
+         (.allowCodeExecution (get-in config [:google :allow-code-execution]))
+         (get-in config [:google :include-code-execution-output])
+         (.includeCodeExecutionOutput (get-in config [:google :include-code-execution-output]))
+         (get-in config [:google :response-logprobs])
+         (.responseLogprobs (get-in config [:google :response-logprobs]))
+         (get-in config [:google :enable-enhanced-civic-answers])
+         (.enableEnhancedCivicAnswers (get-in config [:google :enable-enhanced-civic-answers]))
+         (get-in config [:google :logprobs])
+         (.logprobs (int (get-in config [:google :logprobs])))))))
♻️ Duplicate comments (1)
src/clojure_mcp/config/schema.clj (1)

245-248: Nice: XOR constraint on :content vs :file-path is in place.

This closes the footgun where neither or both could be provided.

🧹 Nitpick comments (2)
src/clojure_mcp/config/schema.clj (2)

164-167: Use Path for file path lists in :context.

Schema says “file paths list” but validates generic strings. Prefer Path for consistency.

-   [:context {:optional true
-              :description "Context to provide: true (default), false (none), or file paths list"}
-    [:or :boolean [:sequential NonBlankString]]]
+   [:context {:optional true
+              :description "Context to provide: true (default), false (none), or file paths list"}
+    [:or :boolean [:sequential Path]]]

30-41: Make env lookup overridable for tests and align with model namespace.

EnvRef validation always calls System/getenv, ignoring test overrides used elsewhere. Add an overridable getter.

+(def ^:dynamic *get-env*
+  "Overrideable env lookup used by schema-time checks (defaults to System/getenv)."
+  (fn [k] (System/getenv k)))
 
 (def EnvRef
   "Schema for environment variable references like [:env \"VAR_NAME\"]"
   [:and
    [:tuple {:description "Environment variable reference"}
     [:= :env]
     NonBlankString]
    [:fn {:error/message "Environment variable can't be empty"}
     (fn [[_ env-var]]
       (or (not *validate-env-vars*)
-          (let [val (System/getenv env-var)]
+          (let [val (*get-env* env-var)]
             (and val
                  (not (string/blank? val))))))]])

Usage in tests:
(binding [clojure-mcp.config.schema/get-env (fn [k] (get {"OPENAI_API_KEY" "x"} k))] ...)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0ce3c1d and c282737.

📒 Files selected for processing (4)
  • src/clojure_mcp/agent/langchain/model.clj (4 hunks)
  • src/clojure_mcp/agent/langchain/model_spec.clj (0 hunks)
  • src/clojure_mcp/config/schema.clj (1 hunks)
  • test/clojure_mcp/agent/langchain/model_spec_test.clj (0 hunks)
💤 Files with no reviewable changes (2)
  • test/clojure_mcp/agent/langchain/model_spec_test.clj
  • src/clojure_mcp/agent/langchain/model_spec.clj
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{clj,cljc}: Use :require with ns aliases for imports (e.g., [clojure.string :as string])
Include clear tool :description for LLM guidance
Validate inputs and provide helpful error messages in MCP tools
Return structured data with both result and error status in MCP tools
Maintain atom-based state for consistent service access in MCP tools

Files:

  • src/clojure_mcp/agent/langchain/model.clj
  • src/clojure_mcp/config/schema.clj
**/*.{clj,cljc}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{clj,cljc}: Use kebab-case for vars/functions; end predicates with ? (e.g., is-top-level-form?)
Use try/catch with specific exception handling; use atom for tracking errors
Use 2-space indentation and maintain whitespace in edited forms
Align namespaces with directory structure (e.g., clojure-mcp.repl-tools)

Files:

  • src/clojure_mcp/agent/langchain/model.clj
  • src/clojure_mcp/config/schema.clj
🔇 Additional comments (6)
src/clojure_mcp/config/schema.clj (2)

254-297: LGTM on top-level Config shape and key metadata.

Closed maps + spell-checking and added descriptions improve UX.


337-360: Validation helpers look good.

Clear ex-info payloads with humanized Malli errors.

src/clojure_mcp/agent/langchain/model.clj (4)

4-4: Good switch to schema validators.

Alias and dependency update match the migration away from spec.


406-408: Validation hook on builder creation: good.

Model key + config validated before use.


430-431: Validation on create-builder-from-config: good.


514-516: Validation on nREPL-driven path: good.

@bhauman bhauman merged commit fef5e77 into main Sep 16, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants