Skip to content

The MCP Connector JSON Config Schema: What Saves and What Fails at Execution

How the mcp connector json config schema is validated, how secrets become secret_paths, and three mistakes that pass the save and fail at execution.

Pranasri Marripudi 7 min read
A JSON connector config beside a read-back response showing public values and a list of secret paths with no values

The situation

GitHub is not in Elaichi's connector catalog. Neither is every internal service a company runs. When the catalog does not have the app, an organization authors the connector itself. The mcp connector json config schema is what that authored connector is checked against at save.

The first decision is which of two shapes to use. One is a JSON config, checked against the catalog's integration config schema. The other is a remote_mcp definition, pointing at a server that already speaks MCP. A create request sends exactly one of the two. GitHub itself is a case for the second shape, because it runs its own MCP server that a team can bring in as a remote MCP connector. GitHub documents a separate policy for MCP servers in Copilot, which it says is disabled by default (checked October 2026, GitHub's own docs).

This post is about the first shape. The mcp connector json config schema is what a POST /connector body with a config object is checked against. Knowing where that check stops is most of the skill. MCP means Model Context Protocol, the way AI clients find and run tools; the protocol's own documentation covers both local and remote servers (checked October 2026, modelcontextprotocol.io). A connector turns one app's HTTP API into those tools.

Two things gate authoring at all. The first is the connector:create permission, which among the built-in roles only Org Owner and Org Admin hold. The second is the custom_connectors plan feature, which Gold includes. connector:create is the one permission Elaichi flags as high trust, and the role editor shows a "High trust" tag on it. The reason sits in the next section.

What the mcp connector json config schema actually validates

It validates shape, not destination. A config is checked against the catalog's integration config schema. Problems come back as HTTP 422, with the issues listed one per problem rather than as one generic failure.

The config carries top-level keys the connector's methods share. Pull review treats nine of them as a unit: base_url, headers, query, query_array_format, authorization, credentials, pagination, rate_limit and error_expression. Each is taken whole from upstream or kept as is when a fork pulls. That is a useful hint about how Elaichi thinks of them. They are the connector's envelope, and the methods are what fills it.

Beyond the config itself, a few identifiers are checked at save. The slug is 2 to 64 characters of lowercase letters, digits and hyphens. It must start and end with a letter or digit, and new is reserved. The label is 1 to 120 characters, the category up to 80. One rule bites more often than it should: two methods of one connector cannot answer to the same tool name. A config edit, a documentation update or an upstream pull that would cause a clash is refused.

What the check does not do is look at where the connector points. Elaichi checks a custom connector's config for shape, not for destination. The connector:create permission text warns that a connector "can be pointed at any destination". That matters for governance. Restrictions bind a connector's identity, its slug and the forks that declare descent from it, never the host it calls. A block on one connector does not stop a new connector aimed at the same API.

The audit row records the hosts, though. Creating, forking and editing a custom connector writes connector.created, connector.forked or connector.updated. The row carries up to 8 hosts the config calls, plus the total count. An edit that changes them also records the previous hosts. If you want to know whether somebody pointed a connector somewhere new, that is where to look.

How secrets become secret_paths

Secrets do not live in Elaichi's main data, and they do not read back. A separate credential service holds every connected account's secrets in encrypted form. Elaichi's own row carries only ownership and lifecycle. Stored connector secrets are encrypted at rest with AES-256-GCM. Each ciphertext records the id of the key that wrote it, so the key can be rotated without re-encrypting old rows.

Read-back is where this shows. Reading a connected account's configuration returns its public values plus secret_paths. That is the list of dot-paths the credential service encrypted away. secret_paths carries none of the actual values. It is also the only thing that decides whether a configuration variable counts as a secret on Elaichi's side. So a review of a connection's config tells you which fields are secret, and nothing about what they contain.

Writing to one is refused. A write to a configuration variable named in secret_paths answers 409 secret_variable. The router has its own pre-check, and the credential service refuses independently. Both are translated into the identical 409 response, so a caller cannot tell which side caught it.

For an OAuth connector, the organization's own OAuth app is a separate sub-resource under /connector/:slug/oauth-app. It is gated on connector:manage rather than connection:manage. Its write body accepts only client_id, an optional client_secret, scope, optional_scope and user_scope. Everything endpoint-shaped is deliberately unrepresentable in it: an authorization URL, a token URL, any other OAuth endpoint field. Changes are audited as connector.oauth_app.updated on save and connector.oauth_app.cleared on removal.

Three mistakes that pass the save and fail at execution

All three save cleanly. Each surfaces the first time a person or an AI client runs something.

The connector saves but nobody can see it. A new custom connector is private until shared. Only its owner and the members a grant reaches can list or open it. Everyone else gets the same 404 as for a connector that does not exist, including Org Owners and holders of connector:view or connector:manage. There are three grant levels. view lets a member see and fork it. use lets them create connections from it. edit lets them change its config and documentation, pull from upstream and manage its shares. The author sees it working, the team sees nothing.

The connector is shared but a connection is blocked. A custom connector can stop being shared with the owner of a connection built on it. That connection is then blocked with connector_not_shared. Every tool call through it is refused, for that connection's grantees too, until the connector is shared with its owner again. A toolbox entry pinned to that connection goes unmet for everyone who opens the toolbox, its owner included. This failure looks like a broken connector and is really a share.

The tool tier is not what the name suggests. Elaichi tiers a connected tool from the catalog by its HTTP verb and unified method name. An HTTP DELETE is destructive whatever the method is called. A list or get method is read only when its verb is GET or HEAD, and otherwise write. A delete method is destructive, and anything else, a custom method included, is write. So a method you named get_report that sends a POST is tiered as a write. The gate re-derives the tier at call time. A custom tool edited from GET to DELETE is dispatched as a delete on the next call, not on the definition the reviewer approved.

