Add a custom MCP connector, then read its open-source rule order
Adding a custom MCP connector takes a name, a URL and a sign-in. What decides whether each call runs is a four-level rule order. Kortix is the open-source CompanyOS, and it keeps that order in kortix.yaml, where a reviewer can read it before an agent acts.
A custom connector is an entry in the project file
Claude's help page for a custom connector asks for a name, a remote URL and a sign-in (Claude Help Center). That is the setup, and it takes a minute. What decides whether each call runs comes after, and in Kortix it is a file.
A custom MCP connector in Kortix is an entry under connectors: in kortix.yaml. It names a server somebody else runs, the actions Kortix may call on it, and the rules that decide each call. You do not build the server and you do not host it. The entry is your team's side of the connection, which is the difference between a connector and a server.
Two ways in. An app in the catalog is installed from Customize → Connectors with Install, and Kortix connects 3,000+ apps in a click, plus MCP, OpenAPI, Postman, GraphQL and raw HTTP. An app that is not in the catalog can still connect. Choose New → MCP server and paste its address, or New → Add a custom connector for another kind of API. New → Create in chat lets an agent set it up and open a change for you to review.
The entry holds the address, the transport, the rules and the account strategy. It never holds a credential. Connector credentials are brokered server-side and never enter the machine.
The fields that decide the call
A provider: mcp entry needs a url, and transport is http (the default) or sse. The other providers each need their own address: base_url for http, endpoint for graphql, spec for openapi and postman, app for composio and pipedream (connectors keys).
slug: The name every rule and grant usesprovider: How Kortix reaches the appurl: The MCP server's addresstransport:httporsseauthorization_strategy: Whether accounts belong to the project or to each personpolicies: The connector's base rulespolicy_sets: Named rule sets an agent can be givensensitive: Whether unmatched calls wait for a person
enabled: false turns the connector off. auth says how Kortix sends the credential, and the credential lives in Kortix, never in the file. headers are static request headers, at most 32, and their values are plain text in Git, so a token pasted there is a token in the repository.
A policy set is a named group of rules on one connector, and an agent binds it by name. The docs' example project gives a helpdesk connector a read-only set that runs get_* and blocks *, then binds that set to one agent (example project). A set name is lowercase letters, digits, - and _, at most 64 characters, and not base. A connector can carry at most 32 sets of 200 rules each, and channel connectors carry none. Five slugs are reserved for the built-in connectors: kortix_slack, kortix_teams, kortix_email, kortix_telegram and computer.
The four-level order, read on one call
Kortix decides each connector call in four places, and in each list the first rule that matches decides. The project policies come first. Then the calling agent's policy set on that connector, agents.<name>.policy_sets. Then the connector's own policies, its base rules. Then no rule matched, where a sensitive connector holds the call and otherwise policy.default_mode answers (connector rules and approvals). A set or a base rule cannot loosen a project rule. A set can loosen a base rule for its agent, and a set the connector does not define blocks every call of that agent on it.
A project rule is the one place a team can say that nothing deletes anything. It holds on every server, including the one somebody adds next month. A connector's base rules are the local habit: reads run, the rest asks. The agent's set is the exception, granted by name to one agent. Read in that order, a call has one answer, and the answer is the same on Tuesday as it was on the day of the review.
A delete on Linear matches *.delete* at the project level, so it is blocked. The Linear entry's own * rule says require_approval, and that rule is never read, because level one already matched. This is the part a client that shows you a URL and a sign-in cannot give you. The connector's rule looks permissive, the project's rule is what runs, and only the file shows both.
linear.create_issue is the second case. The project rule names it and asks for approval. The connector's * rule would also ask. Same outcome, different level, and the difference matters the day someone loosens the connector's base rules, because the project rule still holds.
The Notion entry puts a condition to work. create_page runs when parent.database_id matches 3f2a1c*, and every condition must hold. A page created anywhere else falls through to the entry's * rule and waits for a person. match is a glob over <connector-slug>.<tool>, case-insensitive, and a condition is { arg, match, negate }, with arg a dot path into the call's arguments.
Sentry shows the fourth level on its own. The entry sets sensitive: true, so a call that no rule matches needs approval, reads included. A read on that connector waits, which is the right default for a server whose tools nobody has read yet.
The file that decides each call
Three MCP connectors, the project rules that outrank them and the one agent grant, quoted from the example file.
project rules
policy:
default_mode: allow_all
policies:
- match: "*.delete*"
action: block
- match: "linear.create_issue"
action: require_approvallinear
connectors:
- slug: linear
name: Linear
provider: mcp
url: https://mcp.linear.app/mcp
transport: http
policies:
- match: "list_*"
action: always_run
- match: "get_*"
action: always_run
- match: "*"
action: require_approvalnotion
connectors:
- slug: notion
name: Notion
provider: mcp
url: https://mcp.notion.com/mcp
transport: sse
policies:
- match: "read_*"
action: always_run
- match: "create_page"
action: always_run
conditions:
- arg: parent.database_id
match: "3f2a1c*"
- match: "*"
action: require_approvalsentry and the grant
connectors:
- slug: sentry
name: Sentry
provider: mcp
url: https://mcp.sentry.dev/mcp
sensitive: true
agents:
triage:
connectors: [linear, notion, sentry]Where a rule belongs when a project runs ten servers is a question of its own. The order is in the file, so a reviewer can check it before an agent acts.
The account the call runs as
authorization_strategy is project (the connector's accounts belong to the project) or user (each person connects their own account); the default is project. An account is private to one person or shared with the project, and a private account works only in its owner's private sessions. Automations never use a private account; they need an account shared with the whole project. A connector a schedule will call therefore needs a shared account before the schedule exists, and whose permissions a call carries takes the choice apart.
Validate, then test one write
Approval gates you set. Off until you set them, so a file with no rules is a file that runs everything policy.default_mode allows. kortix validate prints each issue with its file, line and column (when kortix.yaml has a problem), and it does not check policy or policies (connector rules and approvals). When Kortix syncs connectors, it skips a malformed rule and reports it as a connector sync error. A rule with a typo passes the check a reader trusts most, then disappears at sync.
So a clean run is the start. Five checks after it:
- Name the level. For the action you care about, say which of the four decides it. If two could match, the higher one wins and the lower one is decoration.
- Send one read. It should run, and it should appear in the session's record.
- Send one write that a rule holds. The agent's turn should pause, and the approval should name the action and the account.
- Change one argument, then approve. The changed call needs a new approval, because an approval covers one exact action.
- Check the account. A call from a schedule or a trigger must run as a shared account, or it will not run at all.
The file is the part you can review
The entry does not hold the tool list, which the server publishes, or the account, which is a live connection. It holds the decision about both: which actions may run, and which account strategy applies. What an agent can reach covers the three layers together.
The example manifest is a working file: three MCP connectors, the project rules that outrank them and one agent grant, in a public example repository. It ships a small checker that fails when a rule is malformed, which covers the gap kortix validate leaves. Clone it, replace the servers with yours, and the first review is a diff.
Questions about adding a custom connector
The two steps that matter, and what the file does not check.
How to connect custom MCP?
Add the server's address as a connector. In Kortix, open Customize → Connectors, then New → MCP server, paste the URL and sign in; the connector's page then lists its tools and the rule on each. A server that is not in the catalog connects the same way.
How do I add a custom connector?
Open Customize → Connectors and select New → Add a custom connector for an API that is not an MCP server, or New → MCP server for one that is. New → Create in chat lets an agent write the entry and open a change for you to review.
Write the entry, read the order, test one write.
Connectors, rules and grants in one open-source repo your team reviews.