Current section
Files
Jump to
Current section
Files
mix.exs
defmodule Wymcp.MixProject do
use Mix.Project
@source_url "https://git.sr.ht/~wyper/wymcp"
def project do
[
app: :wymcp,
version: "0.1.1",
elixir: "~> 1.19",
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
description: "Model Context Protocol server library for Elixir with Plug integration",
package: package(),
name: "Wymcp",
source_url: @source_url,
homepage_url: @source_url,
docs: [
main: "readme",
api_reference: false,
source_url_pattern: "#{@source_url}/tree/main/item/%{path}#L%{line}",
formatters: ["html"],
before_closing_head_tag: &docs_before_closing_head_tag/1,
before_closing_body_tag: &docs_before_closing_body_tag/1,
extras: [{"README.md", title: "README"}] ++ Path.wildcard("docs/*.md"),
groups_for_extras: [
Development: [
"docs/mcp-spec-2025-11-25-overview.md",
"docs/mcp-spec-2026-07-28-overview.md"
]
],
groups_for_modules: [
"Consumer surface": [
Wymcp.Auth,
Wymcp.Auth.Noop,
Wymcp.Context,
Wymcp.Help,
Wymcp.Hint,
Wymcp.ProtocolVersion,
Wymcp.Router,
Wymcp.Server,
Wymcp.Session,
Wymcp.Telemetry,
Wymcp.Testing,
Wymcp.Tool
],
Internals: [
Wymcp.JsonRpc,
Wymcp.Modern,
Wymcp.Plugs.Auth,
Wymcp.Plugs.Classify,
Wymcp.Plugs.Dispatch,
Wymcp.Plugs.Era,
Wymcp.Plugs.OriginCheck,
Wymcp.Plugs.Pipeline,
Wymcp.Plugs.ProtocolFields,
Wymcp.Plugs.Session,
Wymcp.Plugs.SingletonHeaders,
Wymcp.Plugs.Validate,
Wymcp.Response,
Wymcp.ServerInfo,
Wymcp.Tool.Schema,
Wymcp.Transport.SSE,
Wymcp.Transport.Stream
]
]
]
]
end
def application do
[
mod: {Wymcp.Application, []},
extra_applications: [:logger, :crypto]
]
end
def cli do
[preferred_envs: [precommit: :test]]
end
defp package do
[
licenses: ["MIT"],
links: %{"Bug Tracker" => "https://todo.sr.ht/~wyper/wymcp", "sourcehut" => @source_url},
files:
~w(lib priv/schema-2025-11-25.json priv/schema-2026-07-28.json .formatter.exs mix.exs README.md LICENSE)
]
end
defp aliases do
[
precommit: [
"compile --warnings-as-errors",
"deps.unlock --unused",
"format",
"credo --strict",
"deps.audit",
"test --warnings-as-errors"
]
]
end
defp deps do
[
{:bandit, "~> 1.0", only: [:dev, :test]},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:ex_doc, "~> 0.34", only: :dev, runtime: false},
{:ex_slop, "~> 0.4", only: [:dev, :test], runtime: false},
{:jsv, "~> 0.16"},
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
{:mix_test_watch, "~> 1.0", only: :dev, runtime: false},
{:plug, "~> 1.15"},
{:req, "~> 0.7", only: [:dev, :test]},
{:thousand_island, "~> 1.5", only: [:dev, :test]}
]
end
defp docs_before_closing_body_tag(:html), do: ""
defp docs_before_closing_body_tag(:epub), do: ""
defp docs_before_closing_head_tag(:html) do
"""
<script>
window.addEventListener("exdoc:loaded", () => {
document.querySelectorAll('[data-toc]').forEach(tocContainer => {
const content = tocContainer.closest('.content-inner') || document.querySelector('.content-inner');
const headings = content.querySelectorAll('h2, h3, h4');
// Filter out headings inside admonitions (sections with class "admonition")
const filteredHeadings = Array.from(headings).filter(heading => {
return !heading.closest('.admonition');
});
if (filteredHeadings.length === 0) return;
const toc = document.createElement('nav');
toc.className = 'toc';
// Add "Table of Contents" heading
const tocHeading = document.createElement('h3');
tocHeading.textContent = 'Table of Contents';
tocHeading.className = 'toc-heading';
toc.appendChild(tocHeading);
const list = document.createElement('ul');
filteredHeadings.forEach(heading => {
const li = document.createElement('li');
li.className = `toc-${heading.tagName.toLowerCase()}`;
const link = document.createElement('a');
link.href = '#' + heading.id;
link.textContent = heading.textContent;
li.appendChild(link);
list.appendChild(li);
});
toc.appendChild(list);
tocContainer.replaceWith(toc);
});
});
</script>
<script defer src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<script>
window.addEventListener("exdoc:loaded", () => {
if (typeof mermaid === "undefined") {
// mermaid.min.js hasn't executed yet — wait and retry
const waitForMermaid = setInterval(() => {
if (typeof mermaid !== "undefined") {
clearInterval(waitForMermaid);
initMermaid();
}
}, 50);
} else {
initMermaid();
}
function initMermaid() {
mermaid.initialize({
startOnLoad: false,
theme: document.body.className.includes("dark") ? "dark" : "default"
});
let id = 0;
for (const codeEl of document.querySelectorAll("pre code.mermaid")) {
const preEl = codeEl.parentElement;
const graphDefinition = codeEl.textContent;
const graphEl = document.createElement("div");
const graphId = "mermaid-graph-" + id++;
mermaid.render(graphId, graphDefinition).then(({svg, bindFunctions}) => {
graphEl.innerHTML = svg;
bindFunctions?.(graphEl);
preEl.insertAdjacentElement("afterend", graphEl);
preEl.remove();
}).catch((error) => {
console.error("Mermaid render failed for " + graphId + ":", error);
preEl.style.borderLeft = "4px solid #e74c3c";
preEl.title = "Mermaid diagram failed to render";
});
}
}
});
</script>
"""
end
defp docs_before_closing_head_tag(:epub), do: ""
end