A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live
mcp.projection
The MCP projection of this registry
What it is
Who answers `initialize` and with which protocol versions; the transports and the sessions attached over each; the methods served, and that prompts and notifications are not; every tool (or only those with one effect) with its capability, effect and the hints its annotations are derived from; the tools that write the repository; the resources; and whether each client the distribution declares has a configuration here that starts this repository's server. Reads the registry and one file per declared client; writes nothing.
Descriptor
- Canonical id
mcp.projection- Module
mcp- Kind
- query
- Stability
- experimental
- Provenance
- declared
- Source
apps/majordomus-cli/src/capability/builtin/mcp.rs- Cache
disabled- Benchmark
required- Tags
- mcpintrospectionprojections
Run
queryA query. It reads and changes nothing.
Projections
| Interface | As | Where |
|---|---|---|
| MCP tool | majordomus_mcp | - |
| MCP resource | majordomus://mcp | - |
| HTTP | GET /api/v1/mcp | Swagger |
| Command line | not exposed | - |
Schemas
Input · McpProjectionInput
{
"type": "object",
"additionalProperties": false,
"properties": {
"effect": {
"anyOf": [
{
"$ref": "#/$defs/ExecutionEffect"
},
{
"type": "null"
}
],
"description": "List only the tools with this effect: `read`, `process_state` or\n`repository_mutation`. Absent lists every tool."
}
},
"description": "The input of `mcp.projection`: which tools to list. Everything else in the answer is\nabout the projection as a whole and is always given.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpProjectionInput;\nuse majordomus_cli::capability::Effect;\nlet all: McpProjectionInput = serde_json::from_str(\"{}\").unwrap();\nassert!(all.effect.is_none());\nlet writers: McpProjectionInput =\n serde_json::from_str(r#\"{\"effect\":\"repository_mutation\"}\"#).unwrap();\nassert_eq!(writers.effect, Some(Effect::RepositoryMutation));\n```",
"title": "McpProjectionInput",
"$defs": {
"ExecutionEffect": {
"oneOf": [
{
"type": "string",
"const": "read",
"description": "Nothing changes. Every query and every resource of this executable."
},
{
"type": "string",
"const": "process_state",
"description": "This process's own memory changes, and nothing outside it."
},
{
"type": "string",
"const": "repository_mutation",
"description": "The repository changes.\n\nFor most of this executable's life nothing classified to this, because no capability\nof any kind wrote to the repository. That day arrived with [ADR 0040]: development\nsemantics are capabilities of this runtime, and the first of them — `plan.transition`\n— stamps a field into an issue's own tracked record. The variant was put on the model\nin advance so that when one did, it would say so *here*, where a projection already\nreads it and a client already asks before running it, rather than in whichever page\nhappens to render its button.\n\nIt is not classified from the kind, because the kind cannot know it: a command that\nannounces a peer and a command that writes a tracked file are the same kind. A\nhandler that writes the repository says so with\n`ExecutionPolicy::writes_repository`, the way one that stops says so with\n`ExecutionPolicy::stoppable`.\n\n[ADR 0040]: https://github.com/korczis/prismatic-majordomus/blob/master/.ai/repo/adrs/0040-development-semantics-are-capabilities-of-one-runtime.md"
}
],
"description": "What running a capability changes outside the caller.\n\nClassified, never declared: it follows from the kind, which is the field a declaration\nalready carries. A projection reads this to decide whether to ask before running\nsomething — the Cockpit's confirmation is derived from it — instead of naming\ncapabilities it must treat carefully, which is a list that goes stale the day after it\nis written."
}
}
}Output · McpProjection
{
"type": "object",
"properties": {
"server": {
"$ref": "#/$defs/McpServerIdentity",
"description": "Who answers `initialize`."
},
"protocol_versions": {
"type": "array",
"items": {
"type": "string"
},
"description": "The protocol versions accepted, newest first."
},
"transports": {
"type": "array",
"items": {
"$ref": "#/$defs/McpTransportEntry"
},
"description": "How a client reaches the server."
},
"serving": {
"$ref": "#/$defs/McpServing",
"description": "What of the protocol is served."
},
"tool_count": {
"type": "integer",
"format": "uint",
"minimum": 0,
"description": "How many tools the registry projects, whatever the filter asked for."
},
"tools": {
"type": "array",
"items": {
"$ref": "#/$defs/McpToolEntry"
},
"description": "The tools asked for, in registry order."
},
"effects": {
"type": "array",
"items": {
"$ref": "#/$defs/McpEffectCount"
},
"description": "The tools counted by effect, every effect present."
},
"writers": {
"type": "array",
"items": {
"type": "string"
},
"description": "The tools that write the repository, by name. The `initialize` instructions name the\nsame ones."
},
"resources": {
"$ref": "#/$defs/McpResourceSummary",
"description": "The resources."
},
"launcher_in_repository": {
"type": "boolean",
"description": "Whether this repository carries the launcher at `bin/majordomus-mcp`. Where it does\nnot, the installed command of the same name is what a configuration names."
},
"clients": {
"type": "array",
"items": {
"$ref": "#/$defs/McpClient"
},
"description": "The clients the distribution declares a configuration for, and where each stands."
},
"findings": {
"type": "array",
"items": {
"$ref": "#/$defs/McpFinding"
},
"description": "What a person should act on. Empty is nothing to do."
}
},
"required": [
"server",
"protocol_versions",
"transports",
"serving",
"tool_count",
"tools",
"effects",
"writers",
"resources",
"launcher_in_repository",
"clients",
"findings"
],
"description": "The MCP projection of this registry in this repository.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpProjection;\nlet p: McpProjection = serde_json::from_value(serde_json::json!({\n \"server\": { \"name\": \"majordomus\", \"title\": \"Majordomus\", \"version\": \"0.14.0\" },\n \"protocol_versions\": [\"2025-06-18\"],\n \"transports\": [{ \"id\": \"stdio\", \"reached_by\": \"majordomus mcp\", \"attached\": 0 }],\n \"serving\": { \"methods\": [\"initialize\"], \"tools\": true, \"resources\": true, \"prompts\": false, \"notifications\": false },\n \"tool_count\": 2,\n \"tools\": [],\n \"effects\": [{ \"effect\": \"read\", \"tools\": 1 }, { \"effect\": \"repository_mutation\", \"tools\": 1 }],\n \"writers\": [\"majordomus_plan_transition\"],\n \"resources\": { \"builtin\": 1, \"objects\": 0, \"template\": \"majordomus://<kind>/<identity>\" },\n \"launcher_in_repository\": false,\n \"clients\": [],\n \"findings\": [],\n})).unwrap();\n// the count is of every tool, whatever the `effect` filter narrowed `tools` to\nassert_eq!(p.tool_count, p.effects.iter().map(|e| e.tools).sum::<usize>());\nassert!(p.tools.is_empty() && p.writers.len() == 1);\n```",
"title": "McpProjection",
"$defs": {
"McpServerIdentity": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The server name."
},
"title": {
"type": "string",
"description": "The name a person reads."
},
"version": {
"type": "string",
"description": "The version: this executable's, never one of the protocol adapter's own."
}
},
"required": [
"name",
"title",
"version"
],
"description": "Who answers `initialize`, as it answers.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpServerIdentity;\nlet s: McpServerIdentity = serde_json::from_value(serde_json::json!({\n \"name\": \"majordomus\", \"title\": \"Majordomus\", \"version\": \"0.14.0\",\n})).unwrap();\n// the version is the executable's: the protocol adapter has none of its own to drift\nassert_eq!((s.name.as_str(), s.version.as_str()), (\"majordomus\", \"0.14.0\"));\n```"
},
"McpTransportEntry": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "`stdio` or `http`."
},
"reached_by": {
"type": "string",
"description": "How it is reached: the command a client starts, or the path on the shared server."
},
"attached": {
"type": "integer",
"format": "uint",
"minimum": 0,
"description": "Sessions attached to this process over it right now."
}
},
"required": [
"id",
"reached_by",
"attached"
],
"description": "One way a client reaches the server.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpTransportEntry;\nlet t: McpTransportEntry = serde_json::from_value(serde_json::json!({\n \"id\": \"stdio\", \"reached_by\": \"majordomus mcp\", \"attached\": 1,\n})).unwrap();\n// a count of sessions at the moment of asking, not a claim that a server is running\nassert_eq!((t.id.as_str(), t.attached), (\"stdio\", 1));\n```"
},
"McpServing": {
"type": "object",
"properties": {
"methods": {
"type": "array",
"items": {
"type": "string"
},
"description": "The methods a request may name. Anything else is answered `-32601`."
},
"tools": {
"type": "boolean",
"description": "Tools: every capability that declares one."
},
"resources": {
"type": "boolean",
"description": "Resources: every capability that declares one, and every object of the layer."
},
"prompts": {
"type": "boolean",
"description": "Prompts. Not served: the repository's prompt assets are resources, because a prompt\nserved unrendered would read as finished."
},
"notifications": {
"type": "boolean",
"description": "Subscriptions and list-change notifications. Not served: this server sends nothing\nunasked."
}
},
"required": [
"methods",
"tools",
"resources",
"prompts",
"notifications"
],
"description": "What the server serves of the protocol, and what it does not.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpServing;\nlet s: McpServing = serde_json::from_value(serde_json::json!({\n \"methods\": [\"initialize\", \"tools/call\"], \"tools\": true, \"resources\": true,\n \"prompts\": false, \"notifications\": false,\n})).unwrap();\n// what is not served is stated, so a client does not learn it from a refusal\nassert!(s.tools && !s.prompts && !s.notifications);\nassert!(!s.methods.iter().any(|m| m.starts_with(\"prompts/\")));\n```"
},
"McpToolEntry": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name a client calls."
},
"capability": {
"type": "string",
"description": "The capability it projects; `capabilities.describe` answers its schemas."
},
"module": {
"type": "string",
"description": "The module that composes the capability."
},
"title": {
"type": "string",
"description": "The title."
},
"effect": {
"$ref": "#/$defs/ExecutionEffect",
"description": "What a call changes."
},
"hints": {
"$ref": "#/$defs/ExecutionHints",
"description": "What a caller may assume before calling; the tool's annotations are this value."
}
},
"required": [
"name",
"capability",
"module",
"title",
"effect",
"hints"
],
"description": "One tool, as the registry projects it.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpToolEntry;\nuse majordomus_cli::capability::Effect;\nlet t: McpToolEntry = serde_json::from_value(serde_json::json!({\n \"name\": \"majordomus_plan_transition\", \"capability\": \"plan.transition\",\n \"module\": \"plan\", \"title\": \"Move an issue\", \"effect\": \"repository_mutation\",\n \"hints\": { \"read_only\": false, \"destructive\": true, \"idempotent\": false, \"open_world\": false },\n})).unwrap();\nassert_eq!(t.effect, Effect::RepositoryMutation);\n// the hints are the effect's classification, carried beside it rather than restated\nassert!(t.hints.destructive && !t.hints.read_only && !t.hints.idempotent);\n```"
},
"ExecutionEffect": {
"oneOf": [
{
"type": "string",
"const": "read",
"description": "Nothing changes. Every query and every resource of this executable."
},
{
"type": "string",
"const": "process_state",
"description": "This process's own memory changes, and nothing outside it."
},
{
"type": "string",
"const": "repository_mutation",
"description": "The repository changes.\n\nFor most of this executable's life nothing classified to this, because no capability\nof any kind wrote to the repository. That day arrived with [ADR 0040]: development\nsemantics are capabilities of this runtime, and the first of them — `plan.transition`\n— stamps a field into an issue's own tracked record. The variant was put on the model\nin advance so that when one did, it would say so *here*, where a projection already\nreads it and a client already asks before running it, rather than in whichever page\nhappens to render its button.\n\nIt is not classified from the kind, because the kind cannot know it: a command that\nannounces a peer and a command that writes a tracked file are the same kind. A\nhandler that writes the repository says so with\n`ExecutionPolicy::writes_repository`, the way one that stops says so with\n`ExecutionPolicy::stoppable`.\n\n[ADR 0040]: https://github.com/korczis/prismatic-majordomus/blob/master/.ai/repo/adrs/0040-development-semantics-are-capabilities-of-one-runtime.md"
}
],
"description": "What running a capability changes outside the caller.\n\nClassified, never declared: it follows from the kind, which is the field a declaration\nalready carries. A projection reads this to decide whether to ask before running\nsomething — the Cockpit's confirmation is derived from it — instead of naming\ncapabilities it must treat carefully, which is a list that goes stale the day after it\nis written."
},
"ExecutionHints": {
"type": "object",
"properties": {
"read_only": {
"type": "boolean",
"description": "The call changes nothing, anywhere."
},
"destructive": {
"type": "boolean",
"description": "The call may overwrite or remove something that outlives this process."
},
"idempotent": {
"type": "boolean",
"description": "A second identical call changes nothing the first did not."
},
"open_world": {
"type": "boolean",
"description": "The call reaches beyond this machine. No handler of this executable does: observing\nthe forge and talking to an advisor are commands a person runs, not capabilities."
}
},
"required": [
"read_only",
"destructive",
"idempotent",
"open_world"
],
"description": "What a caller may assume about a call before making it: the four facts MCP names tool\nannotations, held once on the model so that no projection states its own.\n\nClassified by `ExecutionPolicy::hints`, never declared.\n\n```\nuse majordomus_cli::capability::{CapabilityKind, ExecutionPolicy, Hints};\nlet read: Hints = ExecutionPolicy::classify(CapabilityKind::Query).hints();\nassert_eq!(\n serde_json::to_value(read).unwrap(),\n serde_json::json!({ \"read_only\": true, \"destructive\": false, \"idempotent\": true, \"open_world\": false })\n);\n```"
},
"McpEffectCount": {
"type": "object",
"properties": {
"effect": {
"$ref": "#/$defs/ExecutionEffect",
"description": "The effect."
},
"tools": {
"type": "integer",
"format": "uint",
"minimum": 0,
"description": "How many tools have it."
}
},
"required": [
"effect",
"tools"
],
"description": "How many tools have one effect.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpEffectCount;\nuse majordomus_cli::capability::Effect;\nlet c: McpEffectCount =\n serde_json::from_value(serde_json::json!({ \"effect\": \"read\", \"tools\": 149 })).unwrap();\nassert_eq!((c.effect, c.tools), (Effect::Read, 149));\n```"
},
"McpResourceSummary": {
"type": "object",
"properties": {
"builtin": {
"type": "integer",
"format": "uint",
"minimum": 0,
"description": "Resources a builtin capability answers."
},
"objects": {
"type": "integer",
"format": "uint",
"minimum": 0,
"description": "Objects of this repository's layer, each one more resource."
},
"template": {
"type": "string",
"description": "The URI shape of an object's resource."
}
},
"required": [
"builtin",
"objects",
"template"
],
"description": "The resources a client can read.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpResourceSummary;\nlet r: McpResourceSummary = serde_json::from_value(serde_json::json!({\n \"builtin\": 54, \"objects\": 1742, \"template\": \"majordomus://<kind>/<identity>\",\n})).unwrap();\n// the two counts together are the length of `resources/list`\nassert_eq!(r.builtin + r.objects, 1796);\n```"
},
"McpClient": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The provider id."
},
"title": {
"type": "string",
"description": "The name a person knows it by."
},
"config": {
"type": "string",
"description": "The file it reads, repository-relative."
},
"standing": {
"$ref": "#/$defs/McpClientStanding",
"description": "Where that file stands here."
}
},
"required": [
"id",
"title",
"config",
"standing"
],
"description": "One client the distribution declares a project-scoped configuration for.\n\n```\nuse majordomus_cli::capability::builtin::mcp::{McpClient, McpClientStanding};\nlet mut clients: Vec<McpClient> = serde_json::from_value(serde_json::json!([\n { \"id\": \"gemini\", \"title\": \"Gemini CLI\", \"config\": \".gemini/settings.json\", \"standing\": \"absent\" },\n { \"id\": \"claude-code\", \"title\": \"Claude Code\", \"config\": \".mcp.json\", \"standing\": \"wired\" },\n])).unwrap();\n// ordered by the provider's id, whatever order the distribution declared them in\nmajordomus_cli::order::canonical(&mut clients);\nassert_eq!(clients[0].id, \"claude-code\");\nassert_eq!(clients[0].standing, McpClientStanding::Wired);\n```"
},
"McpClientStanding": {
"oneOf": [
{
"type": "string",
"const": "wired",
"description": "The file exists and names the launcher: this client starts this repository's server."
},
{
"type": "string",
"const": "foreign",
"description": "The file exists and does not name the launcher: the client reads it and starts\nsomething else, or nothing."
},
{
"type": "string",
"const": "absent",
"description": "The file does not exist: this client starts no server here until it is written."
}
],
"description": "Where a client's project-scoped configuration stands in this repository.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpClientStanding;\n// the words a caller reads, as they are written on every surface\nassert_eq!(serde_json::to_value(McpClientStanding::Wired).unwrap(), \"wired\");\nassert_eq!(serde_json::to_value(McpClientStanding::Foreign).unwrap(), \"foreign\");\nassert_eq!(serde_json::to_value(McpClientStanding::Absent).unwrap(), \"absent\");\n```"
},
"McpFinding": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "The stable code."
},
"message": {
"type": "string",
"description": "What is wrong."
},
"remedy": {
"type": "string",
"description": "What to do about it."
}
},
"required": [
"code",
"message",
"remedy"
],
"description": "Something about the projection a person should act on.\n\n```\nuse majordomus_cli::capability::builtin::mcp::McpFinding;\nlet f: McpFinding = serde_json::from_value(serde_json::json!({\n \"code\": \"mcp_client_config_foreign\",\n \"message\": \".mcp.json exists and does not name majordomus-mcp\",\n \"remedy\": \"add a `majordomus` server to .mcp.json whose command is majordomus-mcp\",\n})).unwrap();\n// a finding always says what to do, not only what is wrong\nassert!(!f.code.is_empty() && !f.remedy.is_empty());\n```"
}
}
}Examples
These are the capability's own benchmark cases: the same inputs the benchmark runs and the OpenAPI document shows.
every-tool
{}writers
{
"effect": "repository_mutation"
}