@zackbart/connecta 0.8.1 → 0.9.1
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/CHANGELOG.md +90 -0
- package/README.md +9 -8
- package/SECURITY.md +5 -11
- package/dist/auth/downstream-oauth.d.ts +15 -6
- package/dist/auth/downstream-oauth.d.ts.map +1 -1
- package/dist/auth/downstream-oauth.js +60 -11
- package/dist/auth/downstream-oauth.js.map +1 -1
- package/dist/catalog-service.d.ts +8 -0
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +24 -2
- package/dist/catalog-service.js.map +1 -1
- package/dist/catalog.d.ts +34 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +264 -40
- package/dist/catalog.js.map +1 -1
- package/dist/connectors/remote-mcp.d.ts +1 -1
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js +65 -50
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +3 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +50 -13
- package/dist/execute.js.map +1 -1
- package/dist/meta-tools.d.ts +1 -1
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +19 -17
- package/dist/meta-tools.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +63 -44
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/oauth.js +1 -1
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/shared.d.ts +2 -2
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/skills.d.ts +2 -2
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +7 -7
- package/dist/skills.js.map +1 -1
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/src/auth/downstream-oauth.ts +106 -24
- package/src/catalog-service.ts +43 -0
- package/src/catalog.ts +327 -33
- package/src/connectors/remote-mcp.ts +96 -64
- package/src/errors.ts +2 -0
- package/src/execute.ts +55 -12
- package/src/meta-tools.ts +22 -18
- package/src/routes/mcp.ts +70 -44
- package/src/routes/oauth.ts +1 -1
- package/src/routes/shared.ts +2 -2
- package/src/skills.ts +7 -7
- package/src/types.ts +10 -2
- package/src/version.ts +1 -1
package/src/errors.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type ConnectorCallErrorCode =
|
|
|
8
8
|
| "rate_limited"
|
|
9
9
|
| "unavailable"
|
|
10
10
|
| "invalid_args"
|
|
11
|
+
| "input_required_unsupported"
|
|
11
12
|
| "connector_call_failed";
|
|
12
13
|
|
|
13
14
|
/** Agent-visible recovery class attached only to `auth_required` failures. */
|
|
@@ -22,6 +23,7 @@ const RETRYABLE_BY_CODE: Record<ConnectorCallErrorCode, boolean> = {
|
|
|
22
23
|
unavailable: true,
|
|
23
24
|
auth_required: false,
|
|
24
25
|
invalid_args: false,
|
|
26
|
+
input_required_unsupported: false,
|
|
25
27
|
connector_call_failed: false,
|
|
26
28
|
};
|
|
27
29
|
|
package/src/execute.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { ActivityRequestContext } from "./activity.js";
|
|
4
4
|
import {
|
|
@@ -150,6 +150,7 @@ export async function buildSandboxProviders(
|
|
|
150
150
|
maxHostCalls?: number;
|
|
151
151
|
hostCallTimeoutMs?: number;
|
|
152
152
|
discoveryConcurrency?: number;
|
|
153
|
+
onInvocationFailure?: (failure: InvocationFailure) => void;
|
|
153
154
|
} = {},
|
|
154
155
|
): Promise<ExecutorProvider[]> {
|
|
155
156
|
// All host calls made by one execute_code invocation share a downstream
|
|
@@ -210,7 +211,11 @@ export async function buildSandboxProviders(
|
|
|
210
211
|
args ?? {},
|
|
211
212
|
invocationContext(),
|
|
212
213
|
);
|
|
213
|
-
if (!outcome.ok)
|
|
214
|
+
if (!outcome.ok) {
|
|
215
|
+
const failure = new InvocationFailure(outcome.error);
|
|
216
|
+
limits.onInvocationFailure?.(failure);
|
|
217
|
+
throw failure;
|
|
218
|
+
}
|
|
214
219
|
return outcome.value;
|
|
215
220
|
};
|
|
216
221
|
const callNamespace = async (
|
|
@@ -225,7 +230,11 @@ export async function buildSandboxProviders(
|
|
|
225
230
|
args ?? {},
|
|
226
231
|
invocationContext(),
|
|
227
232
|
);
|
|
228
|
-
if (!outcome.ok)
|
|
233
|
+
if (!outcome.ok) {
|
|
234
|
+
const failure = new InvocationFailure(outcome.error);
|
|
235
|
+
limits.onInvocationFailure?.(failure);
|
|
236
|
+
throw failure;
|
|
237
|
+
}
|
|
229
238
|
return outcome.value;
|
|
230
239
|
};
|
|
231
240
|
|
|
@@ -270,11 +279,20 @@ export async function buildSandboxProviders(
|
|
|
270
279
|
offset?: number;
|
|
271
280
|
fullDescriptions?: boolean;
|
|
272
281
|
includeSchemas?: "compact" | "json";
|
|
282
|
+
includeSchemaKeys?: boolean;
|
|
273
283
|
};
|
|
274
|
-
const result = flatSearchResult(
|
|
284
|
+
const result = flatSearchResult(
|
|
285
|
+
await catalog.search({
|
|
286
|
+
...args,
|
|
287
|
+
// Key metadata rides along with schemas by default, since that is
|
|
288
|
+
// the whole point of it in code mode. It stays opt-out because it
|
|
289
|
+
// counts against the same hard discovery-byte ceiling.
|
|
290
|
+
includeSchemaKeys: args.includeSchemaKeys !== false,
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
275
293
|
boundedDiscoveryText(
|
|
276
294
|
result,
|
|
277
|
-
"Request a smaller limit, omit fullDescriptions,
|
|
295
|
+
"Request a smaller limit, omit fullDescriptions, use compact schemas, or pass includeSchemaKeys: false.",
|
|
278
296
|
);
|
|
279
297
|
return result;
|
|
280
298
|
},
|
|
@@ -317,6 +335,7 @@ export function createExecuteTool(
|
|
|
317
335
|
}
|
|
318
336
|
let lease;
|
|
319
337
|
let outcome;
|
|
338
|
+
const invocationFailures: InvocationFailure[] = [];
|
|
320
339
|
try {
|
|
321
340
|
// Admission comes before provider construction: queued calls retain no
|
|
322
341
|
// catalogs, request scopes, or one-closure-per-tool provider arrays.
|
|
@@ -335,6 +354,9 @@ export function createExecuteTool(
|
|
|
335
354
|
activity,
|
|
336
355
|
{
|
|
337
356
|
signal: controller.signal,
|
|
357
|
+
onInvocationFailure: (failure) => {
|
|
358
|
+
invocationFailures.push(failure);
|
|
359
|
+
},
|
|
338
360
|
...(config.discoveryConcurrency !== undefined
|
|
339
361
|
? { discoveryConcurrency: config.discoveryConcurrency }
|
|
340
362
|
: {}),
|
|
@@ -386,6 +408,27 @@ export function createExecuteTool(
|
|
|
386
408
|
)
|
|
387
409
|
: undefined;
|
|
388
410
|
if (outcome.error) {
|
|
411
|
+
// Executor bridges necessarily reduce thrown host errors to strings.
|
|
412
|
+
// Match that terminal string back to the request-local typed failure so
|
|
413
|
+
// an unhandled tool failure keeps the same structured contract as
|
|
414
|
+
// call_tool and batch_call. Failures caught by model code never reach
|
|
415
|
+
// outcome.error and therefore remain under that code's control.
|
|
416
|
+
let invocationFailure: InvocationFailure | undefined;
|
|
417
|
+
for (let i = invocationFailures.length - 1; i >= 0; i--) {
|
|
418
|
+
const candidate = invocationFailures[i];
|
|
419
|
+
if (candidate && outcome.error.includes(candidate.message)) {
|
|
420
|
+
invocationFailure = candidate;
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (invocationFailure) {
|
|
425
|
+
const result = jsonResult({
|
|
426
|
+
error: invocationFailure.details,
|
|
427
|
+
...(logs ? { logs } : {}),
|
|
428
|
+
});
|
|
429
|
+
result.isError = true;
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
389
432
|
return errorResult(
|
|
390
433
|
`Error: ${outcome.error}${logs ? `\n\nLogs:\n${logs}` : ""}`,
|
|
391
434
|
);
|
|
@@ -408,18 +451,18 @@ export function createExecuteTool(
|
|
|
408
451
|
};
|
|
409
452
|
}
|
|
410
453
|
|
|
411
|
-
const EXECUTE_DESC = `Use for dependent multi-step calls, loops, joins, branching, or reducing large results in a sandbox.
|
|
454
|
+
const EXECUTE_DESC = `Use for dependent multi-step calls, loops, joins, branching, or reducing large results in a sandbox. Never use execute_code for search-only discovery or one downstream call: use search_tools, then call_tool when needed. For 2–10 independent calls use batch_call. Only tools explicitly annotated readOnlyHint: true are available. Each run is limited to ${EXECUTE_MAX_HOST_CALLS} host calls; connecta.batch accepts at most ${EXECUTE_MAX_BATCH_CALLS}; each host call has a ${EXECUTE_HOST_CALL_TIMEOUT_MS / 1_000}-second deadline.
|
|
412
455
|
|
|
413
456
|
Write an async arrow function. It runs with NO network, filesystem, timers, or imports — the only capabilities are:
|
|
414
457
|
- One global per connector: every address <connectorId>.<toolName> from search_tools is callable as <connectorId>.<toolName>(args) with a single args object matching the schema from describe_tools. Names are sanitized to JS identifiers: characters outside [A-Za-z0-9_$] become "_" (e.g. my-service.get.thing → my_service.get_thing), leading digits get "_" prefixed, reserved words get "_" appended.
|
|
415
458
|
- connecta.call(address, args) and connecta.batch(calls) — call raw addresses.
|
|
416
|
-
- connecta.search(args) and connecta.describe(args) — load and inspect request-local catalogs on demand.
|
|
459
|
+
- connecta.search(args) and connecta.describe(args) — load and inspect request-local catalogs on demand. Matches carrying schemas also list inputKeys, requiredInputKeys, and outputKeys — the same names the schema shows, ready to check against before building args. They are absent when a schema is not a plain object shape, so read the schema itself rather than assuming a missing list means no fields.
|
|
417
460
|
- console.log(...) — captured and returned alongside the result.
|
|
418
461
|
|
|
419
462
|
Tool calls return plain values (MCP text content is JSON-parsed when possible) and throw on downstream errors — use try/catch to handle them. Return a JSON-serializable value; large results are truncated, so reduce data in code instead of returning raw payloads.
|
|
420
463
|
|
|
421
|
-
|
|
422
|
-
|
|
464
|
+
Plain JavaScript only — no TypeScript syntax. For unknown-address dependent work, use one execute_code call: search inside it, read the compact schemas, and continue to the dependent calls; do not return search results for a second execute_code call. Compact schemas are TypeScript-like strings, not JSON Schema objects: write the property names they display, never a positional guess or an invented alias.
|
|
465
|
+
Dependent example (only when the second call requires a value returned by the first): async () => { const { tools } = await connecta.search({ query: "pipeline run job logs", includeSchemas: "compact" }); const pick = (suffix) => { const match = tools.find((tool) => tool.address.endsWith(suffix)); if (!match) throw new Error("no tool matching " + suffix); return match.address; }; const run = await connecta.call(pick(".get_run"), { runId: 42 }); const logs = await connecta.call(pick(".get_job_logs"), { jobId: run.failedJobId }); return [run, logs]; }`;
|
|
423
466
|
|
|
424
467
|
/** Register the execute_code meta-tool. Only called when an executor is configured. */
|
|
425
468
|
export function registerExecuteTool(
|
|
@@ -448,11 +491,11 @@ export function registerExecuteTool(
|
|
|
448
491
|
"execute_code",
|
|
449
492
|
{
|
|
450
493
|
description: EXECUTE_DESC,
|
|
451
|
-
inputSchema: {
|
|
494
|
+
inputSchema: z.object({
|
|
452
495
|
code: z
|
|
453
496
|
.string()
|
|
454
497
|
.describe("A JavaScript async arrow function to execute."),
|
|
455
|
-
},
|
|
498
|
+
}),
|
|
456
499
|
// The sandbox exposes only tools that are explicitly read-only, and the
|
|
457
500
|
// executor grants no network, filesystem, env, or timer capabilities.
|
|
458
501
|
annotations: {
|
|
@@ -463,7 +506,7 @@ export function registerExecuteTool(
|
|
|
463
506
|
},
|
|
464
507
|
async (args, extra) => {
|
|
465
508
|
const controller = new AbortController();
|
|
466
|
-
const signals = [extra.signal, ctx.requestSignal].filter(
|
|
509
|
+
const signals = [extra.mcpReq.signal, ctx.requestSignal].filter(
|
|
467
510
|
(signal): signal is AbortSignal => signal !== undefined,
|
|
468
511
|
);
|
|
469
512
|
const forwarders = signals.map((signal) => {
|
package/src/meta-tools.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type {
|
|
4
4
|
ActivityCallSource,
|
|
@@ -611,6 +611,7 @@ export function createMetaTools(
|
|
|
611
611
|
if (!outcome.ok) {
|
|
612
612
|
const failedResult =
|
|
613
613
|
outcome.error.code === "auth_required" ||
|
|
614
|
+
outcome.error.code === "input_required_unsupported" ||
|
|
614
615
|
call.resultMode === "value"
|
|
615
616
|
? jsonResult({
|
|
616
617
|
ok: false,
|
|
@@ -620,7 +621,10 @@ export function createMetaTools(
|
|
|
620
621
|
...(call.diagnostics ? { timing: outcome.timing } : {}),
|
|
621
622
|
})
|
|
622
623
|
: errorResult(outcome.error.message);
|
|
623
|
-
if (
|
|
624
|
+
if (
|
|
625
|
+
outcome.error.code === "auth_required" ||
|
|
626
|
+
outcome.error.code === "input_required_unsupported"
|
|
627
|
+
) {
|
|
624
628
|
failedResult.isError = true;
|
|
625
629
|
}
|
|
626
630
|
return {
|
|
@@ -1114,8 +1118,8 @@ export function createMetaTools(
|
|
|
1114
1118
|
|
|
1115
1119
|
const LIST_DESC =
|
|
1116
1120
|
"List connectors with status, cached tool count, and recent real-call health. Use probe=false for a fast inventory; use probe=true (default) only to diagnose live health or authorization.";
|
|
1117
|
-
const SEARCH_DESC = `
|
|
1118
|
-
const DESCRIBE_DESC = `
|
|
1121
|
+
const SEARCH_DESC = `Unknown address: use 2–4 distinctive action/object terms, not the full request; omit limit initially (default ${DEFAULT_SEARCH_LIMIT}) and page only if needed, up to ${MAX_SEARCH_LIMIT}. includeSchemas="compact" adds the input and any declared output shape; matches also carry declared annotations. Call directly when sufficient. Empty query browses all.`;
|
|
1122
|
+
const DESCRIBE_DESC = `Only when search_tools omitted schemas, a compact shape is ambiguous, or exact JSON constraints are needed. Inspects up to ${MAX_DESCRIBE_ADDRESSES} addresses with schemas and annotations; "compact" is default, while "json" preserves exact constraints.`;
|
|
1119
1123
|
const CALL_DESC =
|
|
1120
1124
|
'Use for one tool explicitly annotated readOnlyHint: true. For 2–10 independent read-only calls use batch_call; for dependent steps or data reduction use execute_code when available. Unannotated, write-capable, and destructive tools are refused and require call_destructive_tool. fields selects JSON dot-paths, resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
|
|
1121
1125
|
const CALL_DESTRUCTIVE_DESC =
|
|
@@ -1222,7 +1226,7 @@ export function registerMetaTools(
|
|
|
1222
1226
|
"skills",
|
|
1223
1227
|
{
|
|
1224
1228
|
description: describedFor(registry, SKILLS_DESC, "skills"),
|
|
1225
|
-
inputSchema: { name: z.string().optional() },
|
|
1229
|
+
inputSchema: z.object({ name: z.string().optional() }),
|
|
1226
1230
|
annotations: READ_ONLY_LOCAL,
|
|
1227
1231
|
},
|
|
1228
1232
|
async (args) => mt.skills(args as SkillArgs),
|
|
@@ -1232,7 +1236,7 @@ export function registerMetaTools(
|
|
|
1232
1236
|
"list_connectors",
|
|
1233
1237
|
{
|
|
1234
1238
|
description: LIST_DESC,
|
|
1235
|
-
inputSchema: { probe: z.boolean().optional() },
|
|
1239
|
+
inputSchema: z.object({ probe: z.boolean().optional() }),
|
|
1236
1240
|
annotations: READ_ONLY_REMOTE,
|
|
1237
1241
|
},
|
|
1238
1242
|
async (args) => mt.listConnectors(args as ListArgs),
|
|
@@ -1242,14 +1246,14 @@ export function registerMetaTools(
|
|
|
1242
1246
|
"search_tools",
|
|
1243
1247
|
{
|
|
1244
1248
|
description: describedFor(registry, SEARCH_DESC, "search"),
|
|
1245
|
-
inputSchema: {
|
|
1249
|
+
inputSchema: z.object({
|
|
1246
1250
|
query: z.string().optional(),
|
|
1247
1251
|
connector: z.string().optional(),
|
|
1248
1252
|
limit: z.number().int().positive().max(MAX_SEARCH_LIMIT).optional(),
|
|
1249
1253
|
offset: z.number().int().nonnegative().optional(),
|
|
1250
1254
|
fullDescriptions: z.boolean().optional(),
|
|
1251
1255
|
includeSchemas: z.enum(["compact", "json"]).optional(),
|
|
1252
|
-
},
|
|
1256
|
+
}),
|
|
1253
1257
|
annotations: READ_ONLY_REMOTE,
|
|
1254
1258
|
},
|
|
1255
1259
|
async (args) => mt.searchTools(args as SearchArgs),
|
|
@@ -1259,11 +1263,11 @@ export function registerMetaTools(
|
|
|
1259
1263
|
"describe_tools",
|
|
1260
1264
|
{
|
|
1261
1265
|
description: describedFor(registry, DESCRIBE_DESC, "describe"),
|
|
1262
|
-
inputSchema: {
|
|
1266
|
+
inputSchema: z.object({
|
|
1263
1267
|
addresses: z.array(z.string()).max(MAX_DESCRIBE_ADDRESSES),
|
|
1264
1268
|
format: z.enum(["compact", "json"]).optional(),
|
|
1265
1269
|
fullDescriptions: z.boolean().optional(),
|
|
1266
|
-
},
|
|
1270
|
+
}),
|
|
1267
1271
|
annotations: READ_ONLY_REMOTE,
|
|
1268
1272
|
},
|
|
1269
1273
|
async (args) => mt.describeTools(args as DescribeArgs),
|
|
@@ -1273,7 +1277,7 @@ export function registerMetaTools(
|
|
|
1273
1277
|
"call_tool",
|
|
1274
1278
|
{
|
|
1275
1279
|
description: CALL_DESC,
|
|
1276
|
-
inputSchema: CALL_INPUT_SCHEMA,
|
|
1280
|
+
inputSchema: z.object(CALL_INPUT_SCHEMA),
|
|
1277
1281
|
// call_tool admits only tools that are themselves explicitly read-only;
|
|
1278
1282
|
// anything else is refused and routed to call_destructive_tool.
|
|
1279
1283
|
annotations: READ_ONLY_REMOTE,
|
|
@@ -1285,7 +1289,7 @@ export function registerMetaTools(
|
|
|
1285
1289
|
"call_destructive_tool",
|
|
1286
1290
|
{
|
|
1287
1291
|
description: CALL_DESTRUCTIVE_DESC,
|
|
1288
|
-
inputSchema: CALL_INPUT_SCHEMA,
|
|
1292
|
+
inputSchema: z.object(CALL_INPUT_SCHEMA),
|
|
1289
1293
|
annotations: {
|
|
1290
1294
|
destructiveHint: true,
|
|
1291
1295
|
readOnlyHint: false,
|
|
@@ -1299,10 +1303,10 @@ export function registerMetaTools(
|
|
|
1299
1303
|
"authorize_connector",
|
|
1300
1304
|
{
|
|
1301
1305
|
description: AUTHORIZE_DESC,
|
|
1302
|
-
inputSchema: {
|
|
1306
|
+
inputSchema: z.object({
|
|
1303
1307
|
connector: z.string(),
|
|
1304
1308
|
force: z.boolean().optional(),
|
|
1305
|
-
},
|
|
1309
|
+
}),
|
|
1306
1310
|
// Starts (or with force, resets) a downstream OAuth flow — it changes
|
|
1307
1311
|
// stored connector auth state, so it is deliberately not read-only.
|
|
1308
1312
|
annotations: {
|
|
@@ -1318,7 +1322,7 @@ export function registerMetaTools(
|
|
|
1318
1322
|
"get_result",
|
|
1319
1323
|
{
|
|
1320
1324
|
description: GET_RESULT_DESC,
|
|
1321
|
-
inputSchema: {
|
|
1325
|
+
inputSchema: z.object({
|
|
1322
1326
|
id: z.string(),
|
|
1323
1327
|
// Both bounds are the shared rules (isValidResultOffset,
|
|
1324
1328
|
// isValidMaxResultBytes) expressed for the wire: spelling them against
|
|
@@ -1326,7 +1330,7 @@ export function registerMetaTools(
|
|
|
1326
1330
|
// in-handler checks if either floor ever moves.
|
|
1327
1331
|
offset: z.number().int().min(MIN_RESULT_OFFSET).optional(),
|
|
1328
1332
|
maxBytes: z.number().int().min(MIN_MAX_RESULT_BYTES).optional(),
|
|
1329
|
-
},
|
|
1333
|
+
}),
|
|
1330
1334
|
annotations: READ_ONLY_LOCAL,
|
|
1331
1335
|
},
|
|
1332
1336
|
async (args) => mt.getResult(args as GetResultArgs),
|
|
@@ -1336,7 +1340,7 @@ export function registerMetaTools(
|
|
|
1336
1340
|
"batch_call",
|
|
1337
1341
|
{
|
|
1338
1342
|
description: BATCH_DESC,
|
|
1339
|
-
inputSchema: {
|
|
1343
|
+
inputSchema: z.object({
|
|
1340
1344
|
calls: z
|
|
1341
1345
|
.array(z.object(CALL_INPUT_SCHEMA))
|
|
1342
1346
|
.min(1)
|
|
@@ -1345,7 +1349,7 @@ export function registerMetaTools(
|
|
|
1345
1349
|
timeoutMs: z.number().int().positive().optional(),
|
|
1346
1350
|
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
1347
1351
|
diagnostics: z.boolean().optional(),
|
|
1348
|
-
},
|
|
1352
|
+
}),
|
|
1349
1353
|
// Same gate as call_tool: every call in the batch must be explicitly
|
|
1350
1354
|
// read-only or the batch is refused.
|
|
1351
1355
|
annotations: READ_ONLY_REMOTE,
|
package/src/routes/mcp.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
createMcpHandler,
|
|
3
|
+
isLegacyRequest,
|
|
4
|
+
McpServer,
|
|
5
|
+
WebStandardStreamableHTTPServerTransport,
|
|
6
|
+
} from "@modelcontextprotocol/server";
|
|
3
7
|
import type { ActivityActor, ActivityRequestContext } from "../activity.js";
|
|
4
8
|
import { registerExecuteTool } from "../execute.js";
|
|
5
9
|
import {
|
|
@@ -21,7 +25,7 @@ export const MCP_CORS_HEADERS = {
|
|
|
21
25
|
"Access-Control-Allow-Origin": "*",
|
|
22
26
|
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
23
27
|
"Access-Control-Allow-Headers":
|
|
24
|
-
"Content-Type, Authorization, mcp-protocol-version, mcp-session-id",
|
|
28
|
+
"Content-Type, Authorization, mcp-protocol-version, mcp-session-id, mcp-method, mcp-name",
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
// Browser-based MCP clients call /mcp cross-origin. Without CORS on every
|
|
@@ -188,54 +192,76 @@ async function serveMcp(
|
|
|
188
192
|
registry: RegistryView,
|
|
189
193
|
runtimeContext?: RuntimeExecutionContext,
|
|
190
194
|
): Promise<Response> {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
:
|
|
216
|
-
|
|
217
|
-
? { probeTimeoutMs: opts.probeTimeoutMs }
|
|
218
|
-
: {}),
|
|
219
|
-
...(opts.discoveryConcurrency !== undefined
|
|
220
|
-
? { discoveryConcurrency: opts.discoveryConcurrency }
|
|
221
|
-
: {}),
|
|
222
|
-
requestSignal: request.signal,
|
|
223
|
-
...(runtimeContext
|
|
224
|
-
? { defer: runtimeContext.waitUntil.bind(runtimeContext) }
|
|
225
|
-
: {}),
|
|
226
|
-
});
|
|
227
|
-
if (opts.executor) {
|
|
228
|
-
registerExecuteTool(server, registry, {
|
|
195
|
+
const createServer = (): McpServer => {
|
|
196
|
+
const server = new McpServer(opts.serverInfo, {
|
|
197
|
+
instructions: CONNECTA_INSTRUCTIONS,
|
|
198
|
+
cacheHints: {
|
|
199
|
+
"tools/list": {
|
|
200
|
+
ttlMs: 3_600_000,
|
|
201
|
+
cacheScope: "private",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
const activity: ActivityRequestContext | undefined = opts.activity
|
|
206
|
+
? {
|
|
207
|
+
sink: opts.activity,
|
|
208
|
+
actor,
|
|
209
|
+
requestId: crypto.randomUUID(),
|
|
210
|
+
serverInfo: opts.serverInfo,
|
|
211
|
+
...(opts.activityDeploymentId
|
|
212
|
+
? { deploymentId: opts.activityDeploymentId }
|
|
213
|
+
: {}),
|
|
214
|
+
...(runtimeContext?.waitUntil
|
|
215
|
+
? { defer: runtimeContext.waitUntil.bind(runtimeContext) }
|
|
216
|
+
: {}),
|
|
217
|
+
logger: opts.logger,
|
|
218
|
+
}
|
|
219
|
+
: undefined;
|
|
220
|
+
registerMetaTools(server, registry, {
|
|
229
221
|
baseUrl,
|
|
230
|
-
executor: opts.executor,
|
|
231
|
-
logger: opts.logger,
|
|
232
222
|
...(activity ? { activity } : {}),
|
|
233
|
-
|
|
223
|
+
...(opts.defaultToolTimeoutMs !== undefined
|
|
224
|
+
? { defaultToolTimeoutMs: opts.defaultToolTimeoutMs }
|
|
225
|
+
: {}),
|
|
226
|
+
...(opts.probeTimeoutMs !== undefined
|
|
227
|
+
? { probeTimeoutMs: opts.probeTimeoutMs }
|
|
228
|
+
: {}),
|
|
234
229
|
...(opts.discoveryConcurrency !== undefined
|
|
235
230
|
? { discoveryConcurrency: opts.discoveryConcurrency }
|
|
236
231
|
: {}),
|
|
232
|
+
requestSignal: request.signal,
|
|
233
|
+
...(runtimeContext
|
|
234
|
+
? { defer: runtimeContext.waitUntil.bind(runtimeContext) }
|
|
235
|
+
: {}),
|
|
237
236
|
});
|
|
237
|
+
if (opts.executor) {
|
|
238
|
+
registerExecuteTool(server, registry, {
|
|
239
|
+
baseUrl,
|
|
240
|
+
executor: opts.executor,
|
|
241
|
+
logger: opts.logger,
|
|
242
|
+
...(activity ? { activity } : {}),
|
|
243
|
+
requestSignal: request.signal,
|
|
244
|
+
...(opts.discoveryConcurrency !== undefined
|
|
245
|
+
? { discoveryConcurrency: opts.discoveryConcurrency }
|
|
246
|
+
: {}),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return server;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// The v2 entry's built-in legacy fallback streams 2025 results as SSE.
|
|
253
|
+
// Connecta's established wire contract is JSON, so retain the documented
|
|
254
|
+
// user-land legacy branch with the same transport setting while the modern
|
|
255
|
+
// branch uses the fetch-native handler.
|
|
256
|
+
if (!(await isLegacyRequest(request))) {
|
|
257
|
+
return createMcpHandler(createServer, {
|
|
258
|
+
legacy: "reject",
|
|
259
|
+
onerror: (error) => opts.logger.error("[connecta] MCP handler error", error),
|
|
260
|
+
}).fetch(request);
|
|
238
261
|
}
|
|
262
|
+
|
|
263
|
+
// Fresh server + transport per legacy request, stateless and JSON-shaped.
|
|
264
|
+
const server = createServer();
|
|
239
265
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
240
266
|
enableJsonResponse: true,
|
|
241
267
|
});
|
package/src/routes/oauth.ts
CHANGED
|
@@ -309,7 +309,7 @@ export async function routeOAuthCallback(
|
|
|
309
309
|
return refused();
|
|
310
310
|
}
|
|
311
311
|
try {
|
|
312
|
-
await connector.finishAuth(code, connectorContext);
|
|
312
|
+
await connector.finishAuth(code, connectorContext, url.searchParams);
|
|
313
313
|
await opts.registry.invalidateStored(id);
|
|
314
314
|
return html(
|
|
315
315
|
`Connected "${id}". You can close this window.`,
|
package/src/routes/shared.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Implementation } from "@modelcontextprotocol/server";
|
|
2
2
|
import type { ActivityActor, ActivityReadGate, ActivityStore } from "../activity.js";
|
|
3
3
|
import type { CredentialVault } from "../credentials.js";
|
|
4
4
|
import type { DeferredWork } from "../connector-scope.js";
|
|
@@ -18,7 +18,7 @@ export interface ServerOptions {
|
|
|
18
18
|
publicUrl?: string;
|
|
19
19
|
// The SDK's Implementation shape: name/version plus optional title,
|
|
20
20
|
// websiteUrl, and icons (MCP icons spec) that clients may render.
|
|
21
|
-
serverInfo:
|
|
21
|
+
serverInfo: Implementation;
|
|
22
22
|
logger: Logger;
|
|
23
23
|
activity?: ActivityStore;
|
|
24
24
|
activityReadGate?: ActivityReadGate;
|
package/src/skills.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { Connector } from "./types.js";
|
|
2
2
|
|
|
3
3
|
export const CONNECTA_INSTRUCTIONS =
|
|
4
|
-
'Connecta exposes
|
|
4
|
+
'Connecta exposes integrations behind meta-tools. Unknown address: use search_tools with 2–4 distinctive action/object terms, no initial limit, and includeSchemas="compact"; describe_tools only if that shape is ambiguous or exact JSON constraints are needed. Use call_tool for one explicitly read-only call, batch_call for 2–10 independent read-only calls, and execute_code (when available) only for dependencies, loops, joins, or substantial reduction — searching inside that one run rather than searching first. Use call_destructive_tool individually for unannotated, write-capable, or destructive tools. authorize_connector follows auth_required; get_result follows truncation. If this routing is unfamiliar, fetch skills({ name: "usage" }).';
|
|
5
5
|
|
|
6
6
|
export const USAGE_SKILL = `# Connecta usage
|
|
7
7
|
|
|
8
8
|
## Choose the smallest execution tool
|
|
9
9
|
|
|
10
|
-
Use exact addresses returned by discovery; never invent one.
|
|
10
|
+
Use exact addresses returned by discovery; never invent one. Search with 2–4 distinctive action/object terms rather than the full request, and omit \`limit\` initially so the default page stays small.
|
|
11
11
|
|
|
12
|
-
- Unknown address: \`search_tools({ query, includeSchemas: "compact" })
|
|
13
|
-
-
|
|
12
|
+
- Unknown address: \`search_tools({ query, includeSchemas: "compact" })\`; every match then includes its input shape plus any declared output shape and annotations.
|
|
13
|
+
- Compact shape still ambiguous: \`describe_tools({ addresses: [...] })\`; use \`format: "json"\` only for exact constraints.
|
|
14
14
|
- One explicitly read-only call: \`call_tool\`.
|
|
15
15
|
- Two to ten independent explicitly read-only calls: \`batch_call\`.
|
|
16
16
|
- Dependent read-only calls, loops, joins, branching, or large-result reduction: \`execute_code\` when available.
|
|
@@ -18,15 +18,15 @@ Use exact addresses returned by discovery; never invent one.
|
|
|
18
18
|
- Truncated result: retry with \`fields\` when possible; otherwise page it with \`get_result\`.
|
|
19
19
|
- \`auth_required\`: use \`authorize_connector\`, give its recovery handoff to the operator, then retry the original call.
|
|
20
20
|
|
|
21
|
-
Use \`list_connectors({ probe: false })\` for a fast
|
|
21
|
+
Use \`list_connectors({ probe: false })\` for a fast observed-health inventory; use \`probe: true\` only to diagnose live health or authorization.
|
|
22
22
|
|
|
23
23
|
## Code mode
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Unknown addresses plus dependent calls: search inside the run, not in an outer \`search_tools\`. Parallelize independent calls with \`Promise.all\` or \`connecta.batch\`.
|
|
26
26
|
|
|
27
27
|
Connector namespace calls and \`connecta.call\` use the same read-only gate and throw on downstream errors. Catch only failures the workflow can handle; let authorization failures return to the agent for recovery.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Skip code mode for one call, calls suited to \`batch_call\`, or tools lacking \`readOnlyHint: true\`. Return only the needed reduction.
|
|
30
30
|
`;
|
|
31
31
|
|
|
32
32
|
/**
|
package/src/types.ts
CHANGED
|
@@ -276,8 +276,16 @@ export interface Connector {
|
|
|
276
276
|
* pending URL could complete consent with their own account.
|
|
277
277
|
*/
|
|
278
278
|
verifyState?(state: string | null, ctx: ConnectorContext): Promise<boolean>;
|
|
279
|
-
/**
|
|
280
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Optional: complete a downstream OAuth flow (called by
|
|
281
|
+
* /oauth/callback/<id>). `callbackParams` preserves the authorization
|
|
282
|
+
* server's RFC 9207 `iss` response parameter for SDK validation.
|
|
283
|
+
*/
|
|
284
|
+
finishAuth?(
|
|
285
|
+
code: string,
|
|
286
|
+
ctx: ConnectorContext,
|
|
287
|
+
callbackParams?: URLSearchParams,
|
|
288
|
+
): Promise<void>;
|
|
281
289
|
/**
|
|
282
290
|
* Optional: serve a connector-owned HTTP route — for example a signed
|
|
283
291
|
* download link minted by one of the connector's tools. Called only after
|
package/src/version.ts
CHANGED