@cursor/july 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/AGENTS.md +1 -0
  2. package/dist/bin/agent-serve.js +17 -3
  3. package/dist/channels/github/github-channel.d.ts.map +1 -1
  4. package/dist/channels/github/github-channel.js +2 -1
  5. package/dist/channels/github/types.d.ts +4 -0
  6. package/dist/channels/github/types.d.ts.map +1 -1
  7. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  8. package/dist/channels/slack/cursor-account.js +1 -1
  9. package/dist/channels/slack/dispatch.d.ts +13 -0
  10. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  11. package/dist/channels/slack/dispatch.js +36 -11
  12. package/dist/channels/slack/eval-directive.d.ts +63 -0
  13. package/dist/channels/slack/eval-directive.d.ts.map +1 -0
  14. package/dist/channels/slack/eval-directive.js +283 -0
  15. package/dist/channels/slack/index.d.ts +1 -0
  16. package/dist/channels/slack/index.d.ts.map +1 -1
  17. package/dist/channels/slack/index.js +1 -0
  18. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  19. package/dist/channels/slack/slack-channel.js +4 -2
  20. package/dist/channels/slack/types.d.ts +7 -0
  21. package/dist/channels/slack/types.d.ts.map +1 -1
  22. package/dist/connections.d.ts +10 -5
  23. package/dist/connections.d.ts.map +1 -1
  24. package/dist/connections.js +10 -5
  25. package/dist/docs/404.html +2 -2
  26. package/dist/docs/ab.html +4 -4
  27. package/dist/docs/assets/{app.CY2buaEE.js → app.Dh_JkJjI.js} +1 -1
  28. package/dist/docs/assets/chunks/@localSearchIndexroot.Er_R50vY.js +1 -0
  29. package/dist/docs/assets/chunks/{VPLocalSearchBox.Ix954SFJ.js → VPLocalSearchBox.CptQNLkR.js} +1 -1
  30. package/dist/docs/assets/chunks/{theme.PDwuJbyi.js → theme.DWflH-LQ.js} +2 -2
  31. package/dist/docs/assets/{deployment.md.DC5-ImcM.js → deployment.md.TecHo0_2.js} +3 -3
  32. package/dist/docs/assets/{deployment.md.DC5-ImcM.lean.js → deployment.md.TecHo0_2.lean.js} +1 -1
  33. package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.js +29 -0
  34. package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.lean.js +1 -0
  35. package/dist/docs/assets/{index.md.COiu-1jL.js → index.md.t0TM2Qzz.js} +3 -3
  36. package/dist/docs/assets/{index.md.COiu-1jL.lean.js → index.md.t0TM2Qzz.lean.js} +1 -1
  37. package/dist/docs/assets/{reference_cli.md.CjfiV2Q-.js → reference_cli.md.B74NrVzb.js} +3 -3
  38. package/dist/docs/assets/{reference_cli.md.CjfiV2Q-.lean.js → reference_cli.md.B74NrVzb.lean.js} +1 -1
  39. package/dist/docs/assets/{reference_connections.md.FbWupyzW.js → reference_connections.md.CsSr3rDz.js} +8 -3
  40. package/dist/docs/assets/{reference_connections.md.FbWupyzW.lean.js → reference_connections.md.CsSr3rDz.lean.js} +1 -1
  41. package/dist/docs/assets/reference_prompt.md.BaiweQxE.js +1 -0
  42. package/dist/docs/assets/reference_prompt.md.BaiweQxE.lean.js +1 -0
  43. package/dist/docs/assets/{reference_tools.md.CqgJroI0.js → reference_tools.md.BRSDnTbN.js} +16 -3
  44. package/dist/docs/assets/{reference_tools.md.CqgJroI0.lean.js → reference_tools.md.BRSDnTbN.lean.js} +1 -1
  45. package/dist/docs/assets/{troubleshooting.md.DkovjS7h.js → troubleshooting.md.CmQkmnzC.js} +1 -1
  46. package/dist/docs/assets/{troubleshooting.md.DkovjS7h.lean.js → troubleshooting.md.CmQkmnzC.lean.js} +1 -1
  47. package/dist/docs/building-with-agents.html +4 -4
  48. package/dist/docs/concepts.html +4 -4
  49. package/dist/docs/deployment.html +7 -7
  50. package/dist/docs/evals.html +4 -4
  51. package/dist/docs/example-agents/approval-buddy.html +4 -4
  52. package/dist/docs/example-agents/benny.html +4 -4
  53. package/dist/docs/example-agents/bugbot.html +4 -4
  54. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  55. package/dist/docs/example-agents/codeowners-review.html +4 -4
  56. package/dist/docs/example-agents/concierge.html +4 -4
  57. package/dist/docs/example-agents/fsd.html +4 -4
  58. package/dist/docs/example-agents/index.html +4 -4
  59. package/dist/docs/example-agents/knowledge-base.html +4 -4
  60. package/dist/docs/example-agents/oncall.html +4 -4
  61. package/dist/docs/example-agents/security-reviewer.html +4 -4
  62. package/dist/docs/example-agents/slack-agent.html +4 -4
  63. package/dist/docs/example-agents/weather-agent.html +4 -4
  64. package/dist/docs/guides/agent-to-agent.html +4 -4
  65. package/dist/docs/guides/cloud-runtime.html +4 -4
  66. package/dist/docs/guides/github.html +4 -4
  67. package/dist/docs/guides/human-in-the-loop.html +4 -4
  68. package/dist/docs/guides/mcp-oauth.html +53 -0
  69. package/dist/docs/guides/slack.html +4 -4
  70. package/dist/docs/guides/webhooks.html +4 -4
  71. package/dist/docs/hashmap.json +1 -1
  72. package/dist/docs/hillclimbing.html +4 -4
  73. package/dist/docs/index.html +6 -6
  74. package/dist/docs/quickstart.html +4 -4
  75. package/dist/docs/reference/agent-config.html +4 -4
  76. package/dist/docs/reference/channels.html +4 -4
  77. package/dist/docs/reference/cli.html +6 -6
  78. package/dist/docs/reference/connections.html +12 -7
  79. package/dist/docs/reference/hooks.html +4 -4
  80. package/dist/docs/reference/http-api.html +4 -4
  81. package/dist/docs/reference/instructions.html +4 -4
  82. package/dist/docs/reference/playground.html +4 -4
  83. package/dist/docs/reference/project-layout.html +4 -4
  84. package/dist/docs/reference/prompt.html +33 -0
  85. package/dist/docs/reference/schedules.html +4 -4
  86. package/dist/docs/reference/sessions.html +4 -4
  87. package/dist/docs/reference/skills.html +5 -5
  88. package/dist/docs/reference/subagents.html +4 -4
  89. package/dist/docs/reference/tools.html +20 -7
  90. package/dist/docs/scaffolding-agents.html +4 -4
  91. package/dist/docs/storage.html +4 -4
  92. package/dist/docs/troubleshooting.html +5 -5
  93. package/dist/evals.d.ts +39 -0
  94. package/dist/evals.d.ts.map +1 -1
  95. package/dist/index.d.ts +3 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +2 -1
  98. package/dist/internal/cli-mcp-oauth.d.ts +16 -0
  99. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -0
  100. package/dist/internal/cli-mcp-oauth.js +194 -0
  101. package/dist/internal/cli-mcp.d.ts +4 -1
  102. package/dist/internal/cli-mcp.d.ts.map +1 -1
  103. package/dist/internal/cli-mcp.js +19 -1
  104. package/dist/internal/cursor/account-mcp.d.ts +17 -4
  105. package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
  106. package/dist/internal/cursor/account-mcp.js +105 -17
  107. package/dist/internal/cursor-event-relay.d.ts +2 -0
  108. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  109. package/dist/internal/cursor-event-relay.js +3 -0
  110. package/dist/internal/discovery.d.ts.map +1 -1
  111. package/dist/internal/discovery.js +64 -10
  112. package/dist/internal/distribution.d.ts.map +1 -1
  113. package/dist/internal/distribution.js +1 -0
  114. package/dist/internal/json-dir-store.js +100 -0
  115. package/dist/internal/logs-client.js +1 -1
  116. package/dist/internal/mcp-host.d.ts.map +1 -1
  117. package/dist/internal/mcp-host.js +49 -26
  118. package/dist/internal/mcp-oauth.d.ts +88 -0
  119. package/dist/internal/mcp-oauth.d.ts.map +1 -0
  120. package/dist/internal/mcp-oauth.js +385 -0
  121. package/dist/internal/resolved-connections.d.ts.map +1 -1
  122. package/dist/internal/resolved-connections.js +4 -1
  123. package/dist/internal/sdk-runner.d.ts +9 -0
  124. package/dist/internal/sdk-runner.d.ts.map +1 -1
  125. package/dist/internal/sdk-runner.js +28 -2
  126. package/dist/internal/server.d.ts.map +1 -1
  127. package/dist/internal/server.js +13 -5
  128. package/dist/internal/session-engine.d.ts +7 -9
  129. package/dist/internal/session-engine.d.ts.map +1 -1
  130. package/dist/internal/session-engine.js +96 -64
  131. package/dist/internal/sessions-client.d.ts.map +1 -1
  132. package/dist/internal/sessions-client.js +2 -1
  133. package/dist/internal/stream-progress.d.ts.map +1 -1
  134. package/dist/internal/stream-progress.js +8 -4
  135. package/dist/internal/turn-governor.d.ts +54 -0
  136. package/dist/internal/turn-governor.d.ts.map +1 -0
  137. package/dist/internal/turn-governor.js +111 -0
  138. package/dist/playground/assets/index-1dzluIcW.js +86 -0
  139. package/dist/playground/assets/index-kZjeJs1v.css +1 -0
  140. package/dist/playground/index.html +2 -2
  141. package/dist/prompt.d.ts +39 -0
  142. package/dist/prompt.d.ts.map +1 -0
  143. package/dist/prompt.js +70 -0
  144. package/dist/types.d.ts +78 -5
  145. package/dist/types.d.ts.map +1 -1
  146. package/dist/types.js +6 -0
  147. package/docs/README.md +4 -0
  148. package/docs/deployment.md +6 -1
  149. package/docs/guides/mcp-oauth.md +157 -0
  150. package/docs/reference/cli.md +28 -0
  151. package/docs/reference/connections.md +28 -0
  152. package/docs/reference/prompt.md +47 -0
  153. package/docs/reference/tools.md +21 -0
  154. package/docs/troubleshooting.md +11 -0
  155. package/package.json +9 -2
  156. package/skills/create-agent/SKILL.md +7 -3
  157. package/skills/framework-map/SKILL.md +1 -0
  158. package/skills/mcp-auth/SKILL.md +138 -0
  159. package/src/bin/agent-serve.ts +20 -3
  160. package/src/channels/github/github-channel.ts +2 -0
  161. package/src/channels/github/types.ts +4 -0
  162. package/src/channels/slack/cursor-account.ts +2 -0
  163. package/src/channels/slack/dispatch.ts +49 -0
  164. package/src/channels/slack/eval-directive.ts +348 -0
  165. package/src/channels/slack/index.ts +1 -0
  166. package/src/channels/slack/slack-channel.ts +3 -0
  167. package/src/channels/slack/types.ts +7 -0
  168. package/src/connections.ts +10 -5
  169. package/src/evals.ts +43 -0
  170. package/src/index.ts +3 -0
  171. package/src/internal/cli-mcp-oauth.ts +254 -0
  172. package/src/internal/cli-mcp.ts +25 -2
  173. package/src/internal/cursor/account-mcp.ts +153 -41
  174. package/src/internal/cursor-event-relay.ts +5 -0
  175. package/src/internal/discovery.ts +70 -8
  176. package/src/internal/distribution.ts +1 -0
  177. package/src/internal/logs-client.ts +1 -1
  178. package/src/internal/mcp-host.ts +33 -9
  179. package/src/internal/mcp-oauth.ts +496 -0
  180. package/src/internal/resolved-connections.ts +6 -1
  181. package/src/internal/sdk-runner.ts +37 -1
  182. package/src/internal/server.ts +13 -5
  183. package/src/internal/session-engine.ts +115 -74
  184. package/src/internal/sessions-client.ts +2 -1
  185. package/src/internal/stream-progress.ts +8 -0
  186. package/src/internal/turn-governor.ts +145 -0
  187. package/src/prompt.ts +83 -0
  188. package/src/types.ts +76 -6
  189. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  190. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  191. package/dist/docs/assets/chunks/@localSearchIndexroot.OfDZ5CXg.js +0 -1
  192. package/dist/internal/persistence-coordinator.d.ts +0 -127
  193. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  194. package/dist/persistence.d.ts +0 -184
  195. package/dist/persistence.d.ts.map +0 -1
  196. package/dist/playground/assets/index-C0-QEG4O.js +0 -86
  197. package/dist/playground/assets/index-z3JY9O2x.css +0 -1
