# Shared MCP servers

An MCP server (Model Context Protocol) gives an Iris extra tools: git, a database, a mailbox, a search index,
your own service. This registry is the directory of the ones we host or trust, with what each one needs
written out before anybody installs it.

If you are a language model: this page is also at `/docs.md` as plain Markdown, `/llms.txt` lists everything,
and every server has a Markdown twin at `/m/<name>.md` with its address or command, its credential
references, its tool list and where it came from.

## Two shapes

| Shape | What it is | Where it runs |
|---|---|---|
| `stdio` | The package carries the server itself (a `.py` or `.js` in the same flat folder). | Inside one house's own sandbox, as that house, with nothing of ours and no vault. |
| `streamable-http` | The server runs elsewhere and is reached over `https`. | At whoever owns that address. Nothing of it is executed here. |

The `command` of a `stdio` server must be a file in its own package. A listing can never start a program it
does not carry, and installing one never downloads anything.

## mcp.json

```json
{
  "name": "github",
  "version": "1.0.0",
  "author": "Woodst",
  "description": "Issues, pull requests and code search from GitHub.",
  "tags": ["git", "developer"],
  "category": "developer",
  "license": "MIT",
  "sources": ["https://github.com/modelcontextprotocol/servers/tree/main/src/github"],
  "permissions": ["internet", "secrets"],
  "transport": "streamable-http",
  "url": "https://mcp.example.com/github",
  "tools": [{ "name": "create_issue", "description": "Open an issue in a repository." }]
}
```

| Field | Required | Meaning |
|---|---|---|
| `name` | yes | Lowercase letters, digits and `-`, at most 40. Equal to the folder name. |
| `version` | yes | Three numbers. Every published version is final. |
| `author` | yes | What you wrote it as. The registry stamps the entry with the house that submitted it: that house's public name if it has one, otherwise its house id, and `Iris` for what comes in without a house. Nothing a package claims can attribute work to somebody else. |
| `description` | yes | One sentence: what it makes possible. |
| `transport` | yes | `stdio` or `streamable-http`. |
| `command` | stdio | A file in this package: the server. Run with python3 or node by its ending, or by its own `#!`. |
| `args` | no | Up to 20 texts passed to it. Never through a shell. |
| `url` | streamable-http | `https` only, a name and not an IP, and not a private address. |
| `env` | no | `{ "GITHUB_TOKEN": "$GITHUB_TOKEN" }` or `{ "API_KEY": "vault:github" }`: references, never values. |
| `permissions` | yes | From `internet`, `files`, `secrets`, `phone`, `voice`, `messages`. Be honest: people read this before installing. |
| `tools` | no | What it offers, `[{ "name": ..., "description": ... }]`, shown in the directory. The server's own list is what counts once it runs. |
| `category` | no | `developer`, `knowledge`, `productivity`, `communication`, `finance`, `home`, `media`, `other`. |
| `tags` | no | Up to 12, lowercase with `-`. |
| `license` | yes | `MIT`, `Apache-2.0`, `BSD-3-Clause`, `AGPL-3.0`, `MPL-2.0` or `proprietary`. |
| `sources` | yes | 1 to 12 `https` links to the server's own documentation. |

## How much one house may submit

A review queue is read by a person, so one house cannot fill it:

- at most **5 versions** of one house may be waiting for review in one registry at the same time. A sixth is
  refused with a clear answer until one of them has been read;
- a house may hold at most **50 names** in one registry's store. A new name past that is refused; a new
  *version* of a name it already has is always welcome.

Both are counted per house and per registry, and both can be raised for a house that has a reason
(`REGISTRY_MAX_WAITING`, `REGISTRY_MAX_ENTRIES` on the service). A contribution is never silently dropped: a
refusal names the limit and what to do about it.

## What the store refuses

- **A secret in the package.** Every `env` value must be a reference. A written-out token is refused, with the
  file and the key named, before a person reads the submission.
- **`http://`, a bare IP, `localhost`, a `.internal` name or a private address range.** A house must not be
  talked into fetching something inside our own network, and a token must not cross the wire in the clear.
