Tip
Use this MCP server to easily expose Makefile targets as MCP tools. Let AI agents execute your Makefile targets through the Model Context Protocol.
- What is the Value of mcp-makefile-server?
- Features
- TL;DR - Simplest Setup
- Usage Patterns
- Quick Start
- Advanced Configuration
- Removing and Uninstalling
- Troubleshooting
- Makefile Format
- Development
- Best Practices
- Examples
- License
| Value | What you get (benefit) | Why it matters in practice |
|---|---|---|
| Turn your workflow into tools instantly | Anything you can run from a Makefile: scripts, CLIs, one-liners, pipelines, etc. become a first-class MCP tool | You stop “rewriting tooling” for every assistant/client and just expose what already works |
| Tooling without building a tool platform | An MCP server that “just works” off your existing Make targets | You avoid bespoke MCP coding, schemas, and glue logic because your Makefile is the integration layer |
| No need to remind the coding tool about the Makefile | The coding tool doesn't need to know about the Makefile, it finds out what tools it has automatically via MCP. | You can simply add new targets to the Makefile and the coding tool will automatically know about them. |
| Self-service automation | Your assistant can add/adjust targets as needs evolve (you review + merge like normal code) | Tooling grows at the speed of your project |
| One source of truth for “how we do things” | The Makefile becomes the canonical catalog of project actions (build, test, lint, release, migrate, etc.) | No drift between docs, tribal knowledge, CI steps, etc. |
| Safer execution by design | You expose only what you want (allowlists, internal/skip markers) and keep dangerous stuff hidden | Only give the coding tool access to what it needs to do its job |
| Better guidance at the point of use | ## comments become the tool’s instructions: options, inputs, side effects, outputs |
The “how to use it” travels with the command, so it stays accurate as the target evolves |
| Composable building blocks | Targets can depend on other targets (e.g., build: test lint) and form reliable workflows |
You get a clean, modular automation graph |
| Tooling portability | Makefiles work almost everywhere; you’re not locked into a specific agent ecosystem | Your automation survives client churn. New assistant? Same Make targets |
| Feature | Description |
|---|---|
| Automatic Tool Discovery | Parses Makefile and exposes documented targets as MCP tools |
| Target Filtering | Use allowlists to control which targets are exposed |
| Progress Notifications | MCP clients receive start/completion status updates for long-running targets |
| Category Support | Organize targets with ## Category: headers |
| Internal Targets | Mark targets with @internal or @skip to exclude them |
| Async Execution | Non-blocking target execution with timeout support |
| Output Management | Optional truncation, file output with organized subdirectories, customizable temp location |
| Configurable Timeouts | Set custom timeout per target execution (default: 300s) |
In your project directory with a Makefile:
# Install uv (if needed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Add MCP server to your project
claude mcp add-json --scope project makefile-server '{
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/ccollicutt/mcp-makefile-server",
"mcp-makefile-server",
"./Makefile"
]
}'
# Restart Claude CodeDone! Your Makefile targets with ## comments are now available as tools.
Recommended: Project-Scoped
The best way to use this MCP server is to point it at your current project's Makefile using --scope project. This approach:
- Gives Claude Code access to project-specific targets
- Keeps each project's automation isolated and relevant
- Allows different projects to have different Makefile targets
Alternative: Global Configuration
You can use --scope global to make the server available across all projects. This is useful if you have:
- A shared utilities Makefile with common tasks
- Cross-project tooling that you want available everywhere
Both Configurations
You can configure both a global instance and project-specific instances:
- Global instance: Points to a shared utilities Makefile (
~/makefiles/common.mk) - Project instances: Each project points to its own
./Makefile
Each instance can point to a different Makefile and expose different targets. The global instance provides shared tools, while project instances provide project-specific automation.
Install uv (if you don't have it):
curl -LsSf https://astral.sh/uv/install.sh | shThis gives you both uv and uvx commands.
No cloning needed! uvx runs directly from GitHub:
# Test it works - preview your Makefile targets
uvx --from git+https://github.com/ccollicutt/mcp-makefile-server mcp-makefile-server preview ./MakefileWhat this does:
- Downloads and caches the server from GitHub
- Runs the
previewcommand on your./Makefile - Shows what tools would be exposed
Example output:
Found 3 targets in ./Makefile:
• test - Run test suite
• build - Build package
• deploy - Deploy to production
That's it! Now skip to Claude Code Setup below.
Use this if you need to modify the code.
Step 1: Clone and install
git clone https://github.com/ccollicutt/mcp-makefile-server.git
cd mcp-makefile-server
uv pip install .Step 2: Test it works
# Preview your Makefile targets
uv run python -m mcp_makefile preview /path/to/your/Makefile
# Or just list tool names
uv run python -m mcp_makefile list /path/to/your/MakefileFor Development: See DEVELOP.md for development setup instructions.
Configure the MCP server for your project:
If you used Method 1 (uvx):
cd ~/projects/my-app
claude mcp add-json --scope project makefile-server '{
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/ccollicutt/mcp-makefile-server",
"mcp-makefile-server",
"./Makefile"
]
}'If you used Method 2 (local installation):
cd ~/projects/my-app
claude mcp add-json --scope project makefile-server '{
"type": "stdio",
"command": "uv",
"args": [
"--directory",
"/path/to/mcp-makefile-server",
"run",
"python",
"-m",
"mcp_makefile",
"./Makefile"
]
}'What this does:
- Creates
.mcp.jsonin your project root (project-scoped) - Configures Claude Code to use your Makefile targets as tools when working in this project
For global setup (available in all projects):
Replace --scope project with --scope global in the commands above. This creates a global MCP configuration, though project-scoped is recommended since each project typically has its own Makefile with project-specific targets.
You can configure both: A global instance for shared utilities and project-specific instances for each project's Makefile.
Next step: Restart Claude Code to load the server.
Restrict which targets can be executed:
Using uvx:
claude mcp add-json --scope project makefile-server '{
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/ccollicutt/mcp-makefile-server",
"mcp-makefile-server",
"./Makefile",
"--allowed-targets",
"test",
"build",
"lint"
]
}'Using local installation:
claude mcp add-json --scope project makefile-server '{
"type": "stdio",
"command": "uv",
"args": [
"--directory",
"/path/to/mcp-makefile-server",
"run",
"python",
"-m",
"mcp_makefile",
"./Makefile",
"--allowed-targets",
"test",
"build",
"lint"
]
}'The server works out of the box with sensible defaults:
| Setting | Default Value | What it means |
|---|---|---|
| Output Length | Unlimited (0) |
All output is returned without truncation |
| File Output | Disabled | Output is not written to files (only returned in response) |
| Temp Directory | /tmp |
Where temporary files are created (if file output is enabled) |
| Timeout | 300 seconds | Maximum execution time per target |
| Allowed Targets | All non-internal | All targets with ## comments are exposed (except @internal/@skip) |
| Log Level | INFO |
Standard logging verbosity |
In other words: The server returns all output directly to the client with no truncation or file writing, executes any documented target, and times out after 5 minutes.
The server can also be configured via environment variables:
| Environment Variable | Description | Default |
|---|---|---|
MCP_MAKEFILE_PATH |
Path to Makefile | ./Makefile |
MCP_MAKEFILE_LOG_LEVEL |
Logging level (DEBUG, INFO, WARNING, ERROR) | INFO |
MCP_MAKEFILE_ALLOWED_TARGETS |
Comma-separated list of allowed targets | All non-internal targets |
MCP_MAKEFILE_MAX_OUTPUT_CHARS |
Maximum characters to return from target output (0 = unlimited) | 0 (unlimited) |
MCP_MAKEFILE_WRITE_TO_FILE |
Write full output to temporary files (true/false) | false |
MCP_MAKEFILE_TEMP_DIR |
Base directory for temporary files | /tmp |
Using environment variables in Claude Code:
You can set environment variables in your .mcp.json configuration:
claude mcp add-json --scope project makefile-server '{
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"git+https://github.com/ccollicutt/mcp-makefile-server",
"mcp-makefile-server",
"./Makefile"
],
"env": {
"MCP_MAKEFILE_LOG_LEVEL": "DEBUG",
"MCP_MAKEFILE_MAX_OUTPUT_CHARS": "5000",
"MCP_MAKEFILE_WRITE_TO_FILE": "true",
"MCP_MAKEFILE_TEMP_DIR": "/var/tmp"
}
}'This configures the server to:
- Use DEBUG logging
- Truncate output at 5000 characters
- Write full output to files in
/var/tmp/mcp-makefile-{random-id}/
See Claude Code Settings Documentation for more information.
Setting environment variables in your shell:
export MCP_MAKEFILE_PATH=/path/to/Makefile
export MCP_MAKEFILE_LOG_LEVEL=DEBUG
export MCP_MAKEFILE_ALLOWED_TARGETS="test,build,lint"
export MCP_MAKEFILE_MAX_OUTPUT_CHARS=5000
export MCP_MAKEFILE_WRITE_TO_FILE=true
export MCP_MAKEFILE_TEMP_DIR=/var/tmpThe server provides two options for managing large output:
Option 1: Truncate Output (Optional)
By default, output is unlimited. To prevent token overload, you can enable truncation:
Via environment variable:
export MCP_MAKEFILE_MAX_OUTPUT_CHARS=5000 # Set >0 to truncate, 0 = unlimitedVia command-line argument:
mcp-makefile-server serve ./Makefile --max-output-chars 5000When output is truncated, you'll see a message like:
Note: Output exceeded 5000 characters and was truncated.
Configure targets to log verbose output to files and return summaries instead.
Option 2: Write to Temporary File
Write full output to temporary files and return the file path. The server creates a unique subdirectory for each session to organize output files.
Via environment variable:
export MCP_MAKEFILE_WRITE_TO_FILE=trueVia command-line argument:
mcp-makefile-server serve ./Makefile --write-to-fileWhen enabled, you'll see:
Full output written to: /tmp/mcp-makefile-4a3f2e1b/test-1234567890.log
Customize temp directory location:
# Via environment variable
export MCP_MAKEFILE_TEMP_DIR=/var/tmp
# Via command-line argument
mcp-makefile-server serve ./Makefile --write-to-file --temp-dir /var/tmpThe server automatically creates a randomized subdirectory (e.g., mcp-makefile-{random-id}) within the temp directory to organize all output files for that session.
You can combine both options to truncate the returned output while keeping a full copy in a file.
To remove the MCP server from your project:
cd ~/projects/my-app
claude mcp remove "makefile-server" -s projectThis removes the server from .mcp.json. Restart Claude Code to apply changes.
If you used Method 1 (uvx):
The server is cached automatically. To clear it:
# Clear specific package from cache
uv cache clean mcp-makefile-server
# Or clear entire uv cache
uv cache cleanIf you used Method 2 (local installation):
# Uninstall the package
uv pip uninstall mcp-makefile-server
# Optionally, remove the cloned directory
rm -rf /path/to/mcp-makefile-serverIf Claude Code shows "Failed to reconnect to makefile-server":
- Check the command name is
mcp-makefile-server(notmcp-makefile) - Verify the Makefile path is correct
- Check the server logs: Look at Claude Code's output panel
- Test the server manually:
uvx --from git+https://github.com/ccollicutt/mcp-makefile-server mcp-makefile-server preview ./Makefile
Your Makefile must use the standard self-documenting format with ## comments:
.PHONY: test build deploy
## Category: Testing
test: ## Run test suite with pytest (outputs results to stdout)
pytest
lint: ## Check code style and formatting with ruff (reports issues found)
ruff check .
## Category: Building
build: test ## Build Python package distribution (runs tests first, creates dist/ directory with wheel and sdist)
python -m build
# Mark targets as internal (NOT exposed)
deploy-prod: ## @internal Deploy to production
./deploy.sh --prod
# Regular targets ARE exposed
deploy-staging: test ## Deploy to staging environment (runs tests first, creates deploy.log)
./deploy.sh --stagingFormat Rules:
| Target Type | Result |
|---|---|
Targets with ## descriptions |
Exposed as MCP tools |
Targets with ## @internal or ## @skip |
NOT exposed |
Targets without ## |
Ignored |
For development setup, testing, Python API usage, and contributing guidelines, see DEVELOP.md.
MCP responses consume tokens and bandwidth. Keep output concise:
Good practices:
# Use variables for options, not separate targets
VERBOSE ?= 0
LOG_FILE ?= test-results.log
test: ## Run test suite with pytest (VERBOSE=1 for detailed output, LOG_FILE=path to save results, default: quiet mode with summary)
@echo "Running tests..."
@if [ "$(VERBOSE)" = "1" ]; then \
pytest -v > $(LOG_FILE) 2>&1 && echo "✓ Tests complete (verbose). Full output in $(LOG_FILE)"; \
else \
pytest --quiet --tb=short > $(LOG_FILE) 2>&1 && echo "✓ Tests complete. Full output in $(LOG_FILE)" || \
(echo "✗ Tests failed. Check $(LOG_FILE) for errors" && exit 1); \
fi
build: ## Build Python package distribution (creates dist/ with wheel and sdist, full output saved to build.log)
@echo "Building package..."
@python -m build --quiet > build.log 2>&1 && echo "✓ Build complete. See build.log" || \
(echo "✗ Build failed. Check build.log for errors" && exit 1)
lint: ## Check code style with ruff (reports only issues found, use FIX=1 to auto-fix problems)
@if [ "$(FIX)" = "1" ]; then \
ruff check --fix . && echo "✓ Linting complete (auto-fixed)"; \
else \
ruff check . --quiet && echo "✓ No linting issues" || echo "✗ Linting failed"; \
fiAvoid:
# DON'T: Create many similar targets
test: ## Run tests
pytest --quiet
test-verbose: ## Run tests verbosely
pytest -vvv # Separate target for same thing!
test-coverage: ## Run tests with coverage
pytest --cov # Another target!
# DON'T: Poor descriptions
build: ## Build # What does it build? What are the options?
python -m build
# DON'T: Print everything
deploy: ## Deploy
npm install # Prints hundreds of lines
npm run build # Prints more lines
kubectl apply -f . # Even more outputDescription Best Practices:
The ## description is sent to the MCP client/AI, so make it informative:
# GOOD: Clear description with options explained
test: ## Run test suite (VERBOSE=1 for details, TEST=pattern to filter, COVERAGE=1 for coverage report)
...
# GOOD: Explains what it does and what happens
deploy-staging: ## Deploy to staging environment (runs tests first, creates deploy.log with details)
...
# BAD: Too vague
test: ## Test
...
# BAD: Missing important info
deploy: ## Deploy
...Tips:
| Tip | Description | Example |
|---|---|---|
| Write AI-friendly descriptions | Use human-readable comments (#) for developers, but make ## comments verbose natural language instructions for the AI. Include all variables, options, and capabilities. Keep targets multi-purpose to reduce total count. |
test: ## Run test suite with pytest. Options: VERBOSE=1 for detailed output, TEST=pattern to filter, COVERAGE=1 for coverage report, PARALLEL=1 for parallel execution. Creates test-results.log with full output. |
| Mark helper targets as internal | Sub-functions and helpers the AI doesn't need should be marked @internal or @skip to keep the tool list focused |
_setup-env: ## @internal Initialize environment variables |
| Use variables, not multiple targets | Pass options via variables instead of creating separate targets | make test VERBOSE=1 instead of make test-verbose |
| Write clear descriptions | Explain what it does and what options are available | See Description Best Practices above |
| Log verbose output to files | Commands that produce lots of output should log to a file and return only a summary to avoid overloading tokens | command > output.log 2>&1 && echo "✓ Done. See output.log" |
Use @ prefix |
Suppress command echo to reduce output noise | @pytest instead of pytest |
Use --quiet/-q flags |
Use quiet flags when available | pytest --quiet, ruff check --quiet |
| Redirect to log files | Save verbose output for later analysis | pytest -v > test.log 2>&1 |
| Print concise summaries | Show brief success/failure instead of full output | echo "✓ Tests passed (23 tests, 2.5s)" |
| Combine redirect + summary | Redirect full output to file, then echo summary message | pytest -v > test.log 2>&1 && echo "✓ Done. See test.log" |
| Exit codes matter | Return non-zero on failure for AI to detect errors | Always preserve exit codes |
See tests/fixtures/ for example Makefiles:
| File | Description |
|---|---|
simple.mk |
Basic targets |
categorized.mk |
With category organization |
mixed.mk |
Shows internal targets and filtering |
MIT License