Sources
A source is where capabilities come from. Three types are supported, they normalize into one internal format, and each of them tells you what it would not import.
One shape, three inputs
An MCP tool, an OpenAPI operation and a repository route are three different things on the wire. Meetext normalizes all of them into a single capability specification: a name, a description, a JSON Schema for arguments, a risk level, and a binding that records how to execute it.
That normalization is why the destinations behave identically. A Slack installation does not know or care whether the capability behind a tool call came from an MCP server or a Flask route, and neither does the hosted MCP endpoint.
| Source type | Meetext reads | Execution goes to |
|---|---|---|
| mcp | tools/list on your Streamable HTTP MCP server | A tools/call back to that same server |
| openapi | An OpenAPI document, by URL or inline | An HTTP request built from the operation's method, path and parameters |
| repository | A checked out repository, scanned per framework | An HTTP request against the route the candidate came from |
MCP servers
The most direct source. Meetext connects as an MCP client, performs initialize, calls tools/list, and turns each tool into a capability. Tool names, descriptions and input schemas are taken as given.
{
"type": "mcp",
"mcp": {
"url": "https://acme.example.com/mcp",
"auth_header": "authorization",
"auth_scheme": "Bearer",
"auth_token": "..."
},
"discover_now": true
}Both a JSON response and a text/event-stream response to the POST are accepted, because Streamable HTTP servers may answer with either.
Refused: a tool with no name, and a tool whose input schema is not a usable JSON Schema object. Both are reported against the tool rather than dropped.
OpenAPI documents
Give Meetext a URL or paste the document inline. Every path and method pair becomes a capability candidate. Parameters from the path item and from the operation are merged, request body schemas are folded into the argument schema, and local $ref pointers are inlined.
{
"type": "openapi",
"openapi": {
"url": "https://api.acme.example.com/openapi.json",
"base_url": "https://api.acme.example.com",
"auth_header": "authorization",
"auth_scheme": "Bearer",
"auth_token": "..."
}
}Refused, with the reason recorded per operation:
- Remote references. A
$refpointing at another host is rejected rather than fetched. Resolving it would mean a document you control can make Meetext issue requests to a host you did not name. - Deprecated operations. An operation marked
deprecated: trueis skipped. Shipping a deprecated endpoint to enterprise customers is a support burden with a known expiry date. - Operations beyond the limit. Import stops at 800 operations, and every operation past that is listed as not imported. A document that large is usually the wrong unit to expose whole.
- Operations whose schema cannot be built. A malformed parameter or an unresolvable body schema fails that one operation and no others.
Repositories
For products that have neither an MCP server nor a published OpenAPI document. Meetext detects which frameworks the repository uses and scans for routes and service functions.
| Framework | What is scanned |
|---|---|
| FastAPI | Route decorators, path and query parameters, request models, plus service layer functions |
| Flask | Route decorators and their methods |
| Django | URL configuration and the views it resolves to |
| Next.js | Route handlers under the app and pages API directories |
| Express | Router and app method registrations |
A scan produces candidates, not capabilities. You choose which candidates to promote, and only then are they turned into capabilities with schemas. A repository is the least precise source, so it is the one where the human step is not optional.
Refused: a repository with no supported framework detected, which is reported as one issue naming the five frameworks that are supported, and any file that cannot be parsed, reported against its path.
What gets refused
Every source type produces the same pair: a list of specifications and a list of issues. An issue carries the identifier of the thing refused and the reason. Both appear in the discovery response and in the dashboard next to the source.
{
"discovery": {
"status": "completed",
"imported": 24,
"issues": [
{
"identifier": "DELETE /v1/accounts/{id}",
"reason": "operation is deprecated; skipped"
},
{
"identifier": "POST /v1/exports",
"reason": "remote $ref is not resolved"
}
]
}
}Name collisions are the one case that is resolved rather than refused. Two operations that normalize to the same capability name are disambiguated deterministically, so the same document always produces the same names.
Fingerprints and rediscovery
Every discovery result carries a fingerprint: a hash over every capability name and schema in the surface. It is stored on the source.
Rediscovery recomputes it. When the fingerprint moves, Meetext knows the source changed shape since you last approved it, and it can name which capabilities changed rather than telling you something somewhere is different. Approval for a changed capability is revoked. See Capabilities for what happens next.
curl -X POST \
"$MEETEXT_API/v1/products/$PRODUCT_ID/sources/$SOURCE_ID/rediscover" \
-H "authorization: Bearer $MEETEXT_API_KEY"Customers already installed are unaffected until you publish. They are serving a frozen package.
Source credentials
The token you give a source is the one Meetext uses to call your backend. It is written to Secret Manager on connect; the source row keeps the secret name and the header and scheme to send it with. It is never returned by the API and never written to a log or an audit entry.
This credential is entirely separate from the per customer credentials obtained when an enterprise authorizes the app in their own workspace. Those are scoped to one customer environment and are covered in the security model.