Codex CLI for Scala Development Teams: Metals MCP, sbt, and Idiomatic Functional Workflows
Codex CLI for Scala Development Teams: Metals MCP, sbt, and Idiomatic Functional Workflows
Dedicated language-specific Codex CLI articles exist for Go, Rust, Ruby/Rails, Python/Django/FastAPI, C/C++, Elixir/Phoenix, Swift, Kotlin/Android, Java/Spring, PHP/Laravel, and .NET — but not for Scala, despite it being a top-20 language powering some of the most demanding data-processing and financial-services systems in production. The gap matters because Scala’s combination of advanced type systems, functional programming idioms, and JVM interop creates failure modes that generic agent configurations miss entirely.
This article covers three layers that turn Codex CLI into an effective Scala pair programmer: the Metals MCP server for semantic code intelligence, sbt integration patterns for compilation and testing, and an AGENTS.md template that encodes Scala-specific conventions. Everything targets Scala 3.9 LTS 1, sbt 2.x 2, and Codex CLI v0.132 3.
Why Scala Needs Special Agent Configuration
AI coding models default to Scala 2-style syntax — implicit keyword over given/using, classic for-comprehensions over direct-style effect handling, verbose pattern matching over Scala 3 enums 4. Without explicit guidance, a Codex session generates code that compiles against Scala 3 but fails code review because it violates the idioms your team adopted precisely to benefit from the language’s evolution.
Three Scala-specific concerns demand configuration:
- Type inference depth. Scala’s type system is substantially more expressive than Go or Python. Agents that over-annotate produce noise; agents that under-annotate produce code that confuses Metals and slows incremental compilation.
- Effect system conventions. Teams standardise on Cats Effect, ZIO, or direct-style effects. Mixing paradigms within a codebase creates maintenance nightmares that generic agents blithely produce.
- Build tool semantics. sbt’s incremental compilation, cross-building, and multi-project layouts require specific commands and directory conventions that differ from Maven or Gradle patterns.
Layer 1: Metals MCP Server
Metals 5 is the Scala language server that powers IDE features across VS Code, IntelliJ, Neovim, and Emacs. Since Metals v1.5.3 6, it ships with a built-in MCP server that exposes semantic code intelligence to AI agents. Connecting it to Codex CLI gives the agent compiler feedback, symbol navigation, and project-aware code generation.
The Nine MCP Tools
Metals currently exposes nine tools through its MCP interface 7:
| Tool | Purpose | Agent Impact |
|---|---|---|
compile-file |
Compile a single Scala file | Immediate error feedback without full rebuild |
compile-full |
Compile the entire project | Verify cross-module changes |
inspect |
Inspect a Scala symbol (members, constructors, signatures) | Navigate unfamiliar codebases |
get-usages |
Find all references to a symbol | Safe refactoring |
typed-glob-search |
Search symbols by substring and kind | Discover APIs without reading every file |
run-diagnostics |
Current compiler diagnostics state | Check for latent errors |
list-modules |
List available project modules | Multi-module project navigation |
run-tests |
Execute test suites | Verify changes |
goto-definition |
Navigate to symbol source | Understand implementation details |
The single most valuable tool for agent workflows is compile-file — it creates a tight feedback loop where the agent writes code, compiles it, receives errors, and corrects immediately without waiting for a full sbt compile cycle 7.
Connecting Metals MCP to Codex CLI
The recommended approach uses metals-standalone-client 8, a headless launcher that starts Metals and exposes its MCP server without requiring an IDE:
# Install metals-standalone-client
curl -Lo metals-standalone-client \
https://github.com/jpablo/metals-standalone-client/releases/latest/download/metals-standalone-client-linux
chmod +x metals-standalone-client
# Launch against your project
./metals-standalone-client --workspace /path/to/your-project
The client discovers your Metals installation via Coursier, performs the LSP handshake, and writes a .mcp.json file at your project root with the connection URL 8.
Configure Codex CLI to connect in config.toml:
[mcp_servers.metals]
type = "sse"
url = "http://localhost:7101/mcp"
enabled = true
# Restrict to semantic tools — keep the surface small
enabled_tools = [
"compile-file",
"compile-full",
"inspect",
"get-usages",
"typed-glob-search",
"run-diagnostics"
]
For project-scoped configuration, add to .codex/config.toml in your repository root so every team member inherits the same Metals MCP setup.
The Feedback Loop
With Metals connected, the agent loop changes fundamentally:
graph TD
A[Agent writes Scala code] --> B[apply_patch to file]
B --> C[MCP: compile-file]
C --> D{Compiler errors?}
D -->|Yes| E[Agent reads diagnostics]
E --> F[Agent fixes errors]
F --> B
D -->|No| G[MCP: run-diagnostics]
G --> H{Warnings?}
H -->|Yes| E
H -->|No| I[sbt test via shell]
I --> J{Tests pass?}
J -->|Yes| K[Done]
J -->|No| E
This loop catches type errors, missing implicits, and exhaustivity warnings before the agent moves on — preventing the cascading failures that plague unguided Scala code generation.
Layer 2: sbt Integration Patterns
Essential AGENTS.md Build Commands
sbt’s interactive shell and triggered execution model require specific guidance. Include these commands in your AGENTS.md:
## Build Commands
- Compile: `sbt compile`
- Test all: `sbt test`
- Test single suite: `sbt "testOnly com.example.MySpec"`
- Triggered test: `sbt "~testOnly com.example.MySpec"` (auto-reruns on save)
- Clean build: `sbt clean compile`
- Format: `sbt scalafmtAll` (requires sbt-scalafmt plugin)
- Lint: `sbt "scalafix --check"` (requires sbt-scalafix plugin)
- Multi-project compile: `sbt "project core" compile`
Cross-Building and Multi-Project Layouts
Scala projects frequently cross-build against multiple Scala versions and use multi-project sbt builds. The agent needs to understand the project topology:
# .codex/config.toml — project-scoped
[sandbox_workspace_write]
# sbt downloads dependencies to ~/.cache/coursier and ~/.sbt
writable_roots = ["~/.cache/coursier", "~/.sbt", "~/.ivy2"]
For multi-module projects, create directory-level AGENTS.md overrides:
project-root/
├── AGENTS.md # Global conventions
├── core/
│ └── AGENTS.override.md # Core module specifics
├── api/
│ └── AGENTS.override.md # API layer specifics
└── build.sbt
Incremental Compilation Awareness
sbt’s Zinc incremental compiler 2 is one of Scala’s strongest productivity features, but agents that run sbt clean compile on every iteration destroy the incremental cache. Your AGENTS.md should explicitly state:
## Build Rules
- NEVER run `sbt clean` unless explicitly asked. Use `sbt compile` for incremental builds.
- After changing build.sbt, run `sbt reload` before `sbt compile`.
- Use `compile-file` MCP tool for single-file checks before running full `sbt compile`.
Layer 3: AGENTS.md Template for Scala Teams
The following template encodes the conventions that prevent the most common agent failures in Scala codebases:
# AGENTS.md — Scala 3 Project
## Language Version
This project uses **Scala 3.9 LTS**. Use Scala 3 syntax exclusively:
- `given`/`using` instead of `implicit`
- `enum` instead of sealed trait hierarchies for ADTs
- `extension` methods instead of implicit classes
- Indentation-based syntax (significant indentation) is preferred
- `export` clauses for selective forwarding
## Effect System
This project uses **Cats Effect 3**. All effectful code must return `IO[A]`.
- Never use `Future` — all async code uses `IO`
- Use `Resource[IO, A]` for anything that needs cleanup
- Prefer `cats.syntax.all.*` imports over individual syntax imports
- Use `EitherT` or `OptionT` only when the monad transformer
genuinely simplifies the code; prefer pattern matching on `IO[Either[E, A]]`
## Error Handling
- Domain errors: sealed `enum` ADTs in the `errors` package
- Use `MonadError` / `ApplicativeError` for effect-level errors
- Never catch `Throwable` — catch specific exception types
- Log errors with context using structured logging (log4cats)
## Testing
- Framework: **MUnit** with `munit-cats-effect` for IO-based tests
- Test command: `sbt test`
- Single suite: `sbt "testOnly com.example.*Spec"`
- Property-based tests: ScalaCheck generators in `src/test/scala/generators/`
- Test naming: `test("description of behaviour")`
## Formatting and Linting
- Run `sbt scalafmtAll` before committing
- Run `sbt "scalafix --check"` to verify lint rules
- Max line length: 120 characters
- Imports: use `import cats.syntax.all.*` not individual imports
## Project Structure
src/main/scala/com/example/ ├── domain/ # Case classes, enums, value types ├── errors/ # Error ADTs ├── services/ # Business logic (tagless final or direct IO) ├── http/ # HTTP routes (http4s) ├── persistence/ # Database access (doobie/skunk) └── config/ # Application configuration (ciris/pureconfig)
## Build
- Build tool: sbt 2.x
- NEVER run `sbt clean` unless explicitly requested
- Use `sbt compile` for incremental compilation
- After modifying build.sbt: `sbt reload` then `sbt compile`
Scala 2 vs Scala 3 Guardrails
The most frequent agent failure is generating Scala 2 syntax in a Scala 3 project 4. Add explicit negative examples:
## Anti-Patterns — Do NOT Generate
```scala
// WRONG: Scala 2 implicit
implicit val encoder: Encoder[User] = deriveEncoder
// CORRECT: Scala 3 given
given Encoder[User] = Encoder.derived
// WRONG: Scala 2 implicit class
implicit class StringOps(s: String):
def toSlug: String = ...
// CORRECT: Scala 3 extension
extension (s: String)
def toSlug: String = ...
// WRONG: sealed trait + case objects
sealed trait Color
case object Red extends Color
// CORRECT: Scala 3 enum
enum Color:
case Red, Green, Blue
## Codex CLI Configuration Profile
Create a named Scala profile for quick switching:
```toml
# ~/.codex/config.toml
[profiles.scala]
model = "gpt-5.5"
model_reasoning_effort = "high"
approval_policy = "on-request"
[profiles.scala.mcp_servers.metals]
type = "sse"
url = "http://localhost:7101/mcp"
enabled = true
enabled_tools = [
"compile-file",
"compile-full",
"inspect",
"get-usages",
"typed-glob-search",
"run-diagnostics"
]
Activate with:
codex --profile scala
Use gpt-5.5 for Scala work — the type system complexity benefits from higher reasoning capability 9. For subagents handling simpler tasks (test generation, formatting), drop to gpt-5.4-mini with model_reasoning_effort = "medium".
Practical Workflow: Adding an HTTP Endpoint
Here is a concrete workflow for adding a new endpoint to an http4s service:
codex --profile scala
Then prompt:
Add a GET /api/v1/users/:id endpoint to the UserRoutes.
It should return 200 with the user JSON or 404 with an error body.
Use the existing UserService.findById method.
Write an MUnit test covering both cases.
The agent will:
- Use
inspectMCP tool to examineUserServiceandUserRoutessignatures - Generate the route using http4s DSL with
IOeffects - Call
compile-fileto verify the route compiles - Generate the MUnit test with
munit-cats-effect - Run
sbt "testOnly *UserRoutesSpec"to verify
Hooks for Scala Quality Gates
Add a PreToolUse hook that runs scalafmt before any shell command commits changes:
# .codex/config.toml
[[hooks]]
event = "PostToolUse"
tool_name = "apply_patch"
command = "sbt scalafmtAll 2>/dev/null; true"
timeout_ms = 30000
This ensures every file the agent modifies is formatted before the next compilation cycle, preventing formatting drift that pollutes diffs.
Effect System Guidance by Stack
Different Scala teams use different effect systems. Adjust the AGENTS.md effect section accordingly:
| Stack | Key AGENTS.md Rule | Import Pattern |
|---|---|---|
| Cats Effect | All effects return IO[A]; use Resource for lifecycle |
import cats.effect.* |
| ZIO | All effects return ZIO[R, E, A]; use ZLayer for DI |
import zio.* |
| Direct Style (Scala 3.5+) | Use boundary/break and CanThrow capabilities 10 |
import scala.util.boundary |
| Ox | Use structured concurrency with supervised blocks |
import ox.* |
⚠️ Direct-style effects and the Ox library represent newer patterns — verify that your team’s Scala version and compiler plugins support them before adding to AGENTS.md.
Common Agent Failures in Scala
| Failure | Root Cause | Fix |
|---|---|---|
Generates implicit instead of given |
Model defaults to Scala 2 training data | Explicit Scala 3 mandate in AGENTS.md |
| Missing type annotations on public APIs | Agent relies on inference for everything | Rule: “Annotate all public method return types” |
Mixes Future with IO |
Model conflates JVM concurrency primitives | Specify single effect system in AGENTS.md |
Runs sbt clean compile repeatedly |
No awareness of incremental compilation | Explicit build rules section |
| Generates Java-style null checks | Model falls back to Java patterns | Rule: “Use Option, never null” |
| Ignores exhaustivity warnings | Treats warnings as non-blocking | Connect Metals MCP for warning-as-error feedback |
Comparison with Other Language Setups
The Scala setup follows the same three-layer pattern as the Go 11 and Rust 12 articles but differs in key ways:
- MCP server: Metals MCP provides compiler-level feedback comparable to gopls MCP and rust-analyzer MCP, but Metals’
inspecttool is uniquely valuable for navigating Scala’s deeper type hierarchies. - Build tool: sbt’s incremental compilation and multi-project support require more nuanced build commands than
go buildorcargo build. - AGENTS.md: Scala requires more explicit negative examples (Scala 2 vs 3) because the model’s training data spans both language versions.
What This Does Not Cover
This article focuses on Codex CLI configuration for Scala teams. It does not cover:
- Spark or big-data-specific workflows (these warrant a dedicated article given the cluster execution model)
- Play Framework web applications (covered in part by the general sbt patterns here, but Play’s route file conventions deserve separate treatment)
- Scala.js cross-compilation workflows
Citations
-
Scala 3.9 LTS release roadmap — https://www.scala-lang.org/blog/2025/02/20/scala-3-roadmap-for-2025.html — Scala 3.9 designated as next LTS distribution, Q2 2026 ↩
-
sbt 2.x documentation — https://www.scala-sbt.org/2.x/docs/ — sbt with Zinc incremental compiler ↩ ↩2
-
OpenAI Codex CLI Changelog v0.132.0 — https://developers.openai.com/codex/changelog — Python SDK auth, exec resume –output-schema ↩
-
SoftwareMill, “A Beginner’s Guide to Using Scala Metals With its Model Context Protocol Server” — https://softwaremill.com/a-beginners-guide-to-using-scala-metals-with-its-model-context-protocol-server/ — Notes agents default to Scala 2 syntax ↩ ↩2
-
Metals — Scala Language Server — https://scalameta.org/metals/ ↩
-
Metals v1.5.3 release notes — https://scalameta.org/metals/blog/2025/05/13/strontium/ — Introduced MCP server support ↩
-
Metals MCP tools reference — https://softwaremill.com/a-beginners-guide-to-using-scala-metals-with-its-model-context-protocol-server/ — Nine tools including compile-file, inspect, get-usages ↩ ↩2
-
metals-standalone-client — https://github.com/jpablo/metals-standalone-client — Headless Metals launcher with MCP support ↩ ↩2
-
OpenAI Models documentation — https://developers.openai.com/codex/models — GPT-5.5 recommended for complex coding tasks ↩
-
Scala 3 boundary/break documentation — https://docs.scala-lang.org/scala3/reference/experimental/boundary-break.html — Direct-style error handling ↩
-
Codex CLI for Go Development Teams — https://codex.danielvaughan.com/2026/05/21/codex-cli-for-go-development-teams-gopls-mcp-skills-agents-md-workflows/ ↩
-
Codex CLI for Rust Development Teams — https://codex.danielvaughan.com/2026/04/29/codex-cli-rust-teams-rust-analyzer-mcp-cargo-hooks-agent-driven-workflows/ ↩