The Clojure MCP server supports defining custom AI agents through configuration. Each configured agent becomes its own tool in the MCP interface, allowing you to create specialized assistants without writing code.
The agent tool builder dynamically creates MCP tools from agent configurations defined in your .clojure-mcp/config.edn file. Each agent can have:
- Its own system prompt and personality
- Access to specific tools (read, write, execute, or none)
- Custom context (files, project info)
- Different AI models
- Unique name and description
Agents have NO tools by default. You must explicitly specify which tools an agent can use via :enable-tools. This is a safety feature to prevent unintended access to powerful capabilities.
Agents can now access ALL available tools, including:
- Read-only tools (grep, read_file, etc.)
- File editing tools (file_write, clojure_edit, etc.)
- Code execution tools (clojure_eval, bash)
- Agent tools (dispatch_agent, architect)
Always consider the security implications when granting tool access.
Add an :agents key to your .clojure-mcp/config.edn:
{:agents [{:id :my-agent
:name "my_agent"
:description "A custom agent for specific tasks"
:system-message "You are a helpful assistant specialized in..."
:context true
:enable-tools [:read_file :grep] ; Must explicitly list tools
:disable-tools nil}]}Each agent configuration supports the following keys:
:id- Unique keyword identifier for the agent:name- String name that appears as the tool name in MCP:description- String description shown in the MCP interface:system-message- System prompt that defines the agent's behavior
:model- AI model to use (keyword reference or model object)- References models defined in
:modelsconfig - Falls back to default if not specified
- References models defined in
:context- What context to provide the agent:true- Include PROJECT_SUMMARY.md and code indexfalseornil- No context["file1.md", "file2.clj"]- Specific file paths
:enable-tools- Controls which tools the agent can use:nil- No tools (default if omitted)[:all]- All available tools[:tool1 :tool2]- Specific tools only
:disable-tools- List of tool IDs to disable (applied after enable-tools):memory-size- Controls conversation memory behavior:nil,false, or< 10- Stateless (default): Clears memory on each chat>= 10- Persistent: Accumulates messages up to limit, then resets completely
:track-file-changes- Whether to track and show file diffs (default:true)true- Shows diffs of file modifications made by the agentfalse- Disables file change tracking
Agents can potentially access all MCP 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 |
:clojure_inspect_project |
Project structure analysis |
| Tool ID | Description |
|---|---|
:clojure_eval |
Execute Clojure code in REPL |
:bash |
Execute shell commands |
| 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 |
| Tool ID | Description |
|---|---|
:dispatch_agent |
Launch sub-agents |
:architect |
Technical planning assistant |
:scratch_pad |
Persistent data storage |
:code_critique |
Code review feedback |
{:id :minimal-agent
:name "minimal_agent"
:description "Agent with no tools"
:system-message "You provide advice based only on your training"
;; :enable-tools nil ; Can omit - nil is default
}{:id :research-agent
:name "research_agent"
:description "Can read but not modify"
:system-message "You research and analyze code"
:enable-tools [:read_file :grep :glob_files :clojure_inspect_project]}{:id :code-writer
:name "code_writer"
:description "Can create and modify files"
:system-message "You write and refactor code. Always test before writing."
:enable-tools [:read_file :grep :clojure_eval :file_write :clojure_edit]}{:id :full-access-agent
:name "full_access"
:description "Access to all tools - use with caution"
:system-message "You have full system access. Confirm destructive operations."
:enable-tools [:all]
:disable-tools [:dispatch_agent] ; Can still exclude specific tools
}Here's a comprehensive configuration with agents of varying capability levels:
{:allowed-directories ["." "src" "test" "resources"]
;; Define custom models (optional)
:models {:anthropic/fast
{:model-name "claude-3-haiku-20240307"
:api-key [:env "ANTHROPIC_API_KEY"]
:temperature 0.3
:max-tokens 2048}
:openai/smart
{:model-name "gpt-4-turbo-preview"
:api-key [:env "OPENAI_API_KEY"]
:temperature 0.2
:max-tokens 4096}}
;; Agent definitions - from least to most capable
:agents [;; Minimal agent - no tools
{:id :advisor
:name "advisor"
:description "Provides advice without any tool access"
:system-message "You are a technical advisor. Provide guidance based on
your knowledge without accessing files or running code."
:context false
;; No :enable-tools means no tools
;; No :memory-size means stateless (default)
}
;; Read-only research agent - stateless
{:id :research-agent
:name "research_agent"
:description "Researches code patterns and finds examples"
:system-message "You are a research specialist. Find patterns, examples,
and analyze structure. Be thorough and provide specific locations."
:model :anthropic/fast
:context true
:memory-size false ; Explicitly stateless
:enable-tools [:grep :glob_files :read_file :clojure_inspect_project]
:disable-tools nil}
;; Documentation specialist - stateless
{:id :doc-reader
:name "doc_reader"
:description "Reads and summarizes documentation"
:system-message "You are a documentation specialist. Summarize clearly
and focus on practical usage."
:context ["README.md" "doc/"]
:memory-size nil ; Stateless (same as omitting)
:enable-tools [:read_file :glob_files]}
;; Test runner - can execute but not modify
{:id :test-runner
:name "test_runner"
:description "Runs tests and analyzes results"
:system-message "You run tests and analyze results. You can execute code
but cannot modify files."
:context ["test/"]
:memory-size 5 ; < 10 = stateless
:enable-tools [:read_file :grep :glob_files :clojure_eval :bash]
:disable-tools [:file_write :file_edit :clojure_edit]}
;; Code writer - persistent memory for multi-step refactoring
{:id :code-writer
:name "code_writer"
:description "Writes and modifies code files"
:system-message "You are a code writing assistant. Create and edit files
responsibly. Always test code before writing to files.
Explain changes clearly."
:model :openai/smart
:context true
:memory-size 50 ; Persistent - remembers recent edits
:enable-tools [:read_file :grep :glob_files
:clojure_eval :bash
:file_write :file_edit
:clojure_edit :clojure_edit_replace_sexp]}
;; Full access agent with large memory
{:id :admin-agent
:name "admin_agent"
:description "Full system access - use with extreme caution"
:system-message "You have complete system access. Always confirm destructive
operations. Explain risks before taking actions. Never delete
or overwrite without explicit confirmation."
:context true
:memory-size 200 ; Large persistent memory for complex operations
:enable-tools [:all] ; Access to everything
:disable-tools nil}]}Once configured, your agents appear as individual tools in your MCP client (like Claude Desktop):
- Each agent shows up with its configured name (e.g.,
research_agent,code_writer) - Select the appropriate agent tool for your task
- Send your prompt to the agent
- The agent responds according to its specialized configuration and tool access
User: research_agent("Find all uses of multimethods in this project")
Research Agent: I'll search for multimethod usage across the project...
[Uses only read-only tools to search and analyze]
User: code_writer("Add a new test for the filter-tools function")
Code Writer: I'll create a test for filter-tools. Let me first examine the existing tests...
[Can read, evaluate, and write files]
User: advisor("What's the best way to structure a Clojure web app?")
Advisor: Based on common patterns and best practices...
[Provides advice without accessing any tools]
Always give agents the minimum tools necessary for their task:
- No tools for general advice/consultation
- Read-only tools for analysis and research
- Execution tools only when testing is required
- Write tools only when file modification is needed
- Full access only in controlled environments with trusted users
| Access Level | Risk | Use Cases |
|---|---|---|
| No tools | None | General advice, explanations |
| Read-only | Low | Code review, research, analysis |
| Read + Execute | Medium | Testing, debugging, validation |
| Read + Write | High | Code generation, refactoring |
| All tools | Very High | System administration, complex automation |
- Default to no tools - Only add what's needed
- Use
:disable-toolsto remove dangerous tools even from:all - Clear system messages - Instruct agents about responsible tool use
- Audit configurations - Regularly review agent capabilities
- Test in sandbox - Try new configurations in safe environments first
The :context field controls what information the agent has access to:
Includes:
PROJECT_SUMMARY.mdif it exists- Current project structure from
clojure-inspect-project .clojure-mcp/code_index.txtif it exists
:context ["README.md" ; Single file
"doc/" ; All files in directory
"src/my_app/core.clj" ; Specific source file
"../other-project/summary.md"] ; Relative paths workAgent starts with only its system message, useful for general-purpose agents.
Agents can use custom models defined in the :models configuration:
{:models {:openai/o3-mini
{:model-name "o3-mini"
:api-key [:env "OPENAI_API_KEY"]
:temperature 0.1}}
:agents [{:id :reasoning-agent
:name "reasoning_agent"
:model :openai/o3-mini ; Reference the model
;; ... other config
}]}The :memory-size field controls how agents handle conversation memory between invocations. Unlike a sliding window that continuously drops old messages, persistent agents accumulate messages until approaching their limit, then completely reset.
| Configuration | Mode | Behavior |
|---|---|---|
nil (default) |
Stateless | Clears memory before each chat, starts fresh |
false |
Stateless | Explicitly stateless, same as nil |
< 10 |
Stateless | Numbers less than 10 treated as stateless |
>= 10 |
Persistent | Accumulates messages up to limit, then resets |
Stateless agents clear their memory at the start of each chat but maintain a 100-message buffer during the conversation:
{:id :task-agent
:name "task_agent"
;; No memory-size specified = stateless (default)
:description "Performs independent tasks"
:system-message "You complete tasks independently..."}
{:id :analyzer
:name "analyzer"
:memory-size false ; Explicitly stateless
:description "Analyzes code without conversation history"
:system-message "You analyze code..."}Benefits of Stateless:
- Predictable behavior - each invocation starts fresh
- No context pollution from previous tasks
- Ideal for single-purpose operations
- Still supports complex multi-turn conversations (100-message buffer)
Persistent agents maintain conversation history across invocations, accumulating messages until approaching the configured limit:
{:id :assistant
:name "assistant"
:memory-size 50 ; Accumulates up to ~35 messages, then resets
:description "Conversational assistant with memory"
:system-message "You are a helpful assistant..."}
{:id :tutor
:name "tutor"
:memory-size 100 ; Accumulates up to ~85 messages, then resets
:description "Educational tutor that remembers previous lessons"
:system-message "You are a patient tutor..."}Persistent Memory Behavior:
- Accumulates conversation history across multiple invocations
- When approaching memory limit (size - 15 messages), completely resets
- Re-adds original context after reset to maintain coherence
- NOT a sliding window - it's a buffer that resets when nearly full
The dispatch_agent tool defaults to persistent mode with a 100-message window. This can be overridden in :tools-config:
{:tools-config {:dispatch_agent {:memory-size 200}} ; Increase dispatch agent memory
;; or
:tools-config {:dispatch_agent {:memory-size false}}} ; Make dispatch agent stateless{:agents [;; Stateless examples
{:id :code-reviewer
:name "code_reviewer"
;; Default: no memory-size = stateless
:description "Reviews code independently each time"
:system-message "Review the provided code..."}
{:id :test-runner
:name "test_runner"
:memory-size 5 ; < 10 = stateless
:description "Runs tests without history"
:system-message "Run and analyze test results..."}
;; Persistent examples
{:id :chat-bot
:name "chat_bot"
:memory-size 30 ; Small buffer - resets after ~15 messages
:description "Simple chatbot with short-term memory"
:system-message "You are a friendly chatbot..."}
{:id :project-assistant
:name "project_assistant"
:memory-size 100 ; Standard buffer - resets after ~85 messages
:description "Project assistant with conversation memory"
:system-message "You help with ongoing project tasks..."}
{:id :long-context-agent
:name "long_context"
:memory-size 300 ; Large buffer - resets after ~285 messages
:description "Agent for complex, long-running conversations"
:system-message "You handle complex multi-part tasks..."}]}Use Stateless (default) when:
- Each task is independent
- You want predictable, fresh behavior
- Handling sensitive data that shouldn't persist
- Building single-purpose tools
- Avoiding context pollution is important
Use Persistent when:
- Building conversational interfaces
- Tasks build on previous interactions
- Maintaining context across multiple queries
- Creating tutoring or coaching agents
- Implementing multi-step workflows
Stateless agents:
- Clear memory at the start of each
chatinvocation - Re-initialize with configured context
- Maintain 100-message buffer during the conversation
- Perfect for complex single tasks with many tool calls
Persistent agents:
- Keep conversation history between invocations
- Accumulate messages until approaching limit (size - 15)
- Completely reset memory when limit approached (not a sliding window)
- Re-add original context after reset
- Ensures conversations remain coherent by avoiding partial memory loss
- If
:enable-toolsisnilor omitted: No tools enabled - If
:enable-toolsis[:all]: All available tools enabled - If
:enable-toolsis[...]: Only listed tools enabled :disable-toolsis applied after:enable-toolsto remove specific tools
Examples:
;; No tools (default)
{:enable-tools nil} ; or omit entirely
;; Specific tools only
{:enable-tools [:read_file :grep]}
;; All tools except some
{:enable-tools [:all]
:disable-tools [:bash :file_write]}- Agents are cached after first creation for performance
- The same agent instance is reused across invocations
- Cache key is based on the agent's
:id - Memory behavior depends on
:memory-sizeconfiguration:- Stateless agents: Memory clears on each invocation (fresh start)
- Persistent agents: Memory persists across invocations within the session
- Agent cache persists for the duration of the MCP server session
- Check that
:agentsis properly formatted inconfig.edn - Ensure all required fields (
:id,:name,:description,:system-message) are present - Restart the MCP server after configuration changes
- Remember: agents have no tools by default
- Explicitly list tools in
:enable-tools - Check tool IDs match exactly (use underscores:
:read_filenot:read-file)
- Verify model is defined in
:modelsconfiguration - Check environment variables for API keys
- Ensure model name is correct for the provider
- Verify tool IDs in
:enable-toolsmatch available tools - Check that tools aren't disabled in
:disable-tools - Ensure the agent has the necessary tool combination (e.g., needs
:read_filebefore:clojure_edit)
- Agent doesn't remember previous conversation: Check if
:memory-sizeis>= 10for persistent memory - Agent remembers unwanted context: Set
:memory-sizetonil,false, or< 10for stateless behavior - Conversation becomes incoherent: Memory may be too small; increase
:memory-sizefor longer conversations - Agent resets mid-conversation: This happens when memory approaches limit (size - 15); increase
:memory-size - Important: Persistent memory is NOT a sliding window - it accumulates messages then resets completely
If you have existing agent configurations:
- Agents had all read-only tools by default
- Could not access write or execution tools
- Agents have NO tools by default
- Can access any tools when explicitly enabled
- Review existing agent configs
- Add explicit
:enable-toolslists - For read-only agents, add:
:enable-tools [:read_file :grep :glob_files ...] - Test thoroughly before deploying
The agent tool builder is automatically included in the main MCP server. When the server starts:
- Reads the
:agentsconfiguration from.clojure-mcp/config.edn - Creates a separate tool for each configured agent
- Each agent gets only its specified tools
- Registers these tools with the MCP protocol
- Each agent appears as an individual tool in your MCP client
No code changes are required - just add agent configurations to your config.edn file and restart the server.