The Integration Boundary Is the Product

Corsair is an open-source integration layer for applications and AI agents. The question is not simply how it calls GitHub, Slack, Gmail, or another service; it is how Corsair organizes the boundary between those services and the application that must make them useful to people. That boundary includes credentials, tenant identity, synchronized data, incoming events, and the choices an agent is allowed to make. Understanding that model lets a builder decide which Corsair surface to use instead of treating every problem as another API request.

The distinction matters because third-party integration starts as plumbing and quickly becomes product architecture. A single request may need a provider-specific token, a provider-specific request shape, a retry rule, a local representation for the UI, and a way to associate a later webhook with the user who authorized the connection. If those concerns are scattered across feature code, every new provider multiplies the places where state and authority can go wrong. Corsair’s proposition is to centralize the shared boundary while leaving the provider’s own semantics visible.

What Corsair owns—and what it does not

A useful first model has five participants. The application owns the Corsair instance, chooses its plugins, supplies the database and encryption key, and decides how users and business records are represented. A provider plugin describes how one external service works. The external service remains the authority for its live resources and authentication rules. The application database holds Corsair’s encrypted connection state and local projection of provider entities. Optional Corsair Hub supplies public-URL surfaces such as hosted connection and approval pages; it does not need to sit in the path of every provider API call.

flowchart LR App[Your application] --> Runtime[Corsair runtime] Agent[AI agents] --> Runtime Dashboard[Dashboards and local reads] --> DB[(Application database)] Runtime --> DB Runtime --> Plugins[Provider plugins] Plugins <--> Providers[GitHub, Slack, Gmail, and other services] Providers --> Events[Provider events] Events --> Runtime Hub[Optional Hub: public-URL relay] <--> Runtime Workflows[Workflow reactions] --> Runtime

The arrows do not mean that Corsair takes ownership of everything on the other side. A successful live call still acts on the provider. Corsair’s runtime adds a consistent place to resolve the right key, apply hooks and error behavior, record the result, and expose it to the rest of the application. A provider event still originates outside the application, but it can enter through a common processing path before it updates local state or triggers application logic. Hub can carry a connection or approval result to the application, but the application remains responsible for accepting the signed delivery and storing the resulting tenant credentials.

This is also why the repository describes Corsair as more than an MCP tool. MCP is one way to expose operations to an agent. The same runtime can be called directly by backend code, used behind a customer dashboard, or placed behind a management API. A dashboard does not need to know how a Slack token is refreshed, and an agent adapter does not need to invent a second version of the Slack client. Both use the same instance and therefore meet the same application-owned boundary.

The model has a practical implication: adding a plugin is not merely adding a menu item. It adds a new provider contract to the application’s runtime. The application must still decide which users can connect it, what local data is worth retaining, what events matter, and whether an agent should be allowed to mutate it. Corsair supplies the common machinery for those decisions; it does not make them disappear.

That division of labor is easy to miss when a library presents a clean method call. The clean call is the front of a longer relationship. A Slack message and a GitHub issue can both appear as familiar nested operations, but they have different identifiers, scopes, event payloads, and meanings. The shared surface reduces the amount of plumbing a feature author must learn; it does not make a message interchangeable with an issue. The application still has to preserve the distinctions that matter to its users.

It also matters who is allowed to make a choice. A dashboard usually knows its operation before the request arrives. An agent chooses from a sentence, so it needs discovery and a policy boundary. A webhook begins with a provider event, so the system must identify and verify the source before application code reacts. These are different control flows over the same runtime. Naming that difference now prevents a later mistake: using an agent interface for a known, sensitive mutation simply because it looks flexible, or using a local read where the product actually needs the provider’s current answer.

One runtime for four application jobs

The source material describes four common shapes for an application built on Corsair. They differ by who chooses the operation and where the result is used, not by requiring separate integration foundations.

A dashboard is the most direct case. The application knows that a page should show open GitHub issues, upcoming calendar events, or Slack messages. It can read the synchronized local representation for the page and call a live operation when a person creates, edits, deletes, or explicitly refreshes something. The UI therefore has a stable local read path without pretending that every displayed value came from a permanent snapshot.

