@cursor/july 0.1.36 → 0.1.38
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.
- package/README.md +3 -1
- package/dist/channels/slack/api.d.ts +5 -0
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +58 -19
- package/dist/channels/slack/constants.d.ts +2 -0
- package/dist/channels/slack/constants.d.ts.map +1 -1
- package/dist/channels/slack/constants.js +2 -0
- package/dist/connections.d.ts +14 -2
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +13 -1
- package/dist/docs/404.html +3 -3
- package/dist/docs/ab.html +6 -6
- package/dist/docs/assets/{app.DEcxy4oz.js → app.B4nJHNwO.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.B6oHmJzD.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.n1VOZcy3.js → VPLocalSearchBox.DHrr3pLB.js} +2 -2
- package/dist/docs/assets/chunks/theme.DQNgSMha.js +2 -0
- package/dist/docs/assets/{example-agents_weather-agent.md.CrGZ0SqR.js → example-agents_weather-agent.md.DC3lINjo.js} +5 -6
- package/dist/docs/assets/{reference_connections.md.DGqAsFXb.js → reference_connections.md.CSQiNzre.js} +11 -2
- package/dist/docs/assets/{reference_connections.md.DGqAsFXb.lean.js → reference_connections.md.CSQiNzre.lean.js} +1 -1
- package/dist/docs/assets/{style.Bj-IXa5W.css → style.CK3hwt6q.css} +1 -1
- package/dist/docs/building-with-agents.html +6 -6
- package/dist/docs/concepts.html +6 -6
- package/dist/docs/deployment.html +6 -6
- package/dist/docs/evals.html +6 -6
- package/dist/docs/example-agents/approval-buddy.html +6 -6
- package/dist/docs/example-agents/benny.html +6 -6
- package/dist/docs/example-agents/bugbot.html +6 -6
- package/dist/docs/example-agents/codebase-wiki.html +6 -6
- package/dist/docs/example-agents/codeowners-review.html +6 -6
- package/dist/docs/example-agents/concierge.html +6 -6
- package/dist/docs/example-agents/fsd.html +6 -6
- package/dist/docs/example-agents/index.html +6 -6
- package/dist/docs/example-agents/knowledge-base.html +6 -6
- package/dist/docs/example-agents/oncall.html +6 -6
- package/dist/docs/example-agents/security-reviewer.html +6 -6
- package/dist/docs/example-agents/slack-agent.html +6 -6
- package/dist/docs/example-agents/weather-agent.html +10 -11
- package/dist/docs/guides/agent-to-agent.html +6 -6
- package/dist/docs/guides/cloud-runtime.html +6 -6
- package/dist/docs/guides/github.html +6 -6
- package/dist/docs/guides/human-in-the-loop.html +6 -6
- package/dist/docs/guides/mcp-oauth.html +6 -6
- package/dist/docs/guides/slack.html +6 -6
- package/dist/docs/guides/webhooks.html +6 -6
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +6 -6
- package/dist/docs/index.html +6 -6
- package/dist/docs/quickstart.html +6 -6
- package/dist/docs/reference/agent-config.html +6 -6
- package/dist/docs/reference/artifacts.html +6 -6
- package/dist/docs/reference/channels.html +6 -6
- package/dist/docs/reference/cli.html +6 -6
- package/dist/docs/reference/connections.html +17 -8
- package/dist/docs/reference/hooks.html +6 -6
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/instructions.html +6 -6
- package/dist/docs/reference/playground.html +5 -5
- package/dist/docs/reference/project-layout.html +6 -6
- package/dist/docs/reference/prompt.html +6 -6
- package/dist/docs/reference/schedules.html +6 -6
- package/dist/docs/reference/sessions.html +6 -6
- package/dist/docs/reference/skills.html +6 -6
- package/dist/docs/reference/subagents.html +6 -6
- package/dist/docs/reference/tools.html +6 -6
- package/dist/docs/scaffolding-agents.html +5 -5
- package/dist/docs/storage.html +6 -6
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +13 -2
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +95 -2
- package/dist/internal/framework-storage-selection.d.ts +37 -0
- package/dist/internal/framework-storage-selection.d.ts.map +1 -0
- package/dist/internal/framework-storage-selection.js +35 -0
- package/dist/internal/inline-stdio-define-stub.d.ts +3 -0
- package/dist/internal/inline-stdio-define-stub.d.ts.map +1 -0
- package/dist/internal/inline-stdio-define-stub.js +4 -0
- package/dist/internal/inline-stdio-mcp.d.ts +32 -0
- package/dist/internal/inline-stdio-mcp.d.ts.map +1 -0
- package/dist/internal/inline-stdio-mcp.js +180 -0
- package/dist/internal/package-inline-stdio.d.ts +12 -0
- package/dist/internal/package-inline-stdio.d.ts.map +1 -0
- package/dist/internal/package-inline-stdio.js +109 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +9 -2
- package/dist/playground/assets/index-Bs6TgymR.css +1 -0
- package/dist/playground/assets/{index-DOnKC85G.js → index-DLtPsWuV.js} +48 -48
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +20 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/docs/example-agents/weather-agent.md +25 -22
- package/docs/reference/connections.md +17 -0
- package/package.json +3 -1
- package/skills/framework-map/SKILL.md +1 -1
- package/src/channels/slack/api.ts +73 -21
- package/src/channels/slack/constants.ts +2 -0
- package/src/connections.ts +18 -1
- package/src/files-backends/cursor-hosted.ts +13 -2
- package/src/internal/discovery.ts +111 -1
- package/src/internal/framework-storage-selection.ts +45 -0
- package/src/internal/inline-stdio-define-stub.ts +4 -0
- package/src/internal/inline-stdio-mcp.ts +229 -0
- package/src/internal/package-inline-stdio.ts +121 -0
- package/src/internal/session-engine.ts +9 -2
- package/src/types.ts +27 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.ByFYcFly.js +0 -1
- package/dist/docs/assets/chunks/theme.BaF1MQ9c.js +0 -2
- package/dist/playground/assets/index-DoQjqj5w.css +0 -1
- /package/dist/docs/assets/{example-agents_weather-agent.md.CrGZ0SqR.lean.js → example-agents_weather-agent.md.DC3lINjo.lean.js} +0 -0
|
@@ -464,10 +464,21 @@ export function cursorHostedFiles(
|
|
|
464
464
|
redirect: "manual",
|
|
465
465
|
}
|
|
466
466
|
);
|
|
467
|
-
if (response.status === 404
|
|
468
|
-
// A presigned GET for a missing object surfaces as 403 on S3.
|
|
467
|
+
if (response.status === 404) {
|
|
469
468
|
return undefined;
|
|
470
469
|
}
|
|
470
|
+
// A 403 here is deliberately *not* absence. Absence was already
|
|
471
|
+
// settled above: the store refuses to presign a path with no live
|
|
472
|
+
// object, so reaching this point means the object existed moments
|
|
473
|
+
// ago. What produces a 403 after a good presign is an expired or
|
|
474
|
+
// clock-skewed signature, a policy change, or a delete that raced the
|
|
475
|
+
// GET — all transient or retryable, none of them "definitively gone".
|
|
476
|
+
//
|
|
477
|
+
// Reporting them as absence is how a store blip becomes data loss:
|
|
478
|
+
// `undefined` on a continuation read is taken as "unknown token", and
|
|
479
|
+
// the engine mints a fresh session under it, forking the thread. The
|
|
480
|
+
// delete race resolves itself on retry, where the presign returns 404
|
|
481
|
+
// and this correctly reads as a miss.
|
|
471
482
|
if (!response.ok) {
|
|
472
483
|
throw new Error(
|
|
473
484
|
`cursorHostedFiles: download of "${key}" failed (HTTP ${response.status})`
|
|
@@ -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,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
|
-
|
|
374
|
+
frameworkStorage === undefined
|
|
368
375
|
? undefined
|
|
369
376
|
: new StorageCoordinator({
|
|
370
|
-
definition:
|
|
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`,
|