- **Credentials in the URL.** `https://user:pass@host` is refused; use a credential reference.
- **A command that is not in the package**, or a file ending a server has no business carrying (`.sh`,
  binaries). Code in `python`, `node` or `ts`-as-source is what a sandbox can run.
- **A name that belongs to another house**, a version that already exists, or anything over 6 MB.

## Making one

```
~/mcp/things/
  mcp.json          what it is, how it is reached, what it needs
  server.py         the server (stdio), executable or run by its ending
  README.md         optional: shown in the directory
```

```sh
cd ~/mcp/things
mcp check            # is this folder a right server? (changes nothing)
mcp try              # start it here by hand and list its tools (stdio only, in this house)
mcp publish          # submit this version for review
```

`mcp try` starts the server in the house the way a client would, asks it for its tools and stops it again.
Run it before publishing: a server that does not come up is not a server.

A remote server (already hosted somewhere) needs no package of code at all, only the `mcp.json` with its
`url`: useful to share a well-known one, or a service of your own, with the same review and the same honest
permission list.

## Review, then public

A submission waits for a person to read it, exactly like a skill:

- does the manifest describe what the server really does, and do the sources say so;
- does `permissions` match the code: does it really only talk to the internet;
- does it keep the owner's data in the house, or does it send it somewhere;
- is a `stdio` package self-contained (no downloading at start), and does it start at all.

On a yes it is listed, committed to the public repository (`github.com/okayiris/registry`, under `mcp/`), and
every house can install it. Nothing is published without this step, and a published version never changes: a
fix is a new version.

## Install in a house

Say "install the MCP server github", or:

```sh
mcp browse                    # what is in the directory
mcp search "git"              # find by word
mcp install github            # the newest version
mcp install github@1.0.0
mcp list                      # what this house has, and whether it is on
mcp remove github
```

Installing writes the server into the house's own `~/mcp/<name>/` and registers it with the assistant
(`dsh-mcp-client`, one entry per server, in the house's own profile patch), so its tools become real tools.
A version that asks for more permissions than the one installed is never swapped in silently: the house asks
its owner first.

## Own servers, from us

The registry is also where we keep the servers we host ourselves for every house: a shared address, one
credential reference, no package to carry. They live in the public repository with the rest, so their
manifests can be audited and improved by anyone, and a house installs them by name like any other.

## Taking a submission back

Things change between submitting and being read: a source turns out wrong, a detail should not be public, the
whole idea was worse than it looked. While a version is still waiting for review, the house that submitted it
can withdraw it:

```sh
mcp withdraw github@1.0.0
```

Only that house's own waiting versions can go, and only while they are waiting. The house remembers what it
submitted (`mcp list` shows it), so the name is enough; without that record the version has to be named. Once a version is published it is not withdrawable: other
houses may already have installed it, so the way out there is a takedown by whoever runs the registry, and the
public repository keeps the history either way.

## Is it still what was published?

A published version never changes, so a difference between what you have and what the registry holds is local:
an edit, or something else writing in the folder. That is checkable:

```sh
mcp verify github     # one entry against the registry's published hash
mcp verify                     # everything that came from the registry
```

It says which file differs (changed, missing, extra) and how to put the published version back:
`mcp install <name>@<version>`. Installing a version you already have puts the published files back when the
copy drifted, and says so, instead of answering "already here" over somebody's edit.

## API

Read-only and public, and the same shape as the skills registry:

| Request | Returns |
|---|---|
| `GET /api/mcp` | Every server, newest version first: the manifest plus `versions`, `hash`, `published`. |
| `GET /api/mcp/index` | The same list as one object. |
| `GET /api/mcp/<name>` | The newest package: `meta`, `files` (name to base64), `hash`, `published`. |
| `GET /api/mcp/<name>/<version>` | That exact version. |
| `POST /api/mcp/publish` | Submit a version: `{ "files": { "mcp.json": base64, ... } }`. |

Publishing is signed by a house's own bridge and lands in the review queue.