@@ -43,6 +43,7 @@ also provide `agentkit slack help` and `agentkit github help`.
43
43
  | [`rotate-token`](#rotate-token) | Replace a deployment's alias token |
44
44
  | [`rotate-pod-credential`](#rotate-pod-credential) | Replace a deployment's pod credential |
45
45
  | [`secrets`](#secrets) | Manage deployment secrets |
46
+ | [`mcp oauth`](#mcp-oauth) | Authorize host MCP OAuth; optional `--store` to deployment secrets |
46
47
  | [`slack ...`](#slack) | Set up and check Slack channels |
47
48
  | [`github ...`](#github) | Forward, replay, and inspect GitHub webhook channels |
48
49
 
@@ -560,6 +561,33 @@ The command returns only the masked key and revokes the old credential
560
561
  immediately. Redeploy at once to inject the replacement; the current
561
562
  pod can't authenticate to Cursor in between.
562
563
 
564
+ ## mcp oauth
565
+
566
+ `mcp oauth` authorizes a `defineConnection({ url, oauth: true })`
567
+ connection with a browser PKCE flow. Tokens are written to
568
+ `mcp-auth.json` under the agent-serve config dir (default
569
+ `~/.config/agent-serve`). Pass `--store` to upsert matching
570
+ `MCP_OAUTH_<CONNECTION>_*` secrets on the hosted deployment.
571
+
572
+ ```bash
573
+ agentkit mcp oauth <connection> [--dir .] [--store] [--slug <slug>] [--team <id>]
574
+ ```
575
+
576
+ `<connection>` is the `agent/mcp-connections/<connection>.ts` basename.
577
+ `--slug` defaults to the `--dir` basename. `--team` defaults to the
578
+ signed-in account's team. You need `agentkit login` (or `--api-key`)
579
+ before `--store`.
580
+
581
+ Secret names are `MCP_OAUTH_<NAME>_ACCESS_TOKEN`,
582
+ `_REFRESH_TOKEN`, and `_CLIENT_ID` (`<NAME>` is the connection id in
583
+ upper snake case). Declare them in `hosting.secretNames` so deploy
584
+ validation expects them. Secrets apply on the next deploy.
585
+
586
+ Tokens are bound to the connection's resource URL. Changing the URL
587
+ invalidates the local entry; run `mcp oauth` again.
588
+
589
+ See the [Host MCP OAuth guide](../guides/mcp-oauth.md).
590
+
563
591
  ## secrets
564
592
 
565
593
  `secrets` manages environment secrets for a deployment.
@@ -27,6 +27,33 @@ export default defineConnection({
27
27
 
28
28
  Tokens come from env vars. Never hardcode them in the file.
29
29
 
30
+ ## Host MCP OAuth
31
+
32
+ For servers that speak OAuth, set `oauth: true` and authorize with the
33
+ CLI. Tokens live in `~/.config/agent-serve/mcp-auth.json`. `--store`
34
+ copies them onto the hosted deployment as `MCP_OAUTH_<NAME>_*` secrets.
35
+
36
+ ```ts
37
+ export default defineConnection({
38
+ url: "https://mcp.example.com/inventory",
39
+ oauth: true,
40
+ hostOnly: true, // model cannot call; host.mcp still can
41
+ });
42
+ ```
43
+
44
+ ```bash
45
+ agentkit mcp oauth inventory # browser PKCE → local mcp-auth.json
46
+ agentkit mcp oauth inventory --store # also upsert deployment secrets
47
+ ```
48
+
49
+ Full walkthrough: [Host MCP OAuth](../guides/mcp-oauth.md). Companion
50
+ skill: [`skills/mcp-auth/SKILL.md`](../../skills/mcp-auth/SKILL.md).
51
+
52
+ Use `hostOnly: true` when only deterministic host tools should call the
53
+ server (deploys, admin APIs). Account MCP
54
+ (`cursorAccount: true`) stays the right choice for connectors already
55
+ linked in the Cursor dashboard.
56
+
30
57
  ## Local stdio MCP server
31
58
 
32
59
  Run a local MCP server as a child process with `command`.
@@ -134,6 +161,7 @@ lazily on first use.
134
161
 
135
162
  Continue with these pages:
136
163
 
164
+ - [Host MCP OAuth](../guides/mcp-oauth.md): `mcp oauth`, `--store`, `hostOnly`
137
165
  - [Agent-to-agent](../guides/agent-to-agent.md): peers in depth
138
166
  - [Tools](./tools.md): authored tools that wrap MCP connections
139
167
  - [Webhooks](../guides/webhooks.md): calling MCP connections from handlers
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: "prompt"
3
+ description: "Dedented multi-line strings for tool descriptions, reminder prompts, channel context, and errors."
4
+ ---
5
+
6
+ # `prompt`
7
+
8
+ Authoring helper for long strings that live next to indented TypeScript:
9
+ tool descriptions, reminder `prompt` fields, GitHub channel `context`,
10
+ and error messages.
11
+
12
+ ```ts
13
+ import { prompt } from "@cursor/july";
14
+ // or: import { prompt } from "@cursor/july/prompt";
15
+ ```
16
+
17
+ ## `prompt\`…\``
18
+
19
+ Returns a single dedented string. Common leading whitespace is stripped;
20
+ a leading newline after the opening backtick is dropped so the usual
21
+ multiline form stays readable in source.
22
+
23
+ ```ts
24
+ throw new Error(prompt`
25
+ It is outside business hours (Mon–Fri 9am–5pm ET).
26
+ Use request_author_approval, or pass approval=human_request.
27
+ `);
28
+ ```
29
+
30
+ Blank lines inside the body are preserved. Relative indentation after the
31
+ common prefix is kept (handy for nested bullet lists).
32
+
33
+ When interpolating multi-line values (for example a list of services), give
34
+ those lines the same indent as the `prompt` body so dedent stays consistent.
35
+
36
+ ## `prompt.lines\`…\``
37
+
38
+ Same dedent rules, but returns `string[]` — one entry per line. Use this
39
+ where an API wants separate lines (for example GitHub channel `context`):
40
+
41
+ ```ts
42
+ context: prompt.lines`
43
+ Merged PR detected: ${pr.url} by ${author}.
44
+
45
+ Call plan_deploy, then follow its nextStep.
46
+ `
47
+ ```
@@ -39,6 +39,27 @@ code that runs it. With a Zod `inputSchema`, the input is validated before
39
39
  `execute` runs and the input type is inferred. A plain JSON Schema
40
40
  object is forwarded as-is and the input arrives as raw JSON.
41
41
 
42
+ For multi-line descriptions, reminder prompts, and error messages, use
43
+ [`prompt`](./prompt.md) so the string can sit indented with the surrounding
44
+ TypeScript:
45
+
46
+ ```ts
47
+ import { prompt } from "@cursor/july";
48
+ import { defineTool } from "@cursor/july/tools";
49
+ import { z } from "zod";
50
+
51
+ export default defineTool({
52
+ description: prompt`
53
+ Inspect a pull request before approval.
54
+ Prefer this over guessing from the title alone.
55
+ `,
56
+ inputSchema: z.object({ prUrl: z.string().url() }),
57
+ async execute({ prUrl }) {
58
+ return { prUrl };
59
+ },
60
+ });
61
+ ```
62
+
42
63
  ### The `ctx` parameter
43
64
 
44
65
  `execute` receives a `ctx` with the runtime accessors:
@@ -63,6 +63,17 @@ Match your symptom below. Keep the commands as `agentkit`; see
63
63
  | The bot ignores ordinary channel posts | Default engagement is mentions and DMs only. Enable `engagement.channelPosts` with an allowlist, and subscribe the app to `message.channels` / `message.groups`. |
64
64
  | Approve / Deny buttons do nothing | Re-import a Slack app manifest with interactivity enabled. Channels that post approval cards need `toolApprovals: true` (and `interactivity: true` when you compose events by hand). |
65
65
 
66
+ ## What if host MCP OAuth fails?
67
+
68
+ | What you see | What to do |
69
+ | --- | --- |
70
+ | `must be defineConnection({ url, oauth: true })` | The connection file needs `oauth: true`, or you passed the wrong connection name to `agentkit mcp oauth`. |
71
+ | Local auth works; hosted calls unauthorized | Run `agentkit mcp oauth <name> --store`, confirm names with `agentkit secrets list <slug>`, then redeploy. |
72
+ | Model asks for `mcp_auth` or IDE MCP for a privileged server | That connection is likely `hostOnly`. Call it from a host tool via `ctx.host.mcp`, and update instructions. |
73
+
74
+ See [Host MCP OAuth](./guides/mcp-oauth.md) and
75
+ [`skills/mcp-auth/SKILL.md`](../skills/mcp-auth/SKILL.md).
76
+
66
77
  ## What if schedules, reminders, or approvals stall?
67
78
 
68
79
  | What you see | What to do |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -103,6 +103,13 @@
103
103
  "import": "./dist/hooks.js",
104
104
  "default": "./dist/hooks.js"
105
105
  },
106
+ "./prompt": {
107
+ "anysphere-source": "./src/prompt.ts",
108
+ "bun": "./src/prompt.ts",
109
+ "types": "./dist/prompt.d.ts",
110
+ "import": "./dist/prompt.js",
111
+ "default": "./dist/prompt.js"
112
+ },
106
113
  "./evals": {
107
114
  "anysphere-source": "./src/evals.ts",
108
115
  "bun": "./src/evals.ts",
@@ -213,4 +220,4 @@
213
220
  "vitest": "^4.0.18",
214
221
  "zustand": "^5.0.9"
215
222
  }
216
- }
223
+ }
@@ -15,8 +15,9 @@ Stand up one new agent project via a short interview, scaffold it, get
15
15
  channels working
16
16
  with the user, prove a turn runs end-to-end, and set up the improvement loop.
17
17
  Sibling skills: `framework-map` for orientation, `setup-slack` for Slack
18
- wiring, `github` for webhook-driven agents, `evals` for the eval API,
19
- `ab` for live A/B metrics, `hillclimb` for iteration once the agent runs.
18
+ wiring, `mcp-auth` for host MCP OAuth, `github` for webhook-driven
19
+ agents, `evals` for the eval API, `ab` for live A/B metrics, `hillclimb`
20
+ for iteration once the agent runs.
20
21
 
21
22
  Read `framework-map/SKILL.md` first if you haven't; treat the package
22
23
  `AGENTS.md` and `README.md` as ground truth for
@@ -91,7 +92,10 @@ Defaults that make first cuts good:
91
92
  - **Tool per file**, filename = tool name the model sees. Prefer
92
93
  `execution: "server"` with a zod `inputSchema`; use `execution: "agent"`
93
94
  (script) only when the tool must run where the agent runs (or runtime is
94
- cloud). Gate side-effecting tools with `needsApproval: true`.
95
+ cloud). Gate side-effecting tools with `needsApproval: true`. For long
96
+ tool descriptions, reminder prompts, channel `context`, and errors, use
97
+ `prompt\`…\`` / `prompt.lines\`…\`` from `@cursor/july` (dedents indented
98
+ multi-line strings).
95
99
  - **Env prefix** for Slack tokens = upper-snake slug (`my-agent` →
96
100
  `MY_AGENT_SLACK_*`); `agentkit slack init` derives it for you.
97
101
  - **Host prep beats model wandering:** when the purpose has a deterministic
@@ -139,6 +139,7 @@ folder mounts every sibling agent too.
139
139
  - Live A/B metrics → `packages/agent-serve/skills/ab/SKILL.md`
140
140
  - GitHub webhooks → `packages/agent-serve/skills/github/SKILL.md`
141
141
  - Slack → `packages/agent-serve/skills/setup-slack/SKILL.md`
142
+ - Host MCP OAuth → `packages/agent-serve/skills/mcp-auth/SKILL.md`
142
143
  - Local triage → `packages/agent-serve/skills/debug/SKILL.md`
143
144
  - Measured improvement → `packages/agent-serve/skills/hillclimb/SKILL.md`
144
145
  - Dogfood / deploy → `packages/agent-serve/skills/operate/SKILL.md`
@@ -0,0 +1,138 @@
1
+ ---
2
+ name: agentkit-mcp-auth
3
+ description: >-
4
+ Authorize host MCP OAuth for defineConnection({ url, oauth: true }):
5
+ agentkit mcp oauth, local mcp-auth.json, --store deployment secrets,
6
+ hostOnly, and egress. Read when wiring or debugging MCP auth.
7
+ ---
8
+
9
+ # Host MCP OAuth (mcp auth)
10
+
11
+ Use this skill when a human asks to authorize a remote MCP server on an
12
+ agentkit host, store credentials for Cursor-managed hosting, or debug
13
+ `host.mcp` auth failures for `oauth: true` connections.
14
+
15
+ Sibling skills: `framework-map` for layout, `create-agent` when scaffolding,
16
+ `debug` for unrelated local failures, `operate` for deploy/secrets ops.
17
+
18
+ Docs: `docs/guides/mcp-oauth.md`, `docs/reference/connections.md`,
19
+ `docs/reference/cli.md#mcp-oauth`.
20
+
21
+ ## When to use host OAuth vs account MCP
22
+
23
+ | Need | Use |
24
+ | --- | --- |
25
+ | Connector already in Cursor dashboard (Linear, Notion, …) | `defineConnection({ cursorAccount: true, servers: […] })` |
26
+ | Remote URL that speaks OAuth; host must hold tokens | `defineConnection({ url, oauth: true })` + this skill |
27
+ | Static bearer / API key | `headers` / env on a plain `{ url }` connection |
28
+
29
+ Prefer `hostOnly: true` when only host tools / channel handlers should
30
+ call the server (admin APIs, privileged writes). The model will not see
31
+ those tools.
32
+
33
+ ## Goal checklist
34
+
35
+ - [ ] `agent/mcp-connections/<name>.ts` with `url` + `oauth: true` (+ `hostOnly` if privileged)
36
+ - [ ] `hosting.secretNames` lists `MCP_OAUTH_<NAME>_{ACCESS_TOKEN,REFRESH_TOKEN,CLIENT_ID}` when you will `--store`
37
+ - [ ] Hosted engines that reach non-bootstrap hosts declare `hosting.egressDomains`
38
+ - [ ] Local: `agentkit mcp oauth <name>` succeeds
39
+ - [ ] Hosted: `agentkit mcp oauth <name> --store` then redeploy; `secrets list` shows the names
40
+
41
+ ## Phase 0 — Author the connection (agent)
42
+
43
+ ```ts
44
+ // agent/mcp-connections/inventory.ts
45
+ import { defineConnection } from "@cursor/july/connections";
46
+
47
+ export default defineConnection({
48
+ url: "https://mcp.example.com/inventory",
49
+ oauth: true,
50
+ hostOnly: true,
51
+ description: "Privileged inventory MCP; call via ctx.host.mcp only.",
52
+ });
53
+ ```
54
+
55
+ In `agent/agent.ts`:
56
+
57
+ ```ts
58
+ hosting: {
59
+ egressDomains: ["mcp.example.com"], // if hosted + non-bootstrap host
60
+ secretNames: [
61
+ "MCP_OAUTH_INVENTORY_ACCESS_TOKEN",
62
+ "MCP_OAUTH_INVENTORY_REFRESH_TOKEN",
63
+ "MCP_OAUTH_INVENTORY_CLIENT_ID",
64
+ ],
65
+ },
66
+ ```
67
+
68
+ Naming: connection file `inventory.ts` → env prefix `MCP_OAUTH_INVENTORY`.
69
+ Do **not** put `CURSOR_*` or hosted-storage names in `secretNames`.
70
+
71
+ Wire privileged calls through host tools:
72
+
73
+ ```ts
74
+ await ctx.host.mcp.callTool("inventory", "list_warehouses", {
75
+ region: input.region,
76
+ });
77
+ ```
78
+
79
+ Update instructions so the model does not hunt IDE MCP namespaces or
80
+ `mcp_auth` for `hostOnly` servers.
81
+
82
+ ## Phase 1 — Local authorize (agent + human)
83
+
84
+ ```bash
85
+ cd <agent-project>
86
+ agentkit mcp oauth <connection>
87
+ ```
88
+
89
+ Human completes the browser login. Callback:
90
+ `http://localhost:8787/callback`. Tokens:
91
+ `~/.config/agent-serve/mcp-auth.json` (or `$AGENT_SERVE_CONFIG_DIR`).
92
+
93
+ Already authorized → command prints that and exits 0. URL change →
94
+ re-run; resource URL binding drops the old entry.
95
+
96
+ ## Phase 2 — Store on the deployment (agent + human)
97
+
98
+ Requires `agentkit login` (or `CURSOR_API_KEY`) and deploy rights.
99
+
100
+ ```bash
101
+ agentkit mcp oauth <connection> --store [--slug <slug>] [--team <id>]
102
+ agentkit secrets list <slug>
103
+ agentkit deploy # or your usual deploy; secrets apply next deploy
104
+ ```
105
+
106
+ `--store` upserts access (required), refresh (if present), and client id
107
+ (if present). It does not remove unrelated secrets.
108
+
109
+ ## Phase 3 — Verify (agent)
110
+
111
+ Local:
112
+
113
+ ```bash
114
+ agentkit validate --dir .
115
+ agentkit call <tool-that-uses-host.mcp> --dir .
116
+ # or exercise the same path from the playground
117
+ ```
118
+
119
+ Hosted: confirm egress allowlist includes the MCP host, secrets exist,
120
+ redeploy completed, then exercise the same host tool once.
121
+
122
+ ## Failure table
123
+
124
+ | Symptom | Fix |
125
+ | --- | --- |
126
+ | `must be defineConnection({ url, oauth: true })` | Wrong connection name, or missing `oauth: true` |
127
+ | `Unknown MCP connection` | Filename under `agent/mcp-connections/` must match CLI arg |
128
+ | Callback hang | Free port 8787; finish browser flow on this machine |
129
+ | Hosted 401 / unauthorized | `secrets list`; re-run `--store`; redeploy |
130
+ | Model invents `mcp_auth` / IDE MCP | Connection is `hostOnly`; fix instructions + use host tools |
131
+ | Tokens ignored after URL edit | Expected; run `mcp oauth` again for the new resource URL |
132
+
133
+ ## Don't
134
+
135
+ - Don't put raw OAuth tokens in git or in connection source files
136
+ - Don't add `BUGBOTDB_URL` / `AGENT_SERVE_DEPLOYMENT_ID` to `secretNames`
137
+ - Don't use Bun for the CLI (use Node / `tsx`)
138
+ - Don't expect `--store` alone to restart a running engine; redeploy
@@ -51,7 +51,10 @@ import {
51
51
  cmdStop,
52
52
  type DeployCliOptions,
53
53
  } from "../internal/cli-deploy.js";
54
- import { CLI_COMMAND_NAME as CLI } from "../internal/distribution.js";
54
+ import {
55
+ CLI_COMMAND_NAME as CLI,
56
+ packageVersion,
57
+ } from "../internal/distribution.js";
55
58
  import { clearTerminal, stdoutPalette } from "../internal/terminal-style.js";
56
59
  import { cmdUpdate, warnIfOutdated } from "../internal/update-check.js";
57
60
  import type { AgentProject, AgentServeHandle } from "../types.js";
@@ -93,6 +96,7 @@ Usage:
93
96
  | --url <baseUrl> [--bearer-token <t>]
94
97
  ${CLI} mcp install [--name <serverName>] [--print] [--json]
95
98
  (same target flags as mcp)
99
+ ${CLI} mcp oauth <connection> [--dir <path>] [--store] [--slug <slug>]
96
100
  ${CLI} eval [--dir <path>] [evalId...] [--list] [--json]
97
101
  [--tag <tag>]... [--verbose] [--timeout-ms <n>]
98
102
  [--no-stream] [--no-wait]
@@ -106,6 +110,7 @@ Usage:
106
110
  ${CLI} logout
107
111
  ${CLI} whoami [--json]
108
112
  ${CLI} update
113
+ ${CLI} version
109
114
  ${CLI} deploy [--dir <path>] [--slug <slug> | --all] [--team <id>] [--json]
110
115
  [--no-wait] [--repo <url>] [--ref <ref>]
111
116
  [--path <agentPath>] [--cursor-events-repo <owner/name>]...
@@ -170,7 +175,9 @@ Commands:
170
175
  --url targets a running server. stdout is reserved for the
171
176
  MCP wire. \`mcp install\` writes the matching entry into
172
177
  ~/.cursor/mcp.json (--print to preview; --name to override
173
- the server name).
178
+ the server name). \`mcp oauth <connection>\` runs host PKCE
179
+ for defineConnection({ url, oauth: true }); \`--store\` upserts
180
+ MCP_OAUTH_<NAME>_* secrets on the hosted deployment.
174
181
  eval Run filesystem evals under evals/**/*.eval.ts (streams on a TTY).
175
182
  Default: ephemeral local harness against evals on disk. Pass
176
183
  --prod to kick off the batch on the team's hosted deployment
@@ -194,6 +201,7 @@ Commands:
194
201
  whoami Show which Cursor account powers this host (and why).
195
202
  update Upgrade this install to the latest published version (detects
196
203
  global npm/pnpm/yarn/bun installs and project dependencies).
204
+ version Print the installed ${CLI} version and exit (also --version / -V).
197
205
  deploy Deploy onto Cursor's managed agent-serve hosting, then wait for
198
206
  the engine to come up. --repo/--ref/--path stay available;
199
207
  inside an agent git checkout they are inferred when omitted
@@ -224,6 +232,7 @@ Agent loop:
224
232
  ${CLI} dev .
225
233
 
226
234
  Options:
235
+ --version, -V Print the installed version and exit.
227
236
  --dir Project root (defaults to the current directory).
228
237
  --port Port to listen on (default 3000).
229
238
  --host Host to bind (default 127.0.0.1).
@@ -354,6 +363,10 @@ async function main(argv: string[]): Promise<number> {
354
363
  process.stdout.write(HELP);
355
364
  return command === undefined ? 1 : 0;
356
365
  }
366
+ if (command === "version" || command === "--version" || command === "-V") {
367
+ process.stdout.write(`${CLI} ${packageVersion()}\n`);
368
+ return 0;
369
+ }
357
370
 
358
371
  // `session --events` means "dump raw events" (boolean). Rewrite so it does
359
372
  // not collide with run/trajectory's `--events <file.ndjson>` string flag.
@@ -409,6 +422,7 @@ async function main(argv: string[]): Promise<number> {
409
422
  "mount-path": { type: "string" },
410
423
  "base-url": { type: "string" },
411
424
  print: { type: "boolean", default: false },
425
+ store: { type: "boolean", default: false },
412
426
  transport: { type: "string" },
413
427
  prefix: { type: "string" },
414
428
  "no-prefix": { type: "boolean", default: false },
@@ -508,7 +522,8 @@ async function main(argv: string[]): Promise<number> {
508
522
 
509
523
  case "mcp": {
510
524
  const { cmdMcp } = await import("../internal/cli-mcp.js");
511
- return cmdMcp(positionals[0], {
525
+ const mcpSub = positionals[0];
526
+ return cmdMcp(mcpSub, {
512
527
  dir,
513
528
  slug: values.slug,
514
529
  team: values.team,
@@ -519,6 +534,8 @@ async function main(argv: string[]): Promise<number> {
519
534
  name: values.name,
520
535
  print: values.print,
521
536
  json: values.json,
537
+ store: values.store,
538
+ oauthConnection: mcpSub === "oauth" ? positionals[1] : undefined,
522
539
  });
523
540
  }
524
541
 
@@ -116,6 +116,7 @@ const githubPullRequestPayloadSchema: z.ZodType<GitHubPullRequestPayload> =
116
116
  title: z.string(),
117
117
  body: z.string().nullable().optional(),
118
118
  draft: z.boolean().optional(),
119
+ merged: z.boolean().optional(),
119
120
  user: z
120
121
  .object({
121
122
  login: z.string().min(1).optional(),
@@ -330,6 +331,7 @@ export function parseGitHubPullRequestEvent(args: {
330
331
  ? {}
331
332
  : { baseSha: payload.pull_request.base.sha }),
332
333
  draft: payload.pull_request.draft ?? false,
334
+ merged: payload.pull_request.merged === true,
333
335
  ...(payload.pull_request.user?.login === undefined
334
336
  ? {}
335
337
  : { authorLogin: payload.pull_request.user.login }),
@@ -148,6 +148,8 @@ export type GitHubPullRequestPayload = {
148
148
  title: string;
149
149
  body?: string | null;
150
150
  draft?: boolean;
151
+ /** Present on real webhook payloads; false/undefined when not merged. */
152
+ merged?: boolean;
151
153
  user?: {
152
154
  login?: string;
153
155
  id?: number;
@@ -182,6 +184,8 @@ export interface GitHubPullRequestEvent {
182
184
  baseRef: string;
183
185
  baseSha?: string;
184
186
  draft: boolean;
187
+ /** True when the PR was merged (from the webhook payload). */
188
+ merged: boolean;
185
189
  authorLogin?: string;
186
190
  }
187
191
 
@@ -167,6 +167,8 @@ export async function dispatchCursorSlackEnvelope(args: {
167
167
  credentials: runtime.credentials,
168
168
  host: args.host,
169
169
  log,
170
+ evalCommandsEnabled: runtime.options.evalCommands !== false,
171
+ cursorAgentName: envelope.agentName,
170
172
  ...(args.hasContinuationSession === undefined
171
173
  ? {}
172
174
  : { hasContinuationSession: args.hasContinuationSession }),
@@ -6,6 +6,10 @@ import type { HostContext, JsonValue, SendMessageFn } from "../../types.js";
6
6
  import { buildSlackBinding, slackContinuationKey } from "./api.js";
7
7
  import { firstLineTitle, maybeSetThreadTitle } from "./assistant.js";
8
8
  import type { ChannelPostWatcher } from "./channel-watch.js";
9
+ import {
10
+ handleSlackEvalDirective,
11
+ parseSlackEvalDirective,
12
+ } from "./eval-directive.js";
9
13
  import type { EventIdDedupe } from "./event-id-dedupe.js";
10
14
  import {
11
15
  formatSlackContextBlock,
@@ -82,6 +86,13 @@ export interface SlackInboundDispatchDeps {
82
86
  ) => Promise<string | undefined>;
83
87
  /** Cancel the active turn + clear coalesced nudges for this continuation. */
84
88
  interruptContinuation?: (continuationToken: string) => Promise<boolean>;
89
+ /**
90
+ * When false, skip the built-in `eval` directive. Defaults to true when
91
+ * `host.evals` is present.
92
+ */
93
+ evalCommandsEnabled?: boolean;
94
+ /** cursorAccount agent name stripped before `eval` directive parsing. */
95
+ cursorAgentName?: string;
85
96
  }
86
97
 
87
98
  function triggeringMergeMessage(
@@ -114,6 +125,12 @@ export async function dispatchInboundMessage(args: {
114
125
  interruptContinuation?: (continuationToken: string) => Promise<boolean>;
115
126
  log?: SlackLog;
116
127
  eventId?: string;
128
+ evalCommandsEnabled?: boolean;
129
+ /**
130
+ * cursorAccount agent display name — stripped before parsing `eval …`
131
+ * (`OncallBuddy eval list`).
132
+ */
133
+ cursorAgentName?: string;
117
134
  }): Promise<void> {
118
135
  const { message, kind } = args;
119
136
  const log = args.log ?? defaultSlackLog;
@@ -168,6 +185,34 @@ export async function dispatchInboundMessage(args: {
168
185
  return;
169
186
  }
170
187
 
188
+ // Built-in eval directive: run playground batches without an agent turn.
189
+ const evalCommandsEnabled = args.evalCommandsEnabled !== false;
190
+ const evalParseOptions =
191
+ args.cursorAgentName === undefined || args.cursorAgentName === ""
192
+ ? undefined
193
+ : { agentName: args.cursorAgentName };
194
+ const evalDirective =
195
+ evalCommandsEnabled && args.host.evals !== undefined
196
+ ? (parseSlackEvalDirective(message.text, evalParseOptions) ??
197
+ parseSlackEvalDirective(message.markdown, evalParseOptions))
198
+ : null;
199
+ if (evalDirective !== null && args.host.evals !== undefined) {
200
+ log.info("eval directive", {
201
+ kind,
202
+ channel: message.channelId,
203
+ thread_ts: message.threadTs,
204
+ continuation,
205
+ event_id: args.eventId,
206
+ directive: evalDirective.kind,
207
+ });
208
+ await handleSlackEvalDirective({
209
+ directive: evalDirective,
210
+ evals: args.host.evals,
211
+ binding,
212
+ });
213
+ return;
214
+ }
215
+
171
216
  if (message.ts === message.threadTs) {
172
217
  await maybeSetThreadTitle({
173
218
  botToken: args.credentials?.botToken,
@@ -393,6 +438,8 @@ export function dispatchInboundFromEventBody(
393
438
  credentials: deps.credentials,
394
439
  host: deps.host,
395
440
  log,
441
+ evalCommandsEnabled: deps.evalCommandsEnabled,
442
+ cursorAgentName: deps.cursorAgentName,
396
443
  ...(deps.hasContinuationSession === undefined
397
444
  ? {}
398
445
  : { hasContinuationSession: deps.hasContinuationSession }),
@@ -431,6 +478,8 @@ export function dispatchInboundFromEventBody(
431
478
  credentials: deps.credentials,
432
479
  host: deps.host,
433
480
  log,
481
+ evalCommandsEnabled: deps.evalCommandsEnabled,
482
+ cursorAgentName: deps.cursorAgentName,
434
483
  ...(deps.hasContinuationSession === undefined
435
484
  ? {}
436
485
  : { hasContinuationSession: deps.hasContinuationSession }),