Lab 8.2: Author A Custom MCP Server¶
Goal¶
Create a tiny custom MCP server with one tool and one resource, wire it into OpenCode, and call it from an agent.
This is not a production integration lab. The goal is to understand the shape of a custom server and the design decisions behind it.
What You Will Practice¶
- Designing a small MCP interface
- Creating one domain-specific tool
- Creating one resource the agent can read
- Adding your local server to OpenCode config
- Testing the server with a bounded prompt
Prerequisites¶
You need:
- OpenCode installed
- A working local development environment for either TypeScript/Node or Python
- A folder where you can create a small server
- Basic comfort running a local command
If the course sample MCP server template exists in your copy, start there:
labs/sample-mcp-server-template
If it does not exist yet, create a small local project using the official MCP SDK pattern for your language. Follow current MCP SDK docs for exact package names and boilerplate.
The Tiny Server Requirement¶
Your server must expose exactly two learner-facing capabilities:
- One tool that does a small action.
- One resource that provides context for that action.
Keep it boring. Boring is good here.
Step 1: Choose A Tiny Domain¶
Pick a domain you understand.
Good options:
- Release notes
- Commit message formatting
- Support ticket triage
- Feature flag naming
- Course lesson title cleanup
- Bug report severity labels
Avoid:
- Payment actions
- Production database writes
- Sending emails or Slack messages
- Anything that requires secrets
- Anything that mutates real customer data
Step 2: Design The Interface¶
Fill this out before writing code:
Server name:
Domain:
Tool name:
Tool input:
Tool output:
Resource name:
Resource content:
Safety boundary:
Example:
Server name: release-helper
Domain: release notes
Tool name: format_release_note
Tool input: change_type, summary
Tool output: one Markdown bullet
Resource name: release-note-style-guide
Resource content: Use present tense. Keep bullets under 120 characters. Prefix with Added, Fixed, Changed, or Removed.
Safety boundary: no network, no file reads, no file writes
Step 3: Implement The Resource¶
Your resource can be static text.
Example content:
Release note style guide:
- Use present tense.
- Start with Added, Fixed, Changed, or Removed.
- Mention user-visible behavior.
- Do not mention internal refactors unless users benefit.
- Keep each bullet under 120 characters.
The resource gives the agent context without requiring the user to paste rules every time.
Step 4: Implement The Tool¶
Your tool should accept a small input and return a deterministic result.
Example behavior:
Input:
change_type = "fixed"
summary = "todo filters now show the correct items"
Output:
- Fixed todo filters so Active and Completed views show the correct items.
Keep the tool simple. It should not need network access, credentials, or file writes.
Step 5: Add The Server To OpenCode Config¶
Wire your local MCP server as a local command.
The config shape should look like this:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"release-helper": {
"type": "local",
"command": ["<command-that-starts-your-server>"],
"enabled": true,
"timeout": 30000
}
}
}
Examples of command shapes:
["node", "server.js"]
["python", "server.py"]
Use the command that matches your implementation.
Step 6: Restart OpenCode¶
Restart OpenCode so it loads the local MCP server.
If the server does not appear or fails to start, check:
- The command works from your terminal
- Dependencies are installed for the server project
- The file path in config is correct
- The server uses stdio/local transport as expected
- The server exits only when OpenCode closes it
Step 7: Test The Resource¶
Ask the agent to read or use the resource only.
Example:
Use the release-helper MCP resource for release note style. Do not call the formatting tool yet. Summarize the style guide in three bullets.
You are checking whether the agent can see the resource.
Step 8: Test The Tool¶
Ask the agent to call the tool with simple input.
Example:
Use the release-helper MCP tool to format this release note:
change_type: fixed
summary: todo filters now show the correct items
Return only the formatted bullet.
Expected result:
- Fixed todo filters so Active and Completed views show the correct items.
Your exact output may differ, but it should follow your style resource.
Step 9: Decide Whether MCP Was Necessary¶
Answer honestly:
- Could this have been a skill instead?
- Could this have been a command instead?
- What did MCP add?
- What complexity did MCP add?
For many tiny examples, a skill or command would be enough. That is fine. The purpose of this lab is to learn when MCP is worth the extra moving parts.
Deliverable¶
Submit a lab note:
# Lab 8.2 Notes
## Design
Server name:
Domain:
Tool name:
Tool input:
Tool output:
Resource name:
Resource content:
Safety boundary:
## Config Snippet
<your OpenCode mcp config without secrets>
## Resource Test
Prompt:
Result:
## Tool Test
Prompt:
Result:
## Reflection
Could this have been a skill or command instead?
When would MCP be worth it for this domain?
Pass Criteria¶
You pass this lab if:
- Your server exposes one tool and one resource.
- The tool has clear inputs and output.
- The resource gives useful context.
- OpenCode can start the server through MCP config, or you document the startup error clearly.
- You test both the resource and the tool.
- You explain whether MCP was actually necessary.
Common Mistakes¶
- Building a server that is too large for a first attempt.
- Adding network calls before the local version works.
- Hiding important behavior in the agent prompt instead of the server interface.
- Forgetting to restart OpenCode after config changes.
- Treating a custom MCP server as automatically safer than an existing one.
Stretch Challenge¶
Add basic validation to the tool input.
For example, reject unknown release note categories and return a helpful error:
Unknown change_type "patched". Use one of: added, fixed, changed, removed.
Then test how the agent responds when the tool rejects invalid input.