@cursor/july 0.1.37 → 0.1.39

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 (122) hide show
  1. package/README.md +3 -1
  2. package/dist/bin/agent-serve.js +0 -0
  3. package/dist/channels/slack/api.d.ts +5 -0
  4. package/dist/channels/slack/api.d.ts.map +1 -1
  5. package/dist/channels/slack/api.js +57 -18
  6. package/dist/channels/slack/constants.d.ts +2 -0
  7. package/dist/channels/slack/constants.d.ts.map +1 -1
  8. package/dist/channels/slack/constants.js +2 -0
  9. package/dist/channels/slack/post-update-delivery.d.ts +85 -0
  10. package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
  11. package/dist/connections.d.ts +14 -2
  12. package/dist/connections.d.ts.map +1 -1
  13. package/dist/connections.js +13 -1
  14. package/dist/docs/404.html +3 -3
  15. package/dist/docs/ab.html +6 -6
  16. package/dist/docs/assets/{app.Bu9SRvZ9.js → app.BQ8Hihdf.js} +1 -1
  17. package/dist/docs/assets/chunks/@localSearchIndexroot.B-VTH4As.js +1 -0
  18. package/dist/docs/assets/chunks/{VPLocalSearchBox.D9u9czHO.js → VPLocalSearchBox.BBCr8Yuy.js} +2 -2
  19. package/dist/docs/assets/chunks/theme.CK_NiGC-.js +2 -0
  20. package/dist/docs/assets/{example-agents_weather-agent.md.CrGZ0SqR.js → example-agents_weather-agent.md.DC3lINjo.js} +5 -6
  21. package/dist/docs/assets/{reference_connections.md.DGqAsFXb.js → reference_connections.md.CSQiNzre.js} +11 -2
  22. package/dist/docs/assets/{reference_connections.md.DGqAsFXb.lean.js → reference_connections.md.CSQiNzre.lean.js} +1 -1
  23. package/dist/docs/assets/{style.Bj-IXa5W.css → style.CK3hwt6q.css} +1 -1
  24. package/dist/docs/building-with-agents.html +6 -6
  25. package/dist/docs/concepts.html +6 -6
  26. package/dist/docs/deployment.html +6 -6
  27. package/dist/docs/evals.html +6 -6
  28. package/dist/docs/example-agents/approval-buddy.html +6 -6
  29. package/dist/docs/example-agents/benny.html +6 -6
  30. package/dist/docs/example-agents/bugbot.html +6 -6
  31. package/dist/docs/example-agents/codebase-wiki.html +6 -6
  32. package/dist/docs/example-agents/codeowners-review.html +6 -6
  33. package/dist/docs/example-agents/concierge.html +6 -6
  34. package/dist/docs/example-agents/fsd.html +6 -6
  35. package/dist/docs/example-agents/index.html +6 -6
  36. package/dist/docs/example-agents/knowledge-base.html +6 -6
  37. package/dist/docs/example-agents/oncall.html +6 -6
  38. package/dist/docs/example-agents/security-reviewer.html +6 -6
  39. package/dist/docs/example-agents/slack-agent.html +6 -6
  40. package/dist/docs/example-agents/weather-agent.html +10 -11
  41. package/dist/docs/guides/agent-to-agent.html +6 -6
  42. package/dist/docs/guides/cloud-runtime.html +6 -6
  43. package/dist/docs/guides/github.html +6 -6
  44. package/dist/docs/guides/human-in-the-loop.html +6 -6
  45. package/dist/docs/guides/mcp-oauth.html +6 -6
  46. package/dist/docs/guides/slack.html +6 -6
  47. package/dist/docs/guides/webhooks.html +6 -6
  48. package/dist/docs/hashmap.json +1 -1
  49. package/dist/docs/hillclimbing.html +6 -6
  50. package/dist/docs/index.html +6 -6
  51. package/dist/docs/quickstart.html +6 -6
  52. package/dist/docs/reference/agent-config.html +6 -6
  53. package/dist/docs/reference/artifacts.html +6 -6
  54. package/dist/docs/reference/channels.html +6 -6
  55. package/dist/docs/reference/cli.html +6 -6
  56. package/dist/docs/reference/connections.html +17 -8
  57. package/dist/docs/reference/hooks.html +6 -6
  58. package/dist/docs/reference/http-api.html +6 -6
  59. package/dist/docs/reference/instructions.html +6 -6
  60. package/dist/docs/reference/playground.html +5 -5
  61. package/dist/docs/reference/project-layout.html +6 -6
  62. package/dist/docs/reference/prompt.html +6 -6
  63. package/dist/docs/reference/schedules.html +6 -6
  64. package/dist/docs/reference/sessions.html +6 -6
  65. package/dist/docs/reference/skills.html +6 -6
  66. package/dist/docs/reference/subagents.html +6 -6
  67. package/dist/docs/reference/tools.html +6 -6
  68. package/dist/docs/scaffolding-agents.html +5 -5
  69. package/dist/docs/storage.html +6 -6
  70. package/dist/docs/troubleshooting.html +5 -5
  71. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  72. package/dist/files-backends/cursor-hosted.js +13 -2
  73. package/dist/internal/discovery.d.ts.map +1 -1
  74. package/dist/internal/discovery.js +95 -2
  75. package/dist/internal/framework-storage-selection.d.ts +37 -0
  76. package/dist/internal/framework-storage-selection.d.ts.map +1 -0
  77. package/dist/internal/framework-storage-selection.js +35 -0
  78. package/dist/internal/inline-stdio-define-stub.d.ts +3 -0
  79. package/dist/internal/inline-stdio-define-stub.d.ts.map +1 -0
  80. package/dist/internal/inline-stdio-define-stub.js +4 -0
  81. package/dist/internal/inline-stdio-mcp.d.ts +32 -0
  82. package/dist/internal/inline-stdio-mcp.d.ts.map +1 -0
  83. package/dist/internal/inline-stdio-mcp.js +180 -0
  84. package/dist/internal/json-dir-store.d.ts +32 -0
  85. package/dist/internal/json-dir-store.d.ts.map +1 -0
  86. package/dist/internal/package-inline-stdio.d.ts +12 -0
  87. package/dist/internal/package-inline-stdio.d.ts.map +1 -0
  88. package/dist/internal/package-inline-stdio.js +109 -0
  89. package/dist/internal/persistence-coordinator.d.ts +127 -0
  90. package/dist/internal/persistence-coordinator.d.ts.map +1 -0
  91. package/dist/internal/session-engine.d.ts.map +1 -1
  92. package/dist/internal/session-engine.js +9 -2
  93. package/dist/multi-tenant.d.ts +80 -0
  94. package/dist/multi-tenant.d.ts.map +1 -0
  95. package/dist/multi-tenant.js +69 -0
  96. package/dist/persistence.d.ts +184 -0
  97. package/dist/persistence.d.ts.map +1 -0
  98. package/dist/playground/assets/index-BRhBbnd4.css +1 -0
  99. package/dist/playground/assets/{index-DRtusTV1.js → index-_2WKw9a-.js} +43 -43
  100. package/dist/playground/index.html +2 -2
  101. package/dist/types.d.ts +20 -1
  102. package/dist/types.d.ts.map +1 -1
  103. package/dist/types.js +4 -0
  104. package/docs/example-agents/weather-agent.md +25 -22
  105. package/docs/reference/connections.md +17 -0
  106. package/package.json +3 -1
  107. package/skills/framework-map/SKILL.md +1 -1
  108. package/src/channels/slack/api.ts +72 -20
  109. package/src/channels/slack/constants.ts +2 -0
  110. package/src/connections.ts +18 -1
  111. package/src/files-backends/cursor-hosted.ts +13 -2
  112. package/src/internal/discovery.ts +111 -1
  113. package/src/internal/framework-storage-selection.ts +45 -0
  114. package/src/internal/inline-stdio-define-stub.ts +4 -0
  115. package/src/internal/inline-stdio-mcp.ts +229 -0
  116. package/src/internal/package-inline-stdio.ts +121 -0
  117. package/src/internal/session-engine.ts +9 -2
  118. package/src/types.ts +27 -0
  119. package/dist/docs/assets/chunks/@localSearchIndexroot.pVFW7oJd.js +0 -1
  120. package/dist/docs/assets/chunks/theme.Bc61GzOf.js +0 -2
  121. package/dist/playground/assets/index-DZp6n4bv.css +0 -1
  122. /package/dist/docs/assets/{example-agents_weather-agent.md.CrGZ0SqR.lean.js → example-agents_weather-agent.md.DC3lINjo.lean.js} +0 -0
