How MCP discovery works
An agent finds artifacts through a single index, not by scanning
storage. The index is a file called versions.json. The MCP
server reads it, and the list tools answer from it. This page
explains what is in the index, what each tool does with it, and
when a new release becomes visible.
The index
versions.json sits at the root of the artifact bucket,
s3://openkata-artifacts/versions.json. It has one map per artifact
type, keyed by artifact name:
{
"skills": {
"grill-me": {
"version": "1.0.1",
"description": "Challenge a plan, spec, ADR, or design ...",
"tags": "category:review, category:architecture"
}
},
"rules": { },
"profiles": { }
}
versionis the newest published version of the artifact.descriptionandtagscome from the frontmatter of the artifact's main file (SKILL.md,RULE.mdor<name>.md).tagsis one string of comma-separated tags, and is left out when the artifact has none.- The file can carry more than the MCP server uses. The website
also reads the tessl score of each skill from it, for example.
The server reads only
version,descriptionandtags.
The publish workflow rebuilds the file each time an artifact is
published: it copies the tagged version to the bucket, then runs
go run ./cmd/generate-versions/, which reads the bucket and
uploads a new versions.json.
The tools
The server is named openkata and speaks MCP over streamable HTTP.
It keeps no session state, so every call stands alone. It has nine
tools:
| Tool | Arguments | Answers from |
|---|---|---|
list_skills |
tag (optional) |
The index and the download counts |
list_rules |
tag (optional) |
The index and the download counts |
list_profiles |
tag (optional) |
The index and the download counts |
install_skill |
skill (required), version (optional) |
The index and the bucket |
install_rule |
rule (required), version (optional) |
The index and the bucket |
install_profile |
profile (required), version (optional) |
The index and the bucket |
skill_versions |
skill (required) |
The bucket |
rule_versions |
rule (required) |
The bucket |
profile_versions |
profile (required) |
The bucket |
Listing
A list call goes through the artifacts of one type in the index,
applies the tag filter, and returns them sorted by name. Each entry
has name, version, description, tags and downloads. It does
not read the bucket. The download counts come from the
openkata-downloads table, and if that cannot be read the counts
are zero and the list is still returned.
The tag filter
The tag argument takes one tag. The server splits each
artifact's tags string on commas, trims the spaces, and keeps the
artifact if one of the tags is equal to the filter.
- The comparison is exact and case-sensitive.
category:reviewmatches an artifact taggedcategory:review.reviewdoes not, and neither doesCategory:Review. - Only one tag works. A filter such as
a,bis compared as one string and matches nothing, even though the tool description mentions comma-separated tags: that describes the tags in the artifact's metadata, not the filter. - The search on the website behaves differently. It is case-insensitive, matches parts of names and descriptions, and treats a term with a colon as a tag.
Resolving a name and a version
install_* first looks the name up in the index. A name that is
not there returns an error such as skill "x" not found, even if
the bucket holds files for it.
Without a version, the install uses the version from the
index. With one, it reads <type>/<name>/<version>/ from the
bucket, and if that holds no files the call returns
version <version> not found for <name>.
*_versions does not use the index. It lists the version folders
under <type>/<name>/ in the bucket, sorted from oldest to
newest, and returns an error if there are none.
When a new release shows up
The server reads versions.json once, when it starts, and keeps it
in memory. If it cannot read the file at startup, it still starts,
with empty lists. On Lambda, a start happens when a function
instance is created, so an instance can keep serving the index it
loaded for as long as it lives.
This means a release becomes visible in steps:
- Right away:
*_versions, andinstall_*with an explicitversion, read the bucket on every call. A version just published to an artifact that is already in the index can be installed by asking for it. - After the server restarts:
list_*and the default (latest) version ofinstall_*follow the index, so they change only when an instance loads the new file. A brand-new artifact cannot be installed until then, because its name is not in the index the instance holds.
The website does not have this delay. It reads versions.json from
the bucket on every request.