-
-
Notifications
You must be signed in to change notification settings - Fork 56
Description
I've worked with Claude to generate a document outlining the implementation of a way to implement config validation.
The goal is to validate the spec on load and provide feedback to the user if something is wrong.
I'm referring to Clojure Spec in the document but I'm mainly interested in providing good feedback to the user so if Malli is better suited then let's use that.
Stretch goal is catching key misspellings.
Please make minimal changes to the main.clj
and core.clj
as we can use clojure-mcp.config/process-config and throw an error that we can catch in core.clj or main.clj to print out the result.
Anyway here is the doc outlining a spec implementation:
Overview
We need to add comprehensive validation for the configuration system in clojure-mcp.config
using Clojure Spec with Expound for friendly error messages. The validation should be integrated directly into the process-config
function to catch errors early in the configuration loading pipeline, failing immediately on invalid configurations to ensure only valid configs are processed.
Background
The Clojure MCP project uses a configuration system that loads settings from .clojure-mcp/config.edn
files. Currently, validation is ad-hoc and scattered throughout the codebase. By adding spec validation with Expound that runs by default, we can:
- Provide clear, human-readable error messages for invalid configurations
- Catch configuration errors at load time in
process-config
- Ensure type safety for all configuration values
- Document expected configuration structure through specs
- Give users helpful guidance on fixing configuration issues
Key Integration Point
The validation should be integrated directly into the clojure-mcp.config/process-config
function (lines 18-41). This ensures:
- Validation happens automatically on every configuration load
- Invalid configurations fail immediately with helpful error messages
- Only valid configurations proceed through the system
Implementation Plan
1. Create New Namespace: clojure-mcp.config.spec
Location: src/clojure_mcp/config/spec.clj
Tasks:
- Create the new file and namespace
- Require
clojure.spec.alpha
- Consider requiring
clojure-mcp.agent.langchain.model-spec
to reuse model-related specs - Follow the pattern established in
src/clojure_mcp/agent/langchain/model_spec.clj
2. Define Basic Type Specs
Define reusable specs for common types:
::path
- String representing a valid file path::env-ref
- Environment variable reference format:[:env "VAR_NAME"]
- Basic types like
::boolean
,::string
,::keyword
if needed
3. Define Specs for Each Configuration Key
Create specs for all configuration keys (reference src/clojure_mcp/config.clj
for all get-*
functions):
Key | Spec Definition | Notes |
---|---|---|
::allowed-directories |
(s/coll-of string?) |
Collection of directory paths |
::emacs-notify |
boolean? |
|
::write-file-guard |
#{:full-read :partial-read false} |
Limited set of values |
::cljfmt |
boolean? |
|
::bash-over-nrepl |
boolean? |
|
::nrepl-env-type |
#{:clj :bb :basilisp :scittle} |
Limited set of environments |
::scratch-pad-load |
boolean? |
|
::scratch-pad-file |
string? |
|
::models |
(s/map-of keyword? ::model-config) |
Map of model configurations |
::tools-config |
(s/map-of keyword? map?) |
Tool-specific configs |
::agents |
(s/coll-of ::agent-config) |
Collection of agent configs |
::mcp-client |
(s/nilable string?) |
Optional string |
::dispatch-agent-context |
(s/or :boolean boolean? :paths (s/coll-of string?)) |
Boolean or file paths |
::enable-tools |
(s/nilable (s/coll-of (s/or :keyword keyword? :string string?))) |
|
::disable-tools |
(s/nilable (s/coll-of (s/or :keyword keyword? :string string?))) |
|
::enable-prompts |
(s/nilable (s/coll-of string?)) |
|
::disable-prompts |
(s/nilable (s/coll-of string?)) |
|
::enable-resources |
(s/nilable (s/coll-of string?)) |
|
::disable-resources |
(s/nilable (s/coll-of string?)) |
|
::resources |
(s/map-of string? ::resource-entry) |
See section 4 for detailed spec |
::prompts |
(s/map-of string? ::prompt-entry) |
See section 4 for detailed spec |
4. Define Complex Nested Specs
Model Configuration Spec
- Reuse/reference specs from
clojure-mcp.agent.langchain.model-spec
- Should validate fields like
:model-name
,:temperature
,:api-key
,:thinking
, etc. - Support environment variable references
Agent Configuration Spec
Define spec for agent configurations including:
:id
- keyword identifier:name
- string display name:description
- string description:model
- model reference:system-prompt
- string or prompt reference- Other agent-specific fields
Resources Configuration Spec
Resources are not just simple maps. Each resource entry requires:
(s/def ::description string?)
(s/def ::file-path string?)
(s/def ::url (s/nilable string?))
(s/def ::mime-type (s/nilable string?))
(s/def ::resource-entry
(s/keys :req-un [::description ::file-path]
:opt-un [::url ::mime-type]))
(s/def ::resources (s/map-of string? ::resource-entry))
See doc/configuring-resources.md
for full documentation.
Prompts Configuration Spec
Prompts are not just simple maps. Each prompt entry requires:
(s/def ::description string?) ; Reused from resources
(s/def ::file string?)
(s/def ::content (s/or :string string?
:file-ref (s/keys :req-un [::file])))
(s/def ::name string?)
(s/def ::required? boolean?)
(s/def ::prompt-arg
(s/keys :req-un [::name ::description]
:opt-un [::required?]))
(s/def ::args (s/coll-of ::prompt-arg))
(s/def ::prompt-entry
(s/keys :req-un [::description]
:opt-un [::content ::args]))
(s/def ::prompts (s/map-of string? ::prompt-entry))
See doc/configuring-prompts.md
for full documentation.
5. Define Main Configuration Spec
(s/def ::config
(s/keys :opt-un [::allowed-directories
::emacs-notify
::write-file-guard
::cljfmt
::bash-over-nrepl
::nrepl-env-type
::scratch-pad-load
::scratch-pad-file
::models
::tools-config
::agents
::mcp-client
::dispatch-agent-context
::enable-tools
::disable-tools
::enable-prompts
::disable-prompts
::enable-resources
::disable-resources
::resources
::prompts]))
6. Create Validation Functions with Expound
Add expound as a dependency for friendly error messages:
expound/expound {:mvn/version "0.9.0"}
Following the pattern from model_spec.clj
, create validation functions with expound support:
(require '[expound.alpha :as expound])
(defn validate-config
"Validates a configuration map against the spec.
Returns the config if valid, throws ex-info with friendly expound output if not."
[config]
(if (s/valid? ::config config)
config
(let [explain-data (s/explain-data ::config config)
friendly-error (with-out-str
(expound/printer explain-data))]
(throw (ex-info (str "Invalid configuration:\n" friendly-error)
{:explain-data explain-data
:config config})))))
(defn validate-key
"Validates a specific configuration key with friendly errors."
[key value]
...)
(defn explain-config
"Returns human-readable explanation of validation errors using expound."
[config]
(defn conform-config
"Conforms and coerces configuration values."
[config]
...)
When validation fails with expound, users will see helpful, readable error messages instead of cryptic spec failures. For example:
Invalid configuration:
-- Spec failed --------------------
{:write-file-guard :invalid-value,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:resources {"my-doc" {:file-path "doc.md"}},
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...}
should be one of: :full-read, :partial-read, false
-- Spec failed --------------------
{:resources {"my-doc" {:file-path "doc.md"}},
^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
...}
Missing required key: :description
Resources must include both :description and :file-path
See doc/configuring-resources.md for details
-- Detected 2 errors -------------------
7. Integration with process-config Function
Primary Integration Point: src/clojure_mcp/config.clj
- process-config
function (lines 18-41)
The validation should be integrated directly into the process-config
function and run by default:
(defn process-config
[{:keys [allowed-directories emacs-notify write-file-guard cljfmt
bash-over-nrepl nrepl-env-type] :as config}
user-dir]
(let [ud (io/file user-dir)]
(assert (and (.isAbsolute ud) (.isDirectory ud)))
;; Always validate the raw config - fail fast on invalid configs
(clojure-mcp.config.spec/validate-config config)) ; This will throw with friendly expound errors if invalid
;; Existing validation for write-file-guard
(when (some? write-file-guard)
(when-not (contains? #{:full-read :partial-read false} write-file-guard)
...))
;; Rest of existing process-config logic
(cond-> config
...)))
Implementation Notes:
- Validation runs by default on every config load
- Invalid configurations immediately throw exceptions with friendly expound messages
- Fail-fast approach ensures only valid configurations are processed
8. Testing
Location: test/clojure_mcp/config/spec_test.clj
Test cases to implement:
- Valid configurations from
resources/configs/*.edn
- Invalid configurations with expected error messages
- Edge cases like environment variable reference resolution
- Validation function behavior
- Test specific invalid cases:
- Invalid
:write-file-guard
values - Invalid
:nrepl-env-type
values - Malformed
:resources
entries (missing required fields) - Malformed
:prompts
entries (invalid arg structures) - Invalid environment variable references
- Invalid
- Verify that
process-config
throws on validation errors
9. Documentation
Resources and References
Key Files to Reference
- Main config namespace:
src/clojure_mcp/config.clj
- Model spec example:
src/clojure_mcp/agent/langchain/model_spec.clj
- Example configurations:
resources/configs/*.edn
- Configuration documentation:
README.md
(Configuration section starting at line 981)PROJECT_SUMMARY.md
(Configuration System section)doc/model-configuration.md
doc/component-filtering.md
doc/configuring-resources.md
(Resource entry structure and requirements)doc/configuring-prompts.md
(Prompt entry structure and requirements)
Example Configuration Files
resources/configs/example-component-filtering.edn
resources/configs/example-models-with-provider.edn
resources/configs/example-agents.edn
resources/configs/example-tools-config.edn
Notes for Implementation
-
Environment Variable References: The configuration supports references like
[:env "OPENAI_API_KEY"]
which are resolved at runtime. The spec should validate the structure but not the actual environment variable existence. -
Fail-Fast Design: Invalid configurations should immediately throw exceptions with clear expound error messages. This ensures users fix configuration problems before the system starts.
-
Model Configuration: The model configuration system already has comprehensive specs in
model_spec.clj
. Consider reusing these rather than duplicating.