What I Learned After Turning the Same Tool Into Both a CLI and an MCP Server

This post records the system design of making content-i18n support both CLI and MCP server

The point is not MCP protocol itself, but how the same workflow can be shared by two entrypoints

Core questions:

  • what CLI is
  • what MCP is
  • what each layer should own
  • why they should not grow separate workflows
  • how content-i18n splits core, CLI, and MCP

What CLI is

CLI means command-line interface, an interface where users operate a program through terminal commands

Example:

1
content-i18n review --config ./content-i18n.yaml --file target.md --source source.md

This command has several parts:

  • content-i18n: program to run
  • review: action to execute
  • --config: config file path
  • --file: target file
  • --source: source file

CLI users are usually humans, shell scripts, CI jobs, or other automation

A good CLI usually needs:

  • clear command names
  • understandable flags
  • readable error messages
  • exit codes scripts can check
  • output that works for both humans and shell usage

CLI is the human-facing entrypoint

How CLI works

CLI is roughly this flow:

1
2
3
4
5
command
  -> argument parsing
  -> config loading
  -> core workflow call
  -> human-facing output

For content-i18n review:

  1. read command and flags
  2. load content-i18n.yaml
  3. find source and target file
  4. call the real review workflow
  5. print result for the user
  6. return exit code based on result

CLI can decide how output is printed, how errors are shown, and how commands are named

But what review checks, how queue state is calculated, and when sync-status can update should not live in the CLI layer

How to design the CLI layer

The CLI layer should own:

  • command tree
  • flag parsing
  • config path loading
  • shell-friendly output
  • human-readable error
  • exit code

The CLI layer should not own:

  • prepare business rule
  • review business rule
  • queue state derivation
  • sync-status decision
  • batch orchestration
  • source / target mapping rule

CLI is an adapter:

1
2
3
human
  -> CLI
  -> internal/core

It converts human commands into input for core workflow, then converts core workflow results into human-readable output

What MCP is

MCP means Model Context Protocol

In this project, MCP lets an agent runtime operate a local tool through structured tool calls

If CLI is the contract between human and local program, MCP is the contract between agent runtime and local program

CLI call:

1
content-i18n review --config ./content-i18n.yaml --file target.md --source source.md

MCP tool call:

CLI is command + flags

MCP is tool name + structured arguments

The form is different, but the final operation can be the same

How MCP works

MCP layer is roughly this flow:

1
2
3
4
tool definition
  -> request decoding
  -> core workflow call
  -> structured response

An MCP tool usually defines:

  • tool name
  • description
  • input schema
  • handler
  • response shape

For content_i18n_review_translation:

  1. declare the tool exists
  2. define required arguments
  3. receive request from agent runtime
  4. decode it into input for core workflow
  5. call review workflow
  6. return structured response to agent

MCP output needs to let the agent continue the next step

So response usually includes:

  • issue list
  • file path
  • source path
  • sync readiness
  • next action hint

But MCP should not implement review by itself

How to design the MCP layer

The MCP layer should own:

  • public tool contract
  • argument schema
  • request decoding
  • handler registration
  • structured response
  • agent-friendly result shape

The MCP layer should not own:

  • deciding review rules
  • deriving queue state
  • deciding completion
  • handling source / target mapping
  • adding batch orchestration rules

content-i18n MCP server was organized like this:

1
2
3
4
5
6
7
8
9
internal/mcp/
  server.go
  tools.go
  tool_defs.go
  tool_handlers.go
  resources.go
  resource_defs.go
  resource_handlers.go
  response.go

This structure makes responsibilities clearer:

  • definition stores tool contract
  • handler receives request and calls core
  • registration attaches tools to server
  • response shapes agent-facing output

Registration also changed to a registry / spec pattern

That keeps the public contract out of a long manual registration block, and makes adding or removing tools less error-prone

Why CLI and MCP should share the same core workflow

When one tool supports both CLI and MCP, the biggest risk is that the two entrypoints slowly become two products

If both sides implement logic by themselves, drift appears quickly:

  • CLI review passes, MCP review fails
  • MCP sync succeeds, CLI queue still shows stale
  • batch path and single-file path check different things
  • agent bypasses review and edits target file directly
  • completion rule means different things in different entrypoints

So workflow needs one authority

In content-i18n, that authority is internal/core

1
2
CLI / MCP = interface
internal/core = workflow owner

CLI and MCP can have different input / output

But prepare, review, sync, queue, and batch can only have one meaning

content-i18n core boundary

Final stable layering:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
consumer repo
  โ”œโ”€ content roots
  โ”œโ”€ content-i18n.yaml
  โ”œโ”€ glossary
  โ””โ”€ style pack
        โ”‚
        โ–ผ
