This guide walks through building MCP servers in Go using this repository, from a minimal tool to resources, prompts, elicitation, and client-side sampling. The examples are packaged for clarity (usecase/server/cmd) so domain logic is separated from transport wiring.
Quick Start
package main
import (
"context"
"encoding/json"
"log"
"github.com/viant/jsonrpc"
proto "github.com/viant/mcp-protocol/server"
"github.com/viant/mcp-protocol/schema"
"github.com/viant/mcp/server"
)
func main() {
// Define a simple tool I/O
type AddIn struct {
A int // json:"a"
B int // json:"b"
Note *string // json:"note,omitempty" description:"Optional note"
}
type AddOut struct { Sum int /* json:"sum" */ }
// Configure handler and register the tool
newHandler := proto.WithDefaultHandler(context.Background(), func(h *proto.DefaultHandler) error {
return proto.RegisterTool[*AddIn, *AddOut](
h.Registry,
"add",
"Add two integers",
func(ctx context.Context, in *AddIn) (*schema.CallToolResult, *jsonrpc.Error) {
data, _ := json.Marshal(&AddOut{Sum: in.A + in.B})
return &schema.CallToolResult{
Content: []schema.CallToolResultContentElem{{Text: string(data)}},
}, nil
},
)
})
srv, err := server.New(
server.WithNewHandler(newHandler),
server.WithImplementation(schema.Implementation{Name: "example", Version: "1.0"}),
)
if err != nil { log.Fatal(err) }
// Choose a transport (see sections below)
// Example: HTTP (SSE by default)
log.Fatal(srv.HTTP(context.Background(), ":4981").ListenAndServe())
}MCP Inspector
npx @modelcontextprotocol/inspector
Contents
- Project structure pattern
- Example matrix (ports + features)
- Run transports (stdio, HTTP SSE/streaming)
- Example 1: Tools (basic)
- Annotation options (schema tags)
- Example 2: Tools (advanced) with elicitation
- Example 3: Resources
- Example 4: Prompts
- Example 5: Full server
- Example 6: Tools using client sampling (CreateMessage)
- Example 7: HTTP-level Authentication
- Next steps and references
Each example uses the same separation:
- usecase: business logic, DTOs, validation-like tags
- server: MCP wiring that registers tools/resources/prompts and delegates to
usecase - cmd/server: entrypoint that instantiates
usecase, builds the server, and starts a transport
Why: this keeps your protocol layer thin and reusable, with business logic independent of transport and schema details.
-
Stdio (typical for editor integrations):
stdioSrv := srv.Stdio(ctx) log.Fatal(stdioSrv.ListenAndServe())
-
HTTP SSE (default):
httpSrv := srv.HTTP(ctx, ":4981") log.Fatal(httpSrv.ListenAndServe())
-
HTTP Streaming:
srv.UseStreaming(true) httpSrv := srv.HTTP(ctx, ":4981") log.Fatal(httpSrv.ListenAndServe())
Note: In this repo’s HTTP server, the JSON-RPC endpoint is mounted at /.
| Example | Path Prefix | Port | Features |
|---|---|---|---|
| Tools (basic) | docs/guide/tools_basic |
4981 | Typed tool, auto-derived schemas |
| Resources | docs/guide/resources |
4982 | Readable resource /hello |
| Prompts | docs/guide/prompts |
4983 | Prompt welcome with arguments |
| Full | docs/guide/full |
4984 | Tools + Resources + Prompts |
| Tools (advanced) | docs/guide/tools_advanced |
4985 | Rich tags + elicitation adapter |
| Tools (sampling) | docs/guide/tools_sampling |
4986 | Client-side CreateMessage (translator) |
| Auth (HTTP-level) | docs/guide/auth_http |
4987 | OAuth2/OIDC at HTTP middleware |
Example path:
- Usecase:
docs/guide/tools_basic/usecase - Server:
docs/guide/tools_basic/server - Main:
docs/guide/tools_basic/cmd/server - Run:
go run ./docs/guide/tools_basic/cmd/server(listens on :4981)
What it does:
- Exposes a typed
addtool - Schemas for input/output are auto-derived from Go structs
Call shape (tools/call params):
{
"method": "tools/call",
"jsonrpc": "2.0",
"id": 1,
"params": {
"name": "add",
"arguments": { "a": 2, "b": 3 }
}
}The result is a text payload containing JSON like { "sum": 5 }.
JSON-RPC snippet (tools/list):
{ "method": "tools/list", "jsonrpc": "2.0", "id": 2 }These tags on your struct fields influence the generated JSON Schema for tools:
json:"name": renames field;json:"-"excludes itomitempty: optional when combined with non-pointer typesrequired:"true": forces requiredrequired:"false"oroptional: forces optionaldescription:"...": human-readable descriptionformat:"email",format:"uri",format:"date-time", etc.choice:"val": may appear multiple times to build an enuminternal:"true": omits the field completely from the schema- Nullability: pointer types are treated as nullable by default
Required vs optional
- Non-pointer + no
omitempty→ required - Pointer or
omitempty→ optional - Overridable with
requiredtag
Nested types, arrays, and maps are supported and mapped to JSON Schema accordingly.
Example path:
- Usecase:
docs/guide/tools_advanced/usecase - Server:
docs/guide/tools_advanced/server - Adapter:
docs/guide/tools_advanced/adapter(elicitation helper) - Main:
docs/guide/tools_advanced/cmd/server - Run:
go run ./docs/guide/tools_advanced/cmd/server(listens on :4985)
What it shows:
- Tool 1:
register_useruses rich tags (choice, email, uri, description, required, internal) - Tool 2:
enrich_profilerequests missing fields via elicitation (elicitation/create)
Elicitation flow (server → client):
- Inspect input, build a minimal requested schema (field name → type/title, required list)
- Call
client.Elicit(...)with message and schema - If user accepts with content, merge values into input and proceed; otherwise, return the action
The advanced example encapsulates this in adapter.ElicitAndMerge, keeping the handler clean.
JSON-RPC snippet (register_user):
{
"method": "tools/call",
"jsonrpc": "2.0",
"id": 10,
"params": {
"name": "register_user",
"arguments": {
"name": "Alice",
"email": "alice@example.com",
"role": "user",
"country": "US"
}
}
}JSON-RPC snippet (enrich_profile – triggers elicitation when fields are missing):
{
"method": "tools/call",
"jsonrpc": "2.0",
"id": 11,
"params": {
"name": "enrich_profile",
"arguments": { "userId": "user-alice-user" }
}
}Example path:
- Usecase:
docs/guide/resources/usecase - Server:
docs/guide/resources/server - Main:
docs/guide/resources/cmd/server - Run:
go run ./docs/guide/resources/cmd/server(listens on :4982)
What it does:
- Registers a readable resource at
/hello - Returns text content and URI
- You can extend it to notify updates (send
resources/updatednotifications)
Read shape (resources/read params):
{
"method": "resources/read",
"jsonrpc": "2.0",
"id": 2,
"params": { "uri": "/hello" }
}Example path:
- Usecase:
docs/guide/prompts/usecase - Server:
docs/guide/prompts/server - Main:
docs/guide/prompts/cmd/server - Run:
go run ./docs/guide/prompts/cmd/server(listens on :4983)
What it does:
- Registers a
welcomeprompt with required argumentname prompts/listshows available promptsprompts/getresolves messages with arguments
Get shape (prompts/get params):
{
"method": "prompts/get",
"jsonrpc": "2.0",
"id": 3,
"params": {
"name": "welcome",
"arguments": { "name": "Alice" }
}
}Example path:
- Usecase:
docs/guide/full/usecase - Server:
docs/guide/full/server - Main:
docs/guide/full/cmd/server - Run:
go run ./docs/guide/full/cmd/server(listens on :4984)
What it does:
- Combines a resource (
/hello), a tool (add), and a prompt (welcome) - Toggle streaming with
srv.UseStreaming(true)if desired
JSON-RPC snippet (tools/call add):
{
"method": "tools/call",
"jsonrpc": "2.0",
"id": 20,
"params": {
"name": "add",
"arguments": { "a": 7, "b": 8 }
}
}Example path:
- Usecase:
docs/guide/tools_sampling/usecase - Server:
docs/guide/tools_sampling/server - Main:
docs/guide/tools_sampling/cmd/server - Run:
go run ./docs/guide/tools_sampling/cmd/server(listens on :4986)
What it does:
- Tool
translateasks the client to sample viasampling/createMessage - Server composes a
SystemPromptand a single user text message - Returns the sampled text as the tool result
Notes:
- The client must advertise the
samplingcapability - If using auth and remote LLMs, see the Authentication guide
JSON-RPC snippet (tools/call translate):
{
"method": "tools/call",
"jsonrpc": "2.0",
"id": 30,
"params": {
"name": "translate",
"arguments": { "text": "Guten Tag", "target": "en", "formality": "less" }
}
}Example path:
- Usecase:
docs/guide/auth_http/usecase - Server:
docs/guide/auth_http/server - Main:
docs/guide/auth_http/cmd/server - Run:
go run ./docs/guide/auth_http/cmd/server(listens on :4987)
What it does:
- Demonstrates OAuth2/OIDC protection at HTTP middleware level only
- Configures a
Policy.Globalwith protected resource metadata (RFC 9728) - Wires
server.WithProtectedResourcesHandlerandserver.WithAuthorizer
Client notes:
- Use the auth RoundTripper in
github.com/viant/mcp/client/auth/transportto handleWWW-Authenticatechallenges and fetch tokens, or includeAuthorization: Bearer <token>directly. See the Authentication guide for details and flows.
JSON-RPC snippet (tools/call secure_add) — include HTTP header:
POST / HTTP/1.1
Host: localhost:4987
Authorization: Bearer <access-token>
Content-Type: application/json
{
"method": "tools/call",
"jsonrpc": "2.0",
"id": 40,
"params": {
"name": "secure_add",
"arguments": { "a": 1, "b": 2 }
}
}
- Starting a server: see this guide’s examples and
/docs/howto.md - Creating clients: see
/docs/client.md - Server features: tools/resources/prompts overview in
/docs/server_guide.md - Authentication/OIDC:
/docs/authentication.md - Bridge (proxy) for stdio/HTTP:
/docs/bridge.md
Tips
- Keep transport and domain logic separated (usecase/server)
- Use tags to drive schemas; leverage pointers +
omitemptyfor optional fields - For elicitation and sampling, encapsulate patterns (adapters) to keep handlers small