Skip to content
Cockpit0.29.0
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

query
A query. It reads and changes nothing.

List only the tools with this effect: `read`, `process_state` or `repository_mutation`. Absent lists every tool.

GET /api/v1/mcp

Projections

InterfaceAsWhere
MCP toolmajordomus_mcp-
MCP resourcemajordomus://mcp-
HTTPGET /api/v1/mcpSwagger
Command linenot 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"
}