Published on
- 13 min read
A Guide to Developing Custom MCP Extensions: From Repo Layout to Release
Custom MCP extensions turn a generic assistant into a specialist that can do work in your systems—safely, repeatably, and with guardrails.
What “MCP Extension” Really Means in MCP Repositories
In MCP repositories, an “extension” is usually one of two things:
- A standalone MCP server you publish (or keep private) that exposes capabilities—tools, resources, and prompts—over the Model Context Protocol.
- A repository module/package that helps build, configure, or deploy MCP servers (for example, a library of shared auth handlers, schema validation, or deployment templates).
Most teams mean the first: a custom MCP server that adds domain-specific capabilities, such as “query our warehouse inventory,” “open a Jira ticket,” “generate a compliance report,” or “summarize last week’s customer calls.”
MCP is intentionally simple at the protocol level, but real-world extensions involve careful design: scope, auth, predictable output, error handling, and documentation that makes the extension usable by humans and models.
Start With Capability Design (Before You Write Code)
The fastest way to build a messy extension is to begin by implementing endpoints. The fastest way to build a reliable extension is to begin with capability design.
Decide What You’re Exposing: Tools vs Resources vs Prompts
MCP servers can expose three core capability types:
- Tools: callable functions that do something. Examples:
create_ticket,search_docs,run_sql_readonly. - Resources: structured data that can be fetched and referenced. Examples: a file tree, a knowledge base article, a customer record.
- Prompts: reusable prompt templates that guide the model consistently for common tasks.
A practical heuristic:
- If it changes state or triggers an action: tool.
- If it’s primarily “read and cite”: resource.
- If it’s “repeat this workflow often”: prompt.
Reduce the Surface Area
Extension maintainers often overestimate how many tools they need. Start with fewer tools that are robust and composable.
Bad pattern:
create_ticket_bugcreate_ticket_taskcreate_ticket_incident
Better pattern:
create_ticketwith a constrainedtypefield, validated server-side.
Your future self will thank you when you need to add logging, quota limits, or permission checks—once, not three times.
Write “Tool Contracts” Like You Mean It
Before implementation, write a one-page contract per tool:
- Name: stable, lower_snake_case
- Purpose: one sentence
- Inputs: schema with types and constraints
- Outputs: schema with examples
- Error modes: typical failures and messages
- Security: required scopes, redactions, audit requirements
- Idempotency: what happens if the tool is called twice?
Treat these contracts as part of your repo. In many MCP repositories, these documents become your best long-term maintenance asset.
Pick an Extension Architecture That Won’t Trap You Later
You’ll typically implement an MCP server as a small service that speaks MCP over stdio or HTTP (depending on your stack and client). Regardless of transport, you want a structure that supports:
- Adding tools without sprawling files
- Centralized auth and policy
- Shared validation and error formatting
- Deterministic outputs
- Testing without real credentials
A Clean Repo Layout for MCP Extensions
A widely workable layout:
src/server/(MCP wiring, registration, transport)tools/(one file per tool or per domain)resources/(resource handlers)prompts/(prompt templates + metadata)lib/(auth, clients, validation, redaction, logging)
tests/unit/integration/fixtures/
docs/tools.mdresources.mdprompts.mdsecurity.mdchangelog.md
examples/(minimal scripts showing local use)mcp.jsonor config template (if your ecosystem uses it)
This structure maps cleanly to how MCP capabilities are consumed: tools/resources/prompts are discoverable, and your policy and glue logic stays centralized.
Make the “Happy Path” Boring
An extension should behave predictably even when the model is being… creative.
Aim for:
- Strict input validation (reject unknown fields)
- Normalized outputs (stable keys, stable types)
- Consistent error shape (machine-friendly)
- Clear “what to do next” messages for recoverable failures
Implementing Tools: Practical Patterns That Work
Tools are where most custom MCP extensions earn their keep. They’re also where they break.
Define Schemas and Validate Them Server-Side
Even if your client validates, your server must validate again. Treat everything as untrusted input.
Common constraints worth enforcing:
- String length limits (names, titles, descriptions)
- Enum constraints (
priority: low/medium/high) - Regex constraints (ticket keys, customer IDs)
- Maximum array sizes (to stop payload explosions)
- Date formats (ISO 8601 only, for sanity)
If your stack supports JSON Schema or a typed validator, use it and fail fast.
Return Outputs That Models Can Reliably Reuse
Models do best with:
- Flat JSON where possible
- Stable IDs
- Explicit status fields
- URLs when relevant
- Minimal prose in fields intended for programmatic use
Example output shape for an action tool:
status:success | failedid: newly created object IDurl: deep linksummary: short human-readable linenext_actions: optional array of recommended follow-ups
Avoid dumping raw API responses unless you also provide a normalized view.
Implement a Standard Error Envelope
A consistent error envelope makes your extension easier to debug and safer to automate. A good envelope includes:
error_code(stable, searchable)message(human readable)details(structured, optional)retryable(boolean)suggested_fix(short suggestion)
When a model hits an error, it can decide whether to retry, ask for missing data, or stop.
Add Rate Limits and Timeouts Early
Even internal extensions can accidentally DDoS internal systems when invoked repeatedly. Put guardrails in from day one:
- Per-tool timeouts (e.g., 10–30 seconds)
- Retry policies with backoff (careful with non-idempotent tools)
- Server-side rate limits (per user/token)
- Circuit breakers for downstream outages
Implementing Resources: Make Data Referenceable, Not Just Fetchable
Resources are underrated. A good resource design helps the model cite and navigate information without turning your server into a “giant text blob provider.”
Prefer Small, Linkable Resources
Instead of one resource called company_handbook, consider:
handbook/indexhandbook/{section_id}handbook/search?q=...
This lets the model pull only what it needs and reduces token waste.
Include Metadata for Traceability
Resource responses should include:
- A stable resource identifier
- Last updated timestamps (if possible)
- Source links or canonical IDs
- Access level (public/internal/restricted)
- Optional excerpts plus a full-content fetch path
Traceability matters for compliance and for debugging “where did that answer come from?”
Prompts: The Quiet Workhorse of Great Extensions
Prompts in MCP are not just “a helpful text.” They’re repeatable workflows you ship with your extension so the assistant behaves consistently.
Useful prompt templates include:
- “Draft a support reply in our tone”
- “Summarize a ticket for an engineer”
- “Generate release notes from merged PR titles”
- “Convert raw query results into an executive brief”
A strong prompt template has:
- A clear role and goal
- Required inputs
- A structured output format
- Guardrails (“If data is missing, ask for it”)
- Style guidance that matches your organization
Prompts also reduce the temptation to pack business rules into tool names. Keep business logic in tools and policies; keep workflow guidance in prompts.
Security and Permissions: The Part You Can’t Patch Later
If your extension touches real systems, it needs real security. “It’s internal” is not a strategy.
Choose an Auth Model: User-Delegated vs Service-Delegated
Two common approaches:
- User-delegated: the extension acts on behalf of a user and respects their permissions. Best for productivity tools that should mirror user access.
- Service-delegated: the extension uses a service account with scoped permissions. Best for controlled automation and read-only dashboards.
User-delegated typically requires token exchange, session handling, and careful auditing. Service-delegated requires strict scoping and may need separate tools for privileged actions.
Scope Tools by Risk
Not all tools are equal. You can group them by risk and apply different gates:
- Read-only: search, list, retrieve
- Write: create, update
- Destructive: delete, purge, revoke
- Financial/legal: billing, contracts, compliance actions
For higher-risk tools, add extra requirements:
- Mandatory confirmation fields
- Two-person approval flow (where applicable)
- “Dry run” mode
- Tight rate limits
- Detailed audit logs
Redact Sensitive Data in Both Directions
Redaction is not only about outgoing logs. It’s also about responses.
If the downstream API returns secrets, personal data, or tokens, your extension should:
- Remove sensitive fields by default
- Provide a safe “masked” representation
- Expose full detail only with explicit permission and clear purpose
Log for Audits Without Leaking Data
A practical logging policy:
- Log tool name, timestamp, user identity, request ID
- Log hashes or counts instead of raw content
- Store raw payloads only in secure audit sinks, if required
- Add correlation IDs for downstream calls
This gives you traceability without turning your log system into a liability.
Testing Custom MCP Extensions Like a Professional
Most extension bugs are not syntax issues. They’re integration and edge-case issues: missing fields, unexpected downstream responses, concurrency, and permission mismatches.
Unit Tests: Validate Schemas and Error Shapes
Unit tests should cover:
- Schema rejects unknown fields
- Required fields enforced
- Enum constraints
- Output normalization
- Error envelope consistency
Integration Tests: Use Sandboxes and Fixtures
For integration tests:
- Use vendor sandboxes (Jira sandbox, GitHub test org, Stripe test mode)
- Mock downstream APIs when sandboxes are unavailable
- Record fixtures for common responses
- Test timeouts and retries
A good integration suite includes “failure rehearsal” tests:
- downstream 500
- downstream 429
- auth expired
- permission denied
- partial data returned
Contract Tests: Keep Tool Contracts Honest
Remember those tool contracts you wrote? Turn them into tests:
- Example input should pass validation
- Example output should match schema
- Known error scenarios should produce known
error_codes
Contract tests prevent silent breaking changes when you refactor.
Documentation That Helps Both Humans and Models
In MCP repositories, documentation is not decorative. It’s how other developers and tool consumers understand what you built.
Minimum docs to ship:
- Quickstart: how to run locally, required env vars, how to connect a client
- Tools reference: inputs/outputs, examples, error codes
- Security: permissions, scopes, audit logging
- Operational guide: deployment, monitoring, incident response
- Changelog: user-facing changes and migrations
Write examples like they’ll be copied into production—because they will be.
Versioning and Backward Compatibility: Avoid Surprise Breakage
Custom MCP extensions evolve. The easiest way to lose trust is to change outputs without warning.
Use Semantic Versioning With Discipline
A workable policy:
- PATCH: bug fixes, no schema changes
- MINOR: additive changes (new optional fields, new tools)
- MAJOR: breaking schema changes, renamed tools, removed fields
Deprecate Before You Remove
If you need to remove a tool or field:
- Mark as deprecated in docs
- Keep it working for a defined window (30–90 days)
- Add warnings in responses if appropriate
- Provide a migration path (“use tool X with field Y instead”)
This matters even more when other repos depend on your extension.
Distribution and Packaging in MCP Repositories
How you distribute depends on your environment:
- Open source MCP server published on GitHub
- Private repo in your organization
- Internal package distributed via an artifact registry
- Container image deployed to a cluster
No matter the distribution method, treat releases as products.
Release Checklist
Before tagging a release:
- Tests green (unit + integration)
- Tool contracts updated
- Docs updated
- Changelog written
- Security review for new tools
- Monitoring dashboards updated (if needed)
If your extension connects to critical systems, run a staged rollout.
Observability and Operations: Keep It Running When Things Go Weird
The model won’t tell you your extension is flaky. Users will—after it wastes their time.
Metrics Worth Capturing
Track at least:
- Tool call count per tool
- Latency p50/p95/p99
- Error rate per tool and per downstream dependency
- Timeout count
- Rate limit triggers
- Auth failures
Add dashboards that let you answer: “Is the extension broken, or is the downstream system broken?”
Tracing Downstream Calls
If your extension calls multiple services, distributed tracing saves hours. Even lightweight correlation IDs can help you reconstruct a failure path.
Safe Degradation
When a dependency is down, don’t just throw cryptic errors. Provide:
- A clear message
- Whether retry might work
- A suggested alternative (read-only mode, cached data)
- A status page link if you have one
This makes the extension feel reliable even under stress.
A Mid-Project Reality Check: The “Extension Quality” Audit
Halfway through building, pause and run an audit across these questions:
- Can every tool be described in one sentence?
- Are tool names consistent and predictable?
- Are schemas strict and validated?
- Are outputs normalized and stable?
- Do error codes exist and map to action?
- Are permissions enforced server-side?
- Is sensitive data redacted?
- Do we have at least one integration test per tool?
- Would a new developer understand how to add a tool in one hour?
If you answer “no” to several, fix the structure now. It only gets harder later.
Building Example Extensions: Three Practical Blueprints
A guide becomes real when you can picture what you’re building. Here are three blueprints that map neatly to common MCP repository use cases. Each one can be implemented as a small server with a handful of tools and resources.
-
- Tools:
search_docs,get_doc_section,list_collections - Resources:
docs/{doc_id},docs/{doc_id}/sections/{section_id} - Prompts: “Answer with citations from docs”
- Key concerns: access control, content chunking, citations, freshness
- Tools:
-
Ticketing and Incident Assistant
- Tools:
create_ticket,update_ticket,link_ticket,summarize_ticket,assign_oncall - Resources:
tickets/{key},incidents/{id} - Prompts: “Triage and propose next steps”
- Key concerns: idempotency for creation, role-based permissions, audit logs
- Tools:
-
**Data Warehouse Read-Only Query Server **
- Tools:
run_query_readonly,explain_query,list_tables,describe_table - Resources:
schemas/{name},tables/{name} - Prompts: “Write safe SQL and summarize results”
- Key concerns: strict read-only enforcement, query timeouts, result limits, PII masking
- Tools:
These blueprints aren’t meant to be copied line-for-line. They’re meant to show what “small but complete” looks like in MCP repositories.
Handling Idempotency, Concurrency, and “Double Calls”
Tool calling systems can trigger repeated calls for the same intent—sometimes due to retries, sometimes due to user re-asks, sometimes due to client reconnects.
Add Idempotency Keys for Write Operations
For tools that create or mutate data, accept an optional idempotency_key:
- If the same key is used again, return the original result.
- Store idempotency records for a reasonable TTL.
- Document how long keys remain valid.
This prevents duplicate tickets, duplicate invoices, duplicate user invites—classic extension failures.
Protect Against Partial Failures
If your tool performs multiple steps:
- Prefer transactions if the downstream supports them.
- Otherwise, design compensation steps (rollback) or a “resume” flow.
- Return intermediate IDs so humans can investigate.
The goal is not to be perfect; it’s to be recoverable.
Making Extensions “Model-Friendly” Without Becoming Model-Dependent
It’s tempting to tailor everything to one model’s habits. Resist that. Build extensions that are client-agnostic and model-agnostic.
Keep Tool Names and Fields Literal
Avoid cute names and overloaded fields. Use terms your business already uses:
customer_idnotcidinvoice_numbernotinvprioritywith a known enum
Literal naming improves correctness and makes your docs searchable.
Put Business Rules in the Server, Not the Prompt
Prompts help models follow rules, but enforcement belongs in code:
- permission checks
- required approvals
- allowed transitions
- policy constraints (e.g., “never query these tables”)
If a rule matters, enforce it. Prompts are guidance, not security.
A Practical Workflow for Adding a New Tool
When a stakeholder asks, “Can we add a tool that does X?”, follow a repeatable workflow.
-
Clarify the job
What’s the user’s real goal? What system is the source of truth? -
Draft the tool contract
Inputs/outputs, errors, permission scope. -
Review for safety
Can it delete data? Can it expose PII? Could it be abused? -
Implement with validation
Strict schema, normalized outputs, error envelope. -
Add tests
Unit tests for schema and errors; integration tests for the downstream call. -
Document and ship
Update tool docs, examples, and changelog. Tag a release.
This workflow is boring—in the best way. It reduces “tribal knowledge” and makes extension development scale.
Common Mistakes in Custom MCP Extensions (And How to Avoid Them)
Mistake: Exposing a “run_any_command” Tool
A tool that runs arbitrary commands or queries is an invitation for trouble. Even read-only “arbitrary SQL” can leak sensitive data if you miss a table or view.
Fix:
- Provide constrained queries or prebuilt report tools
- Enforce allowlists
- Add row limits and column masking
Mistake: Returning Unbounded Text
Returning massive blobs harms performance and increases the chance of the model missing key details.
Fix:
- Return summaries plus a way to fetch more
- Paginate
- Chunk resources intentionally
Mistake: Weak Error Messages
“Something went wrong” is not acceptable when a model is orchestrating calls.
Fix:
- Standardize error codes
- Include suggested fixes
- Mark retryable errors clearly
Mistake: No Operational Plan
If nobody owns monitoring and on-call, the extension will degrade quietly.
Fix:
- Add basic dashboards
- Define ownership
- Document incident steps
Publishing and Maintaining Your Extension Over Time
An MCP extension is not “done” when it works once. The maintenance phase is where trust is built.
Treat Downstream API Changes as a First-Class Risk
If you integrate with SaaS vendors:
- Watch their changelogs
- Pin API versions where possible
- Add integration tests that run regularly
- Include compatibility notes in your docs
Keep a Tight Feedback Loop With Users
Watch how people actually use the extension:
- Which tools are most used?
- Which tools error most?
- What do users ask repeatedly that could become a prompt template?
- Where are people forced into manual steps?
Often the best “new feature” is not a new tool—it’s better defaults, clearer outputs, or a safer workflow.
Establish Contribution Guidelines in Your MCP Repo
If multiple teams will add tools, you need standards:
- Naming conventions
- Schema style
- Error envelope format
- Logging and redaction requirements
- Testing requirements
- Review checklist for security
A short CONTRIBUTING.md and a PR template can prevent weeks of cleanup later.
Closing the Loop: Your Extension Should Feel Trustworthy
A great custom MCP extension feels like a dependable coworker: clear about what it can do, honest about what it can’t, and consistent under pressure. Build small, validate everything, log responsibly, document relentlessly, and version like you expect other people to depend on it—because they will.
External Links
MCP developer guide | Visual Studio Code Extension API Build an MCP App - Model Context Protocol Build your MCP server – Apps SDK | OpenAI Developers Making your own MCP server in VS Code | Microsoft Learn Visual Studio Code + Model Context Protocol (MCP) Servers Getting …