One more case to know. A connection that has not finished sign-in cannot be pinned into a toolbox entry. That write is refused with 409 connection_pending.

The trade-off: expressiveness for reviewability

A config-authored connector is less expressive than a server you write yourself. You describe HTTP calls, headers, pagination and error handling in a schema somebody else designed. The shapes it does not cover are shapes you do not get. That is the cost. It is real for anything with unusual auth, a non-HTTP transport, or behavior that does not decompose into methods.

What you buy is a reviewable artifact. The config is JSON, checked against a schema at save, with problems reported as a list. Its hosts are on the audit row. Its forks declare descent, so a block on the original also blocks every fork. Lineage is walked toward the root for up to 8 hops. A chain that is longer, loops or is broken is refused outright rather than checked partially. A fork with a recorded upstream can pull changes through a review step. That step sorts each difference into new, updated, conflict, removed or config, and starts conflicts and upstream removals unselected.

The other shape is the right answer often enough to say so plainly. If the thing you want already speaks MCP over Streamable HTTP at a public HTTPS address, add it as a remote MCP connector. OpenAI documents the same transport expectation: its Responses API mcp tool works with remote MCP servers that support Streamable HTTP or HTTP/SSE (checked October 2026, OpenAI's own guide). Public reachability is not an Elaichi quirk either. Anthropic says Claude reaches a custom connector's remote MCP server from Anthropic's cloud, so the server has to be reachable from Anthropic's IP ranges over the public internet (checked October 2026, Anthropic's own article).

Elaichi then sits in front of that server. Restrictions and OAuth scopes are checked before a call leaves. Every call that runs is written to the audit log. Only a tool on that connection's own active list can run. A remote MCP connector cannot be forked. On any other connector the config.remote_mcp key is reserved, so a plain connector cannot pose as a remote one.

Deleting, transferring and the ownerless case

Only a custom connector's owner can delete it. Deletion is refused with 409 while any connection in the organization still uses it, naming how many remain. Deleting removes every share on it and its owner record. Forks keep their place in the lineage, so a restriction naming the deleted connector still binds its forks.

Transfer is the owner's too, outside offboarding. POST /connector/:slug/transfer hands it to another active member and leaves existing grants untouched. The previous owner is left with use, unless they already reach it at use or edit, so their own connections keep working. At offboarding the previous owner keeps nothing. Transfer is also the only offboarding action for a connector. A removal is refused with 409 member_owns_connectors while the departing member still owns any custom connector.

A connector can end up with no owner, created before ownership existed, or left behind when its owner was removed without offboarding. edit grantees can still edit it. Nobody can delete or transfer it until someone with connector:manage who can see it assigns an owner. One correction to the permission's own description, which says connector:manage can edit or delete other members' custom connectors: the code does not let it. Editing needs an edit grant or ownership, and deleting needs ownership.

Where to go next

Choosing between authoring a config and bringing in a server that already exists is its own decision. Running your own MCP servers puts real numbers on the second option. For the governance layer on top of whatever you author, MCP governance covers roles and restrictions. The post on why blocks match tool names but allows don't explains why renaming a method cannot slip it past a block. To see which apps are already in the catalog, start at /connectors/.

FAQ

Frequently asked questions

What happens if a custom MCP connector's JSON config is invalid?

Elaichi validates a custom connector's JSON config against the catalog's integration config schema when it is created. An invalid shape returns HTTP 422 with the specific issues listed, not a single generic error. Separate rules apply at save. The slug must be 2 to 64 characters of lowercase letters, digits and hyphens, starting and ending with a letter or digit. Two methods of one connector cannot answer to the same tool name.

Does reading a connection's configuration show its API key or client secret?

No. Reading a connected account's configuration in Elaichi returns its public values plus secret_paths. That is the list of dot-paths the separate credential service encrypted away. secret_paths carries none of the actual values. Writing to a configuration variable named in secret_paths is refused with a 409 secret_variable response.

Who can create a custom connector in Elaichi?

Creating a custom connector needs the connector:create permission, which among Elaichi's built-in roles only Org Owner and Org Admin hold. It also needs the custom_connectors plan feature, which the Gold plan includes. connector:create is the only permission Elaichi flags as high trust. A connector can be pointed at any destination, and restrictions bind a connector's identity rather than the host it calls.

Why does a custom connector work for its author but not for the team?

A new custom connector in Elaichi is private until it is shared. Only its owner and the members a grant reaches can list or open it. Everyone else gets the same 404 as for a connector that does not exist, Org Owners and holders of connector:view or connector:manage included. Grants come at three levels. view lets a member see and fork it. use lets them create connections from it. edit lets them change its config and documentation.

Should I write a JSON config or add a remote MCP server?

If the thing already speaks MCP over Streamable HTTP at a public HTTPS address, add it to Elaichi as a remote MCP connector. Reimplementing it as a JSON config is the longer road. A create request sends exactly one of config or remote_mcp. A JSON config trades expressiveness for reviewability. It is checked against a schema, its hosts are recorded on the audit row, and its forks declare descent, so a block on the original also blocks them.

Put agents to work on your own systems

14 days on Gold, no credit card. Start with one app and one team.

Works with
Claude ChatGPT Cursor and any other MCP client, or the Elaichi Agent.
When the trial ends
Nothing is deleted. Connections, roles and the audit log stay where they are, so subscribing picks up exactly where you left off.