Builders and struct vocabulary for the MCP 2026-07-28 protocol version.
The structs under GenMCP.MCP.V2607.* mirror the MCP schema for this protocol
version: requests, results, content blocks, capabilities, and so on. This
module exposes builder functions that turn convenient Elixir terms (keyword
lists, tuples, and maps) into those structs, so handler code returns readable
values instead of hand-writing nested struct literals.
These builders are what handler callbacks reach for when they return a result.
A tool's GenMCP.Suite.Tool.call/3, a prompt repository's
GenMCP.Suite.PromptRepo.get/4, and a resource repository's
GenMCP.Suite.ResourceRepo.read/3 all build their return value with one of
the functions here. The module is conventionally aliased as MCP:
alias GenMCP.MCP.V2607, as: MCPThe most common builder is call_tool_result/1, which assembles the content,
optional structured content, and error flag of a tool's response:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.call_tool_result(text: "42")
iex> match?(%MCP.CallToolResult{content: [%MCP.TextContent{text: "42"}]}, result)
trueBuilder groups
The builders fall into a few families:
- Tool calls:
call_tool_result/1aggregates content, structured content, and the error flag.content_block/1builds one content block on its own. - Listings:
list_tools_result/2,list_resources_result/3,list_resource_templates_result/2, andlist_prompts_result/3wrap a collection (and, where paginated, a cursor) in the matching result struct. - Reads:
read_resource_result/1andresource_contents/1build the contents returned for aresources/read.get_prompt_result/1builds a prompt's messages. - Discovery:
discover_result/1,capabilities/1, andserver_info/1build theserver/discoverresponse and its parts.
Cache hints
The list and read builders accept optional flat :cache_scope and :ttl_ms
options that populate the cacheScope / ttlMs fields the schema requires.
When omitted, they default to the no-cache hint returned by
default_cache_control/0 (private, immediately stale). Passing only one of the
two fills the other with that same no-cache default.
Summary
Functions
Builds the GenMCP.MCP.V2607.CallToolResult a tool returns from a tools/call.
Normalizes capability flags into a GenMCP.MCP.V2607.ServerCapabilities struct.
Builds a single content block struct from a shorthand term.
Returns the default cache control used when no cache hint is given.
Builds the GenMCP.MCP.V2607.DiscoverResult returned for server/discover.
Builds the GenMCP.MCP.V2607.GetPromptResult returned for prompts/get.
Wraps prompts and a cursor into a GenMCP.MCP.V2607.ListPromptsResult.
Wraps templates into a GenMCP.MCP.V2607.ListResourceTemplatesResult.
Wraps resources and a cursor into a GenMCP.MCP.V2607.ListResourcesResult.
Wraps a list of tools into a GenMCP.MCP.V2607.ListToolsResult.
Builds the GenMCP.MCP.V2607.ReadResourceResult returned for resources/read.
Builds one resource content entry for read_resource_result/1.
Builds the GenMCP.MCP.V2607.Implementation struct describing the server.
Functions
Builds the GenMCP.MCP.V2607.CallToolResult a tool returns from a tools/call.
Pass a list (a keyword list reads well, since keys may repeat) of entries. Each
entry contributes to the result's content, its structuredContent, or its
isError flag:
- Content shorthands (
{:text, _},{:image, _},{:audio, _},{:resource, _},{:link, _}) are turned into content blocks bycontent_block/1and appended tocontent. - Literal content structs (a
GenMCP.MCP.V2607.TextContentand the other content structs) are appended tocontentunchanged. You may mix shorthands and structs in the same list. {:data, map}setsstructuredContentto the map and also mirrors it intocontentas a JSON-encoded text block.{:_data, map}setsstructuredContentwithout the text mirror. A bare map entry behaves like{:data, map}.{:error, true}setsisErrortotrue. The flag is sticky: once any entry sets it, a later{:error, false}does not clear it.{:error, false}and{:error, nil}on their own leave the flag unset.{:error, message}with a binary appends the message as a text block and setsisErrortotrue.
Only one structured content may be set; a second :data, :_data, or bare map
raises ArgumentError. The result's resultType is always "complete".
Examples
The common case is a single text result:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.call_tool_result(text: "42")
iex> match?(%MCP.CallToolResult{content: [%MCP.TextContent{text: "42"}], isError: nil}, result)
trueReturn structured data alongside a human-readable summary. With :data the map
is both set as structuredContent and mirrored as a JSON text block; use
:_data to set the structured content without the extra text block:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.call_tool_result(text: "3 rows", data: %{rows: 3})
iex> result.structuredContent
%{rows: 3}
iex> length(result.content)
2Flag a failure with error:; a binary message is added as text content:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.call_tool_result(error: "boom")
iex> match?(%MCP.CallToolResult{content: [%MCP.TextContent{text: "boom"}], isError: true}, result)
true
@spec capabilities(keyword() | GenMCP.MCP.V2607.ServerCapabilities.t() | map()) :: GenMCP.MCP.V2607.ServerCapabilities.t()
Normalizes capability flags into a GenMCP.MCP.V2607.ServerCapabilities struct.
Each entry of the keyword list or map sets one capability field:
- a value of
truebecomes an empty map%{}(the capability is enabled with no extra options), - a map value is kept as given (use this to pass sub-options such as
listChanged: true), - any other value (
false,nil) leaves the fieldnil, meaning the capability is not advertised.
A GenMCP.MCP.V2607.ServerCapabilities struct passed in is returned unchanged.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> caps = MCP.capabilities(tools: true, resources: %{subscribe: true}, prompts: false)
iex> {caps.tools, caps.resources, caps.prompts}
{%{}, %{subscribe: true}, nil}
Builds a single content block struct from a shorthand term.
Tools and prompts return content blocks. This builder turns a compact tuple
into the matching struct so callers do not write struct literals by hand. It is
used on its own when you need one block, and internally by call_tool_result/1
and get_prompt_result/1 for each entry they receive.
The accepted shorthands are:
{:text, text}- aGenMCP.MCP.V2607.TextContent.{:image, {mime_type, data}}- aGenMCP.MCP.V2607.ImageContent, with base64-encodeddata.{:audio, {mime_type, data}}- aGenMCP.MCP.V2607.AudioContent, with base64-encodeddata.{:resource, %{uri: uri, text: text}}or{:resource, %{uri: uri, blob: blob}}- aGenMCP.MCP.V2607.EmbeddedResource.{:link, %{name: name, uri: uri}}- aGenMCP.MCP.V2607.ResourceLink. Extra keys in the map are carried onto the struct.
Any other term raises ArgumentError.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> %MCP.TextContent{text: text} = MCP.content_block({:text, "hello"})
iex> text
"hello"An image block carries its MIME type and base64 payload:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> block = MCP.content_block({:image, {"image/png", "aGVsbG8="}})
iex> {block.mimeType, block.data}
{"image/png", "aGVsbG8="}
Returns the default cache control used when no cache hint is given.
The list and read builders fall back to this {scope, ttl_ms} tuple for their
:cache_scope and :ttl_ms options. It is the no-cache hint: a private scope
with an immediately stale TTL, which is schema-valid and tells clients not to
reuse the result.
Builds the GenMCP.MCP.V2607.DiscoverResult returned for server/discover.
In the stateless 2026 core, server/discover is where the server advertises
who it is and what it can do. This builder fills that response: it sets the
supported protocol versions and
marks the result as a no-cache snapshot.
Options
:name- the server name, required (passed toserver_info/1).:version- the server version, required (passed toserver_info/1).:title- an optional human-friendly server title.:capabilities- capability flags or maps, passed tocapabilities/1. Defaults to no advertised capabilities.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.discover_result(name: "MyServer", version: "1.0.0", capabilities: [tools: true])
iex> info = result._meta."io.modelcontextprotocol/serverInfo"
iex> {info.name, result.capabilities.tools, result.supportedVersions}
{"MyServer", %{}, ["2026-07-28"]}
@spec get_prompt_result(keyword() | [term()]) :: GenMCP.MCP.V2607.GetPromptResult.t()
Builds the GenMCP.MCP.V2607.GetPromptResult returned for prompts/get.
Pass a list of message entries (a keyword list reads well, since keys repeat),
plus an optional :description. Each message entry becomes a
GenMCP.MCP.V2607.PromptMessage:
{:text, binary}- a"user"message carrying text content.{:assistant, binary}- an"assistant"message carrying text content.- other content shorthands (
{:image, _},{:audio, _},{:resource, _}) - a"user"message carrying that content, built bycontent_block/1. A{:link, _}shorthand is rejected, since a resource link is not a valid prompt message content. - a ready
%{role: role, content: content}map orGenMCP.MCP.V2607.PromptMessagestruct is kept as is. Use this to pair the"assistant"role with non-text content.
The optional :description entry sets the result's description.
Examples
The keyword shorthands cover a simple user/assistant exchange:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.get_prompt_result(text: "hello", assistant: "hi there", description: "greeting")
iex> result.description
"greeting"
iex> Enum.map(result.messages, & &1.role)
["user", "assistant"]To give the assistant role non-text content, pass a full message struct, since
the {:assistant, _} shorthand only takes text:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result =
...> MCP.get_prompt_result([
...> {:text, "describe this sound"},
...> %MCP.PromptMessage{
...> role: "assistant",
...> content: MCP.content_block({:audio, {"audio/mp3", "aGk="}})
...> }
...> ])
iex> Enum.map(result.messages, & &1.role)
["user", "assistant"]
@spec list_prompts_result([term()], term() | nil, keyword()) :: GenMCP.MCP.V2607.ListPromptsResult.t()
Wraps prompts and a cursor into a GenMCP.MCP.V2607.ListPromptsResult.
This is the answer to a prompts/list request. The prompts are placed in the
result as given, and next_cursor becomes nextCursor (pass nil for the
last page).
Options
:cache_scope- thecacheScopecache hint. Defaults to the no-cache hint (seedefault_cache_control/0).:ttl_ms- thettlMscache hint. Same default.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.list_prompts_result([], nil)
iex> {result.prompts, result.nextCursor}
{[], nil}
@spec list_resource_templates_result( [term()], keyword() ) :: GenMCP.MCP.V2607.ListResourceTemplatesResult.t()
Wraps templates into a GenMCP.MCP.V2607.ListResourceTemplatesResult.
This is the answer to a resources/templates/list request. The templates are
placed in the result as given.
Options
:cache_scope- thecacheScopecache hint. Defaults to the no-cache hint (seedefault_cache_control/0).:ttl_ms- thettlMscache hint. Same default.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.list_resource_templates_result([])
iex> result.resourceTemplates
[]
@spec list_resources_result([term()], term() | nil, keyword()) :: GenMCP.MCP.V2607.ListResourcesResult.t()
Wraps resources and a cursor into a GenMCP.MCP.V2607.ListResourcesResult.
This is the answer to a resources/list request. The resources are placed in
the result as given, and next_cursor becomes nextCursor (pass nil for the
last page).
Options
:cache_scope- thecacheScopecache hint. Defaults to the no-cache hint (seedefault_cache_control/0).:ttl_ms- thettlMscache hint. Same default.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.list_resources_result([], "next-page")
iex> {result.resources, result.nextCursor}
{[], "next-page"}
@spec list_tools_result( [GenMCP.Suite.Tool.tool() | GenMCP.MCP.V2607.Tool.t()], keyword() ) :: GenMCP.MCP.V2607.ListToolsResult.t()
Wraps a list of tools into a GenMCP.MCP.V2607.ListToolsResult.
This is what a Suite answers a tools/list request with. Each element is
either a ready GenMCP.MCP.V2607.Tool struct, which is kept as is, or a tool
definition (GenMCP.Suite.Tool.tool/0: a module, a {module, arg} pair, or
a descriptor map), which is converted with GenMCP.Suite.Tool.describe/1.
Options
:cache_scope- thecacheScopecache hint. Defaults to the no-cache hint (seedefault_cache_control/0).:ttl_ms- thettlMscache hint. Same default.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> tool = %MCP.Tool{name: "add", inputSchema: %{"type" => "object"}}
iex> result = MCP.list_tools_result([tool])
iex> [%MCP.Tool{name: name}] = result.tools
iex> name
"add"
@spec read_resource_result(keyword()) :: GenMCP.MCP.V2607.ReadResourceResult.t()
Builds the GenMCP.MCP.V2607.ReadResourceResult returned for resources/read.
There are two ways to give the contents:
- Single content (flat form): pass
:uritogether with:textor:blob(and optional:mime_type). One content entry is built for you withresource_contents/1. - Multiple contents (
:contentsform): pass:contentswith a list of content structs (built byresource_contents/1) or plain maps. When:contentsis given, the flat:uri/:text/:blob/:mime_typeoptions are ignored. The list may be empty.
Options
:uri- the resource URI for the flat form. Required there; raisesKeyErrorif missing.:textor:blob- the resource body for the flat form. One is required; raisesArgumentErrorif neither is given.:mime_type- optional MIME type for the flat form.:contents- a list of content entries, used instead of the flat options.:_meta- metadata set on the result object itself. This is distinct from a per-content_meta, which you attach throughresource_contents/1.:cache_scope- thecacheScopecache hint. Defaults to the no-cache hint (seedefault_cache_control/0).:ttl_ms- thettlMscache hint. Same default.
Examples
The flat form covers the usual single-file read:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result = MCP.read_resource_result(uri: "file:///readme.txt", text: "# Welcome")
iex> [%MCP.TextResourceContents{uri: uri, text: text}] = result.contents
iex> {uri, text}
{"file:///readme.txt", "# Welcome"}Use the :contents form to return several entries, building each with
resource_contents/1:
iex> alias GenMCP.MCP.V2607, as: MCP
iex> result =
...> MCP.read_resource_result(
...> contents: [
...> MCP.resource_contents(uri: "file:///a.txt", text: "first"),
...> MCP.resource_contents(uri: "file:///b.png", blob: "aGk=", mime_type: "image/png")
...> ]
...> )
iex> length(result.contents)
2
@spec resource_contents(keyword()) :: GenMCP.MCP.V2607.TextResourceContents.t() | GenMCP.MCP.V2607.BlobResourceContents.t()
Builds one resource content entry for read_resource_result/1.
Returns a GenMCP.MCP.V2607.TextResourceContents when :text is given, or a
GenMCP.MCP.V2607.BlobResourceContents when :blob is given. Use this to
assemble the :contents list passed to read_resource_result/1 when a read
returns more than one entry.
Options
:uri- the content URI, required. RaisesKeyErrorif missing.:text- the text body. Produces aTextResourceContents.:blob- the base64-encoded binary body. Produces aBlobResourceContents. Provide exactly one of:textor:blob, otherwise it raisesArgumentError.:mime_type- optional MIME type.:_meta- optional metadata attached to this content entry.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> contents = MCP.resource_contents(uri: "file:///a.txt", text: "hi", mime_type: "text/plain")
iex> {contents.uri, contents.text, contents.mimeType}
{"file:///a.txt", "hi", "text/plain"}
@spec server_info(keyword()) :: GenMCP.MCP.V2607.Implementation.t()
Builds the GenMCP.MCP.V2607.Implementation struct describing the server.
This is the io.modelcontextprotocol/serverInfo metadata carried in the
_meta of the server/discover response.
Options
:name- the server name, required. RaisesKeyErrorif missing.:version- the server version, required. RaisesKeyErrorif missing.:title- an optional human-friendly title.
Examples
iex> alias GenMCP.MCP.V2607, as: MCP
iex> info = MCP.server_info(name: "MyServer", version: "1.0.0")
iex> {info.name, info.version, info.title}
{"MyServer", "1.0.0", nil}