@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.
- package/AGENTS.md +1 -0
- package/dist/bin/agent-serve.js +17 -3
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +2 -1
- package/dist/channels/github/types.d.ts +4 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +1 -1
- package/dist/channels/slack/dispatch.d.ts +13 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +36 -11
- package/dist/channels/slack/eval-directive.d.ts +63 -0
- package/dist/channels/slack/eval-directive.d.ts.map +1 -0
- package/dist/channels/slack/eval-directive.js +283 -0
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +1 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -2
- package/dist/channels/slack/types.d.ts +7 -0
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/connections.d.ts +10 -5
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +10 -5
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.CY2buaEE.js → app.Dh_JkJjI.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.Er_R50vY.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.Ix954SFJ.js → VPLocalSearchBox.CptQNLkR.js} +1 -1
- package/dist/docs/assets/chunks/{theme.PDwuJbyi.js → theme.DWflH-LQ.js} +2 -2
- package/dist/docs/assets/{deployment.md.DC5-ImcM.js → deployment.md.TecHo0_2.js} +3 -3
- package/dist/docs/assets/{deployment.md.DC5-ImcM.lean.js → deployment.md.TecHo0_2.lean.js} +1 -1
- package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.js +29 -0
- package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.lean.js +1 -0
- package/dist/docs/assets/{index.md.COiu-1jL.js → index.md.t0TM2Qzz.js} +3 -3
- package/dist/docs/assets/{index.md.COiu-1jL.lean.js → index.md.t0TM2Qzz.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.CjfiV2Q-.js → reference_cli.md.B74NrVzb.js} +3 -3
- package/dist/docs/assets/{reference_cli.md.CjfiV2Q-.lean.js → reference_cli.md.B74NrVzb.lean.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.FbWupyzW.js → reference_connections.md.CsSr3rDz.js} +8 -3
- package/dist/docs/assets/{reference_connections.md.FbWupyzW.lean.js → reference_connections.md.CsSr3rDz.lean.js} +1 -1
- package/dist/docs/assets/reference_prompt.md.BaiweQxE.js +1 -0
- package/dist/docs/assets/reference_prompt.md.BaiweQxE.lean.js +1 -0
- package/dist/docs/assets/{reference_tools.md.CqgJroI0.js → reference_tools.md.BRSDnTbN.js} +16 -3
- package/dist/docs/assets/{reference_tools.md.CqgJroI0.lean.js → reference_tools.md.BRSDnTbN.lean.js} +1 -1
- package/dist/docs/assets/{troubleshooting.md.DkovjS7h.js → troubleshooting.md.CmQkmnzC.js} +1 -1
- package/dist/docs/assets/{troubleshooting.md.DkovjS7h.lean.js → troubleshooting.md.CmQkmnzC.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +7 -7
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +4 -4
- package/dist/docs/example-agents/benny.html +4 -4
- package/dist/docs/example-agents/bugbot.html +4 -4
- package/dist/docs/example-agents/codebase-wiki.html +4 -4
- package/dist/docs/example-agents/codeowners-review.html +4 -4
- package/dist/docs/example-agents/concierge.html +4 -4
- package/dist/docs/example-agents/fsd.html +4 -4
- package/dist/docs/example-agents/index.html +4 -4
- package/dist/docs/example-agents/knowledge-base.html +4 -4
- package/dist/docs/example-agents/oncall.html +4 -4
- package/dist/docs/example-agents/security-reviewer.html +4 -4
- package/dist/docs/example-agents/slack-agent.html +4 -4
- package/dist/docs/example-agents/weather-agent.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +53 -0
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +6 -6
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +6 -6
- package/dist/docs/reference/connections.html +12 -7
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +33 -0
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +5 -5
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +20 -7
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/evals.d.ts +39 -0
- package/dist/evals.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/internal/cli-mcp-oauth.d.ts +16 -0
- package/dist/internal/cli-mcp-oauth.d.ts.map +1 -0
- package/dist/internal/cli-mcp-oauth.js +194 -0
- package/dist/internal/cli-mcp.d.ts +4 -1
- package/dist/internal/cli-mcp.d.ts.map +1 -1
- package/dist/internal/cli-mcp.js +19 -1
- package/dist/internal/cursor/account-mcp.d.ts +17 -4
- package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
- package/dist/internal/cursor/account-mcp.js +105 -17
- package/dist/internal/cursor-event-relay.d.ts +2 -0
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +3 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +64 -10
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/json-dir-store.js +100 -0
- package/dist/internal/logs-client.js +1 -1
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +49 -26
- package/dist/internal/mcp-oauth.d.ts +88 -0
- package/dist/internal/mcp-oauth.d.ts.map +1 -0
- package/dist/internal/mcp-oauth.js +385 -0
- package/dist/internal/resolved-connections.d.ts.map +1 -1
- package/dist/internal/resolved-connections.js +4 -1
- package/dist/internal/sdk-runner.d.ts +9 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +28 -2
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +13 -5
- package/dist/internal/session-engine.d.ts +7 -9
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +96 -64
- package/dist/internal/sessions-client.d.ts.map +1 -1
- package/dist/internal/sessions-client.js +2 -1
- package/dist/internal/stream-progress.d.ts.map +1 -1
- package/dist/internal/stream-progress.js +8 -4
- package/dist/internal/turn-governor.d.ts +54 -0
- package/dist/internal/turn-governor.d.ts.map +1 -0
- package/dist/internal/turn-governor.js +111 -0
- package/dist/playground/assets/index-1dzluIcW.js +86 -0
- package/dist/playground/assets/index-kZjeJs1v.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/prompt.d.ts +39 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +70 -0
- package/dist/types.d.ts +78 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/docs/README.md +4 -0
- package/docs/deployment.md +6 -1
- package/docs/guides/mcp-oauth.md +157 -0
- package/docs/reference/cli.md +28 -0
- package/docs/reference/connections.md +28 -0
- package/docs/reference/prompt.md +47 -0
- package/docs/reference/tools.md +21 -0
- package/docs/troubleshooting.md +11 -0
- package/package.json +9 -2
- package/skills/create-agent/SKILL.md +7 -3
- package/skills/framework-map/SKILL.md +1 -0
- package/skills/mcp-auth/SKILL.md +138 -0
- package/src/bin/agent-serve.ts +20 -3
- package/src/channels/github/github-channel.ts +2 -0
- package/src/channels/github/types.ts +4 -0
- package/src/channels/slack/cursor-account.ts +2 -0
- package/src/channels/slack/dispatch.ts +49 -0
- package/src/channels/slack/eval-directive.ts +348 -0
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/slack-channel.ts +3 -0
- package/src/channels/slack/types.ts +7 -0
- package/src/connections.ts +10 -5
- package/src/evals.ts +43 -0
- package/src/index.ts +3 -0
- package/src/internal/cli-mcp-oauth.ts +254 -0
- package/src/internal/cli-mcp.ts +25 -2
- package/src/internal/cursor/account-mcp.ts +153 -41
- package/src/internal/cursor-event-relay.ts +5 -0
- package/src/internal/discovery.ts +70 -8
- package/src/internal/distribution.ts +1 -0
- package/src/internal/logs-client.ts +1 -1
- package/src/internal/mcp-host.ts +33 -9
- package/src/internal/mcp-oauth.ts +496 -0
- package/src/internal/resolved-connections.ts +6 -1
- package/src/internal/sdk-runner.ts +37 -1
- package/src/internal/server.ts +13 -5
- package/src/internal/session-engine.ts +115 -74
- package/src/internal/sessions-client.ts +2 -1
- package/src/internal/stream-progress.ts +8 -0
- package/src/internal/turn-governor.ts +145 -0
- package/src/prompt.ts +83 -0
- package/src/types.ts +76 -6
- package/dist/channels/slack/post-update-delivery.d.ts +0 -85
- package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.OfDZ5CXg.js +0 -1
- package/dist/internal/persistence-coordinator.d.ts +0 -127
- package/dist/internal/persistence-coordinator.d.ts.map +0 -1
- package/dist/persistence.d.ts +0 -184
- package/dist/persistence.d.ts.map +0 -1
- package/dist/playground/assets/index-C0-QEG4O.js +0 -86
- package/dist/playground/assets/index-z3JY9O2x.css +0 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agentkit mcp oauth <connection> [--store]` for `defineConnection({ url, oauth: true })`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
6
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
7
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
8
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
9
|
+
import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
10
|
+
import { resolveDeployAuth } from "./cli-deploy.js";
|
|
11
|
+
import { loadAgentProject } from "./discovery.js";
|
|
12
|
+
import { CLI_COMMAND_NAME as CLI, LOG_PREFIX } from "./distribution.js";
|
|
13
|
+
import {
|
|
14
|
+
createHostMcpOAuthProvider,
|
|
15
|
+
MCP_OAUTH_CALLBACK_URL,
|
|
16
|
+
mcpOauthSecretNames,
|
|
17
|
+
openBrowser,
|
|
18
|
+
waitForMcpOAuthCallback,
|
|
19
|
+
} from "./mcp-oauth.js";
|
|
20
|
+
import { resolveProdSlug } from "./resolve-prod-target.js";
|
|
21
|
+
|
|
22
|
+
export type McpOauthCliOptions = {
|
|
23
|
+
dir: string;
|
|
24
|
+
connection: string;
|
|
25
|
+
/** Upsert MCP_OAUTH_* on the hosted deployment. */
|
|
26
|
+
store?: boolean;
|
|
27
|
+
slug?: string;
|
|
28
|
+
team?: string;
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
err?: (text: string) => void;
|
|
31
|
+
out?: (text: string) => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function describe(error: unknown): string {
|
|
35
|
+
if (!(error instanceof Error)) {
|
|
36
|
+
return String(error);
|
|
37
|
+
}
|
|
38
|
+
const cause = error.cause;
|
|
39
|
+
if (!(cause instanceof Error)) {
|
|
40
|
+
return error.message;
|
|
41
|
+
}
|
|
42
|
+
const code =
|
|
43
|
+
"code" in cause && typeof cause.code === "string" ? cause.code : undefined;
|
|
44
|
+
const detail =
|
|
45
|
+
code === undefined ? cause.message : `${cause.message} (${code})`;
|
|
46
|
+
return `${error.message}: ${detail}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function cmdMcpOauth(
|
|
50
|
+
options: McpOauthCliOptions
|
|
51
|
+
): Promise<number> {
|
|
52
|
+
const err = options.err ?? ((text) => process.stderr.write(text));
|
|
53
|
+
const out = options.out ?? ((text) => process.stdout.write(text));
|
|
54
|
+
const project = await loadAgentProject(options.dir);
|
|
55
|
+
const connection = project.agent.connections.find(
|
|
56
|
+
(entry) => entry.name === options.connection
|
|
57
|
+
);
|
|
58
|
+
if (connection === undefined) {
|
|
59
|
+
const known =
|
|
60
|
+
project.agent.connections.map((entry) => entry.name).join(", ") ||
|
|
61
|
+
"(none)";
|
|
62
|
+
err(
|
|
63
|
+
`${LOG_PREFIX} Unknown MCP connection "${options.connection}". Authored: ${known}.\n`
|
|
64
|
+
);
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const transport = connection.transport;
|
|
69
|
+
if (!("url" in transport) || transport.oauth !== true) {
|
|
70
|
+
err(
|
|
71
|
+
`${LOG_PREFIX} "${options.connection}" must be defineConnection({ url, oauth: true }).\n`
|
|
72
|
+
);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
out(
|
|
77
|
+
`${LOG_PREFIX} Authorizing "${options.connection}" (${transport.url})…\n`
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
let authorizationUrl: URL | undefined;
|
|
81
|
+
const provider = await createHostMcpOAuthProvider({
|
|
82
|
+
identifier: options.connection,
|
|
83
|
+
resourceUrl: transport.url,
|
|
84
|
+
onRedirect: (url) => {
|
|
85
|
+
authorizationUrl = url;
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const client = new Client({ name: "agentkit-mcp-oauth", version: "0.1.0" });
|
|
90
|
+
const url = new URL(transport.url);
|
|
91
|
+
const requestInit =
|
|
92
|
+
transport.headers === undefined
|
|
93
|
+
? undefined
|
|
94
|
+
: { headers: transport.headers };
|
|
95
|
+
|
|
96
|
+
const makeTransport = ():
|
|
97
|
+
| StreamableHTTPClientTransport
|
|
98
|
+
| SSEClientTransport => {
|
|
99
|
+
if (transport.type === "sse") {
|
|
100
|
+
return new SSEClientTransport(url, {
|
|
101
|
+
authProvider: provider,
|
|
102
|
+
requestInit,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return new StreamableHTTPClientTransport(url, {
|
|
106
|
+
authProvider: provider,
|
|
107
|
+
requestInit,
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
let mcpTransport = makeTransport();
|
|
112
|
+
let authorized = false;
|
|
113
|
+
try {
|
|
114
|
+
await client.connect(mcpTransport);
|
|
115
|
+
out(`${LOG_PREFIX} "${options.connection}" already authorized.\n`);
|
|
116
|
+
await client.close().catch(() => undefined);
|
|
117
|
+
authorized = true;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (!(error instanceof UnauthorizedError)) {
|
|
120
|
+
err(
|
|
121
|
+
`${LOG_PREFIX} Failed to connect to "${options.connection}": ${describe(error)}\n`
|
|
122
|
+
);
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!authorized) {
|
|
128
|
+
const redirect = authorizationUrl ?? provider.lastRedirectUrl;
|
|
129
|
+
if (redirect === undefined) {
|
|
130
|
+
err(
|
|
131
|
+
`${LOG_PREFIX} Auth required but no authorization URL was provided.\n`
|
|
132
|
+
);
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
out(`${LOG_PREFIX} Open if needed:\n ${redirect.toString()}\n`);
|
|
137
|
+
out(`${LOG_PREFIX} Waiting on ${MCP_OAUTH_CALLBACK_URL}…\n`);
|
|
138
|
+
openBrowser(redirect.toString());
|
|
139
|
+
|
|
140
|
+
let callback: Awaited<ReturnType<typeof waitForMcpOAuthCallback>>;
|
|
141
|
+
try {
|
|
142
|
+
callback = await waitForMcpOAuthCallback();
|
|
143
|
+
} catch (error) {
|
|
144
|
+
err(`${LOG_PREFIX} OAuth callback failed: ${describe(error)}\n`);
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
await mcpTransport.finishAuth(callback.code);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
err(`${LOG_PREFIX} Token exchange failed: ${describe(error)}\n`);
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
mcpTransport = makeTransport();
|
|
156
|
+
try {
|
|
157
|
+
await client.connect(mcpTransport);
|
|
158
|
+
const tools = await client.listTools();
|
|
159
|
+
out(
|
|
160
|
+
`${LOG_PREFIX} Authorized "${options.connection}" (${tools.tools.length} tools). Local tokens in the agent-serve config dir.\n`
|
|
161
|
+
);
|
|
162
|
+
await client.close().catch(() => undefined);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
err(
|
|
165
|
+
`${LOG_PREFIX} Reconnect failed: ${describe(error)}. Re-run \`${CLI} mcp oauth ${options.connection}\`.\n`
|
|
166
|
+
);
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (options.store !== true) {
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
return storeOauthSecrets({
|
|
175
|
+
connection: options.connection,
|
|
176
|
+
dir: options.dir,
|
|
177
|
+
slug: options.slug,
|
|
178
|
+
team: options.team,
|
|
179
|
+
apiKey: options.apiKey,
|
|
180
|
+
tokens: provider.tokens(),
|
|
181
|
+
clientId: provider.clientInformation()?.client_id,
|
|
182
|
+
err,
|
|
183
|
+
out,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function storeOauthSecrets(args: {
|
|
188
|
+
connection: string;
|
|
189
|
+
dir: string;
|
|
190
|
+
slug?: string;
|
|
191
|
+
team?: string;
|
|
192
|
+
apiKey?: string;
|
|
193
|
+
tokens: OAuthTokens | undefined;
|
|
194
|
+
clientId: string | undefined;
|
|
195
|
+
err: (text: string) => void;
|
|
196
|
+
out: (text: string) => void;
|
|
197
|
+
}): Promise<number> {
|
|
198
|
+
const tokens = args.tokens;
|
|
199
|
+
if (tokens === undefined || tokens.access_token.trim() === "") {
|
|
200
|
+
args.err(
|
|
201
|
+
`${LOG_PREFIX} No access token to store for "${args.connection}".\n`
|
|
202
|
+
);
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let slug: string;
|
|
207
|
+
try {
|
|
208
|
+
slug = resolveProdSlug({ dir: args.dir, slug: args.slug });
|
|
209
|
+
} catch (error) {
|
|
210
|
+
args.err(`${LOG_PREFIX} ${describe(error)}\n`);
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const auth = await resolveDeployAuth({
|
|
215
|
+
team: args.team,
|
|
216
|
+
apiKey: args.apiKey,
|
|
217
|
+
err: args.err,
|
|
218
|
+
});
|
|
219
|
+
if (typeof auth === "number") {
|
|
220
|
+
return auth;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const names = mcpOauthSecretNames(args.connection);
|
|
224
|
+
const secrets: Array<{ name: string; value: string }> = [
|
|
225
|
+
{ name: names.accessToken, value: tokens.access_token },
|
|
226
|
+
];
|
|
227
|
+
if (
|
|
228
|
+
tokens.refresh_token !== undefined &&
|
|
229
|
+
tokens.refresh_token.trim() !== ""
|
|
230
|
+
) {
|
|
231
|
+
secrets.push({ name: names.refreshToken, value: tokens.refresh_token });
|
|
232
|
+
}
|
|
233
|
+
if (args.clientId !== undefined && args.clientId.trim() !== "") {
|
|
234
|
+
secrets.push({ name: names.clientId, value: args.clientId });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const secretNames = await auth.client.setSecrets({
|
|
239
|
+
teamId: auth.teamId,
|
|
240
|
+
slug,
|
|
241
|
+
secrets,
|
|
242
|
+
});
|
|
243
|
+
args.out(
|
|
244
|
+
`${LOG_PREFIX} Stored on ${slug}: ${secrets.map((s) => s.name).join(", ")}.\n` +
|
|
245
|
+
`${LOG_PREFIX} Secrets now: ${secretNames.join(", ")}. Next deploy picks them up.\n`
|
|
246
|
+
);
|
|
247
|
+
return 0;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
args.err(
|
|
250
|
+
`${LOG_PREFIX} Failed to store secrets on ${slug}: ${describe(error)}\n`
|
|
251
|
+
);
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
package/src/internal/cli-mcp.ts
CHANGED
|
@@ -89,7 +89,10 @@ export interface McpCliOptions {
|
|
|
89
89
|
|
|
90
90
|
export async function cmdMcp(
|
|
91
91
|
subcommand: string | undefined,
|
|
92
|
-
options: McpCliOptions
|
|
92
|
+
options: McpCliOptions & {
|
|
93
|
+
oauthConnection?: string;
|
|
94
|
+
store?: boolean;
|
|
95
|
+
}
|
|
93
96
|
): Promise<number> {
|
|
94
97
|
const err = options.err ?? defaultErr;
|
|
95
98
|
if (subcommand === undefined) {
|
|
@@ -98,9 +101,29 @@ export async function cmdMcp(
|
|
|
98
101
|
if (subcommand === "install") {
|
|
99
102
|
return cmdMcpInstall(options);
|
|
100
103
|
}
|
|
104
|
+
if (subcommand === "oauth") {
|
|
105
|
+
const connection = options.oauthConnection;
|
|
106
|
+
if (connection === undefined || connection.trim() === "") {
|
|
107
|
+
err(
|
|
108
|
+
`Usage: ${CLI} mcp oauth <connection> [--dir .] [--store] [--slug <slug>] [--team <id>]\n` +
|
|
109
|
+
`Authorize defineConnection({ url, oauth: true }) (host PKCE).\n` +
|
|
110
|
+
`--store upserts MCP_OAUTH_<NAME>_* secrets on the hosted deployment.\n`
|
|
111
|
+
);
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
const { cmdMcpOauth } = await import("./cli-mcp-oauth.js");
|
|
115
|
+
return cmdMcpOauth({
|
|
116
|
+
dir: options.dir,
|
|
117
|
+
connection,
|
|
118
|
+
store: options.store,
|
|
119
|
+
slug: options.slug,
|
|
120
|
+
team: options.team,
|
|
121
|
+
apiKey: options.apiKey,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
101
124
|
err(
|
|
102
125
|
`Unknown mcp subcommand "${subcommand}".\n` +
|
|
103
|
-
`Usage: ${CLI} mcp [install] --prod [--slug <slug>] | --url <baseUrl>\n`
|
|
126
|
+
`Usage: ${CLI} mcp [install|oauth <connection>] --prod [--slug <slug>] | --url <baseUrl>\n`
|
|
104
127
|
);
|
|
105
128
|
return 1;
|
|
106
129
|
}
|
|
@@ -213,48 +213,89 @@ export class CursorAccountMcpBridge {
|
|
|
213
213
|
|
|
214
214
|
/**
|
|
215
215
|
* Startup DX: log the account's connector statuses for `refs` without
|
|
216
|
-
* blocking serve startup. Surfaces "needsAuth" connectors
|
|
217
|
-
*
|
|
216
|
+
* blocking serve startup. Surfaces "needsAuth" / "error" connectors with
|
|
217
|
+
* remediation hints (and a backend probe detail for `error`), and filters
|
|
218
|
+
* that matched nothing (typo'd connector names).
|
|
219
|
+
*
|
|
220
|
+
* Returns a promise that settles when every ref's log lines are written —
|
|
221
|
+
* callers may ignore it; tests can await it.
|
|
218
222
|
*/
|
|
219
|
-
logPreflight(refs: CursorAccountConnectionRef[]): void {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (needsAuth.length > 0) {
|
|
244
|
-
this.logger(
|
|
245
|
-
`[agentkit] Cursor account MCP connection "${ref.name}": ${needsAuth
|
|
246
|
-
.map((server) => server.serverIdentifier)
|
|
247
|
-
.join(
|
|
248
|
-
", "
|
|
249
|
-
)} need re-authorization in Cursor before their tools appear`
|
|
223
|
+
logPreflight(refs: CursorAccountConnectionRef[]): Promise<void> {
|
|
224
|
+
return Promise.all(refs.map((ref) => this.runPreflight(ref))).then(
|
|
225
|
+
() => undefined
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private async runPreflight(ref: CursorAccountConnectionRef): Promise<void> {
|
|
230
|
+
try {
|
|
231
|
+
const catalog = await this.getCatalog(ref);
|
|
232
|
+
if (catalog.servers.length === 0) {
|
|
233
|
+
this.logger(
|
|
234
|
+
`[agentkit] Cursor account MCP connection "${ref.name}": no matching connectors on this account — connect servers in Cursor (dashboard → MCP) or fix the \`servers\` filter`
|
|
235
|
+
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const errorDetails = new Map<string, string>();
|
|
240
|
+
await Promise.all(
|
|
241
|
+
catalog.servers
|
|
242
|
+
.filter((server) => server.status === "error")
|
|
243
|
+
.map(async (server) => {
|
|
244
|
+
const detail = await diagnoseConnectorError(
|
|
245
|
+
this.client,
|
|
246
|
+
server.serverIdentifier
|
|
250
247
|
);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
248
|
+
if (detail !== undefined) {
|
|
249
|
+
errorDetails.set(server.serverIdentifier, detail);
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const summary = catalog.servers
|
|
255
|
+
.map((server) =>
|
|
256
|
+
formatServerStatusSummary(
|
|
257
|
+
server,
|
|
258
|
+
errorDetails.get(server.serverIdentifier)
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
.join(", ");
|
|
262
|
+
this.logger(
|
|
263
|
+
`[agentkit] Cursor account MCP connection "${ref.name}": ${summary}`
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const needsAuth = catalog.servers.filter(
|
|
267
|
+
(server) => server.status === "needsAuth"
|
|
268
|
+
);
|
|
269
|
+
if (needsAuth.length > 0) {
|
|
270
|
+
this.logger(
|
|
271
|
+
`[agentkit] Cursor account MCP connection "${ref.name}": ${needsAuth
|
|
272
|
+
.map((server) => server.serverIdentifier)
|
|
273
|
+
.join(
|
|
274
|
+
", "
|
|
275
|
+
)} need re-authorization in Cursor (dashboard → MCP / Settings → MCP) before their tools appear`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const errored = catalog.servers.filter(
|
|
280
|
+
(server) => server.status === "error"
|
|
281
|
+
);
|
|
282
|
+
if (errored.length > 0) {
|
|
283
|
+
const details = errored
|
|
284
|
+
.map((server) => {
|
|
285
|
+
const detail = errorDetails.get(server.serverIdentifier);
|
|
286
|
+
return detail === undefined
|
|
287
|
+
? server.serverIdentifier
|
|
288
|
+
: `${server.serverIdentifier} (${detail})`;
|
|
289
|
+
})
|
|
290
|
+
.join("; ");
|
|
291
|
+
this.logger(
|
|
292
|
+
`[agentkit] Cursor account MCP connection "${ref.name}": ${details} — Cursor backend could not use this connector (not a missing agentkit login). Re-check the server in Cursor dashboard → MCP, confirm the URL is healthy, and retry after the backend can reach it`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
} catch (error: unknown) {
|
|
296
|
+
this.logger(
|
|
297
|
+
`[agentkit] Cursor account MCP connection "${ref.name}" preflight failed: ${describeError(error)}`
|
|
298
|
+
);
|
|
258
299
|
}
|
|
259
300
|
}
|
|
260
301
|
|
|
@@ -283,13 +324,22 @@ export class CursorAccountMcpBridge {
|
|
|
283
324
|
const tool = await this.findTool(ref, request.params.name);
|
|
284
325
|
if (tool === undefined) {
|
|
285
326
|
const catalog = await this.getCatalog(ref);
|
|
327
|
+
const unhealthy = catalog.servers.filter(
|
|
328
|
+
(server) => server.status !== "connected"
|
|
329
|
+
);
|
|
330
|
+
const statusHint =
|
|
331
|
+
unhealthy.length === 0
|
|
332
|
+
? ""
|
|
333
|
+
: ` Unhealthy connectors: ${unhealthy
|
|
334
|
+
.map((server) => `${server.serverIdentifier}=${server.status}`)
|
|
335
|
+
.join(", ")}.`;
|
|
286
336
|
return {
|
|
287
337
|
content: [
|
|
288
338
|
{
|
|
289
339
|
type: "text",
|
|
290
340
|
text: `Unknown tool "${request.params.name}". Available: ${
|
|
291
341
|
catalog.tools.map((t) => t.exposedName).join(", ") || "(none)"
|
|
292
|
-
}
|
|
342
|
+
}.${statusHint}`,
|
|
293
343
|
},
|
|
294
344
|
],
|
|
295
345
|
isError: true,
|
|
@@ -470,3 +520,65 @@ function trimTrailingSlash(url: string): string {
|
|
|
470
520
|
function describeError(error: unknown): string {
|
|
471
521
|
return error instanceof Error ? error.message : String(error);
|
|
472
522
|
}
|
|
523
|
+
|
|
524
|
+
/** One-line status for the preflight summary (`name (12 tools)` / `name (error: …)`). */
|
|
525
|
+
export function formatServerStatusSummary(
|
|
526
|
+
server: AccountMcpServer,
|
|
527
|
+
errorDetail?: string
|
|
528
|
+
): string {
|
|
529
|
+
switch (server.status) {
|
|
530
|
+
case "connected":
|
|
531
|
+
return `${server.serverIdentifier} (${server.tools.length} tools)`;
|
|
532
|
+
case "needsAuth":
|
|
533
|
+
return `${server.serverIdentifier} (needsAuth — re-authorize in Cursor)`;
|
|
534
|
+
case "loading":
|
|
535
|
+
return `${server.serverIdentifier} (loading)`;
|
|
536
|
+
case "error": {
|
|
537
|
+
const detail =
|
|
538
|
+
errorDetail === undefined || errorDetail.trim() === ""
|
|
539
|
+
? "backend could not reach connector"
|
|
540
|
+
: errorDetail.trim();
|
|
541
|
+
return `${server.serverIdentifier} (error: ${detail})`;
|
|
542
|
+
}
|
|
543
|
+
default:
|
|
544
|
+
return `${server.serverIdentifier} (${server.status})`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Best-effort detail for an `error`-status connector. ListSandMcpTools only
|
|
550
|
+
* returns the status enum today, so probe ExecuteSandMcpTool (ping) and surface
|
|
551
|
+
* the backend failure text (e.g. `HTTP MCP tool execution failed: fetch failed`).
|
|
552
|
+
*/
|
|
553
|
+
export async function diagnoseConnectorError(
|
|
554
|
+
client: Pick<CursorBackendClient, "executeAccountMcpTool">,
|
|
555
|
+
serverIdentifier: string
|
|
556
|
+
): Promise<string | undefined> {
|
|
557
|
+
try {
|
|
558
|
+
const result = await client.executeAccountMcpTool({
|
|
559
|
+
serverIdentifier,
|
|
560
|
+
// Conventional health tool; even a tool-not-found path usually still
|
|
561
|
+
// surfaces the same transport failure when the connector is down.
|
|
562
|
+
toolName: `${serverIdentifier}-ping`,
|
|
563
|
+
args: {},
|
|
564
|
+
});
|
|
565
|
+
const text = result.content
|
|
566
|
+
.filter(
|
|
567
|
+
(part): part is { type: "text"; text: string } => part.type === "text"
|
|
568
|
+
)
|
|
569
|
+
.map((part) => part.text.trim())
|
|
570
|
+
.filter((part) => part.length > 0)
|
|
571
|
+
.join(" ");
|
|
572
|
+
if (result.isError) {
|
|
573
|
+
return text.length > 0
|
|
574
|
+
? text
|
|
575
|
+
: "backend reported an error with no detail";
|
|
576
|
+
}
|
|
577
|
+
// Listing said error but ping succeeded — surface that inconsistency.
|
|
578
|
+
return text.length > 0
|
|
579
|
+
? `listing reported error but probe succeeded: ${text}`
|
|
580
|
+
: "listing reported error but probe succeeded";
|
|
581
|
+
} catch (error: unknown) {
|
|
582
|
+
return describeError(error);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
@@ -25,6 +25,8 @@ export type CursorScmPullRequestMeta = {
|
|
|
25
25
|
title?: string;
|
|
26
26
|
body?: string | null;
|
|
27
27
|
draft?: boolean;
|
|
28
|
+
/** True when the PR was merged — required by closed+merged channel hooks. */
|
|
29
|
+
merged?: boolean;
|
|
28
30
|
authorLogin?: string;
|
|
29
31
|
headRef?: string;
|
|
30
32
|
headSha?: string;
|
|
@@ -163,6 +165,9 @@ export function scmMetadataToGitHubPayload(event: {
|
|
|
163
165
|
head: { ref: headRef, sha: headSha },
|
|
164
166
|
base: { ref: baseRef, sha: prMeta?.baseSha },
|
|
165
167
|
};
|
|
168
|
+
if (prMeta?.merged !== undefined) {
|
|
169
|
+
pullRequest.merged = prMeta.merged;
|
|
170
|
+
}
|
|
166
171
|
if (prMeta?.authorLogin !== undefined) {
|
|
167
172
|
pullRequest.user = { login: prMeta.authorLogin };
|
|
168
173
|
}
|
|
@@ -21,6 +21,7 @@ import { type ABConfigFile, resolveABMaxPlaygroundSessions } from "../ab.js";
|
|
|
21
21
|
import { httpChannel } from "../channels.js";
|
|
22
22
|
import type { StorageDefinition } from "../storage.js";
|
|
23
23
|
import {
|
|
24
|
+
type AgentConcurrencyOptions,
|
|
24
25
|
type AgentConfigDefinition,
|
|
25
26
|
type AgentHostingOptions,
|
|
26
27
|
type AgentProject,
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
type HookDefinition,
|
|
41
42
|
type HttpChannelDefinition,
|
|
42
43
|
type InstructionsDefinition,
|
|
44
|
+
MAX_RUNNING_TURNS_LIMIT,
|
|
43
45
|
type ModelSetting,
|
|
44
46
|
type ResolvedAgent,
|
|
45
47
|
type ScheduleDefinition,
|
|
@@ -50,6 +52,7 @@ import {
|
|
|
50
52
|
toPublicCloudInfo,
|
|
51
53
|
} from "../types.js";
|
|
52
54
|
import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
|
|
55
|
+
import { normalizePositiveInt } from "./bounded-int.js";
|
|
53
56
|
import { getDefinitionKind } from "./brand.js";
|
|
54
57
|
import { isValidCron } from "./cron.js";
|
|
55
58
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
@@ -372,6 +375,7 @@ async function loadAgent(
|
|
|
372
375
|
cwd: localCwd,
|
|
373
376
|
};
|
|
374
377
|
const hosting = resolveHosting(ctx, agentDir, config?.hosting);
|
|
378
|
+
const concurrency = resolveConcurrency(ctx, agentDir, config?.concurrency);
|
|
375
379
|
|
|
376
380
|
if (runtime === "cloud") {
|
|
377
381
|
warnCloudRuntimeLimitations(ctx, agentDir, {
|
|
@@ -393,6 +397,7 @@ async function loadAgent(
|
|
|
393
397
|
cloud,
|
|
394
398
|
local,
|
|
395
399
|
hosting,
|
|
400
|
+
concurrency,
|
|
396
401
|
tools,
|
|
397
402
|
skills,
|
|
398
403
|
connections,
|
|
@@ -469,6 +474,37 @@ function resolveHosting(
|
|
|
469
474
|
};
|
|
470
475
|
}
|
|
471
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Validate the authored `concurrency` block. Invalid values are **error**
|
|
479
|
+
* diagnostics (serve refuses to start): silently dropping a turn ceiling
|
|
480
|
+
* would fail open to unlimited parallelism, the opposite of what the
|
|
481
|
+
* author asked for.
|
|
482
|
+
*/
|
|
483
|
+
function resolveConcurrency(
|
|
484
|
+
ctx: DiscoveryContext,
|
|
485
|
+
agentDir: string,
|
|
486
|
+
concurrency: AgentConcurrencyOptions | undefined
|
|
487
|
+
): AgentConcurrencyOptions | undefined {
|
|
488
|
+
if (concurrency === undefined || concurrency.maxRunningTurns === undefined) {
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
return {
|
|
493
|
+
maxRunningTurns: normalizePositiveInt(concurrency.maxRunningTurns, {
|
|
494
|
+
label: "concurrency.maxRunningTurns",
|
|
495
|
+
max: MAX_RUNNING_TURNS_LIMIT,
|
|
496
|
+
}),
|
|
497
|
+
};
|
|
498
|
+
} catch (error) {
|
|
499
|
+
ctx.diagnostics.push({
|
|
500
|
+
severity: "error",
|
|
501
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
502
|
+
message: error instanceof Error ? error.message : String(error),
|
|
503
|
+
});
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
472
508
|
async function loadAgentConfig(
|
|
473
509
|
ctx: DiscoveryContext,
|
|
474
510
|
agentDir: string,
|
|
@@ -831,11 +867,24 @@ function connectionTransportLabel(transport: ConnectionTransport): string {
|
|
|
831
867
|
return `agent:${transport.agent}`;
|
|
832
868
|
}
|
|
833
869
|
if ("url" in transport) {
|
|
834
|
-
return transport.
|
|
870
|
+
return transport.oauth === true
|
|
871
|
+
? `${transport.url} (oauth)`
|
|
872
|
+
: transport.url;
|
|
835
873
|
}
|
|
836
874
|
return transport.command;
|
|
837
875
|
}
|
|
838
876
|
|
|
877
|
+
function hasAuthorizationHeader(
|
|
878
|
+
headers: Record<string, string> | undefined
|
|
879
|
+
): boolean {
|
|
880
|
+
if (headers === undefined) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
return Object.keys(headers).some(
|
|
884
|
+
(key) => key.toLowerCase() === "authorization"
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
839
888
|
function connectionTransport(
|
|
840
889
|
connection: ConnectionDefinition
|
|
841
890
|
): ConnectionTransport | undefined {
|
|
@@ -852,11 +901,10 @@ function connectionTransport(
|
|
|
852
901
|
}
|
|
853
902
|
if ("url" in connection && !isBlank(connection.url)) {
|
|
854
903
|
return {
|
|
855
|
-
|
|
904
|
+
type: connection.type,
|
|
856
905
|
url: connection.url,
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
: { headers: connection.headers }),
|
|
906
|
+
headers: connection.headers,
|
|
907
|
+
oauth: connection.oauth === true ? true : undefined,
|
|
860
908
|
};
|
|
861
909
|
}
|
|
862
910
|
if ("command" in connection && !isBlank(connection.command)) {
|
|
@@ -974,6 +1022,19 @@ async function loadConnections(
|
|
|
974
1022
|
continue;
|
|
975
1023
|
}
|
|
976
1024
|
const connection = definition as ConnectionDefinition;
|
|
1025
|
+
if (
|
|
1026
|
+
"url" in connection &&
|
|
1027
|
+
connection.oauth === true &&
|
|
1028
|
+
hasAuthorizationHeader(connection.headers)
|
|
1029
|
+
) {
|
|
1030
|
+
ctx.diagnostics.push({
|
|
1031
|
+
severity: "error",
|
|
1032
|
+
path: displayPath(ctx, entryPath),
|
|
1033
|
+
message:
|
|
1034
|
+
"MCP connection `oauth: true` cannot set an Authorization header. Drop the header or remove `oauth`.",
|
|
1035
|
+
});
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
977
1038
|
const transport = connectionTransport(connection);
|
|
978
1039
|
if (transport === undefined) {
|
|
979
1040
|
ctx.diagnostics.push({
|
|
@@ -1009,9 +1070,8 @@ async function loadConnections(
|
|
|
1009
1070
|
connections.push({
|
|
1010
1071
|
name,
|
|
1011
1072
|
transport,
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
: { description: connection.description }),
|
|
1073
|
+
description: connection.description,
|
|
1074
|
+
hostOnly: connection.hostOnly,
|
|
1015
1075
|
});
|
|
1016
1076
|
}
|
|
1017
1077
|
return connections;
|
|
@@ -1529,6 +1589,7 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
|
|
|
1529
1589
|
agent.cloud === undefined ? undefined : toPublicCloudInfo(agent.cloud),
|
|
1530
1590
|
local: agent.local,
|
|
1531
1591
|
hosting: agent.hosting,
|
|
1592
|
+
concurrency: agent.concurrency,
|
|
1532
1593
|
mcp: { path: MCP_ENDPOINT_PATH, tools: mcpEndpointToolNames(project) },
|
|
1533
1594
|
instructions:
|
|
1534
1595
|
agent.instructions === undefined
|
|
@@ -1554,6 +1615,7 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
|
|
|
1554
1615
|
connections: agent.connections.map((connection) => ({
|
|
1555
1616
|
name: connection.name,
|
|
1556
1617
|
transport: connectionTransportLabel(connection.transport),
|
|
1618
|
+
hostOnly: connection.hostOnly,
|
|
1557
1619
|
})),
|
|
1558
1620
|
subagents: agent.subagents.map((subagent) => ({
|
|
1559
1621
|
name: subagent.name,
|