@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.cjs
CHANGED
|
@@ -50,15 +50,37 @@ var import_ajv = require("@modelcontextprotocol/server/validators/ajv");
|
|
|
50
50
|
var import_operations = require("@tinycloud/operations");
|
|
51
51
|
|
|
52
52
|
// src/results.ts
|
|
53
|
-
function toMcpToolResult(result, bindings = []) {
|
|
54
|
-
|
|
53
|
+
function toMcpToolResult(result, bindings = [], options = {}) {
|
|
54
|
+
let canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
55
55
|
status: "error",
|
|
56
56
|
operation: {},
|
|
57
57
|
context: {},
|
|
58
58
|
error: { code: "INTERNAL_ERROR" }
|
|
59
59
|
};
|
|
60
|
+
let serialized;
|
|
61
|
+
if (options.surface === "hosted") {
|
|
62
|
+
serialized = JSON.stringify(canonical);
|
|
63
|
+
if (options.readOnly === true && serialized.length > 1e5) {
|
|
64
|
+
const sql = options.operationId?.startsWith("tinycloud.sql.") || options.operationId === "tinycloud.apps.query";
|
|
65
|
+
canonical = {
|
|
66
|
+
status: "error",
|
|
67
|
+
operation: canonical.operation,
|
|
68
|
+
context: canonical.context,
|
|
69
|
+
error: {
|
|
70
|
+
code: sql ? "SQL_RESULT_LIMIT_EXCEEDED" : "KV_RESPONSE_TOO_LARGE",
|
|
71
|
+
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.",
|
|
72
|
+
retryable: false
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
serialized = JSON.stringify(canonical);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
60
78
|
return {
|
|
61
|
-
content: [{
|
|
79
|
+
content: [{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: resultSummary(canonical.status) + (serialized !== void 0 && !(options.operationId === "tinycloud.secrets.get" && canonical.status === "ok") ? `
|
|
82
|
+
${serialized}` : "")
|
|
83
|
+
}],
|
|
62
84
|
structuredContent: canonical
|
|
63
85
|
};
|
|
64
86
|
}
|
|
@@ -95,6 +117,17 @@ function resultSummary(status) {
|
|
|
95
117
|
}
|
|
96
118
|
}
|
|
97
119
|
|
|
120
|
+
// src/logging.ts
|
|
121
|
+
function logToolCall(tool, code, startedAt, replayedGrants) {
|
|
122
|
+
process.stderr.write(`${JSON.stringify({
|
|
123
|
+
tool,
|
|
124
|
+
code,
|
|
125
|
+
durationMs: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
126
|
+
replayedGrants
|
|
127
|
+
})}
|
|
128
|
+
`);
|
|
129
|
+
}
|
|
130
|
+
|
|
98
131
|
// src/tools.ts
|
|
99
132
|
var import_meta = {};
|
|
100
133
|
var TOOL_NAMES = [
|
|
@@ -114,7 +147,11 @@ var TOOL_NAMES = [
|
|
|
114
147
|
"tinycloud_sql_query",
|
|
115
148
|
"tinycloud_sql_execute",
|
|
116
149
|
"tinycloud_secrets_get",
|
|
117
|
-
"tinycloud_secrets_status"
|
|
150
|
+
"tinycloud_secrets_status",
|
|
151
|
+
"tinycloud_apps_context",
|
|
152
|
+
"tinycloud_apps_read",
|
|
153
|
+
"tinycloud_apps_write",
|
|
154
|
+
"tinycloud_apps_query"
|
|
118
155
|
];
|
|
119
156
|
function invocationTargetForMcp(selection) {
|
|
120
157
|
return {
|
|
@@ -129,6 +166,9 @@ var packageRequire = (0, import_node_module.createRequire)(
|
|
|
129
166
|
var generatedCatalog = packageRequire(
|
|
130
167
|
"@tinycloud/operations/operations.json"
|
|
131
168
|
);
|
|
169
|
+
var stableErrorCodes = Object.fromEntries(
|
|
170
|
+
generatedCatalog.stableErrors.map((code) => [code, true])
|
|
171
|
+
);
|
|
132
172
|
var ALL_SURFACES = ["local", "hosted"];
|
|
133
173
|
function bindingSurfaces(binding) {
|
|
134
174
|
return binding.surfaces ?? ALL_SURFACES;
|
|
@@ -292,6 +332,46 @@ var TOOL_BINDINGS = [
|
|
|
292
332
|
// Hosted has no persistent reauthorization path for a revoked session
|
|
293
333
|
// yet (TC-896), so status is local/stdio only.
|
|
294
334
|
surfaces: ["local"]
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "tinycloud_apps_context",
|
|
338
|
+
operationId: "tinycloud.apps.context",
|
|
339
|
+
operationVersion: 1,
|
|
340
|
+
readOnlyHint: true,
|
|
341
|
+
idempotentHint: true,
|
|
342
|
+
destructiveHint: false,
|
|
343
|
+
openWorldHint: true,
|
|
344
|
+
surfaces: ["hosted"]
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "tinycloud_apps_read",
|
|
348
|
+
operationId: "tinycloud.apps.read",
|
|
349
|
+
operationVersion: 1,
|
|
350
|
+
readOnlyHint: true,
|
|
351
|
+
idempotentHint: true,
|
|
352
|
+
destructiveHint: false,
|
|
353
|
+
openWorldHint: true,
|
|
354
|
+
surfaces: ["hosted"]
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: "tinycloud_apps_write",
|
|
358
|
+
operationId: "tinycloud.apps.write",
|
|
359
|
+
operationVersion: 1,
|
|
360
|
+
readOnlyHint: false,
|
|
361
|
+
idempotentHint: false,
|
|
362
|
+
destructiveHint: true,
|
|
363
|
+
openWorldHint: true,
|
|
364
|
+
surfaces: ["hosted"]
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "tinycloud_apps_query",
|
|
368
|
+
operationId: "tinycloud.apps.query",
|
|
369
|
+
operationVersion: 1,
|
|
370
|
+
readOnlyHint: true,
|
|
371
|
+
idempotentHint: true,
|
|
372
|
+
destructiveHint: false,
|
|
373
|
+
openWorldHint: true,
|
|
374
|
+
surfaces: ["hosted"]
|
|
295
375
|
}
|
|
296
376
|
];
|
|
297
377
|
function createJsonSchemaValidator() {
|
|
@@ -336,23 +416,50 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
336
416
|
annotations
|
|
337
417
|
},
|
|
338
418
|
async (input) => {
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
419
|
+
const startedAt = performance.now();
|
|
420
|
+
const metrics = { replayedGrants: 0 };
|
|
421
|
+
let code = "INTERNAL_ERROR";
|
|
422
|
+
try {
|
|
423
|
+
const result = await (0, import_operations.invokeOperation)(
|
|
424
|
+
binding.operationId,
|
|
425
|
+
binding.operationVersion,
|
|
426
|
+
invocationTargetForMcp(selection),
|
|
427
|
+
input,
|
|
428
|
+
metrics
|
|
429
|
+
);
|
|
430
|
+
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
431
|
+
const response = toMcpToolResult(projected, TOOL_BINDINGS, {
|
|
432
|
+
surface: selection.surface,
|
|
433
|
+
readOnly: binding.readOnlyHint,
|
|
434
|
+
operationId: binding.operationId
|
|
435
|
+
});
|
|
436
|
+
code = outcomeCode(response.structuredContent);
|
|
437
|
+
return response;
|
|
438
|
+
} finally {
|
|
439
|
+
if (selection.surface === "hosted") {
|
|
440
|
+
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
347
443
|
}
|
|
348
444
|
);
|
|
349
445
|
}
|
|
350
446
|
}
|
|
447
|
+
function outcomeCode(result) {
|
|
448
|
+
if (result.status === "error") {
|
|
449
|
+
const error = result.error;
|
|
450
|
+
return typeof error?.code === "string" && Object.hasOwn(stableErrorCodes, error.code) ? error.code : "INTERNAL_ERROR";
|
|
451
|
+
}
|
|
452
|
+
if (result.status !== "ok") return result.status;
|
|
453
|
+
const output = result.output;
|
|
454
|
+
if (output?.outcome === "COPY_PARTIAL") return "COPY_PARTIAL";
|
|
455
|
+
if (Array.isArray(output?.ops) && output.ops.some((op) => op.outcome !== "ok")) return "PARTIAL";
|
|
456
|
+
return "ok";
|
|
457
|
+
}
|
|
351
458
|
|
|
352
459
|
// package.json
|
|
353
460
|
var package_default = {
|
|
354
461
|
name: "@tinycloud/mcp",
|
|
355
|
-
version: "0.
|
|
462
|
+
version: "0.6.0-beta.2",
|
|
356
463
|
description: "TinyCloud delegated operations MCP server",
|
|
357
464
|
keywords: [
|
|
358
465
|
"tinycloud",
|
|
@@ -425,7 +532,7 @@ var package_default = {
|
|
|
425
532
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
426
533
|
"@tinycloud/node-sdk": "3.2.5-beta.0",
|
|
427
534
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
428
|
-
"@tinycloud/operations": "0.
|
|
535
|
+
"@tinycloud/operations": "0.6.0-beta.2",
|
|
429
536
|
jose: "6.1.3"
|
|
430
537
|
},
|
|
431
538
|
devDependencies: {
|
|
@@ -651,6 +758,7 @@ var import_node_path3 = require("path");
|
|
|
651
758
|
var import_node_sdk2 = require("@tinycloud/node-sdk");
|
|
652
759
|
var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
653
760
|
var import_operations2 = require("@tinycloud/operations");
|
|
761
|
+
var import_tc_apps = require("@tinycloud/operations/tc-apps");
|
|
654
762
|
var PROFILE = "agent";
|
|
655
763
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
656
764
|
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
@@ -703,6 +811,10 @@ var RemoteTenantStore = class {
|
|
|
703
811
|
tenantStateRoot(subject) {
|
|
704
812
|
return (0, import_node_path3.join)(this.#config.stateDir, "tenants", this.#tenantId(subject));
|
|
705
813
|
}
|
|
814
|
+
/** Prepare only this authenticated account's profile before its first operation. */
|
|
815
|
+
async initializeTenant(subject) {
|
|
816
|
+
return (await this.#ensureTenant(subject)).stateRoot;
|
|
817
|
+
}
|
|
706
818
|
async connectStatus(subject, ownerDids = []) {
|
|
707
819
|
const tenant = await this.#ensureTenant(subject);
|
|
708
820
|
if (await this.#hasValidSession(tenant, ownerDids)) {
|
|
@@ -717,7 +829,23 @@ var RemoteTenantStore = class {
|
|
|
717
829
|
};
|
|
718
830
|
}
|
|
719
831
|
async decorateAuthorityResult(subject, ownerDids, result) {
|
|
720
|
-
if (!isRecord(result)
|
|
832
|
+
if (!isRecord(result)) return result;
|
|
833
|
+
if (result.status === "error" && isRecord(result.error) && (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "AUTH_REQUIRED")) {
|
|
834
|
+
await this.#ensureTenant(subject);
|
|
835
|
+
const pending2 = await this.#createPending(subject, ownerDids, "bootstrap", BOOTSTRAP_PERMISSIONS);
|
|
836
|
+
return {
|
|
837
|
+
...result,
|
|
838
|
+
error: {
|
|
839
|
+
...result.error,
|
|
840
|
+
details: {
|
|
841
|
+
...isRecord(result.error.details) ? result.error.details : {},
|
|
842
|
+
approvalUrl: this.#connectUrl(pending2),
|
|
843
|
+
expiresAt: pending2.expiresAt
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
if (result.status !== "authority_required") return result;
|
|
721
849
|
const request = isRecord(result.request) ? result.request : void 0;
|
|
722
850
|
const requestId = typeof request?.requestId === "string" ? request.requestId : void 0;
|
|
723
851
|
const requested = Array.isArray(request?.requested) ? request.requested.filter(isPermissionEntry) : [];
|
|
@@ -730,7 +858,7 @@ var RemoteTenantStore = class {
|
|
|
730
858
|
kind: "openkey",
|
|
731
859
|
requestId,
|
|
732
860
|
url: this.#connectUrl(pending),
|
|
733
|
-
fallback: "Open the approval URL, then retry the same MCP tool."
|
|
861
|
+
fallback: (0, import_tc_apps.tcAppsApprovalMessage)(requested) ?? "Open the approval URL, then retry the same MCP tool."
|
|
734
862
|
}
|
|
735
863
|
};
|
|
736
864
|
}
|
|
@@ -747,7 +875,7 @@ var RemoteTenantStore = class {
|
|
|
747
875
|
permissions: Buffer.from(JSON.stringify({
|
|
748
876
|
permissions: pending.permissions,
|
|
749
877
|
reason: [
|
|
750
|
-
pending.kind === "bootstrap" ? "Connect this TinyCloud account to the hosted MCP delegate." : "Approve the exact capabilities requested by the TinyCloud MCP operation.",
|
|
878
|
+
pending.kind === "bootstrap" ? "Connect this TinyCloud account to the hosted MCP delegate." : (0, import_tc_apps.tcAppsApprovalReason)(pending.permissions) ?? "Approve the exact capabilities requested by the TinyCloud MCP operation.",
|
|
751
879
|
// Owner DIDs come from the verified OpenKey access token of the MCP
|
|
752
880
|
// client that created this link, never from the link itself.
|
|
753
881
|
...pending.ownerDids.length === 0 ? [] : [`Requested by TinyCloud account ${pending.ownerDids.join(", ")}; approve only if that is you.`]
|
|
@@ -1185,13 +1313,13 @@ function createHostedMcpApp(config) {
|
|
|
1185
1313
|
const handler = (0, import_server5.createMcpHandler)(async ({ authInfo: authInfo2 }) => {
|
|
1186
1314
|
const identity = oauthIdentity(authInfo2);
|
|
1187
1315
|
const { subject, ownerDids } = identity;
|
|
1188
|
-
const stateRoot = store.
|
|
1316
|
+
const stateRoot = await store.initializeTenant(subject);
|
|
1189
1317
|
const validator = createJsonSchemaValidator();
|
|
1190
1318
|
const server = new import_server5.McpServer(
|
|
1191
1319
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1192
1320
|
{
|
|
1193
1321
|
jsonSchemaValidator: validator,
|
|
1194
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged."
|
|
1322
|
+
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."
|
|
1195
1323
|
}
|
|
1196
1324
|
);
|
|
1197
1325
|
registerTinyCloudTools(server, {
|
|
@@ -1303,14 +1431,21 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1303
1431
|
}
|
|
1304
1432
|
},
|
|
1305
1433
|
async () => {
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1434
|
+
const startedAt = performance.now();
|
|
1435
|
+
let code = "INTERNAL_ERROR";
|
|
1436
|
+
try {
|
|
1437
|
+
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1438
|
+
code = status.connected ? "ok" : "authority_required";
|
|
1439
|
+
return {
|
|
1440
|
+
content: [{
|
|
1441
|
+
type: "text",
|
|
1442
|
+
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1443
|
+
}],
|
|
1444
|
+
structuredContent: status
|
|
1445
|
+
};
|
|
1446
|
+
} finally {
|
|
1447
|
+
logToolCall("tinycloud_connect", code, startedAt, 0);
|
|
1448
|
+
}
|
|
1314
1449
|
}
|
|
1315
1450
|
);
|
|
1316
1451
|
}
|