사용 방법
- 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
- 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
- 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/victorzhuk/skills --skill z-go-mcp-server시작 전 확인
- 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
- 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.
Go MCP server
Build MCP servers with the official SDK by default. Treat every tool as a workflow contract for the calling model, not a REST mirror.
SDK choice
Use modelcontextprotocol/go-sdk for new servers unless an existing SDK or a required capability determines the choice. Preserve working mark3labs/mcp-go integrations. Check each selected release's Go floor, protocol support and compatibility policy; SDK maturity does not establish support for every new protocol revision.
Before changing a server, record the pinned SDK version, supported protocol revisions, client versions and transports. Verify that matrix with client/server tests. Examples below use the official SDK's session API and require a compatible pinned release; they are not proof of 2026-07-28 support.
Transport choice
| Transport | Use when | Avoid when |
|---|---|---|
stdio (mcp.StdioTransport) | Local subprocess launched by one client — CLI tools, editor/agent integrations. verify the client supports the selected revision | Multiple concurrent clients, or the server runs remotely |
Streamable HTTP (mcp.NewStreamableHTTPHandler) | Remote server, multiple concurrent sessions, runs behind a load balancer or in Kubernetes | A single local subprocess covers the need — HTTP adds authentication and origin-validation requirements |
| SSE-only (the 2024-11-05 transport) | Never for a new server | Always — superseded by streamable HTTP in spec 2025-03-26; keep it only to serve clients stuck on the old transport |
As checked on 2026-10-13, the current protocol is 2026-07-28. Its core is stateless: requests carry protocol-version metadata and servers support discovery. Earlier revisions use initialize and may use Mcp-Session-Id; preserve that lifecycle only for the revisions your SDK/client pair supports. Do not infer SDK support from the protocol publication date.
For HTTP, validate Origin and Host according to the selected transport revision, authenticate callers, and authorize each tool. Check the pinned SDK's defaults rather than assuming it covers these controls. A generic browser cross-origin helper is not a substitute for the protocol's Origin policy.
Security boundary
- Derive caller and tenant identity from verified authentication. Authorize the requested action and resource on every call, including read-only tools.
- For OAuth, validate issuer, audience, expiry, and required scopes. Accept only tokens issued for this MCP server. Never forward its bearer token to an upstream API; use separately scoped downstream credentials.
- Validate tool arguments in code, including ranges, paths, URLs, and ownership. Schema descriptions, tool annotations, and model confidence are not permission checks.
- Treat returned documents, upstream errors, and tool metadata as untrusted content. Keep result data separate from trusted instructions. Do not accept model-supplied approval flags as authorization.
- For consequential actions, verify the caller's approval policy at execution time and bind any approval to the exact operation and arguments. Preserve existing valid authorization.
- Limit request/result bytes, execution time, concurrency, and downstream work. Apply [[z-go-http-client]] destination controls to tools that fetch URLs.
- Run stdio servers with only the filesystem, network, and credential access they need. Stdio avoids a listening endpoint; it does not sandbox the process.
Use [[z-security-hardening]] for shared prompt-injection defenses and MCP security guidance for the selected protocol's authorization requirements.
This server-side streamable-HTTP SSE is internal transport plumbing, not an app-level streaming feature — for hand-rolled SSE to a browser client, see [[z-go-llm-streaming]].
stdio discipline
Stdout carries protocol JSON-RPC only. Anything else on stdout — a stray fmt.Println, a dependency that logs to stdout by default, a panic trace — corrupts the stream and the client can't parse it.
- Build
slogonos.Stderrexplicitly:slog.New(slog.NewJSONHandler(os.Stderr, nil)). Don't rely on a package default you haven't checked. - Route or silence any third-party logger that defaults to stdout.
mcp.StdioTransportowns stdout once connected — never write to it yourself.
Tool design
The model is the caller. Design tools it can pick correctly and recover from when it picks wrong.
- Name as `service_verb_noun`:
excursions_search,excursions_get_booking, notsearchorgetData. A consistent prefix lets an agent juggling several MCP servers disambiguate at a glance. - Shape tools around a workflow, not an endpoint. A tool mirroring
GET /bookings/{id}/legs1:1 forces three round trips for one task; a tool returning "booking + legs + cancellation window" together is a better contract. When unsure, favor coverage over cleverness — an under-fitted workflow tool blocks real tasks. - Write the description for the model, not a teammate: what it does, every parameter's meaning and constraints, one example call. Skip adjectives that don't change behavior.
- Paginate and filter server-side. Never hand back a raw ClickHouse result set or full Postgres row — cap page size, project only the fields the task needs, summarize the rest.
- Eval against real tasks before shipping: write 5-10 realistic, independently verifiable prompts a user would give an agent, run them against the server, and fix whichever tool the model picks wrong or misuses.
Input schemas from Go structs
mcp.AddTool is generic over the input and output types and derives the JSON Schema from the struct — no separate schema to keep in sync:
type SearchParams struct {
City string `json:"city" jsonschema:"IATA city code, e.g. LON"`
Checkin string `json:"checkin" jsonschema:"date in YYYY-MM-DD"`
Limit int `json:"limit,omitempty" jsonschema:"max results from 1 to 100, default 20"`
}
mcp.AddTool(server, &mcp.Tool{
Name: "excursions_search",
Description: "Search bookable excursions for a city and date, paginated.",
}, func(ctx context.Context, req *mcp.CallToolRequest, in SearchParams) (*mcp.CallToolResult, SearchResult, error) {
if in.Limit == 0 {
in.Limit = 20
}
if in.Limit < 1 || in.Limit > 100 {
return nil, SearchResult{}, errors.New("limit must be between 1 and 100")
}
...
})json tags name the fields; jsonschema tags supply the description the model reads — write that string by the same rule as the tool description above.
The description does not enforce Limit; the handler does. Pass the normalized limit to the query and cap the returned page. Choose the maximum from the tool contract, then test the generated schema and runtime behavior. Validate resource ownership separately from input shape.
Register tools before serving requests. Verify discovery/listing for each supported revision; older session-based clients may derive capabilities during initialization.
Errors the agent can act on
MCP splits failures into two channels — pick the right one:
| Channel | When | Go SDK behavior |
|---|---|---|
Protocol error (JSON-RPC error) | Unknown tool, malformed request, server fault — not something the model can fix by retrying | Only the raw Server.AddTool(ToolHandler) path sends this when it returns error |
Tool execution error (isError: true in the result) | Bad input, upstream API failure, business-rule violation — the model can retry with different arguments | mcp.AddTool's generic handler packs a returned Go error here automatically |
Prefer the generic mcp.AddTool path so ordinary failures land as tool errors. Return a safe error code and concise recovery guidance. Do not echo raw upstream errors, tokens, or arbitrary user text as instructions; the model receives that content.
Cancellation and shutdown
- Tool handlers get the call's
context.Context; a client cancellation notification cancels it. Checkctx.Err()in any loop and return early — don't leave a detached goroutine running past a cancelled call. - stdio:
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM),defer stop(), thenserver.Run(ctx, &mcp.StdioTransport{}).Runblocks until the client disconnects orctxis cancelled, and closes the session either way. - Streamable HTTP:
mcp.NewStreamableHTTPHandleris a plainhttp.Handler— wrap it inhttp.Serverand drain withsrv.Shutdown(ctx)like any other HTTP service.
Testing
Skip subprocess plumbing — connect a client and server over an in-memory pipe and drive real tool calls:
ct, st := mcp.NewInMemoryTransports()
srv := mcp.NewServer(&mcp.Implementation{Name: "excursions", Version: "test"}, nil)
mcp.AddTool(srv, &mcp.Tool{Name: "excursions_search"}, searchHandler)
ss, err := srv.Connect(ctx, st, nil)
require.NoError(t, err)
defer ss.Close()
cli := mcp.NewClient(&mcp.Implementation{Name: "test-client", Version: "v0"}, nil)
session, err := cli.Connect(ctx, ct, nil)
require.NoError(t, err)
defer session.Close()
result, err := session.CallTool(ctx, &mcp.CallToolParams{
Name: "excursions_search",
Arguments: map[string]any{"city": "LON"},
})This exercises the pinned SDK's connection lifecycle, schema validation, and dispatch — no mocking the protocol layer. Assert on result.IsError and result.Content, the same shape the model receives. One test per tool's happy path, one per validation failure that should surface as a tool error.
Reach for golden JSON-RPC frame fixtures only for cross-SDK wire-compatibility regression tests; the in-memory client above already exercises real serialization, so it stays the default for ordinary handler tests.
Do not
- Ship a tool whose only documentation is its name — the model has nothing else to pick with.
- Mirror every REST or gRPC endpoint 1:1 "for completeness" — collapse the common workflow into one tool, add the rest only when a real task needs them.
- Return a bad-input failure through the raw
ToolHandlerprotocol-error path — the model reads a protocol error as unfixable and gives up instead of retrying. - Write anything but JSON-RPC frames to stdout in a stdio server, including via a dependency's default logger.
- Build a new server on the 2024-11-05 SSE-only transport.
- Expose a remote server before implementing the selected protocol/client authentication contract and per-tool authorization. Keep local stdio setup scoped to its launching user.
Verify
go build ./...
timeout 5m go test -parallel 4 -p 2 -timeout 2m ./... -run TestMCP -vVerify the pinned SDK/client protocol matrix: current-revision discovery and per-request version handling where supported, legacy initialization only for legacy revisions, expected tool schemas, cancellation, malformed input, and HTTP authorization/origin rejection. Record any unsupported revision instead of claiming conformance.
Test wrong-audience and expired tokens, cross-tenant resource access, invalid limits, forbidden fetch destinations, and missing or changed approvals. Assert that rejected calls never reach the protected dependency. Include malicious tool-result text; authorization must hold even if the model follows it.

