This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
make build- Build the server binary tobin/mcp-devtoolsmake run- Build and run server with stdio transport (default)make run-http- Build and run server with HTTP transport on port 18080
make test- Run all tests (includes external API tests, ~10s)make test-fast- Run tests without external API calls (~7s)make test-verbose- Show detailed per-test timingmake test-slow- Show slowest tests sorted by durationgo test -short -v ./tests/...- Run specific test suites
make lint- Run linting, formatting and modernisation checks
make deps- Install dependenciesmake update-deps- Update dependenciesgo mod tidy- Clean up go.mod
This is a modular MCP (Model Context Protocol) server built in Go that provides developer tools through a plugin-like architecture.
Please read the README.md for more information, and docs/creating-new-tools.md for details on how to create new tools.
- Tool Registry (
internal/registry/) - Central registry that manages tool registration and discovery - Tool Interface (
internal/tools/tools.go) - Defines the interface all tools must implement - Main Server (
main.go) - MCP server that supports multiple transports (stdio, SSE, HTTP)
Tools are organised into categories under internal/tools/, e.g:
internetsearch/- Internet Search API integrations (web, image, news, video, local)packageversions/- Package version checking across ecosystems (npm, python, go, java, swift, docker, github-actions, bedrock, rust)shadcnui/- shadcn/ui component information and examplesthink/- Structured reasoning tool for AI agentswebfetch/- Web content fetching and conversion to markdownutils/- Shared utilities for tools (e.g. proxy)- etc..
- Create package under
internal/tools/[category]/[toolname]/ - Implement the
tools.Toolinterface withDefinition()andExecute()methods - Register tool in
init()function usingregistry.Register(&YourTool{}) - Import the package in
main.goto trigger registration
Tools automatically get:
- Shared cache (
sync.Map) - Logger (
logrus.Logger) - Context and parsed arguments
The server supports three transport modes:
- stdio (default) - Standard input/output for MCP clients
- http - Streamable HTTP with optional authentication, optional upgrade to SSE if needed
- sse - Legacy Server-Sent Events for web clients (deprecated in favour of streamable HTTP), will be removed in future versions
main.go- Server entry point and transport configurationinternal/registry/registry.go- Tool registry implementationinternal/tools/tools.go- Tool interface definitionMakefile- Build and development commandsgo.mod- Go module dependencies
Tests are organised in tests/ directory:
testutils/- Test helpers and mockstools/- Tool-specific testsunit/- Unit tests for core components
Tests should not depend on external APIs or services unless cleared by the user as necessary.
Use make test-fast for development to avoid external API calls.
Uses Go modules with version information injected at build time through ldflags in the Makefile. The binary includes version, commit hash, and build date.
All tools follow this pattern:
- Define struct implementing
tools.Tool - Register in
init()withregistry.Register() - Implement
Definition()for MCP tool schema - Implement
Execute()for tool logic - Use shared logger and cache for consistency
CRITICAL: Before registering a new tool, verify the following:
- By default, ALL new tools are DISABLED - this is the secure default
- Only add tool to
defaultToolslist inenabledByDefault()(registry.go) if the user has explicitly stated it should be enabled by default - If tool is NOT added to defaultTools: It will require
ENABLE_ADDITIONAL_TOOLSto use (this is correct for most tools) - Tests enable the tool via
ENABLE_ADDITIONAL_TOOLSif not in defaultTools list - When in Doubt - Leave the tool disabled by default. It's safer to require explicit enablement than to accidentally expose destructive functionality.
- CRITICAL: Ensure that when running in stdio mode that we NEVER log to stdout or stderr, as this will break the MCP protocol.
- Any tools we create must work on both macOS and Linux unless the user states otherwise (only add windows workarounds if they're easy to implement, don't reduce functionality for macOS/Linux and are low maintenance).
- When testing the docprocessing tool, unless otherwise instructed always call it with "clear_file_cache": true and do not enable return_inline_only
- If you're wanting to call a tool you've just made changes to directly (rather than using the command line approach), you have to let the user know to restart the conversation otherwise you'll only have access to the old version of the tool functions directly.
- When adding new tools ensure they are registered in the list of available tools in the server (within their init function), ensure they have a basic unit test, and that they have docs/tools/.md with concise, clear information about the tool and that they're mentioned in the main README.md and docs/tools/overview.md.
- Always use British English spelling, we are not American.
- Follow the principle of least privileged security.
- Tool responses should be limited to only include information that is actually useful, there's no point in returning the information an agent provides to call the tool back to them, or any generic information or null / empty fields - these just waste tokens.
- Use 0600 and 0700 permissions for files and directories respectively, unless otherwise specified avoid using 0644 and 0755.
- Unit tests for tools should be located within the tests/tools/ directory, and should be named _test.go.
- We should be mindful of the risks of code injection and other security risks when parsing any information from external sources.
- On occasion the user may ask you to build a new tool and provide reference code or information in a provided directory such as
tmp_repo_clones/<dirname>unless specified otherwise this should only be used for reference and learning purposes, we don't ever want to use code that directory as part of the project's codebase. - After making changes and performing a build if you need to test the MCP server with the updated changes you MUST either test it from the command line - or STOP and ask the user to restart the MCP client otherwise you won't pick up the latest changes
- When creating new MCP tools make sure descriptions are clear and concise as they are what is used as hints to the AI coding agent using the tool, you should also make good use of MCP's annotations.
- The mcp-go package documentation contains useful examples of using the package which you can lookup when asked to implement specific MCP features https://mcp-go.dev/servers/tools
Build for Workflows, Not Just API Endpoints:
- Don't simply wrap existing API endpoints - build thoughtful, high-impact workflow tools
- Consolidate related operations (e.g.,
schedule_eventthat both checks availability and creates event) - Focus on tools that enable complete tasks, not just individual API calls
- Consider what workflows agents actually need to accomplish
Optimise for Limited Context:
- Agents have constrained context windows - make every token count
- Return high-signal information, not exhaustive data dumps
- Provide "concise" vs "detailed" response format options where applicable
- Default to human-readable identifiers over technical codes (names over IDs)
- Consider the agent's context budget as a scarce resource
Design Actionable Error Messages:
- Error messages should guide agents toward correct usage patterns
- Suggest specific next steps: "Try using filter='active_only' to reduce results"
- Make errors educational, not just diagnostic
- Help agents learn proper tool usage through clear feedback
Follow Natural Task Subdivisions:
- Tool names should reflect how humans think about tasks
- Group related tools with consistent prefixes for discoverability
- Design tools around natural workflows, not just API structure
Use Evaluation-Driven Development:
- Create realistic evaluation scenarios early
- Let agent feedback drive tool improvements
- Prototype quickly and iterate based on actual agent performance
To ensure quality, review the code for:
- DRY Principle: No duplicated code between tools
- Composability: Shared logic extracted into functions
- Consistency: Similar operations return similar formats
- Error Handling: All external calls have error handling
- Documentation: Every tool has comprehensive docstrings/descriptions
If the user asks you to look at the tool call error logs, they can be found in ~/.mcp-devtools/logs/tool-errors.log (assuming the user has enabled tool error logging), looking at recent tool calling failures can be useful for improving tools that may be failing often. If looking at the tool error logs, ensure you look for the relevant date/time range when the user was experiencing issues.
- You can debug the tool by running it in debug mode interactively, e.g.
rm -f debug.log; pkill -f "mcp-devtools.*" ; echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "fetch_url", "arguments": {"url": "https://go.dev", "max_length": 500, "raw": false}}}' | ./bin/mcp-devtools stdio, orBRAVE_API_KEY="ask the user if you need this" ./bin/mcp-devtools stdio <<< '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "brave_search", "arguments": {"type": "web", "query": "cat facts", "count": 1}}}'
actionlintCRITICAL: Follow these rules when writing Go code to avoid outdated patterns that modernize would flag:
- Use
anyinstead ofinterface{} - Use
comparablefor type constraints when appropriate
- Use
strings.CutPrefix(s, prefix)instead ofif strings.HasPrefix(s, prefix) { s = strings.TrimPrefix(s, prefix) } - Use
strings.SplitSeq()andstrings.FieldsSeq()in range loops instead ofstrings.Split()andstrings.Fields()
- Use
for range ninstead offor i := 0; i < n; i++when index isn't used - Use
min(a, b)andmax(a, b)instead of if/else conditionals
- Use
slices.Contains(slice, element)instead of manual loops for searching - Use
slices.Sort(s)instead ofsort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) - Use
maps.Copy(dst, src)instead of manualfor k, v := range src { dst[k] = v }loops
- Use
t.Context()instead ofcontext.WithCancel()in tests
- Use
fmt.Appendf(nil, format, args...)instead of[]byte(fmt.Sprintf(format, args...))
Session span parent-child relationships: Session spans in internal/telemetry/tracer.go must be ended immediately followed by ForceFlush() to ensure they export to the backend before child tool spans. Without force flush, the OTEL batch processor exports asynchronously, causing child spans to arrive before their parent, resulting in "invalid parent span IDs" warnings in Jaeger.
sessionSpan.End()
// CRITICAL: Force flush ensures parent exports before children
if tp != nil {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = tp.ForceFlush(ctx)
}Tool spans inherit trace context via W3C Trace Context propagation (inject→extract pattern) in StartToolSpan().
- YOU MUST ALWAYS run
make lint && make test && make buildetc... to build the project rather than gofmt, go build or test directly, and you MUST always do this before stating you've completed your changes! - CRITICAL: If the serena tool is available to you, you must use serena for your semantic code retrieval and editing tools