@@ -45,7 +45,9 @@ import {
45
45
  type DiscoveredTool,
46
46
  type HookDefinition,
47
47
  type HttpChannelDefinition,
48
+ type InlineStdioConnectionConfig,
48
49
  type InstructionsDefinition,
50
+ isInlineStdioConnection,
49
51
  MAX_RUNNING_TURNS_LIMIT,
50
52
  type ModelSetting,
51
53
  normalizeCursorAccountServers,
@@ -75,6 +77,7 @@ import {
75
77
  } from "./hosting.js";
76
78
  import { toolInputJsonSchema } from "./json-schema.js";
77
79
  import { MCP_ENDPOINT_PATH, mcpEndpointToolNames } from "./mcp-endpoint.js";
80
+ import { packageInlineStdioConnection } from "./package-inline-stdio.js";
78
81
  import { withIdentityPreamble } from "./workspace.js";
79
82
 
80
83
  const MODULE_EXTENSIONS = new Set([".ts", ".mts", ".js", ".mjs"]);
@@ -927,6 +930,19 @@ function hasAuthorizationHeader(
927
930
  );
928
931
  }
929
932
 
933
+ function connectionHasSpawnOrRemote(connection: ConnectionDefinition): boolean {
934
+ if ("cursorAccount" in connection && connection.cursorAccount === true) {
935
+ return true;
936
+ }
937
+ if ("agent" in connection && !isBlank(connection.agent)) {
938
+ return true;
939
+ }
940
+ if ("url" in connection && !isBlank(connection.url)) {
941
+ return true;
942
+ }
943
+ return "command" in connection && !isBlank(connection.command);
944
+ }
945
+
930
946
  function connectionTransport(
931
947
  connection: ConnectionDefinition
932
948
  ): ConnectionTransport | undefined {
@@ -1063,6 +1079,31 @@ async function loadConnections(
1063
1079
  continue;
1064
1080
  }
1065
1081
  const connection = definition as ConnectionDefinition;
1082
+ if (
1083
+ isInlineStdioConnection(connection) &&
1084
+ connectionHasSpawnOrRemote(connection)
1085
+ ) {
1086
+ ctx.diagnostics.push({
1087
+ severity: "error",
1088
+ path: displayPath(ctx, entryPath),
1089
+ message:
1090
+ "MCP connection `tools` cannot be combined with `command`, `url`, `agent`, or `cursorAccount`. Author TypeScript tools, or point at an existing server, not both.",
1091
+ });
1092
+ continue;
1093
+ }
1094
+ if (isInlineStdioConnection(connection)) {
1095
+ const packed = await loadInlineStdioConnection(
1096
+ ctx,
1097
+ name,
1098
+ entryPath,
1099
+ connection
1100
+ );
1101
+ if (packed === undefined) {
1102
+ continue;
1103
+ }
1104
+ connections.push(packed);
1105
+ continue;
1106
+ }
1066
1107
  if (
1067
1108
  "url" in connection &&
1068
1109
  connection.oauth === true &&
@@ -1095,7 +1136,7 @@ async function loadConnections(
1095
1136
  severity: "error",
1096
1137
  path: displayPath(ctx, entryPath),
1097
1138
  message:
1098
- "MCP connections require a `url` (http/sse), a `command` (stdio), an `agent` (peer agent slug on the same serve host), or `cursorAccount: true` (the Cursor account's MCP connectors).",
1139
+ "MCP connections require a `url` (http/sse), a `command` (stdio), `tools` (TypeScript packaged as stdio), an `agent` (peer agent slug on the same serve host), or `cursorAccount: true` (the Cursor account's MCP connectors).",
1099
1140
  });
1100
1141
  continue;
1101
1142
  }
