@tinycloud/mcp 0.5.5-beta.0 → 0.6.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 +63 -9
- package/dist/cli.cjs +116 -13
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +116 -13
- package/dist/cli.js.map +1 -1
- package/dist/http-cli.cjs +157 -26
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +157 -26
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +157 -26
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +157 -26
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +162 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +162 -27
- package/dist/index.js.map +1 -1
- package/dist/results.cjs +25 -3
- package/dist/results.cjs.map +1 -1
- package/dist/results.d.cts +9 -6
- package/dist/results.d.ts +9 -6
- package/dist/results.js +25 -3
- package/dist/results.js.map +1 -1
- package/dist/server.cjs +116 -13
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +116 -13
- package/dist/server.js.map +1 -1
- package/dist/tools.cjs +119 -12
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +119 -12
- package/dist/tools.js.map +1 -1
- package/generated/mcp-facts.json +68 -0
- package/package.json +2 -2
- package/skills/tinycloud-delegated-secrets/SKILL.md +4 -0
package/dist/index.js
CHANGED
|
@@ -14,15 +14,37 @@ import { addFormats, Ajv, AjvJsonSchemaValidator } from "@modelcontextprotocol/s
|
|
|
14
14
|
import { invokeOperation } from "@tinycloud/operations";
|
|
15
15
|
|
|
16
16
|
// src/results.ts
|
|
17
|
-
function toMcpToolResult(result, bindings = []) {
|
|
18
|
-
|
|
17
|
+
function toMcpToolResult(result, bindings = [], options = {}) {
|
|
18
|
+
let canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
19
19
|
status: "error",
|
|
20
20
|
operation: {},
|
|
21
21
|
context: {},
|
|
22
22
|
error: { code: "INTERNAL_ERROR" }
|
|
23
23
|
};
|
|
24
|
+
let serialized;
|
|
25
|
+
if (options.surface === "hosted") {
|
|
26
|
+
serialized = JSON.stringify(canonical);
|
|
27
|
+
if (options.readOnly === true && serialized.length > 1e5) {
|
|
28
|
+
const sql = options.operationId?.startsWith("tinycloud.sql.") || options.operationId === "tinycloud.apps.query";
|
|
29
|
+
canonical = {
|
|
30
|
+
status: "error",
|
|
31
|
+
operation: canonical.operation,
|
|
32
|
+
context: canonical.context,
|
|
33
|
+
error: {
|
|
34
|
+
code: sql ? "SQL_RESULT_LIMIT_EXCEEDED" : "KV_RESPONSE_TOO_LARGE",
|
|
35
|
+
message: sql ? "The result exceeds 100,000 characters. Select fewer columns or rows, add a WHERE filter or a smaller LIMIT, or aggregate the data." : "The result exceeds 100,000 characters. Request fewer keys, narrow the prefix, or use a smaller page size.",
|
|
36
|
+
retryable: false
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
serialized = JSON.stringify(canonical);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
24
42
|
return {
|
|
25
|
-
content: [{
|
|
43
|
+
content: [{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: resultSummary(canonical.status) + (serialized !== void 0 && !(options.operationId === "tinycloud.secrets.get" && canonical.status === "ok") ? `
|
|
46
|
+
${serialized}` : "")
|
|
47
|
+
}],
|
|
26
48
|
structuredContent: canonical
|
|
27
49
|
};
|
|
28
50
|
}
|
|
@@ -59,6 +81,17 @@ function resultSummary(status) {
|
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
83
|
|
|
84
|
+
// src/logging.ts
|
|
85
|
+
function logToolCall(tool, code, startedAt, replayedGrants) {
|
|
86
|
+
process.stderr.write(`${JSON.stringify({
|
|
87
|
+
tool,
|
|
88
|
+
code,
|
|
89
|
+
durationMs: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
90
|
+
replayedGrants
|
|
91
|
+
})}
|
|
92
|
+
`);
|
|
93
|
+
}
|
|
94
|
+
|
|
62
95
|
// src/tools.ts
|
|
63
96
|
var TOOL_NAMES = [
|
|
64
97
|
"tinycloud_status",
|
|
@@ -77,7 +110,11 @@ var TOOL_NAMES = [
|
|
|
77
110
|
"tinycloud_sql_query",
|
|
78
111
|
"tinycloud_sql_execute",
|
|
79
112
|
"tinycloud_secrets_get",
|
|
80
|
-
"tinycloud_secrets_status"
|
|
113
|
+
"tinycloud_secrets_status",
|
|
114
|
+
"tinycloud_apps_context",
|
|
115
|
+
"tinycloud_apps_read",
|
|
116
|
+
"tinycloud_apps_write",
|
|
117
|
+
"tinycloud_apps_query"
|
|
81
118
|
];
|
|
82
119
|
function invocationTargetForMcp(selection) {
|
|
83
120
|
return {
|
|
@@ -92,6 +129,9 @@ var packageRequire = createRequire(
|
|
|
92
129
|
var generatedCatalog = packageRequire(
|
|
93
130
|
"@tinycloud/operations/operations.json"
|
|
94
131
|
);
|
|
132
|
+
var stableErrorCodes = Object.fromEntries(
|
|
133
|
+
generatedCatalog.stableErrors.map((code) => [code, true])
|
|
134
|
+
);
|
|
95
135
|
var ALL_SURFACES = ["local", "hosted"];
|
|
96
136
|
function bindingSurfaces(binding) {
|
|
97
137
|
return binding.surfaces ?? ALL_SURFACES;
|
|
@@ -255,6 +295,46 @@ var TOOL_BINDINGS = [
|
|
|
255
295
|
// Hosted has no persistent reauthorization path for a revoked session
|
|
256
296
|
// yet (TC-896), so status is local/stdio only.
|
|
257
297
|
surfaces: ["local"]
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "tinycloud_apps_context",
|
|
301
|
+
operationId: "tinycloud.apps.context",
|
|
302
|
+
operationVersion: 1,
|
|
303
|
+
readOnlyHint: true,
|
|
304
|
+
idempotentHint: true,
|
|
305
|
+
destructiveHint: false,
|
|
306
|
+
openWorldHint: true,
|
|
307
|
+
surfaces: ["hosted"]
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "tinycloud_apps_read",
|
|
311
|
+
operationId: "tinycloud.apps.read",
|
|
312
|
+
operationVersion: 1,
|
|
313
|
+
readOnlyHint: true,
|
|
314
|
+
idempotentHint: true,
|
|
315
|
+
destructiveHint: false,
|
|
316
|
+
openWorldHint: true,
|
|
317
|
+
surfaces: ["hosted"]
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: "tinycloud_apps_write",
|
|
321
|
+
operationId: "tinycloud.apps.write",
|
|
322
|
+
operationVersion: 1,
|
|
323
|
+
readOnlyHint: false,
|
|
324
|
+
idempotentHint: false,
|
|
325
|
+
destructiveHint: true,
|
|
326
|
+
openWorldHint: true,
|
|
327
|
+
surfaces: ["hosted"]
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "tinycloud_apps_query",
|
|
331
|
+
operationId: "tinycloud.apps.query",
|
|
332
|
+
operationVersion: 1,
|
|
333
|
+
readOnlyHint: true,
|
|
334
|
+
idempotentHint: true,
|
|
335
|
+
destructiveHint: false,
|
|
336
|
+
openWorldHint: true,
|
|
337
|
+
surfaces: ["hosted"]
|
|
258
338
|
}
|
|
259
339
|
];
|
|
260
340
|
function createJsonSchemaValidator() {
|
|
@@ -299,23 +379,50 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
299
379
|
annotations
|
|
300
380
|
},
|
|
301
381
|
async (input) => {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
382
|
+
const startedAt = performance.now();
|
|
383
|
+
const metrics = { replayedGrants: 0 };
|
|
384
|
+
let code = "INTERNAL_ERROR";
|
|
385
|
+
try {
|
|
386
|
+
const result = await invokeOperation(
|
|
387
|
+
binding.operationId,
|
|
388
|
+
binding.operationVersion,
|
|
389
|
+
invocationTargetForMcp(selection),
|
|
390
|
+
input,
|
|
391
|
+
metrics
|
|
392
|
+
);
|
|
393
|
+
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
394
|
+
const response = toMcpToolResult(projected, TOOL_BINDINGS, {
|
|
395
|
+
surface: selection.surface,
|
|
396
|
+
readOnly: binding.readOnlyHint,
|
|
397
|
+
operationId: binding.operationId
|
|
398
|
+
});
|
|
399
|
+
code = outcomeCode(response.structuredContent);
|
|
400
|
+
return response;
|
|
401
|
+
} finally {
|
|
402
|
+
if (selection.surface === "hosted") {
|
|
403
|
+
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
310
406
|
}
|
|
311
407
|
);
|
|
312
408
|
}
|
|
313
409
|
}
|
|
410
|
+
function outcomeCode(result) {
|
|
411
|
+
if (result.status === "error") {
|
|
412
|
+
const error = result.error;
|
|
413
|
+
return typeof error?.code === "string" && Object.hasOwn(stableErrorCodes, error.code) ? error.code : "INTERNAL_ERROR";
|
|
414
|
+
}
|
|
415
|
+
if (result.status !== "ok") return result.status;
|
|
416
|
+
const output = result.output;
|
|
417
|
+
if (output?.outcome === "COPY_PARTIAL") return "COPY_PARTIAL";
|
|
418
|
+
if (Array.isArray(output?.ops) && output.ops.some((op) => op.outcome !== "ok")) return "PARTIAL";
|
|
419
|
+
return "ok";
|
|
420
|
+
}
|
|
314
421
|
|
|
315
422
|
// package.json
|
|
316
423
|
var package_default = {
|
|
317
424
|
name: "@tinycloud/mcp",
|
|
318
|
-
version: "0.
|
|
425
|
+
version: "0.6.0-beta.2",
|
|
319
426
|
description: "TinyCloud delegated operations MCP server",
|
|
320
427
|
keywords: [
|
|
321
428
|
"tinycloud",
|
|
@@ -388,7 +495,7 @@ var package_default = {
|
|
|
388
495
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
389
496
|
"@tinycloud/node-sdk": "3.2.5-beta.0",
|
|
390
497
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
391
|
-
"@tinycloud/operations": "0.
|
|
498
|
+
"@tinycloud/operations": "0.6.0-beta.2",
|
|
392
499
|
jose: "6.1.3"
|
|
393
500
|
},
|
|
394
501
|
devDependencies: {
|
|
@@ -633,6 +740,7 @@ import {
|
|
|
633
740
|
} from "@tinycloud/node-sdk";
|
|
634
741
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
635
742
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
743
|
+
import { tcAppsApprovalMessage, tcAppsApprovalReason } from "@tinycloud/operations/tc-apps";
|
|
636
744
|
var PROFILE = "agent";
|
|
637
745
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
638
746
|
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
@@ -685,6 +793,10 @@ var RemoteTenantStore = class {
|
|
|
685
793
|
tenantStateRoot(subject) {
|
|
686
794
|
return join2(this.#config.stateDir, "tenants", this.#tenantId(subject));
|
|
687
795
|
}
|
|
796
|
+
/** Prepare only this authenticated account's profile before its first operation. */
|
|
797
|
+
async initializeTenant(subject) {
|
|
798
|
+
return (await this.#ensureTenant(subject)).stateRoot;
|
|
799
|
+
}
|
|
688
800
|
async connectStatus(subject, ownerDids = []) {
|
|
689
801
|
const tenant = await this.#ensureTenant(subject);
|
|
690
802
|
if (await this.#hasValidSession(tenant, ownerDids)) {
|
|
@@ -699,7 +811,23 @@ var RemoteTenantStore = class {
|
|
|
699
811
|
};
|
|
700
812
|
}
|
|
701
813
|
async decorateAuthorityResult(subject, ownerDids, result) {
|
|
702
|
-
if (!isRecord(result)
|
|
814
|
+
if (!isRecord(result)) return result;
|
|
815
|
+
if (result.status === "error" && isRecord(result.error) && (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "AUTH_REQUIRED")) {
|
|
816
|
+
await this.#ensureTenant(subject);
|
|
817
|
+
const pending2 = await this.#createPending(subject, ownerDids, "bootstrap", BOOTSTRAP_PERMISSIONS);
|
|
818
|
+
return {
|
|
819
|
+
...result,
|
|
820
|
+
error: {
|
|
821
|
+
...result.error,
|
|
822
|
+
details: {
|
|
823
|
+
...isRecord(result.error.details) ? result.error.details : {},
|
|
824
|
+
approvalUrl: this.#connectUrl(pending2),
|
|
825
|
+
expiresAt: pending2.expiresAt
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
if (result.status !== "authority_required") return result;
|
|
703
831
|
const request = isRecord(result.request) ? result.request : void 0;
|
|
704
832
|
const requestId = typeof request?.requestId === "string" ? request.requestId : void 0;
|
|
705
833
|
const requested = Array.isArray(request?.requested) ? request.requested.filter(isPermissionEntry) : [];
|
|
@@ -712,7 +840,7 @@ var RemoteTenantStore = class {
|
|
|
712
840
|
kind: "openkey",
|
|
713
841
|
requestId,
|
|
714
842
|
url: this.#connectUrl(pending),
|
|
715
|
-
fallback: "Open the approval URL, then retry the same MCP tool."
|
|
843
|
+
fallback: tcAppsApprovalMessage(requested) ?? "Open the approval URL, then retry the same MCP tool."
|
|
716
844
|
}
|
|
717
845
|
};
|
|
718
846
|
}
|
|
@@ -729,7 +857,7 @@ var RemoteTenantStore = class {
|
|
|
729
857
|
permissions: Buffer.from(JSON.stringify({
|
|
730
858
|
permissions: pending.permissions,
|
|
731
859
|
reason: [
|
|
732
|
-
pending.kind === "bootstrap" ? "Connect this TinyCloud account to the hosted MCP delegate." : "Approve the exact capabilities requested by the TinyCloud MCP operation.",
|
|
860
|
+
pending.kind === "bootstrap" ? "Connect this TinyCloud account to the hosted MCP delegate." : tcAppsApprovalReason(pending.permissions) ?? "Approve the exact capabilities requested by the TinyCloud MCP operation.",
|
|
733
861
|
// Owner DIDs come from the verified OpenKey access token of the MCP
|
|
734
862
|
// client that created this link, never from the link itself.
|
|
735
863
|
...pending.ownerDids.length === 0 ? [] : [`Requested by TinyCloud account ${pending.ownerDids.join(", ")}; approve only if that is you.`]
|
|
@@ -1167,13 +1295,13 @@ function createHostedMcpApp(config) {
|
|
|
1167
1295
|
const handler = createMcpHandler(async ({ authInfo: authInfo2 }) => {
|
|
1168
1296
|
const identity = oauthIdentity(authInfo2);
|
|
1169
1297
|
const { subject, ownerDids } = identity;
|
|
1170
|
-
const stateRoot = store.
|
|
1298
|
+
const stateRoot = await store.initializeTenant(subject);
|
|
1171
1299
|
const validator = createJsonSchemaValidator();
|
|
1172
1300
|
const server = new McpServer2(
|
|
1173
1301
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1174
1302
|
{
|
|
1175
1303
|
jsonSchemaValidator: validator,
|
|
1176
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged."
|
|
1304
|
+
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."
|
|
1177
1305
|
}
|
|
1178
1306
|
);
|
|
1179
1307
|
registerTinyCloudTools(server, {
|
|
@@ -1285,14 +1413,21 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1285
1413
|
}
|
|
1286
1414
|
},
|
|
1287
1415
|
async () => {
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1416
|
+
const startedAt = performance.now();
|
|
1417
|
+
let code = "INTERNAL_ERROR";
|
|
1418
|
+
try {
|
|
1419
|
+
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1420
|
+
code = status.connected ? "ok" : "authority_required";
|
|
1421
|
+
return {
|
|
1422
|
+
content: [{
|
|
1423
|
+
type: "text",
|
|
1424
|
+
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1425
|
+
}],
|
|
1426
|
+
structuredContent: status
|
|
1427
|
+
};
|
|
1428
|
+
} finally {
|
|
1429
|
+
logToolCall("tinycloud_connect", code, startedAt, 0);
|
|
1430
|
+
}
|
|
1296
1431
|
}
|
|
1297
1432
|
);
|
|
1298
1433
|
}
|