An agent is useful when the operation is selected at runtime from a human request. “Find the issue, comment that it is a duplicate, then close it” is not one fixed endpoint. Corsair’s MCP adapters expose a small discovery layer: an agent can list configured operations, inspect a selected schema, and execute against the existing Corsair instance. The provider remains visible in the operation path, but the application does not have to hand-code a tool definition for every configured endpoint.

A knowledge base uses the same local entities differently. Messages, issues, pages, and other synchronized resources can be searched and combined before they are passed to a model as context. This avoids calling several provider search APIs and reconciling their response formats for every question. It is still a retrieval layer with limits: local substring matching is not semantic ranking, and the corpus contains only what has been backfilled or received through supported event paths.

A workflow begins with an event rather than a page or a sentence. A pull request can lead to a Slack notification and then a Linear follow-up. In the lightweight form documented by Corsair, a webhook hook is ordinary TypeScript that calls another plugin’s API. The same tenant-scoped instance can make the result visible to a dashboard. The repository also documents a separate beta durable workflow system with memoized steps and durable sleeps; that distinction matters because durability and Hub-delivered execution introduce additional constraints.

These shapes are composable. A product might render synchronized issues, offer a chat command for unusual operations, answer questions across its connected data, and send notifications when a provider event arrives. The connection should be made once. Creating a second Corsair instance for the chat feature would split credentials and local state, undermining the reason to have a shared boundary in the first place.

The shared state creates a product-level feedback loop. Suppose an agent closes a GitHub issue after a user asks for it in chat. If the agent uses the same Corsair instance as the dashboard, the API result can update the synchronized entity that the next page read displays. The UI and the agent are not negotiating two private copies of reality. Conversely, a local note attached to that issue remains application data, because it expresses the product’s relationship to the issue rather than a fact owned by GitHub. This is the kind of boundary a common integration layer makes easier to keep visible.

The four shapes also differ in time. A dashboard responds to a person looking at a page. A knowledge base answers from what has already been synchronized. A workflow reacts after an event and may need a queue for slow work. An agent can combine several live operations in a sequence chosen at runtime. They can share plugins and tenant connections, but they do not share the same freshness, latency, or authority assumptions. Corsair’s value is strongest when the application chooses the path deliberately instead of treating the common syntax as permission to ignore those differences.

There is a second separation worth keeping in view. The runtime handles provider operations, local entities, keys, webhooks, and workflows. The management API handles control-plane concerns such as listing plugins, checking connection status, creating connect links, and inspecting permissions. Framework adapters expose that management API through one mounted route, while typed vanilla and React clients make it usable from a frontend. This separation lets a dashboard manage connections without turning its browser code into a direct holder of provider secrets.

The first decision: live operation or local state?

The simplest recurring rule in Corsair is the split between *.api.* and *.db.*. Use the API namespace when the application must change the provider or needs the latest value immediately. Use the database namespace for lists, searches, detail pages, and other reads that should be fast and should not spend a provider rate-limit budget. A successful API response is written into the local entity projection, so a mutation can be followed by a local read rather than a hand-built cache invalidation routine.

Consider a support dashboard that posts a Slack message when a ticket changes. The post belongs on the live API path because Slack must accept the write. The resulting message can then be read through the local database representation, where it can be joined to the application’s support-ticket row. If someone edits the message in Slack, a supported webhook can update the same entity row. The application owns the ticket and its private notes; Corsair maintains the provider-side projection.

That projection is powerful precisely because it has a boundary. It is not a claim that the application database has become the provider. A missed webhook, a provider outage, an unsupported deletion event, or a resource that has never been backfilled can leave local state behind. The documentation recommends a live refresh or polling when complete freshness matters. Likewise, local contains filters can help find known names or phrases, but they do not understand synonyms or rank meaning. An application that needs semantic retrieval can add embeddings in its own tables while using Corsair to recover current entity data.

The opening model therefore ends with four questions rather than a promise that integration has become effortless. How does a plugin preserve provider-specific behavior while presenting a common interface? Which credential belongs to the application, and which belongs to one tenant? How does local state acquire a trustworthy owner when many users connect different accounts? What happens when an external service calls back, or when an agent asks to perform a risky action? The answers determine whether the common syntax is merely convenient or can support a real product.

Corsair’s value begins at that level: it makes the application’s integration boundary explicit and reusable. The next step is to examine the plugin contract that carries provider differences into the runtime without allowing those differences to fracture the application’s model.

References