@@ -1131,6 +1172,75 @@ async function loadConnections(
1131
1172
  return connections;
1132
1173
  }
1133
1174
 
1175
+ async function loadInlineStdioConnection(
1176
+ ctx: DiscoveryContext,
1177
+ name: string,
1178
+ entryPath: string,
1179
+ connection: InlineStdioConnectionConfig & {
1180
+ description?: string;
1181
+ hostOnly?: boolean;
1182
+ }
1183
+ ): Promise<DiscoveredConnection | undefined> {
1184
+ const tools = connection.tools;
1185
+ const names = Object.keys(tools);
1186
+ if (names.length === 0) {
1187
+ ctx.diagnostics.push({
1188
+ severity: "error",
1189
+ path: displayPath(ctx, entryPath),
1190
+ message: "MCP connection `tools` must declare at least one tool.",
1191
+ });
1192
+ return undefined;
1193
+ }
1194
+ for (const toolName of names) {
1195
+ if (!NAME_PATTERN.test(toolName)) {
1196
+ ctx.diagnostics.push({
1197
+ severity: "error",
1198
+ path: displayPath(ctx, entryPath),
1199
+ message: `MCP tool name "${toolName}" must match ${NAME_PATTERN}.`,
1200
+ });
1201
+ return undefined;
1202
+ }
1203
+ const tool = tools[toolName];
1204
+ if (tool === undefined || isBlank(tool.description)) {
1205
+ ctx.diagnostics.push({
1206
+ severity: "error",
1207
+ path: displayPath(ctx, entryPath),
1208
+ message: `MCP tool "${toolName}" requires a non-empty description.`,
1209
+ });
1210
+ return undefined;
1211
+ }
1212
+ if (typeof tool.execute !== "function") {
1213
+ ctx.diagnostics.push({
1214
+ severity: "error",
1215
+ path: displayPath(ctx, entryPath),
1216
+ message: `MCP tool "${toolName}" requires an execute(input) function.`,
1217
+ });
1218
+ return undefined;
1219
+ }
1220
+ }
1221
+ try {
1222
+ const transport = await packageInlineStdioConnection({
1223
+ name,
1224
+ entryPath,
1225
+ tools,
1226
+ absWorkingDir: ctx.rootDir,
1227
+ });
1228
+ return {
1229
+ name,
1230
+ transport,
1231
+ description: connection.description,
1232
+ hostOnly: connection.hostOnly,
1233
+ };
1234
+ } catch (error) {
1235
+ ctx.diagnostics.push({
1236
+ severity: "error",
1237
+ path: displayPath(ctx, entryPath),
1238
+ message: `Failed to package inline stdio MCP connection: ${errorMessage(error)}`,
1239
+ });
1240
+ return undefined;
1241
+ }
1242
+ }
1243
+
1134
1244
  // ============================================================================
