@tinycloud/mcp 0.7.4-beta.1 → 0.8.0-beta.2
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 +65 -9
- package/dist/cli.cjs +69 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +70 -32
- package/dist/cli.js.map +1 -1
- package/dist/http-cli.cjs +130 -47
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +128 -45
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +127 -44
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +2 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +128 -45
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +127 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +128 -45
- package/dist/index.js.map +1 -1
- package/dist/results.cjs +1 -1
- package/dist/results.cjs.map +1 -1
- package/dist/results.js +1 -1
- package/dist/results.js.map +1 -1
- package/dist/server.cjs +69 -31
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +70 -32
- package/dist/server.js.map +1 -1
- package/dist/tools.cjs +67 -29
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +3 -3
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +68 -30
- package/dist/tools.js.map +1 -1
- package/generated/mcp-facts.json +13 -6
- package/package.json +2 -2
- package/skills/tinycloud-delegated-secrets/SKILL.md +4 -4
package/dist/index.cjs
CHANGED
|
@@ -60,7 +60,7 @@ function toMcpToolResult(result, bindings = [], options = {}) {
|
|
|
60
60
|
let serialized;
|
|
61
61
|
if (options.surface === "hosted") {
|
|
62
62
|
serialized = JSON.stringify(canonical);
|
|
63
|
-
if (options.readOnly === true && serialized.length > 1e5) {
|
|
63
|
+
if ((options.readOnly === true || options.operationId === "tinycloud.apps.context" || options.operationId === "tinycloud.apps.query") && serialized.length > 1e5) {
|
|
64
64
|
const sql = options.operationId?.startsWith("tinycloud.sql.") || options.operationId === "tinycloud.apps.query";
|
|
65
65
|
canonical = {
|
|
66
66
|
status: "error",
|
|
@@ -128,6 +128,44 @@ function logToolCall(tool, code, startedAt, replayedGrants) {
|
|
|
128
128
|
`);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// src/schemas.ts
|
|
132
|
+
var DRAFT_7 = "http://json-schema.org/draft-07/schema#";
|
|
133
|
+
var DRAFT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
134
|
+
var schemaMaps = /* @__PURE__ */ new Set(["properties", "patternProperties", "definitions", "$defs"]);
|
|
135
|
+
var schemaArrays = /* @__PURE__ */ new Set(["allOf", "anyOf", "oneOf"]);
|
|
136
|
+
var schemaValues = /* @__PURE__ */ new Set([
|
|
137
|
+
"additionalProperties",
|
|
138
|
+
"items",
|
|
139
|
+
"contains",
|
|
140
|
+
"propertyNames",
|
|
141
|
+
"not",
|
|
142
|
+
"if",
|
|
143
|
+
"then",
|
|
144
|
+
"else"
|
|
145
|
+
]);
|
|
146
|
+
function toMcpJsonSchema(schema) {
|
|
147
|
+
if (typeof schema === "boolean") return schema;
|
|
148
|
+
if (Array.isArray(schema.items) || "additionalItems" in schema || "dependencies" in schema || "$ref" in schema && Object.keys(schema).some((key) => key !== "$ref")) {
|
|
149
|
+
throw new Error("The operations schema needs a semantic migration to JSON Schema 2020-12.");
|
|
150
|
+
}
|
|
151
|
+
const projected = {};
|
|
152
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
153
|
+
if (key === "$schema") {
|
|
154
|
+
if (value !== DRAFT_7) throw new Error(`Unsupported operations schema dialect: ${String(value)}.`);
|
|
155
|
+
projected[key] = DRAFT_2020_12;
|
|
156
|
+
} else if (schemaMaps.has(key)) {
|
|
157
|
+
projected[key] = Object.fromEntries(Object.entries(value).map(([name, child]) => [name, toMcpJsonSchema(child)]));
|
|
158
|
+
} else if (schemaArrays.has(key)) {
|
|
159
|
+
projected[key] = value.map(toMcpJsonSchema);
|
|
160
|
+
} else if (schemaValues.has(key)) {
|
|
161
|
+
projected[key] = toMcpJsonSchema(value);
|
|
162
|
+
} else {
|
|
163
|
+
projected[key] = value;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return projected;
|
|
167
|
+
}
|
|
168
|
+
|
|
131
169
|
// src/tools.ts
|
|
132
170
|
var import_meta = {};
|
|
133
171
|
var TOOL_NAMES = [
|
|
@@ -171,9 +209,9 @@ var generatedCatalog = packageRequire(
|
|
|
171
209
|
var stableErrorCodes = Object.fromEntries(
|
|
172
210
|
generatedCatalog.stableErrors.map((code) => [code, true])
|
|
173
211
|
);
|
|
174
|
-
var
|
|
212
|
+
var PRIVATE_SURFACES = ["local", "hosted"];
|
|
175
213
|
function bindingSurfaces(binding) {
|
|
176
|
-
return binding.surfaces ??
|
|
214
|
+
return binding.surfaces ?? PRIVATE_SURFACES;
|
|
177
215
|
}
|
|
178
216
|
var TOOL_BINDINGS = [
|
|
179
217
|
{
|
|
@@ -357,11 +395,11 @@ var TOOL_BINDINGS = [
|
|
|
357
395
|
name: "tinycloud_apps_context",
|
|
358
396
|
operationId: "tinycloud.apps.context",
|
|
359
397
|
operationVersion: 1,
|
|
360
|
-
readOnlyHint:
|
|
398
|
+
readOnlyHint: false,
|
|
361
399
|
idempotentHint: true,
|
|
362
|
-
destructiveHint:
|
|
363
|
-
openWorldHint:
|
|
364
|
-
surfaces: ["hosted"]
|
|
400
|
+
destructiveHint: true,
|
|
401
|
+
openWorldHint: false,
|
|
402
|
+
surfaces: ["hosted", "public"]
|
|
365
403
|
},
|
|
366
404
|
{
|
|
367
405
|
name: "tinycloud_apps_read",
|
|
@@ -370,8 +408,8 @@ var TOOL_BINDINGS = [
|
|
|
370
408
|
readOnlyHint: true,
|
|
371
409
|
idempotentHint: true,
|
|
372
410
|
destructiveHint: false,
|
|
373
|
-
openWorldHint:
|
|
374
|
-
surfaces: ["hosted"]
|
|
411
|
+
openWorldHint: false,
|
|
412
|
+
surfaces: ["hosted", "public"]
|
|
375
413
|
},
|
|
376
414
|
{
|
|
377
415
|
name: "tinycloud_apps_write",
|
|
@@ -380,34 +418,34 @@ var TOOL_BINDINGS = [
|
|
|
380
418
|
readOnlyHint: false,
|
|
381
419
|
idempotentHint: false,
|
|
382
420
|
destructiveHint: true,
|
|
383
|
-
openWorldHint:
|
|
384
|
-
surfaces: ["hosted"]
|
|
421
|
+
openWorldHint: false,
|
|
422
|
+
surfaces: ["hosted", "public"]
|
|
385
423
|
},
|
|
386
424
|
{
|
|
387
425
|
name: "tinycloud_apps_query",
|
|
388
426
|
operationId: "tinycloud.apps.query",
|
|
389
427
|
operationVersion: 1,
|
|
390
|
-
readOnlyHint:
|
|
428
|
+
readOnlyHint: false,
|
|
391
429
|
idempotentHint: true,
|
|
392
|
-
destructiveHint:
|
|
393
|
-
openWorldHint:
|
|
394
|
-
surfaces: ["hosted"]
|
|
430
|
+
destructiveHint: true,
|
|
431
|
+
openWorldHint: false,
|
|
432
|
+
surfaces: ["hosted", "public"]
|
|
395
433
|
}
|
|
396
434
|
];
|
|
435
|
+
var definitions = new Map(generatedCatalog.operations.map((operation) => [
|
|
436
|
+
`${operation.id}@${operation.version}`,
|
|
437
|
+
{
|
|
438
|
+
...operation,
|
|
439
|
+
input: toMcpJsonSchema(operation.input),
|
|
440
|
+
result: toMcpJsonSchema(operation.result)
|
|
441
|
+
}
|
|
442
|
+
]));
|
|
397
443
|
function createJsonSchemaValidator() {
|
|
398
|
-
|
|
399
|
-
(0, import_ajv.addFormats)(ajv);
|
|
400
|
-
return new import_ajv.AjvJsonSchemaValidator(ajv);
|
|
444
|
+
return new import_ajv.AjvJsonSchemaValidator();
|
|
401
445
|
}
|
|
402
446
|
function registerTinyCloudTools(server, selection, validator) {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
`${operation.id}@${operation.version}`,
|
|
406
|
-
operation
|
|
407
|
-
])
|
|
408
|
-
);
|
|
409
|
-
for (const binding of TOOL_BINDINGS) {
|
|
410
|
-
if (!bindingSurfaces(binding).includes(selection.surface)) continue;
|
|
447
|
+
const bindings = TOOL_BINDINGS.filter((binding) => bindingSurfaces(binding).includes(selection.surface));
|
|
448
|
+
for (const binding of bindings) {
|
|
411
449
|
const operation = definitions.get(`${binding.operationId}@${binding.operationVersion}`);
|
|
412
450
|
if (operation === void 0) {
|
|
413
451
|
throw new Error(`Generated operations catalog is missing ${binding.operationId}@${binding.operationVersion}.`);
|
|
@@ -453,15 +491,15 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
453
491
|
operationVersion: binding.operationVersion,
|
|
454
492
|
input
|
|
455
493
|
});
|
|
456
|
-
const response = toMcpToolResult(projected,
|
|
457
|
-
surface: selection.surface,
|
|
494
|
+
const response = toMcpToolResult(projected, bindings, {
|
|
495
|
+
surface: selection.surface === "local" ? "local" : "hosted",
|
|
458
496
|
readOnly: binding.readOnlyHint,
|
|
459
497
|
operationId: binding.operationId
|
|
460
498
|
});
|
|
461
499
|
code = outcomeCode(response.structuredContent);
|
|
462
500
|
return response;
|
|
463
501
|
} finally {
|
|
464
|
-
if (selection.surface
|
|
502
|
+
if (selection.surface !== "local") {
|
|
465
503
|
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
466
504
|
}
|
|
467
505
|
}
|
|
@@ -484,7 +522,7 @@ function outcomeCode(result) {
|
|
|
484
522
|
// package.json
|
|
485
523
|
var package_default = {
|
|
486
524
|
name: "@tinycloud/mcp",
|
|
487
|
-
version: "0.
|
|
525
|
+
version: "0.8.0-beta.2",
|
|
488
526
|
description: "TinyCloud delegated operations MCP server",
|
|
489
527
|
keywords: [
|
|
490
528
|
"tinycloud",
|
|
@@ -557,7 +595,7 @@ var package_default = {
|
|
|
557
595
|
"@modelcontextprotocol/server": "2.0.0-beta.5",
|
|
558
596
|
"@tinycloud/node-sdk": "3.6.0-beta.2",
|
|
559
597
|
"@tinycloud/node-sdk-wasm": "1.7.8",
|
|
560
|
-
"@tinycloud/operations": "0.7.4-beta.
|
|
598
|
+
"@tinycloud/operations": "0.7.4-beta.2",
|
|
561
599
|
jose: "6.2.4"
|
|
562
600
|
},
|
|
563
601
|
devDependencies: {
|
|
@@ -696,6 +734,7 @@ if (isDirectInvocation()) {
|
|
|
696
734
|
}
|
|
697
735
|
|
|
698
736
|
// src/http.ts
|
|
737
|
+
var import_node_path4 = require("path");
|
|
699
738
|
var import_server5 = require("@modelcontextprotocol/server");
|
|
700
739
|
var import_node_sdk3 = require("@tinycloud/node-sdk");
|
|
701
740
|
|
|
@@ -1018,7 +1057,7 @@ var RemoteTenantStore = class {
|
|
|
1018
1057
|
async approvalRedirect(state) {
|
|
1019
1058
|
const pending = await this.#readPending(state, false);
|
|
1020
1059
|
const tenant = await this.#readTenant(pending.tenantId);
|
|
1021
|
-
const callback = new URL("/connect/callback
|
|
1060
|
+
const callback = new URL(`${this.#config.approvalPath ?? "/connect"}/callback`, this.#config.publicUrl);
|
|
1022
1061
|
callback.searchParams.set("state", state);
|
|
1023
1062
|
const params = new URLSearchParams({
|
|
1024
1063
|
did: tenant.sessionDid,
|
|
@@ -1437,7 +1476,7 @@ var RemoteTenantStore = class {
|
|
|
1437
1476
|
await (0, import_promises.rename)(`${path}.processing`, path);
|
|
1438
1477
|
}
|
|
1439
1478
|
#connectUrl(pending) {
|
|
1440
|
-
const url = new URL("/connect", this.#config.publicUrl);
|
|
1479
|
+
const url = new URL(this.#config.approvalPath ?? "/connect", this.#config.publicUrl);
|
|
1441
1480
|
url.searchParams.set("state", `${pending.tenantId}.${pending.nonce}.${this.#signState(pending.tenantId, pending.nonce)}`);
|
|
1442
1481
|
return url.toString();
|
|
1443
1482
|
}
|
|
@@ -1598,6 +1637,29 @@ function samePendingRequest(pending, expected) {
|
|
|
1598
1637
|
var REQUIRED_SCOPE = "tinycloud:mcp";
|
|
1599
1638
|
function createHostedMcpApp(config) {
|
|
1600
1639
|
const resourceUrl = canonicalResourceUrl(config.publicUrl);
|
|
1640
|
+
const pluginUrl = new URL("./chatgpt/mcp", resourceUrl);
|
|
1641
|
+
const hosted = createHostedSurface(config, resourceUrl, "hosted");
|
|
1642
|
+
const plugin = createHostedSurface({
|
|
1643
|
+
...config,
|
|
1644
|
+
stateDir: (0, import_node_path4.join)(config.stateDir, "chatgpt"),
|
|
1645
|
+
approvalPath: new URL("./connect", pluginUrl).pathname
|
|
1646
|
+
}, pluginUrl, "public");
|
|
1647
|
+
const pluginPaths = {
|
|
1648
|
+
[pluginUrl.pathname]: true,
|
|
1649
|
+
[new URL("./connect", pluginUrl).pathname]: true,
|
|
1650
|
+
[new URL("./connect/callback", pluginUrl).pathname]: true,
|
|
1651
|
+
[new URL((0, import_server5.getOAuthProtectedResourceMetadataUrl)(pluginUrl)).pathname]: true
|
|
1652
|
+
};
|
|
1653
|
+
return {
|
|
1654
|
+
fetch: (request) => (pluginPaths[new URL(request.url).pathname] === true ? plugin : hosted).fetch(request),
|
|
1655
|
+
close: async () => {
|
|
1656
|
+
await Promise.all([hosted.close(), plugin.close()]);
|
|
1657
|
+
}
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
function createHostedSurface(config, resourceUrl, surface) {
|
|
1661
|
+
const connectPath = config.approvalPath ?? "/connect";
|
|
1662
|
+
const callbackPath = `${connectPath}/callback`;
|
|
1601
1663
|
const issuer = String(config.oauthMetadata.issuer);
|
|
1602
1664
|
const jwksUri = String(config.oauthMetadata.jwks_uri);
|
|
1603
1665
|
const verifier = createOpenKeyTokenVerifier({
|
|
@@ -1622,11 +1684,11 @@ function createHostedMcpApp(config) {
|
|
|
1622
1684
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1623
1685
|
{
|
|
1624
1686
|
jsonSchemaValidator: validator,
|
|
1625
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged. For personal records such as weight, workouts, goals or todos, call tinycloud_apps_context first."
|
|
1687
|
+
instructions: surface === "public" ? "Use TinyCloud only for the user's requested task. Call tinycloud_connect first. If an operation returns authority_required, present approval.url and wait for the user's approval before retrying. If approval is declined, stop that operation. For personal records, call tinycloud_apps_context first. Never request or store passwords, API keys, authentication codes, government identifiers, payment-card data, or protected health information." : "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged. For personal records such as weight, workouts, goals or todos, call tinycloud_apps_context first."
|
|
1626
1688
|
}
|
|
1627
1689
|
);
|
|
1628
1690
|
registerTinyCloudTools(server, {
|
|
1629
|
-
surface
|
|
1691
|
+
surface,
|
|
1630
1692
|
profile: "agent",
|
|
1631
1693
|
explicitProfile: true,
|
|
1632
1694
|
allowOwnerProfile: false,
|
|
@@ -1634,7 +1696,7 @@ function createHostedMcpApp(config) {
|
|
|
1634
1696
|
beforeOperation: () => store.validateTenant(subject, ownerDids),
|
|
1635
1697
|
transformOperationResult: (result, invocation) => store.decorateAuthorityResult(subject, ownerDids, result, invocation)
|
|
1636
1698
|
}, validator);
|
|
1637
|
-
registerConnectTool(server, validator, store, identity);
|
|
1699
|
+
registerConnectTool(server, validator, store, identity, surface);
|
|
1638
1700
|
return server;
|
|
1639
1701
|
}, {
|
|
1640
1702
|
legacy: "stateless",
|
|
@@ -1645,7 +1707,7 @@ function createHostedMcpApp(config) {
|
|
|
1645
1707
|
resourceServerUrl: resourceUrl,
|
|
1646
1708
|
serviceDocumentationUrl: new URL("https://docs.tinycloud.xyz/cli/mcp"),
|
|
1647
1709
|
scopesSupported: [REQUIRED_SCOPE],
|
|
1648
|
-
resourceName: "TinyCloud hosted MCP"
|
|
1710
|
+
resourceName: surface === "public" ? "TinyCloud personal records" : "TinyCloud hosted MCP"
|
|
1649
1711
|
};
|
|
1650
1712
|
const allowedHosts = [resourceUrl.hostname];
|
|
1651
1713
|
return {
|
|
@@ -1658,16 +1720,32 @@ function createHostedMcpApp(config) {
|
|
|
1658
1720
|
}
|
|
1659
1721
|
const hostFailure = (0, import_server5.hostHeaderValidationResponse)(request, allowedHosts);
|
|
1660
1722
|
if (hostFailure !== void 0) return hostFailure;
|
|
1661
|
-
if (url.pathname ===
|
|
1723
|
+
if (url.pathname === connectPath) {
|
|
1662
1724
|
if (request.method !== "GET") return methodNotAllowed("GET");
|
|
1663
1725
|
try {
|
|
1664
1726
|
const destination = await store.approvalRedirect(requiredState(url));
|
|
1665
1727
|
return Response.redirect(destination, 302);
|
|
1666
1728
|
} catch {
|
|
1729
|
+
if (request.headers.get("accept")?.includes("text/html")) {
|
|
1730
|
+
return new Response(
|
|
1731
|
+
'<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>TinyCloud approval unavailable</title><main><h1>This approval link is unavailable</h1><p>It may have expired or already been used. No new approval was accepted through this link.</p><p>Return to the conversation that requested access. In ChatGPT, select @TinyCloud and ask it to continue your original request. If access is still needed, it can provide a fresh approval link.</p><p>If you declined access, no further approval is required. Never paste passwords, codes or approval links into support messages.</p></main></html>',
|
|
1732
|
+
{
|
|
1733
|
+
status: 400,
|
|
1734
|
+
headers: {
|
|
1735
|
+
"content-type": "text/html; charset=utf-8",
|
|
1736
|
+
"cache-control": "no-store",
|
|
1737
|
+
"content-security-policy": "default-src 'none'; base-uri 'none'; frame-ancestors 'none'",
|
|
1738
|
+
"referrer-policy": "no-referrer",
|
|
1739
|
+
"x-content-type-options": "nosniff",
|
|
1740
|
+
vary: "Accept"
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1667
1745
|
return json({ error: "The approval link is invalid or expired." }, 400);
|
|
1668
1746
|
}
|
|
1669
1747
|
}
|
|
1670
|
-
if (url.pathname ===
|
|
1748
|
+
if (url.pathname === callbackPath) {
|
|
1671
1749
|
if (request.method === "OPTIONS") return callbackCors(new Response(null, { status: 204 }), config.openkeyHost);
|
|
1672
1750
|
if (request.method !== "POST") return methodNotAllowed("POST");
|
|
1673
1751
|
const originFailure2 = validateCallbackOrigin(request, config.openkeyHost);
|
|
@@ -1707,17 +1785,17 @@ function createHostedMcpApp(config) {
|
|
|
1707
1785
|
close: () => handler.close()
|
|
1708
1786
|
};
|
|
1709
1787
|
}
|
|
1710
|
-
function registerConnectTool(server, validator, store, identity) {
|
|
1788
|
+
function registerConnectTool(server, validator, store, identity, surface) {
|
|
1711
1789
|
const inputSchema = (0, import_server5.fromJsonSchema)({ type: "object", properties: {}, additionalProperties: false }, validator);
|
|
1712
1790
|
const outputSchema = (0, import_server5.fromJsonSchema)({
|
|
1713
1791
|
type: "object",
|
|
1714
1792
|
properties: {
|
|
1715
1793
|
connected: { type: "boolean" },
|
|
1716
|
-
sessionDid: { type: "string" },
|
|
1794
|
+
...surface === "hosted" ? { sessionDid: { type: "string" } } : {},
|
|
1717
1795
|
approvalUrl: { type: "string", format: "uri" },
|
|
1718
1796
|
expiresAt: { type: "string", format: "date-time" }
|
|
1719
1797
|
},
|
|
1720
|
-
required: ["connected", "sessionDid"],
|
|
1798
|
+
required: surface === "hosted" ? ["connected", "sessionDid"] : ["connected"],
|
|
1721
1799
|
additionalProperties: false
|
|
1722
1800
|
}, validator);
|
|
1723
1801
|
server.registerTool(
|
|
@@ -1731,7 +1809,7 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1731
1809
|
readOnlyHint: false,
|
|
1732
1810
|
idempotentHint: true,
|
|
1733
1811
|
destructiveHint: false,
|
|
1734
|
-
openWorldHint:
|
|
1812
|
+
openWorldHint: false
|
|
1735
1813
|
}
|
|
1736
1814
|
},
|
|
1737
1815
|
async () => {
|
|
@@ -1740,12 +1818,17 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1740
1818
|
try {
|
|
1741
1819
|
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1742
1820
|
code = status.connected ? "ok" : "authority_required";
|
|
1821
|
+
const visibleStatus = surface === "hosted" ? status : {
|
|
1822
|
+
connected: status.connected,
|
|
1823
|
+
...status.approvalUrl === void 0 ? {} : { approvalUrl: status.approvalUrl },
|
|
1824
|
+
...status.expiresAt === void 0 ? {} : { expiresAt: status.expiresAt }
|
|
1825
|
+
};
|
|
1743
1826
|
return {
|
|
1744
1827
|
content: [{
|
|
1745
1828
|
type: "text",
|
|
1746
1829
|
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1747
1830
|
}],
|
|
1748
|
-
structuredContent:
|
|
1831
|
+
structuredContent: visibleStatus
|
|
1749
1832
|
};
|
|
1750
1833
|
} finally {
|
|
1751
1834
|
logToolCall("tinycloud_connect", code, startedAt, 0);
|