A directory of the platforms a program might want to talk to, and what it takes to talk to them: where the API lives, what signs a request, what you can ask it to do, what it looks like, and whether it runs an MCP server of its own.
Every API is also one Lua file. GitHub's entire REST API is 1,201 operations in 249 KB that
stock Lua loads in under two milliseconds. With the port in lua/, that file is a complete
client for an agent: no SDK, no service in the middle, no C modules. The port is built for
Tablua's harness, and any Lua host can give it
the three things it needs.
local connect = require "connectory.lua.connect"
local c = connect.new({ fetch = your_fetch }, {
read = function (path) return read_file("connectory/" .. path) end,
secret = function (name) return keychain(name) end, -- the person's own credential, by name
})
c:find("issue tracker") -- { { service = "github", ... }, ... }
c:operations("github", "create an issue") -- { { op = "github.issues_create", args = { "owner*", ... } } }
local issue, err = c:call("github.issues_create", { owner = "you", repo = "yours", title = "it works" })
-- with no credential: err.code == "denied", and err.needs says what to ask the person for853 platforms
842 with a mark (99%)
79 with an MCP server the vendor publishes
758 callable with a token from the environment
A credential is a name and nothing else. There is no OAuth dance here and no service
holding your tokens. auth.env names the credential (STRIPE_SECRET_KEY); the host's
secret(name) reads it from wherever the person keeps it (a keychain, or the environment) at
the moment a call is signed. That is why this repository can be public: there is nothing in
it to leak.
Only the vendor's own description is used. Fuzzy matching finds klarna.com:openai for
OpenAI and Sunshine Conversations for Zendesk β right organisation, wrong product. A wrong
parameter list is worse than an empty one: an empty catalog says "I cannot do that", while a
wrong one builds a call that fails at the vendor or, worse, succeeds and does something else.
So a description is accepted from the vendor's own repository, from their own documentation
site, from an exact domain match, or from a person who checked it. Nowhere else.
A platform nobody describes is still listed, with a name, a logo, a base URL and a
credential β everything except operations. Where the vendor publishes documentation a program
can read, that is kept beside it as docs.md: context for a reader, never a source of
endpoints. An endpoint inferred from prose is a guess wearing the costume of a fact.
That is why the number of platforms and the number with operations are different numbers, and both are printed.
Nothing here is invented. Every operation comes from a machine-readable description the vendor publishes. No hand-written endpoints, because a hand-written endpoint is a guess that looks like a fact.
The one thing written rather than sourced is a sentence for an operation whose description the
vendor left empty β thousands arrive with a method, a path and nothing else, which leaves them
unfindable, because a ranker matching words has no words to match. GET /V1.0/ActionTypes
becomes "Lists action types." It describes the shape of the call and never its meaning, it is
derived from the path the vendor published, and it is marked described: derived so it is
never mistaken for their words.
providers/github/
github.lua every call it accepts: method, URL, where each argument goes
index.json names, descriptions and argument types β what you need to FIND a call
logo.svg
mcp.json the MCP server the vendor publishes, if there is one
docs.md their documentation, when there is no description to be had
alias.json "the same API as this other one, with a different credential"
The split between github.lua and index.json is deliberate. For GitHub the calling table is
317 KB and the prose and schemas around it are 1.3 MB. A program that already knows which
operation it wants should not carry the half that only helps a person choose.
index.json at the root is the whole directory as one searchable file, small enough to hold
in memory. Operations are not in it, on purpose.
APIs move. tools/packs.py asks each vendor whether their description changed using an HTTP
conditional request, so the usual answer is 304 and costs nothing. When the bytes do differ the
pack is rebuilt and the change is reported in operations, not lines β a vendor reformatting
their JSON is not an API change, and an operation disappearing very much is. A scheduled job
runs it and opens a pull request when something moved.
python3 tools/directory.py who exists, and how a request to them is signed
python3 tools/specs.py whose description belongs to whom, and who published it
python3 tools/discover.py the same, looked for on each vendor's documentation site
python3 tools/context.py documentation for the platforms nobody describes
python3 tools/build.py the packs, from those descriptions
python3 tools/logos.py a mark for every platform
python3 tools/mcp.py which of them run an MCP server
python3 tools/index.py the searchable index
lua/connect.lua is the port an agent uses: find a service, list its calls, make one, and say what to ask the
person for when a credential is missing or refused. lua/http.lua builds and signs each call the way the
provider's pack says (auth: a header in the vendor's own format, a bearer token, a query parameter, or a user and
password) with values it asks the host for by name, and refuses a call whose credential is missing before anything
is sent. lua/library.md is the card an agent reads.
The tests run from the folder above this one, with Tablua's core/ on the path for its JSON:
luajit -e 'package.path = "./?.lua;TABLUA/core/?.lua;" .. package.path' connectory/lua/http_test.lua
luajit -e 'package.path = "./?.lua;TABLUA/core/?.lua;" .. package.path' connectory/lua/connect_test.lua
Moonsplice attaches this repository as a submodule, and its agent, Tablua, reaches services through it: it finds a service, reads its calls, asks before any call that changes something, and makes calls with the person's own account. When a credential is missing, the person is asked in a masked field or a prompt that does not echo, and the value goes to the keychain, never to the agent.
The directory of who-exists-and-how-they-authenticate is generated from NangoHQ/nango's provider registry, which maintains the half of an integration that no API description contains β base URL, credential, which header carries it and in what format, and the per-tenant values a URL needs. It contains no operations at all; those come from each vendor's own OpenAPI or Google discovery document.
Marks come from Simple Icons (CC0) where they have one, and from Nango's provider logos otherwise. They are trademarks of the companies they belong to, used to identify the vendor whose API it is, unaltered.
MCP servers come from the official registry and are matched on the reverse-DNS namespace a vendor publishes under, which is the distinction that namespace exists to make.
Apache 2.0. The API descriptions and marks belong to the companies that publish them; see LICENSE for the details.