1135
1245
  // Subagents
1136
1246
  // ============================================================================
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Which backend framework session state is mirrored to.
3
+ *
4
+ * Framework state — session records, event chunks, the continuation index —
5
+ * is how a session outlives the process that served it. Today that mirror
6
+ * exists only when the agent author wrote `agent/storage.ts`, which makes
7
+ * platform durability an agent-level decision: of the first-party agents,
8
+ * some choose `cursorHostedStorage()` and some choose `fileKv()` (pod-local
9
+ * disk, lost on pod replacement), and one selects between them at runtime by
10
+ * reading the deployment-id environment variable itself.
11
+ *
12
+ * That selection is a hosting-mode question, not an authoring question, and
13
+ * this module is where it will move. It deliberately reproduces today's
14
+ * behavior exactly for now — authored definition or nothing — so the change
15
+ * that makes framework durability automatic is a change to
16
+ * {@link selectFrameworkStorageDefinition} alone, reviewable on its own and
17
+ * revertable without touching the engine.
18
+ *
19
+ * Local disk is not part of this decision. `<stateRoot>/sessions/<id>/`
20
+ * already holds `session.json` and `events.ndjson` unconditionally, through
21
+ * the session store; what this selects is the durable mirror *beyond* the
22
+ * pod's filesystem.
23
+ */
24
+
25
+ import type { StorageDefinition } from "../storage.js";
26
+
27
+ export interface FrameworkStorageSelectionInput {
28
+ /** `agent/storage.ts`, when the author wrote one. */
29
+ readonly authored: StorageDefinition | undefined;
30
+ }
31
+
32
+ /**
33
+ * Pick the backend for framework session state.
34
+ *
35
+ * Returns `undefined` when there is no mirror, in which case the pod's local
36
+ * disk is the only copy — the condition F0 exists to remove for hosted
37
+ * deployments.
38
+ */
39
+ export function selectFrameworkStorageDefinition(
40
+ input: FrameworkStorageSelectionInput
41
+ ): StorageDefinition | undefined {
42
+ // Today: the author decides, or there is no mirror. Hosting-mode selection
43
+ // replaces this body; every caller already routes through here.
44
+ return input.authored;
45
+ }
@@ -0,0 +1,4 @@
1
+ /** Bundled stand-in for `@anysphere/agent-serve/connections` / `@cursor/july/connections`. */
2
+ export function defineConnection<T>(config: T): T {
3
+ return config;
4
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Generic newline-delimited JSON-RPC stdio MCP server for packaged
3
+ * `defineConnection({ tools })` modules. Bundled into `node -e` so a
4
+ * repo-less cloud VM can spawn it. Keep this file free of framework
5
+ * barrels and the official MCP SDK.
6
+ */
7
+
8
+ const PROTOCOL_VERSION = "2025-03-26";
9
+
10
+ type JsonRpcId = string | number | null;
11
+
12
+ type JsonRpc = {
13
+ jsonrpc?: string;
14
+ id?: JsonRpcId;
15
+ method?: string;
16
+ params?: Record<string, unknown>;
17
+ };
18
+
19
+ type InlineTool = {
20
+ description: string;
21
+ inputSchema?: {
22
+ safeParse?: (value: unknown) => {
23
+ success: boolean;
24
+ data?: unknown;
25
+ error?: { issues?: Array<{ path: unknown[]; message: string }> };
26
+ };
27
+ };
28
+ execute: (input: Record<string, unknown>) => unknown | Promise<unknown>;
29
+ };
30
+
31
+ export type InlineStdioCatalogEntry = {
32
+ description: string;
33
+ inputSchema: Record<string, unknown>;
34
+ };
35
+
36
+ export type InlineStdioDefinition = {
37
+ tools?: Record<string, InlineTool>;
38
+ };
39
+
40
+ function write(message: Record<string, unknown>): void {
41
+ process.stdout.write(`${JSON.stringify(message)}\n`);
42
+ }
43
+
44
+ function stringifyResult(result: unknown): string {
45
+ if (typeof result === "string") {
46
+ return result;
47
+ }
48
+ return JSON.stringify(result);
49
+ }
50
+
51
+ function isMcpEnvelope(
52
+ result: unknown
53
+ ): result is { content: unknown[]; isError?: boolean } {
54
+ return (
55
+ typeof result === "object" &&
56
+ result !== null &&
57
+ "content" in result &&
58
+ Array.isArray((result as { content: unknown }).content)
59
+ );
60
+ }
61
+
62
+ function validateInput(
63
+ tool: InlineTool,
64
+ args: unknown
65
+ ):
66
+ | { ok: true; value: Record<string, unknown> }
67
+ | { ok: false; message: string } {
68
+ const record =
69
+ typeof args === "object" && args !== null
70
+ ? (args as Record<string, unknown>)
71
+ : {};
72
+ const schema = tool.inputSchema;
73
+ if (schema === undefined || typeof schema.safeParse !== "function") {
74
+ return { ok: true, value: record };
75
+ }
76
+ const parsed = schema.safeParse(args ?? {});
77
+ if (parsed.success) {
78
+ const value =
79
+ typeof parsed.data === "object" && parsed.data !== null
80
+ ? (parsed.data as Record<string, unknown>)
81
+ : record;
82
+ return { ok: true, value };
83
+ }
84
+ const issues = parsed.error?.issues ?? [];
85
+ const message =
86
+ issues
87
+ .map((issue) => {
88
+ const path = issue.path.join(".");
89
+ return path === "" ? issue.message : `${path}: ${issue.message}`;
90
+ })
91
+ .join("; ") || "invalid input";
92
+ return { ok: false, message };
93
+ }
94
+
95
+ async function handle(
96
+ message: JsonRpc,
97
+ serverName: string,
98
+ tools: Record<string, InlineTool>,
99
+ catalog: Record<string, InlineStdioCatalogEntry>
100
+ ): Promise<void> {
101
+ const method = message.method;
102
+ if (typeof method !== "string" || !("id" in message)) {
103
+ return;
104
+ }
105
+ const id = message.id;
106
+ const params =
107
+ typeof message.params === "object" && message.params !== null
108
+ ? message.params
109
+ : {};
110
+
111
+ try {
112
+ if (method === "initialize") {
113
+ const requested = params.protocolVersion;
114
+ write({
115
+ jsonrpc: "2.0",
116
+ id,
117
+ result: {
118
+ protocolVersion:
119
+ typeof requested === "string" ? requested : PROTOCOL_VERSION,
120
+ capabilities: { tools: {} },
121
+ serverInfo: { name: serverName, version: "0.1.0" },
122
+ },
123
+ });
124
+ return;
125
+ }
126
+ if (method === "ping") {
127
+ write({ jsonrpc: "2.0", id, result: {} });
128
+ return;
129
+ }
130
+ if (method === "tools/list") {
131
+ write({
132
+ jsonrpc: "2.0",
133
+ id,
134
+ result: {
135
+ tools: Object.entries(catalog).map(([name, entry]) => ({
136
+ name,
137
+ description: entry.description,
138
+ inputSchema: entry.inputSchema,
139
+ })),
140
+ },
141
+ });
142
+ return;
143
+ }
144
+ if (method === "tools/call") {
145
+ const name = params.name;
146
+ const tool = typeof name === "string" ? tools[name] : undefined;
147
+ if (tool === undefined) {
148
+ write({
149
+ jsonrpc: "2.0",
150
+ id,
151
+ result: {
152
+ content: [{ type: "text", text: `Unknown tool: ${String(name)}` }],
153
+ isError: true,
154
+ },
155
+ });
156
+ return;
157
+ }
158
+ const checked = validateInput(tool, params.arguments);
159
+ if (!checked.ok) {
160
+ write({
161
+ jsonrpc: "2.0",
162
+ id,
163
+ result: {
164
+ content: [{ type: "text", text: checked.message }],
165
+ isError: true,
166
+ },
167
+ });
168
+ return;
169
+ }
170
+ const result = await tool.execute(checked.value);
171
+ if (isMcpEnvelope(result)) {
172
+ write({ jsonrpc: "2.0", id, result });
173
+ return;
174
+ }
175
+ write({
176
+ jsonrpc: "2.0",
177
+ id,
178
+ result: {
179
+ content: [{ type: "text", text: stringifyResult(result) }],
180
+ },
181
+ });
182
+ return;
183
+ }
184
+ write({
185
+ jsonrpc: "2.0",
186
+ id,
187
+ error: { code: -32601, message: `Method not found: ${method}` },
188
+ });
189
+ } catch (error) {
190
+ write({
191
+ jsonrpc: "2.0",
192
+ id,
193
+ error: {
194
+ code: -32603,
195
+ message: error instanceof Error ? error.message : String(error),
196
+ },
197
+ });
198
+ }
199
+ }
200
+
201
+ export function serveInlineStdioMcp(
202
+ serverName: string,
203
+ definition: InlineStdioDefinition,
204
+ catalog: Record<string, InlineStdioCatalogEntry>
205
+ ): void {
206
+ const tools = definition.tools ?? {};
207
+ let buffer = "";
208
+ process.stdin.setEncoding("utf8");
209
+ process.stdin.on("data", (chunk) => {
210
+ buffer += chunk;
211
+ let newline = buffer.indexOf("\n");
212
+ while (newline !== -1) {
213
+ const line = buffer.slice(0, newline).replace(/\r$/, "");
214
+ buffer = buffer.slice(newline + 1);
215
+ if (line.length > 0) {
216
+ try {
217
+ void handle(JSON.parse(line) as JsonRpc, serverName, tools, catalog);
218
+ } catch (error) {
219
+ process.stderr.write(
220
+ `${serverName}: failed to parse MCP message: ${
221
+ error instanceof Error ? error.message : String(error)
222
+ }\n`
223
+ );
224
+ }
225
+ }
226
+ newline = buffer.indexOf("\n");
227
+ }
228
+ });
229
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Package `defineConnection({ tools })` into a `node -e` stdio transport a
3
+ * repo-less cloud VM can spawn.
4
+ */
5
+
6
+ import { existsSync } from "node:fs";
7
+ import { createRequire } from "node:module";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import * as esbuild from "esbuild";
11
+ import type {
12
+ ConnectionTransport,
13
+ InlineStdioConnectionConfig,
14
+ JsonSchemaObject,
15
+ } from "../types.js";
16
+ import type { InlineStdioCatalogEntry } from "./inline-stdio-mcp.js";
17
+ import { toolInputJsonSchema } from "./json-schema.js";
18
+
19
+ const require = createRequire(import.meta.url);
20
+
21
+ const CONNECTION_SPECIFIERS = new Set([
22
+ "@anysphere/agent-serve/connections",
23
+ "@cursor/july/connections",
24
+ "@cursor/agentkit/connections",
25
+ ]);
26
+
27
+ function sibling(basename: string): string {
28
+ const dir = fileURLToPath(new URL(".", import.meta.url));
29
+ const ts = join(dir, `${basename}.ts`);
30
+ return existsSync(ts) ? ts : join(dir, `${basename}.js`);
31
+ }
32
+
33
+ function jsonSchemaCatalog(
34
+ tools: InlineStdioConnectionConfig["tools"]
35
+ ): Record<string, InlineStdioCatalogEntry> {
36
+ const catalog: Record<string, InlineStdioCatalogEntry> = {};
37
+ for (const [name, tool] of Object.entries(tools)) {
38
+ catalog[name] = {
39
+ description: tool.description,
40
+ inputSchema: toolInputJsonSchema(tool.inputSchema) as JsonSchemaObject,
41
+ };
42
+ }
43
+ return catalog;
44
+ }
45
+
46
+ export async function packageInlineStdioConnection(options: {
47
+ name: string;
48
+ entryPath: string;
49
+ tools: InlineStdioConnectionConfig["tools"];
50
+ absWorkingDir: string;
51
+ }): Promise<ConnectionTransport> {
52
+ const runtimePath = sibling("inline-stdio-mcp");
53
+ const stubPath = sibling("inline-stdio-define-stub");
54
+ const catalog = jsonSchemaCatalog(options.tools);
55
+ const result = await esbuild.build({
56
+ entryPoints: ["inline-stdio-entry"],
57
+ bundle: true,
58
+ write: false,
59
+ platform: "node",
60
+ format: "esm",
61
+ target: "node22",
62
+ minify: true,
63
+ legalComments: "none",
64
+ absWorkingDir: options.absWorkingDir,
65
+ alias: {
66
+ zod: require.resolve("zod"),
67
+ },
68
+ plugins: [
69
+ {
70
+ name: "inline-stdio-entry",
71
+ setup(build) {
72
+ build.onResolve({ filter: /^inline-stdio-entry$/ }, () => ({
73
+ path: "inline-stdio-entry",
74
+ namespace: "inline-stdio",
75
+ }));
76
+ build.onLoad({ filter: /.*/, namespace: "inline-stdio" }, () => ({
77
+ contents: `import definition from ${JSON.stringify(options.entryPath)};
78
+ import { serveInlineStdioMcp } from ${JSON.stringify(runtimePath)};
79
+ serveInlineStdioMcp(
80
+ ${JSON.stringify(options.name)},
81
+ definition,
82
+ ${JSON.stringify(catalog)}
83
+ );
84
+ `,
85
+ resolveDir: dirname(options.entryPath),
86
+ loader: "ts",
87
+ }));
88
+ },
89
+ },
90
+ {
91
+ name: "inline-stdio-connection-stub",
92
+ setup(build) {
93
+ build.onResolve({ filter: /.*/ }, (args) => {
94
+ if (CONNECTION_SPECIFIERS.has(args.path)) {
95
+ return { path: stubPath };
96
+ }
97
+ return undefined;
98
+ });
99
+ },
100
+ },
101
+ ],
102
+ });
103
+
104
+ const file = result.outputFiles?.[0];
105
+ if (file === undefined) {
106
+ throw new Error(
107
+ `Failed to package inline stdio MCP connection "${options.name}": esbuild produced no output.`
108
+ );
109
+ }
110
+ const source = file.text.replace(/^#!.*\r?\n/, "");
111
+ if (source.trim() === "") {
112
+ throw new Error(
113
+ `Failed to package inline stdio MCP connection "${options.name}": empty bundle.`
114
+ );
115
+ }
116
+ return {
117
+ type: "stdio",
118
+ command: "node",
119
+ args: ["--input-type=module", "-e", source],
120
+ };
121
+ }
@@ -80,6 +80,7 @@ import {
80
80
  import { isDevMode } from "./dev-mode.js";
81
81
  import { EventLogStore } from "./event-log.js";
82
82
  import { TurnEventMapper } from "./event-mapper.js";
83
+ import { selectFrameworkStorageDefinition } from "./framework-storage-selection.js";
83
84
  import { createHostFilesApi } from "./host-files.js";
84
85
  import { createHostKvApi } from "./host-kv.js";
85
86
  import {
@@ -363,11 +364,17 @@ export class SessionEngine {
363
364
  `[agent-sdk] failed to persist events for ${sessionId}: ${describeError(error)}`
364
365
  )
365
366
  );
367
+ // Framework session state (records, event chunks, continuations) is
368
+ // mirrored to whatever this selects; see framework-storage-selection.ts
369
+ // for why the choice does not belong to the agent author.
370
+ const frameworkStorage = selectFrameworkStorageDefinition({
371
+ authored: options.project.storage,
372
+ });
366
373
  this.storage =
367
- options.project.storage === undefined
374
+ frameworkStorage === undefined
368
375
  ? undefined
369
376
  : new StorageCoordinator({
370
- definition: options.project.storage,
377
+ definition: frameworkStorage,
371
378
  agentName: options.project.name,
372
379
  projectRoot: options.project.rootDir,
373
380
  logger: this.logger,
package/src/types.ts CHANGED
@@ -861,6 +861,18 @@ export function isSymbolicConnectionTransport(
861
861
  );
862
862
  }
863
863
 
864
+ /** One TypeScript tool on `defineConnection({ tools })`. */
865
+ export type InlineStdioToolConfig = {
866
+ description: string;
867
+ inputSchema?: z.ZodTypeAny | JsonSchemaObject;
868
+ execute: (input: JsonObject) => unknown | Promise<unknown>;
869
+ };
870
+
871
+ /** `defineConnection({ tools })`. TypeScript in; stdio MCP out. */
872
+ export type InlineStdioConnectionConfig = {
873
+ tools: Record<string, InlineStdioToolConfig>;
874
+ };
875
+
864
876
  /**
865
877
  * Authored MCP connection config (`defineConnection(...)`).
866
878
  *
@@ -868,6 +880,10 @@ export function isSymbolicConnectionTransport(
868
880
  * `["*"]` to forward every connected HTTP/SSE connector; a concrete
869
881
  * `string[]` allowlists by connector name. Discovery normalizes the
870
882
  * wildcard forms to an unfiltered {@link ConnectionTransport}.
883
+ *
884
+ * `{ tools }` authors TypeScript handlers. Discovery packages them into a
885
+ * stdio MCP server the agent environment can spawn, including a repo-less
886
+ * cloud VM.
871
887
  */
872
888
  export type ConnectionConfig = (
873
889
  | Exclude<ConnectionTransport, { cursorAccount: true }>
@@ -883,6 +899,7 @@ export type ConnectionConfig = (
883
899
  */
884
900
  servers?: string[] | "*";
885
901
  }
902
+ | InlineStdioConnectionConfig
886
903
  ) & {
887
904
  /** Documentation only; shown in `info` output. */
888
905
  description?: string;
@@ -894,6 +911,16 @@ export type ConnectionConfig = (
894
911
  hostOnly?: boolean;
895
912
  };
896
913
 
914
+ /** True when the connection authors tools instead of a spawn/URL/peer. */
915
+ export function isInlineStdioConnection(
916
+ connection: ConnectionConfig
917
+ ): connection is InlineStdioConnectionConfig & {
918
+ description?: string;
919
+ hostOnly?: boolean;
920
+ } {
921
+ return "tools" in connection && connection.tools !== undefined;
922
+ }
923
+
897
924
  /**
898
925
  * An MCP connection is available to the agent (SDK `mcpServers`), host
899
926
  * (`ctx.host.mcp`), and channel/schedule handlers — unless `hostOnly`,