diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json
index 209d2fa67..186a049ab 100644
--- a/.cursor-plugin/marketplace.json
+++ b/.cursor-plugin/marketplace.json
@@ -657,6 +657,11 @@
"name": "rippling",
"source": "third_party/rippling",
"description": "Look up people, time off, and company policy in Rippling, and file time-off requests and draft hires."
+ },
+ {
+ "name": "corsair",
+ "source": "third_party/corsair",
+ "description": "Use your connected apps from your agent: read and act on them, search synced data, and set up automations."
}
]
}
diff --git a/README.md b/README.md
index bd459e6fc..fbed27a3f 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,7 @@ Official Cursor plugins for popular developer tools, frameworks, and SaaS produc
| `ringcentral-chat` | [RingCentral Chat](third_party/ringcentral-chat/) | Cursor | Integrations | Read team chats and posts, send messages, and manage teams, tasks, and notes. |
| `servicenow` | [ServiceNow](third_party/servicenow/) | Cursor | Integrations | Call the tools your admin publishes on a ServiceNow instance. |
| `rippling` | [Rippling](third_party/rippling/) | Cursor | Integrations | Look up people, time off, and company policy in Rippling, and file time-off requests and draft hires. |
+| `corsair` | [Corsair](third_party/corsair/) | Cursor | Integrations | Use your connected apps from your agent: read and act on them, search synced data, and set up automations. |
Author values match each plugin’s `plugin.json` `author.name` (Cursor lists `plugins@cursor.com` in the manifest).
## Repository structure
diff --git a/third_party/corsair/.cursor-plugin/plugin.json b/third_party/corsair/.cursor-plugin/plugin.json
new file mode 100644
index 000000000..df065f787
--- /dev/null
+++ b/third_party/corsair/.cursor-plugin/plugin.json
@@ -0,0 +1,28 @@
+{
+ "name": "corsair",
+ "displayName": "Corsair",
+ "version": "0.1.2",
+ "description": "Use your connected apps from your agent: read and act on them, search synced data, and set up automations.",
+ "author": {
+ "name": "Cursor",
+ "email": "plugins@cursor.com"
+ },
+ "homepage": "https://corsair.dev",
+ "repository": "https://github.com/corsairdev/agent-plugins",
+ "license": "Apache-2.0",
+ "logo": "assets/logo.png",
+ "keywords": [
+ "corsair",
+ "integrations",
+ "automation",
+ "mcp"
+ ],
+ "category": "integrations",
+ "tags": [
+ "corsair",
+ "automation",
+ "mcp"
+ ],
+ "mcpServers": "./mcp.json",
+ "skills": "./skills/"
+}
diff --git a/third_party/corsair/LICENSE b/third_party/corsair/LICENSE
new file mode 100644
index 000000000..48d02c3f9
--- /dev/null
+++ b/third_party/corsair/LICENSE
@@ -0,0 +1,191 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (which shall not include Communications that are clearly marked or
+ otherwise designated in writing by the copyright owner as "Not a Work").
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution".
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright (c) 2026 Corsair
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/third_party/corsair/README.md b/third_party/corsair/README.md
new file mode 100644
index 000000000..6298b272c
--- /dev/null
+++ b/third_party/corsair/README.md
@@ -0,0 +1,47 @@
+# Corsair
+
+Use the apps you've connected in [Corsair](https://corsair.dev) (Gmail, Slack, GitHub, Linear, Notion, and about 400 more) from your agent: search synced data, read apps live, make changes, and set up automations that run on a schedule or when an app sends an event.
+
+The plugin adds the Corsair MCP server and four skills:
+
+| Skill | Covers |
+| --- | --- |
+| `corsair-connect-apps` | Finding an app, handing over its sign-in link, and confirming the connection |
+| `corsair-data` | When to search synced data, read an app live, or make a change |
+| `corsair-automations` | Building automations from plain English or exact steps, then checking their runs |
+| `corsair-approvals` | Permission modes, and what to do when an action needs approval |
+
+## Install
+
+1. Install the **Corsair** plugin from the Cursor marketplace.
+2. The first time Cursor connects, sign in to Corsair with OAuth and approve the connection.
+3. Connect each app you want to use once on Corsair (the agent can hand you the sign-in link).
+
+To add the server manually instead, put this in your MCP config:
+
+```json
+{
+ "mcpServers": {
+ "corsair": {
+ "type": "http",
+ "url": "https://mcp.corsair.dev/mcp"
+ }
+ }
+}
+```
+
+## Try it
+
+- "What apps do I have connected in Corsair?"
+- "List open issues in my repo on GitHub."
+- "Every Friday at 5pm, post new GitHub issues to Slack."
+
+## Support
+
+Source: [corsairdev/agent-plugins](https://github.com/corsairdev/agent-plugins). Questions or problems: [primary@corsair.dev](mailto:primary@corsair.dev).
+
+[Privacy policy](https://corsair.dev/privacy-policy) · [Terms](https://corsair.dev/terms-of-service)
+
+## License
+
+Apache-2.0
diff --git a/third_party/corsair/assets/logo.png b/third_party/corsair/assets/logo.png
new file mode 100644
index 000000000..fee0768b2
Binary files /dev/null and b/third_party/corsair/assets/logo.png differ
diff --git a/third_party/corsair/mcp.json b/third_party/corsair/mcp.json
new file mode 100644
index 000000000..f45dfe241
--- /dev/null
+++ b/third_party/corsair/mcp.json
@@ -0,0 +1,8 @@
+{
+ "mcpServers": {
+ "corsair": {
+ "type": "http",
+ "url": "https://mcp.corsair.dev/mcp"
+ }
+ }
+}
diff --git a/third_party/corsair/skills/corsair-approvals/SKILL.md b/third_party/corsair/skills/corsair-approvals/SKILL.md
new file mode 100644
index 000000000..52d09471c
--- /dev/null
+++ b/third_party/corsair/skills/corsair-approvals/SKILL.md
@@ -0,0 +1,45 @@
+---
+name: corsair-approvals
+description: Use when a Corsair call or automation run says it needs approval, was denied, timed out waiting for approval, or is blocked by a permission mode, or when the user asks what Corsair's permission modes allow.
+---
+
+# Approvals and permission modes
+
+Every action has a risk level: read, write, or destructive. The Corsair connection this agent uses, a link or a client sign-in, has a mode, and the mode decides which risk levels go through, which wait for a person to approve them, and which are refused.
+
+| Mode (as Corsair labels it) | Reads | Writes | Destructive |
+| --- | --- | --- | --- |
+| Read (`readonly`) | allowed | refused | refused |
+| Cautious (`cautious`, the default) | allowed | allowed | need approval |
+| Strict (`strict`) | allowed | need approval | refused |
+| Open (`open`) | allowed | allowed | allowed |
+
+You can't change the mode. Whoever manages the connection sets it in Corsair.
+
+No tool reports this connection's mode, so don't guess it. Work from what you can see: no `add_triggers` tool usually means the link is Read, no `add_integrations` tool means it can't add apps, and a "blocked by permission mode" error names the mode. Otherwise explain the four modes and say the link's settings in Corsair show which one applies.
+
+Your client may also ask the user to confirm each `run_script` call, because the tool can make changes. `read_data` is marked read-only, so clients usually run it without asking. Those prompts come from the client, not from Corsair, and are separate from everything below.
+
+## What you'll see and what to do
+
+**"Approval required. Visit to approve or deny, then tell the agent to retry this action."**
+
+1. Give the user the link exactly as returned and say in plain words what the action does ("delete the repo acme/old-site").
+2. Stop and wait. Don't retry on a timer, and don't look for another way to get the same result.
+3. When the user says they approved it, call that one action again with exactly the arguments from the first call: copy them, don't recompute them. Leave out any other changes the first script already made, so they don't happen twice. An approval covers that one call, once. Different arguments need a new approval.
+
+**"... was denied by the user. Await further instructions before proceeding."**
+Stop. Tell the user it was denied and ask what they'd like instead. Don't retry.
+
+**"... timed out waiting for approval."**
+The request expired. Ask whether to request it again. If yes, run the call again to get a new link.
+
+**"... is blocked by permission mode 'readonly'"** (or `'strict'`)
+This connection can't do that kind of action at all. Tell the user which action was blocked and that whoever manages the connection can change its mode in Corsair. Don't look for a workaround.
+
+**"... requires user approval before it can run"** with no link
+Corsair couldn't create an approval link. Tell the user what it said and stop.
+
+## Automations
+
+A run that `list_runs` shows as "needs approval before it can run" is held before any step runs, because the automation needs approval or one of its steps is destructive. Call `get_run` to see what it would do, and tell the user. It's approved or denied in Corsair, not from here. Don't run its steps yourself with `run_script`.
diff --git a/third_party/corsair/skills/corsair-automations/SKILL.md b/third_party/corsair/skills/corsair-automations/SKILL.md
new file mode 100644
index 000000000..fe3a81864
--- /dev/null
+++ b/third_party/corsair/skills/corsair-automations/SKILL.md
@@ -0,0 +1,74 @@
+---
+name: corsair-automations
+description: Use when the user wants something to happen automatically through Corsair, either when an app event occurs ("when a Linear issue is marked Done, post it in Slack") or on a schedule ("every Monday at 9"), or asks whether an automation ran or why it failed, or wants to list, change or switch off an automation. Covers add_triggers, list_triggers, list_runs, get_run and remove_triggers.
+---
+
+# Build and check Corsair automations
+
+An automation is a trigger (an app event or a clock) plus the steps that run when it fires. Saving one is half the job. The other half is confirming it fires and works, because an automation can save cleanly and still fail on its first real event.
+
+## Before you add one
+
+- Call `list_triggers`. If something already watches the same event and does the same thing, tell the user instead of adding a second one.
+- If there's no `add_triggers` tool, this connection is read-only: automations can't be added or switched off from here (`list_triggers`, `list_runs` and `get_run` still work). Tell the user. Don't recreate the automation with `run_script`.
+- Every app the automation uses has to be connected. If one isn't, connect it first (see the corsair-connect-apps skill).
+
+## Path A: describe it in plain English
+
+This is the default. Call `add_triggers` with only `intent`, written the way the user would say it, with the specifics: which app, which event, which channel or person, what the message says.
+
+```json
+{ "intent": "When a Linear issue is marked Done, post its title in the #ships Slack channel." }
+```
+
+Corsair builds it in the background, so it doesn't exist yet when the call returns.
+
+1. Wait briefly, then call `list_triggers`. Check a couple of times; if it's still building, tell the user and check later instead of polling in a loop. A build in progress shows as "one you described is still being built".
+2. If it "needs one question answered", give the user the question and its options. Send their choice back with `{"job": "", "answer": ""}` and nothing else in the call.
+3. If it "could not be built", tell the user and offer Path B.
+4. Once it's live, read its description back and check it matches what the user meant.
+
+## Path B: exact trigger and steps
+
+Use this when you know exactly which event and which calls you need, or Path A built the wrong thing.
+
+1. Find the event: `list_integration_operations {"integration": "linear", "type": "webhooks"}`.
+2. Look up each action a step will call, for example `list_integration_operations {"integration": "slack", "operation": "slack.api.messages.post"}`. Corsair refuses to save a step that calls an action it doesn't recognize, so take names and arguments from here, not from memory.
+3. Call `add_triggers` with `trigger` and `action`:
+
+```json
+{
+ "name": "Linear done to #ships",
+ "trigger": {
+ "integration": "linear",
+ "event": "Issue.update",
+ "equals": { "data.state.name": "Done" }
+ },
+ "action": [
+ {
+ "name": "notify",
+ "code": "await corsair.slack.api.messages.post({ channel: 'C0123ABCD', text: payload.data.title });"
+ }
+ ]
+}
+```
+
+- `equals` paths start with `data.`, which holds what the app sent. With nothing narrowing it, a trigger fires on every event of that kind and runs a lot. The result says so when that's the case; pass the warning on.
+- Use real ids in steps. Look up a channel id or user id with `read_data` first.
+- For a schedule, leave out `integration` and `event` and set `"every": "30m"` (the first run is one interval from now) or `"cron": "0 9 * * 1"` (UTC, 5 fields). Convert the user's local time to UTC and tell them the time you used.
+- A step's `code` is a JS body with `corsair` and `payload` (the triggering event) in scope. Other step `verb`s: `sleep` (`ms`), `sleepUntil` (`when`), `waitFor` (`event`, `match`, `timeout`), and `ai`, `bool` or `score` (`prompt`, `input`) to ask a model.
+- Automations are matched by `name`. Sending an existing name replaces that automation's steps. Without a `name`, changed steps create a second automation, so to fix or change one, pass its exact name from `list_triggers`. For a new one, use a new name.
+- The example fires on every update to an issue that is Done, including edits after it was marked Done. Mention that when it could post twice.
+
+## Check that it works
+
+1. Ask the user to make the event happen, or tell them what to watch for. For a schedule, tell the user when the first run is due, and check `list_runs` after it.
+2. Call `list_runs`. It lists recent runs, newest first, with each run's id and whether it succeeded, failed, is still running, or needs approval (see corsair-approvals).
+3. For a failed run, call `get_run {"run": ""}`. The failing step usually says what went wrong, often a value an action didn't accept. Fix it with Path B using the same `name`, and check again.
+4. No run at all means the trigger didn't match. Compare the event name and the `equals` values with what `list_integration_operations` shows.
+
+Don't tell the user an automation works until a run has succeeded.
+
+## Stop one
+
+`remove_triggers {"trigger": ""}` switches an automation off. Its steps stay saved in Corsair. If two automations look alike, confirm which one with the user first.
diff --git a/third_party/corsair/skills/corsair-connect-apps/SKILL.md b/third_party/corsair/skills/corsair-connect-apps/SKILL.md
new file mode 100644
index 000000000..4d3bda6fa
--- /dev/null
+++ b/third_party/corsair/skills/corsair-connect-apps/SKILL.md
@@ -0,0 +1,50 @@
+---
+name: corsair-connect-apps
+description: Use when the user wants to use an app through Corsair that isn't connected yet, asks which apps are connected, or a Corsair call fails because an app isn't connected. Covers finding the app, handing the user the sign-in link from add_integrations, and confirming the connection.
+---
+
+# Connect an app through Corsair
+
+Corsair can reach the user's apps (Gmail, Slack, GitHub, Linear and hundreds more). Most need the user to sign in first. Your part is to find the app, get the sign-in link, hand it over, and confirm it worked. You never sign in on the user's behalf.
+
+## Steps
+
+1. **See what's connected.** Call `list_integrations` with no arguments. Each app is in one of four states:
+ - "connected and ready to use": use it.
+ - "set up, but nobody has signed in to it yet": go to step 3.
+ - "can be connected from here": go to step 3.
+ - "needs setting up before it can be connected": see "When there's no link" below.
+2. **Look up an app that isn't listed.** Call `list_integrations` with `search`, for example `{"search": "notion"}`. Use the `id` it returns in every later call. Don't guess ids: Google Calendar is `googlecalendar`.
+3. **Get the link.** Call `add_integrations` with `{"integration": ""}`.
+4. **Hand over the link exactly as returned.** Give the user the `connect_url` (the result text repeats it), ask them to tell you when they've signed in, and stop.
+5. **Confirm.** When the user says they're done, call `list_integrations` again. The app should read "connected and ready to use". Then do what they first asked for.
+
+## Rules for the link
+
+- Copy it character for character. Don't shorten it, rebuild it, or put it behind another URL.
+- Never write a link yourself. If `add_integrations` returned no link, there is no link.
+- Call `add_integrations` once per app. Each call mints a new sign-in session, so calling it again only produces more links. Get a new one only if the user says the link failed or expired (`expires_at` in the result).
+- Don't open the link or try to finish the sign-in yourself.
+
+## When it comes back ready
+
+A few apps that Corsair provides directly come back with `connected: true` and no link. There is nothing to sign in to. Use the app right away.
+
+## When there's no link
+
+- **There's no `add_integrations` tool.** This connection can't add apps. Tell the user to connect the app in Corsair, or to allow this link to add apps, then confirm with `list_integrations`.
+- **`connect_blocked_by` is set, or the result says the app "has no sign-in link yet".** Whoever manages this Corsair connection, or the Corsair project behind it, has to finish setting the app up first, for example by adding its OAuth credentials. Tell the user that, quote the reason, and stop.
+- **"not permitted to authorize it. An admin has to connect it."** This connection can use apps but not connect new ones. Whoever manages the connection can connect the app in Corsair, or allow it to send connect links.
+
+## When a call fails because an app isn't connected
+
+`run_script` can fail with " is not connected. Connect it here: ". Treat that URL the same way: hand it over, wait, confirm with `list_integrations`, then run the original call again. If it says "no connect link could be made" instead, tell the user the reason it gave and stop.
+
+## Example
+
+User: "Post the standup notes to Slack."
+
+1. `list_integrations` returns "slack can be connected from here."
+2. `add_integrations {"integration": "slack"}` returns a link.
+3. Reply: "Slack isn't connected yet. Sign in here: . Tell me when you're done and I'll post the notes."
+4. The user confirms. `list_integrations` now says Slack is connected and ready to use. Post the notes.
diff --git a/third_party/corsair/skills/corsair-data/SKILL.md b/third_party/corsair/skills/corsair-data/SKILL.md
new file mode 100644
index 000000000..f07f425bd
--- /dev/null
+++ b/third_party/corsair/skills/corsair-data/SKILL.md
@@ -0,0 +1,65 @@
+---
+name: corsair-data
+description: Use when answering a question from the user's connected apps through Corsair (find a message, summarize recent emails, check a calendar, look up an issue) or doing something in an app right now (send, create, update). Explains when to search synced data with search_data and open results with fetch_record, read live with read_data, and make changes with run_script.
+---
+
+# Read and act on connected apps
+
+Three Corsair tools reach the user's data:
+
+| | `search_data` | `read_data` | `run_script` |
+| --- | --- | --- | --- |
+| Reads | Data Corsair has already synced (Slack messages, Gmail, Calendar and so on) | The app's live API | The app's live API |
+| Writes | No | No: the script stops at the first write, before that write runs | Yes: send, create, update, delete |
+| Best for | Finding things ("what did Sarah say about the refund?") | Current state and exact lists | Any change |
+
+Start with `search_data` when the user is looking for something. Use `read_data` when they need the current state or a complete list (every open issue, today's events). Use `run_script` only to change something. If the server has no `read_data` tool, use `run_script` for live reads too.
+
+## search_data
+
+Matching is by keyword with stemming, not by meaning. So:
+
+- Put the main terms in `query` and synonyms in `keywords`. For "refund requests": `keywords: ["refund", "chargeback", "money back"]`.
+- Put people, emails, channels, repos, projects and companies in `about`, not in `query`. `about: ["Sarah Chen"]` also matches messages she wrote.
+- Put time limits in `after` and `before` as ISO timestamps, not in `query`. Work out the dates from today's date first.
+- Narrow with `plugins` (`["slack", "gmail"]`) and `entityTypes` (`["messages"]`, `["events"]`) when you know where the answer lives.
+- For a question with several parts, make one call per part.
+
+- For the newest items ("my latest emails"), set `sort: "recent"`. Every word of `query` must then appear, and `query` can be empty.
+
+Each result shows its app, date, title, the text that matched and an `id`. Often that's enough to answer. When it isn't, such as to read a whole email, document or meeting notes, open it with `fetch_record`:
+
+- Pass the `id` from `search_data`, or up to 20 of them in `ids` to read several in one call.
+- Long text comes in pages, and the reply says when there is more. Ask for the next one with `page`.
+- It reads Corsair's synced copy, not the app, so it answers fast. For the current state, use `read_data`.
+
+No match means nothing in the synced copy matched. It doesn't mean the thing doesn't exist. Try other keywords, then a live read with `read_data`.
+
+## read_data and run_script
+
+Both take the same code: the body of an async function. `corsair` is the only thing in scope, and you `return` the result. There is no network, filesystem or `import`.
+
+1. Find the action: `list_integration_operations {"integration": "slack", "type": "api"}`.
+2. Get its exact arguments: `list_integration_operations {"integration": "slack", "operation": "slack.api.messages.post"}`. Don't guess argument names.
+3. Run it: reads with `read_data`, changes with `run_script`. Do the lookup and the action in one `run_script` call with ordinary JS rather than several round trips:
+
+```js
+const { channels = [] } = await corsair.slack.api.channels.list({});
+const ships = channels.find((c) => c.name === 'ships');
+if (!ships) return 'No #ships channel';
+return await corsair.slack.api.messages.post({ channel: ships.id, text: 'Deploy finished' });
+```
+
+- Return only what answers the question. Map large responses down to the fields you need; very long results are cut short in the text you get back.
+- Change something only when the user asked for that change. If they didn't say exactly what gets sent or changed, or the change messages other people, deletes, or touches many records, say what you'll do and wait for a yes.
+- If `read_data` stops because the script reached a write, don't rerun it with `run_script` unless the user asked for that change.
+- If the script fails, read the error:
+ - "is not connected": connect the app first (see corsair-connect-apps), then run it again.
+ - "Approval required", "requires user approval", "was denied", "timed out waiting for approval", or "is blocked by permission mode": see corsair-approvals.
+ - A rejected argument: check the operation's schema again and fix the call.
+- A script that fails partway has already made the changes before the failing line. Rerun only the part that didn't happen.
+- If a tool reports a problem with Corsair itself, tell the user what it said and stop. Don't work around it.
+
+## Doing it later instead of now
+
+`run_script` does the work once, now. For something that should happen every time an event occurs or on a schedule, use `add_triggers` (see corsair-automations).