@tinycloud/mcp 0.5.5-beta.0 → 0.6.0-beta.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/README.md +26 -3
- package/dist/cli.cjs +106 -13
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +106 -13
- package/dist/cli.js.map +1 -1
- package/dist/http-cli.cjs +147 -26
- package/dist/http-cli.cjs.map +1 -1
- package/dist/http-cli.js +147 -26
- package/dist/http-cli.js.map +1 -1
- package/dist/http.cjs +147 -26
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +147 -26
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +151 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +151 -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 +106 -13
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +106 -13
- package/dist/server.js.map +1 -1
- package/dist/tools.cjs +108 -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 +108 -12
- package/dist/tools.js.map +1 -1
- package/generated/mcp-facts.json +51 -0
- package/package.json +2 -2
- package/skills/tinycloud-delegated-secrets/SKILL.md +3 -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,10 @@ 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"
|
|
81
117
|
];
|
|
82
118
|
function invocationTargetForMcp(selection) {
|
|
83
119
|
return {
|
|
@@ -92,6 +128,9 @@ var packageRequire = createRequire(
|
|
|
92
128
|
var generatedCatalog = packageRequire(
|
|
93
129
|
"@tinycloud/operations/operations.json"
|
|
94
130
|
);
|
|
131
|
+
var stableErrorCodes = Object.fromEntries(
|
|
132
|
+
generatedCatalog.stableErrors.map((code) => [code, true])
|
|
133
|
+
);
|
|
95
134
|
var ALL_SURFACES = ["local", "hosted"];
|
|
96
135
|
function bindingSurfaces(binding) {
|
|
97
136
|
return binding.surfaces ?? ALL_SURFACES;
|
|
@@ -255,6 +294,36 @@ var TOOL_BINDINGS = [
|
|
|
255
294
|
// Hosted has no persistent reauthorization path for a revoked session
|
|
256
295
|
// yet (TC-896), so status is local/stdio only.
|
|
257
296
|
surfaces: ["local"]
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "tinycloud_apps_context",
|
|
300
|
+
operationId: "tinycloud.apps.context",
|
|
301
|
+
operationVersion: 1,
|
|
302
|
+
readOnlyHint: true,
|
|
303
|
+
idempotentHint: true,
|
|
304
|
+
destructiveHint: false,
|
|
305
|
+
openWorldHint: true,
|
|
306
|
+
surfaces: ["hosted"]
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "tinycloud_apps_read",
|
|
310
|
+
operationId: "tinycloud.apps.read",
|
|
311
|
+
operationVersion: 1,
|
|
312
|
+
readOnlyHint: true,
|
|
313
|
+
idempotentHint: true,
|
|
314
|
+
destructiveHint: false,
|
|
315
|
+
openWorldHint: true,
|
|
316
|
+
surfaces: ["hosted"]
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "tinycloud_apps_write",
|
|
320
|
+
operationId: "tinycloud.apps.write",
|
|
321
|
+
operationVersion: 1,
|
|
322
|
+
readOnlyHint: false,
|
|
323
|
+
idempotentHint: false,
|
|
324
|
+
destructiveHint: true,
|
|
325
|
+
openWorldHint: true,
|
|
326
|
+
surfaces: ["hosted"]
|
|
258
327
|
}
|
|
259
328
|
];
|
|
260
329
|
function createJsonSchemaValidator() {
|
|
@@ -299,23 +368,50 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
299
368
|
annotations
|
|
300
369
|
},
|
|
301
370
|
async (input) => {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
371
|
+
const startedAt = performance.now();
|
|
372
|
+
const metrics = { replayedGrants: 0 };
|
|
373
|
+
let code = "INTERNAL_ERROR";
|
|
374
|
+
try {
|
|
375
|
+
const result = await invokeOperation(
|
|
376
|
+
binding.operationId,
|
|
377
|
+
binding.operationVersion,
|
|
378
|
+
invocationTargetForMcp(selection),
|
|
379
|
+
input,
|
|
380
|
+
metrics
|
|
381
|
+
);
|
|
382
|
+
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
383
|
+
const response = toMcpToolResult(projected, TOOL_BINDINGS, {
|
|
384
|
+
surface: selection.surface,
|
|
385
|
+
readOnly: binding.readOnlyHint,
|
|
386
|
+
operationId: binding.operationId
|
|
387
|
+
});
|
|
388
|
+
code = outcomeCode(response.structuredContent);
|
|
389
|
+
return response;
|
|
390
|
+
} finally {
|
|
391
|
+
if (selection.surface === "hosted") {
|
|
392
|
+
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
310
395
|
}
|
|
311
396
|
);
|
|
312
397
|
}
|
|
313
398
|
}
|
|
399
|
+
function outcomeCode(result) {
|
|
400
|
+
if (result.status === "error") {
|
|
401
|
+
const error = result.error;
|
|
402
|
+
return typeof error?.code === "string" && Object.hasOwn(stableErrorCodes, error.code) ? error.code : "INTERNAL_ERROR";
|
|
403
|
+
}
|
|
404
|
+
if (result.status !== "ok") return result.status;
|
|
405
|
+
const output = result.output;
|
|
406
|
+
if (output?.outcome === "COPY_PARTIAL") return "COPY_PARTIAL";
|
|
407
|
+
if (Array.isArray(output?.ops) && output.ops.some((op) => op.outcome !== "ok")) return "PARTIAL";
|
|
408
|
+
return "ok";
|
|
409
|
+
}
|
|
314
410
|
|
|
315
411
|
// package.json
|
|
316
412
|
var package_default = {
|
|
317
413
|
name: "@tinycloud/mcp",
|
|
318
|
-
version: "0.
|
|
414
|
+
version: "0.6.0-beta.1",
|
|
319
415
|
description: "TinyCloud delegated operations MCP server",
|
|
320
416
|
keywords: [
|
|
321
417
|
"tinycloud",
|
|
@@ -388,7 +484,7 @@ var package_default = {
|
|
|
388
484
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
389
485
|
"@tinycloud/node-sdk": "3.2.5-beta.0",
|
|
390
486
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
391
|
-
"@tinycloud/operations": "0.
|
|
487
|
+
"@tinycloud/operations": "0.6.0-beta.1",
|
|
392
488
|
jose: "6.1.3"
|
|
393
489
|
},
|
|
394
490
|
devDependencies: {
|
|
@@ -633,6 +729,7 @@ import {
|
|
|
633
729
|
} from "@tinycloud/node-sdk";
|
|
634
730
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
635
731
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
732
|
+
import { tcAppsApprovalMessage, tcAppsApprovalReason } from "@tinycloud/operations/tc-apps";
|
|
636
733
|
var PROFILE = "agent";
|
|
637
734
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
638
735
|
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
@@ -685,6 +782,10 @@ var RemoteTenantStore = class {
|
|
|
685
782
|
tenantStateRoot(subject) {
|
|
686
783
|
return join2(this.#config.stateDir, "tenants", this.#tenantId(subject));
|
|
687
784
|
}
|
|
785
|
+
/** Prepare only this authenticated account's profile before its first operation. */
|
|
786
|
+
async initializeTenant(subject) {
|
|
787
|
+
return (await this.#ensureTenant(subject)).stateRoot;
|
|
788
|
+
}
|
|
688
789
|
async connectStatus(subject, ownerDids = []) {
|
|
689
790
|
const tenant = await this.#ensureTenant(subject);
|
|
690
791
|
if (await this.#hasValidSession(tenant, ownerDids)) {
|
|
@@ -699,7 +800,23 @@ var RemoteTenantStore = class {
|
|
|
699
800
|
};
|
|
700
801
|
}
|
|
701
802
|
async decorateAuthorityResult(subject, ownerDids, result) {
|
|
702
|
-
if (!isRecord(result)
|
|
803
|
+
if (!isRecord(result)) return result;
|
|
804
|
+
if (result.status === "error" && isRecord(result.error) && (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "AUTH_REQUIRED")) {
|
|
805
|
+
await this.#ensureTenant(subject);
|
|
806
|
+
const pending2 = await this.#createPending(subject, ownerDids, "bootstrap", BOOTSTRAP_PERMISSIONS);
|
|
807
|
+
return {
|
|
808
|
+
...result,
|
|
809
|
+
error: {
|
|
810
|
+
...result.error,
|
|
811
|
+
details: {
|
|
812
|
+
...isRecord(result.error.details) ? result.error.details : {},
|
|
813
|
+
approvalUrl: this.#connectUrl(pending2),
|
|
814
|
+
expiresAt: pending2.expiresAt
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
if (result.status !== "authority_required") return result;
|
|
703
820
|
const request = isRecord(result.request) ? result.request : void 0;
|
|
704
821
|
const requestId = typeof request?.requestId === "string" ? request.requestId : void 0;
|
|
705
822
|
const requested = Array.isArray(request?.requested) ? request.requested.filter(isPermissionEntry) : [];
|
|
@@ -712,7 +829,7 @@ var RemoteTenantStore = class {
|
|
|
712
829
|
kind: "openkey",
|
|
713
830
|
requestId,
|
|
714
831
|
url: this.#connectUrl(pending),
|
|
715
|
-
fallback: "Open the approval URL, then retry the same MCP tool."
|
|
832
|
+
fallback: tcAppsApprovalMessage(requested) ?? "Open the approval URL, then retry the same MCP tool."
|
|
716
833
|
}
|
|
717
834
|
};
|
|
718
835
|
}
|
|
@@ -729,7 +846,7 @@ var RemoteTenantStore = class {
|
|
|
729
846
|
permissions: Buffer.from(JSON.stringify({
|
|
730
847
|
permissions: pending.permissions,
|
|
731
848
|
reason: [
|
|
732
|
-
pending.kind === "bootstrap" ? "Connect this TinyCloud account to the hosted MCP delegate." : "Approve the exact capabilities requested by the TinyCloud MCP operation.",
|
|
849
|
+
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
850
|
// Owner DIDs come from the verified OpenKey access token of the MCP
|
|
734
851
|
// client that created this link, never from the link itself.
|
|
735
852
|
...pending.ownerDids.length === 0 ? [] : [`Requested by TinyCloud account ${pending.ownerDids.join(", ")}; approve only if that is you.`]
|
|
@@ -1167,13 +1284,13 @@ function createHostedMcpApp(config) {
|
|
|
1167
1284
|
const handler = createMcpHandler(async ({ authInfo: authInfo2 }) => {
|
|
1168
1285
|
const identity = oauthIdentity(authInfo2);
|
|
1169
1286
|
const { subject, ownerDids } = identity;
|
|
1170
|
-
const stateRoot = store.
|
|
1287
|
+
const stateRoot = await store.initializeTenant(subject);
|
|
1171
1288
|
const validator = createJsonSchemaValidator();
|
|
1172
1289
|
const server = new McpServer2(
|
|
1173
1290
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1174
1291
|
{
|
|
1175
1292
|
jsonSchemaValidator: validator,
|
|
1176
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged."
|
|
1293
|
+
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
1294
|
}
|
|
1178
1295
|
);
|
|
1179
1296
|
registerTinyCloudTools(server, {
|
|
@@ -1285,14 +1402,21 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1285
1402
|
}
|
|
1286
1403
|
},
|
|
1287
1404
|
async () => {
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1405
|
+
const startedAt = performance.now();
|
|
1406
|
+
let code = "INTERNAL_ERROR";
|
|
1407
|
+
try {
|
|
1408
|
+
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1409
|
+
code = status.connected ? "ok" : "authority_required";
|
|
1410
|
+
return {
|
|
1411
|
+
content: [{
|
|
1412
|
+
type: "text",
|
|
1413
|
+
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1414
|
+
}],
|
|
1415
|
+
structuredContent: status
|
|
1416
|
+
};
|
|
1417
|
+
} finally {
|
|
1418
|
+
logToolCall("tinycloud_connect", code, startedAt, 0);
|
|
1419
|
+
}
|
|
1296
1420
|
}
|
|
1297
1421
|
);
|
|
1298
1422
|
}
|