- MCP: Model Context Protocol, a protocol for LLMs to call tools for performing specific actions.
Create a simple MCP server in Go.
- The codes serves for learning and demo purposes, so keep it as simple and concise as possible.
- The MCP server shall communicate via http.
- The MCP server shall provide a method "moonphase" that calculates the phase of the moon based on a received date and time, or if the date and time is empty, based on the current date and time.
- The MCP server shall answer a tool call with two values: The moon age and the illumination percentage (as an integer between 0 and 100).
- The HTTP endpoints shall be secured by an API key. The API key shall be read from the environment.
- Write a function that takes a
time.Timevalue and calculates the phase of the moon for this time. The function returns the moon age and the illumination percentage between 0 and 100. - Write an HTTP server that listens on port 8181.
- Write HTTP handlers that handle the MCP protocol's methods:
tools/listandtools/call. Require an API key as X-Api-Token header. - Write a handler to manage shutdown if the MCP client requests it.
- Create a tool definition for the MCP server in
crush.json. - Write unit tests for any unit-testable function.
The MCP server shall communicate over HTTP. It shall listen on the port 8181.
A handler for the route /mcp handles all requests. The server shall respond with a 404 to equests to all other routes.
All messages between MCP clients and servers MUST follow the JSON-RPC 2.0 specification. The protocol defines these types of messages:
Requests are sent from the client to the server or vice versa, to initiate an operation.
The JSON-RPC schema:
{
jsonrpc: "2.0";
id: string | number;
method: string;
params?: {
[key: string]: unknown;
};
}Requests MUST include a string or integer ID. Unlike base JSON-RPC, the ID MUST NOT be null. The request ID MUST NOT have been previously used by the requestor within the same session.
The handler must implement the tools/list and tools/call methods.
For tools/list, it must return the available tools
Responses are sent in reply to requests, containing the result or error of the operation.
{
jsonrpc: "2.0";
id: string | number;
result?: {
[key: string]: unknown;
}
error?: {
code: number;
message: string;
data?: unknown;
}
}Responses MUST include the same ID as the request they correspond to. Responses are further sub-categorized as either successful results or errors. Either a result or an error MUST be set. A response MUST NOT set both. Results MAY follow any JSON object structure, while errors MUST include an error code and message at minimum. Error codes MUST be integers.
To discover available tools, clients send a tools/list request. This operation supports pagination.
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"cursor": "optional-cursor-value"
}
}Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "get_weather",
"title": "Weather Information Provider",
"description": "Get current weather information for a location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or zip code"
}
},
"required": ["location"]
}
}
],
"nextCursor": "next-page-cursor"
}
}To invoke a tool, clients send a tools/call request:
Request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
}
}
}Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}Protocol Revision: 2025-06-18
The Model Context Protocol (MCP) defines a rigorous lifecycle for client-server connections that ensures proper capability negotiation and state management.
- Initialization: Capability negotiation and protocol version agreement
- Operation: Normal protocol communication
- Shutdown: Graceful termination of the connection
sequenceDiagram
participant Client
participant Server
Note over Client,Server: Initialization Phase
activate Client
Client->>+Server: initialize request
Server-->>Client: initialize response
Client--)Server: initialized notification
Note over Client,Server: Operation Phase
rect rgb(200, 220, 250)
note over Client,Server: Normal protocol operations
end
Note over Client,Server: Shutdown
Client--)-Server: Disconnect
deactivate Server
Note over Client,Server: Connection closed
The initialization phase MUST be the first interaction between client and server. During this phase, the client and server:
- Establish protocol version compatibility
- Exchange and negotiate capabilities
- Share implementation details
The client MUST initiate this phase by sending an initialize request containing:
- Protocol version supported
- Client capabilities
- Client implementation information
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
},
"sampling": {},
"elicitation": {}
},
"clientInfo": {
"name": "ExampleClient",
"title": "Example Client Display Name",
"version": "1.0.0"
}
}
}The server MUST respond with its own capabilities and information:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"logging": {},
"prompts": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"tools": {
"listChanged": true
}
},
"serverInfo": {
"name": "ExampleServer",
"title": "Example Server Display Name",
"version": "1.0.0"
},
"instructions": "Optional instructions for the client"
}
}After successful initialization, the client MUST send an initialized notification
to indicate it is ready to begin normal operations:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}- The client SHOULD NOT send requests other than
pings before the server has responded to the
initializerequest. - The server SHOULD NOT send requests other than
pings and
logging before receiving the
initializednotification.
In the initialize request, the client MUST send a protocol version it supports.
This SHOULD be the latest version supported by the client.
If the server supports the requested protocol version, it MUST respond with the same version. Otherwise, the server MUST respond with another protocol version it supports. This SHOULD be the latest version supported by the server.
If the client does not support the version in the server's response, it SHOULD disconnect.
If using HTTP, the client **MUST** include the `MCP-Protocol-Version: ` HTTP header on all subsequent requests to the MCP server. For details, see [the Protocol Version Header section in Transports](/specification/2025-06-18/basic/transports#protocol-version-header).Client and server capabilities establish which optional protocol features will be available during the session.
Key capabilities include:
| Category | Capability | Description |
|---|---|---|
| Client | roots |
Ability to provide filesystem roots |
| Client | sampling |
Support for LLM sampling requests |
| Client | elicitation |
Support for server elicitation requests |
| Client | experimental |
Describes support for non-standard experimental features |
| Server | prompts |
Offers prompt templates |
| Server | resources |
Provides readable resources |
| Server | tools |
Exposes callable tools |
| Server | logging |
Emits structured log messages |
| Server | completions |
Supports argument autocompletion |
| Server | experimental |
Describes support for non-standard experimental features |
Capability objects can describe sub-capabilities like:
listChanged: Support for list change notifications (for prompts, resources, and tools)subscribe: Support for subscribing to individual items' changes (resources only)
During the operation phase, the client and server exchange messages according to the negotiated capabilities.
Both parties MUST:
- Respect the negotiated protocol version
- Only use capabilities that were successfully negotiated
During the shutdown phase, one side (usually the client) cleanly terminates the protocol connection. No specific shutdown messages are defined—instead, the underlying transport mechanism should be used to signal connection termination:
For the stdio transport, the client SHOULD initiate shutdown by:
- First, closing the input stream to the child process (the server)
- Waiting for the server to exit, or sending
SIGTERMif the server does not exit within a reasonable time - Sending
SIGKILLif the server does not exit within a reasonable time afterSIGTERM
The server MAY initiate shutdown by closing its output stream to the client and exiting.
For HTTP transports, shutdown is indicated by closing the associated HTTP connection(s).
Implementations SHOULD establish timeouts for all sent requests, to prevent hung connections and resource exhaustion. When the request has not received a success or error response within the timeout period, the sender SHOULD issue a cancellation notification for that request and stop waiting for a response.
SDKs and other middleware SHOULD allow these timeouts to be configured on a per-request basis.
Implementations MAY choose to reset the timeout clock when receiving a progress notification corresponding to the request, as this implies that work is actually happening. However, implementations SHOULD always enforce a maximum timeout, regardless of progress notifications, to limit the impact of a misbehaving client or server.
Implementations SHOULD be prepared to handle these error cases:
- Protocol version mismatch
- Failure to negotiate required capabilities
- Request timeouts
Example initialization error:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Unsupported protocol version",
"data": {
"supported": ["2024-11-05"],
"requested": "1.0.0"
}
}
}Requests must include an X-Api-Token header. The token is set via env var MOONPHASE_API_KEY.
Some MCP clients use MCP configuration files for adding MCP servers.
Example
{
"$schema": "https://charm.land/crush.json",
"mcp": {
"filesystem": {
"type": "stdio",
"command": "node",
"args": ["/path/to/mcp-server.js"],
"env": {
"NODE_ENV": "production"
}
},
"github": {
"type": "http",
"url": "https://example.com/mcp/",
"headers": {
"Authorization": "$(echo Bearer $EXAMPLE_MCP_TOKEN)"
}
},
"streaming-service": {
"type": "sse",
"url": "https://example.com/mcp/sse",
"headers": {
"API-Key": "$(echo $API_KEY)"
}
}
}
}