CLI / MCP
        โ”‚
        โ–ผ
internal/core
  โ”œโ”€ init
  โ”œโ”€ prepare/review/sync
  โ”œโ”€ queue
  โ”œโ”€ batch orchestration
  โ””โ”€ site validation entrypoint
        โ”‚
        โ”œโ”€ validator
        โ”œโ”€ structure
        โ”œโ”€ frontmatter
        โ”œโ”€ content
        โ””โ”€ providers

Responsibility split:

Layer Responsibility
CLI command parsing, flags, config path, exit code, human-readable output
MCP tool definition, request decoding, handler registration, structured response
internal/core prepare, review, sync, queue, batch, validation workflow
validator / structure / frontmatter / content low-level document rules
providers DeepL, Google, ai-harness provider boundary

CLI and MCP call internal/core

internal/core does not need to know whether the caller is CLI or MCP

Review rule changes once, and both CLI and MCP get the same behaviour

How MCP tool surface should be designed

MCP tool surface should not be too granular

Early low-level primitives can look flexible:

  • read source
  • create work packet
  • validate translation
  • write translation target
  • next translation
  • repair translation

But they are risky for agents

Tool surface is not only an API list. It also hints at how the agent should work

With too many raw primitives, the agent can assemble its own workflow:

1
2
3
4
5
read source
  -> inspect file
  -> edit target directly
  -> run partial validation
  -> skip sync-status

That is tool bypass

So content-i18n reduced the public MCP surface to workflow-level tools:

  • content_i18n_status
  • content_i18n_prepare_translation
  • content_i18n_review_translation
  • content_i18n_sync_status
  • content_i18n_translation_queue
  • content_i18n_translate_batch
  • content_i18n_validate_site

These 7 tools fit agents better than low-level primitives:

  • prepare_translation is closer to a real workflow step than read_source + create_work_packet
  • translation_queue can already return candidate, so extra next_translation is not needed
  • review_translation returns structured issues and sync readiness, so it is more complete than a thin validator wrapper
  • translate_batch keeps batch orchestration inside the tool, instead of making agent build its own batch loop
  • sync_status turns completion into official state, not only file existence

A better MCP tool design has:

  • fewer public tools
  • higher-level operations
  • fuller responses
  • less room for caller-invented workflow

How queue state works

Queue model turns translation workflow from a one-time task into a maintainable system

content-i18n uses three states:

  • completed
  • stale
  • missing

These states come from:

  • source discovery
  • expected target path
  • source hash
  • translation status

Meanings:

State Meaning
missing source exists, expected target does not exist
stale target exists, but source changed after last sync
completed review / validation passed, target synced with source

Translation is not once-and-done

A target completed today can become stale tomorrow if source changes

Queue is not only a list of files to translate. It answers:

  • which targets do not exist
  • which targets are no longer synced with source
  • which targets are complete
  • which post should be handled next

MCP tools also need to respect queue model

If agent can skip queue, review, and sync, workflow authority fails

Failure modes and final rules

This design avoids several failure modes

CLI / MCP drift

If CLI and MCP each implement review, they become inconsistent

Fix:

  • CLI and MCP only call core
  • review rule exists once
  • queue state derives from core

Wrapper logic leak

If MCP wrapper starts adding workflow rules, wrapper keeps getting thicker

Fix:

  • MCP handler decodes request
  • handler calls core
  • handler formats structured response
  • handler does not rejudge business rule

Low-level tool bypass

Too many low-level tools let agent build its own workflow

Fix:

  • public surface uses workflow-level tools
  • response carries enough information
  • review and sync-status become official path

Fuzzy completion

If completion only means target file exists, queue state becomes unreliable

Fix:

  • review / validation passes
  • source-language leftovers are removed
  • sync-status succeeds

Duplicated state logic

If CLI, MCP, and batch each calculate queue state, result can diverge

Fix:

  • queue state derives in core
  • callers use core result

Final rules:

  1. CLI and MCP are entrypoints, not two products
  2. internal/core is the only workflow owner
  3. MCP tools should be workflow-level
  4. Public surface should be small, response should be complete
  5. Queue state must be system-derived
  6. Completion needs an official path
  7. Tool design should prevent agent bypass

This keeps CLI and MCP using the same tool core, instead of slowly becoming two different systems

References

  1. content-i18n GitHub Repository
  2. Model Context Protocol โ€” What is MCP?
  3. Model Context Protocol โ€” Architecture Overview
  4. Model Context Protocol Specification โ€” Basic Overview
  5. Model Context Protocol โ€” SDKs