@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.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,10 @@ 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"
|
|
118
154
|
];
|
|
119
155
|
function invocationTargetForMcp(selection) {
|
|
120
156
|
return {
|
|
@@ -129,6 +165,9 @@ var packageRequire = (0, import_node_module.createRequire)(
|
|
|
129
165
|
var generatedCatalog = packageRequire(
|
|
130
166
|
"@tinycloud/operations/operations.json"
|
|
131
167
|
);
|
|
168
|
+
var stableErrorCodes = Object.fromEntries(
|
|
169
|
+
generatedCatalog.stableErrors.map((code) => [code, true])
|
|
170
|
+
);
|
|
132
171
|
var ALL_SURFACES = ["local", "hosted"];
|
|
133
172
|
function bindingSurfaces(binding) {
|
|
134
173
|
return binding.surfaces ?? ALL_SURFACES;
|
|
@@ -292,6 +331,36 @@ var TOOL_BINDINGS = [
|
|
|
292
331
|
// Hosted has no persistent reauthorization path for a revoked session
|
|
293
332
|
// yet (TC-896), so status is local/stdio only.
|
|
294
333
|
surfaces: ["local"]
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: "tinycloud_apps_context",
|
|
337
|
+
operationId: "tinycloud.apps.context",
|
|
338
|
+
operationVersion: 1,
|
|
339
|
+
readOnlyHint: true,
|
|
340
|
+
idempotentHint: true,
|
|
341
|
+
destructiveHint: false,
|
|
342
|
+
openWorldHint: true,
|
|
343
|
+
surfaces: ["hosted"]
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "tinycloud_apps_read",
|
|
347
|
+
operationId: "tinycloud.apps.read",
|
|
348
|
+
operationVersion: 1,
|
|
349
|
+
readOnlyHint: true,
|
|
350
|
+
idempotentHint: true,
|
|
351
|
+
destructiveHint: false,
|
|
352
|
+
openWorldHint: true,
|
|
353
|
+
surfaces: ["hosted"]
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "tinycloud_apps_write",
|
|
357
|
+
operationId: "tinycloud.apps.write",
|
|
358
|
+
operationVersion: 1,
|
|
359
|
+
readOnlyHint: false,
|
|
360
|
+
idempotentHint: false,
|
|
361
|
+
destructiveHint: true,
|
|
362
|
+
openWorldHint: true,
|
|
363
|
+
surfaces: ["hosted"]
|
|
295
364
|
}
|
|
296
365
|
];
|
|
297
366
|
function createJsonSchemaValidator() {
|
|
@@ -336,23 +405,50 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
336
405
|
annotations
|
|
337
406
|
},
|
|
338
407
|
async (input) => {
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
408
|
+
const startedAt = performance.now();
|
|
409
|
+
const metrics = { replayedGrants: 0 };
|
|
410
|
+
let code = "INTERNAL_ERROR";
|
|
411
|
+
try {
|
|
412
|
+
const result = await (0, import_operations.invokeOperation)(
|
|
413
|
+
binding.operationId,
|
|
414
|
+
binding.operationVersion,
|
|
415
|
+
invocationTargetForMcp(selection),
|
|
416
|
+
input,
|
|
417
|
+
metrics
|
|
418
|
+
);
|
|
419
|
+
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
420
|
+
const response = toMcpToolResult(projected, TOOL_BINDINGS, {
|
|
421
|
+
surface: selection.surface,
|
|
422
|
+
readOnly: binding.readOnlyHint,
|
|
423
|
+
operationId: binding.operationId
|
|
424
|
+
});
|
|
425
|
+
code = outcomeCode(response.structuredContent);
|
|
426
|
+
return response;
|
|
427
|
+
} finally {
|
|
428
|
+
if (selection.surface === "hosted") {
|
|
429
|
+
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
347
432
|
}
|
|
348
433
|
);
|
|
349
434
|
}
|
|
350
435
|
}
|
|
436
|
+
function outcomeCode(result) {
|
|
437
|
+
if (result.status === "error") {
|
|
438
|
+
const error = result.error;
|
|
439
|
+
return typeof error?.code === "string" && Object.hasOwn(stableErrorCodes, error.code) ? error.code : "INTERNAL_ERROR";
|
|
440
|
+
}
|
|
441
|
+
if (result.status !== "ok") return result.status;
|
|
442
|
+
const output = result.output;
|
|
443
|
+
if (output?.outcome === "COPY_PARTIAL") return "COPY_PARTIAL";
|
|
444
|
+
if (Array.isArray(output?.ops) && output.ops.some((op) => op.outcome !== "ok")) return "PARTIAL";
|
|
445
|
+
return "ok";
|
|
446
|
+
}
|
|
351
447
|
|
|
352
448
|
// package.json
|
|
353
449
|
var package_default = {
|
|
354
450
|
name: "@tinycloud/mcp",
|
|
355
|
-
version: "0.
|
|
451
|
+
version: "0.6.0-beta.1",
|
|
356
452
|
description: "TinyCloud delegated operations MCP server",
|
|
357
453
|
keywords: [
|
|
358
454
|
"tinycloud",
|
|
@@ -425,7 +521,7 @@ var package_default = {
|
|
|
425
521
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
426
522
|
"@tinycloud/node-sdk": "3.2.5-beta.0",
|
|
427
523
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
428
|
-
"@tinycloud/operations": "0.
|
|
524
|
+
"@tinycloud/operations": "0.6.0-beta.1",
|
|
429
525
|
jose: "6.1.3"
|
|
430
526
|
},
|
|
431
527
|
devDependencies: {
|
|
@@ -651,6 +747,7 @@ var import_node_path3 = require("path");
|
|
|
651
747
|
var import_node_sdk2 = require("@tinycloud/node-sdk");
|
|
652
748
|
var import_node_sdk_wasm = require("@tinycloud/node-sdk-wasm");
|
|
653
749
|
var import_operations2 = require("@tinycloud/operations");
|
|
750
|
+
var import_tc_apps = require("@tinycloud/operations/tc-apps");
|
|
654
751
|
var PROFILE = "agent";
|
|
655
752
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
656
753
|
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
@@ -703,6 +800,10 @@ var RemoteTenantStore = class {
|
|
|
703
800
|
tenantStateRoot(subject) {
|
|
704
801
|
return (0, import_node_path3.join)(this.#config.stateDir, "tenants", this.#tenantId(subject));
|
|
705
802
|
}
|
|
803
|
+
/** Prepare only this authenticated account's profile before its first operation. */
|
|
804
|
+
async initializeTenant(subject) {
|
|
805
|
+
return (await this.#ensureTenant(subject)).stateRoot;
|
|
806
|
+
}
|
|
706
807
|
async connectStatus(subject, ownerDids = []) {
|
|
707
808
|
const tenant = await this.#ensureTenant(subject);
|
|
708
809
|
if (await this.#hasValidSession(tenant, ownerDids)) {
|
|
@@ -717,7 +818,23 @@ var RemoteTenantStore = class {
|
|
|
717
818
|
};
|
|
718
819
|
}
|
|
719
820
|
async decorateAuthorityResult(subject, ownerDids, result) {
|
|
720
|
-
if (!isRecord(result)
|
|
821
|
+
if (!isRecord(result)) return result;
|
|
822
|
+
if (result.status === "error" && isRecord(result.error) && (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "AUTH_REQUIRED")) {
|
|
823
|
+
await this.#ensureTenant(subject);
|
|
824
|
+
const pending2 = await this.#createPending(subject, ownerDids, "bootstrap", BOOTSTRAP_PERMISSIONS);
|
|
825
|
+
return {
|
|
826
|
+
...result,
|
|
827
|
+
error: {
|
|
828
|
+
...result.error,
|
|
829
|
+
details: {
|
|
830
|
+
...isRecord(result.error.details) ? result.error.details : {},
|
|
831
|
+
approvalUrl: this.#connectUrl(pending2),
|
|
832
|
+
expiresAt: pending2.expiresAt
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
if (result.status !== "authority_required") return result;
|
|
721
838
|
const request = isRecord(result.request) ? result.request : void 0;
|
|
722
839
|
const requestId = typeof request?.requestId === "string" ? request.requestId : void 0;
|
|
723
840
|
const requested = Array.isArray(request?.requested) ? request.requested.filter(isPermissionEntry) : [];
|
|
@@ -730,7 +847,7 @@ var RemoteTenantStore = class {
|
|
|
730
847
|
kind: "openkey",
|
|
731
848
|
requestId,
|
|
732
849
|
url: this.#connectUrl(pending),
|
|
733
|
-
fallback: "Open the approval URL, then retry the same MCP tool."
|
|
850
|
+
fallback: (0, import_tc_apps.tcAppsApprovalMessage)(requested) ?? "Open the approval URL, then retry the same MCP tool."
|
|
734
851
|
}
|
|
735
852
|
};
|
|
736
853
|
}
|
|
@@ -747,7 +864,7 @@ var RemoteTenantStore = class {
|
|
|
747
864
|
permissions: Buffer.from(JSON.stringify({
|
|
748
865
|
permissions: pending.permissions,
|
|
749
866
|
reason: [
|
|
750
|
-
pending.kind === "bootstrap" ? "Connect this TinyCloud account to the hosted MCP delegate." : "Approve the exact capabilities requested by the TinyCloud MCP operation.",
|
|
867
|
+
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
868
|
// Owner DIDs come from the verified OpenKey access token of the MCP
|
|
752
869
|
// client that created this link, never from the link itself.
|
|
753
870
|
...pending.ownerDids.length === 0 ? [] : [`Requested by TinyCloud account ${pending.ownerDids.join(", ")}; approve only if that is you.`]
|
|
@@ -1185,13 +1302,13 @@ function createHostedMcpApp(config) {
|
|
|
1185
1302
|
const handler = (0, import_server5.createMcpHandler)(async ({ authInfo: authInfo2 }) => {
|
|
1186
1303
|
const identity = oauthIdentity(authInfo2);
|
|
1187
1304
|
const { subject, ownerDids } = identity;
|
|
1188
|
-
const stateRoot = store.
|
|
1305
|
+
const stateRoot = await store.initializeTenant(subject);
|
|
1189
1306
|
const validator = createJsonSchemaValidator();
|
|
1190
1307
|
const server = new import_server5.McpServer(
|
|
1191
1308
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1192
1309
|
{
|
|
1193
1310
|
jsonSchemaValidator: validator,
|
|
1194
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged."
|
|
1311
|
+
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
1312
|
}
|
|
1196
1313
|
);
|
|
1197
1314
|
registerTinyCloudTools(server, {
|
|
@@ -1303,14 +1420,21 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1303
1420
|
}
|
|
1304
1421
|
},
|
|
1305
1422
|
async () => {
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1423
|
+
const startedAt = performance.now();
|
|
1424
|
+
let code = "INTERNAL_ERROR";
|
|
1425
|
+
try {
|
|
1426
|
+
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1427
|
+
code = status.connected ? "ok" : "authority_required";
|
|
1428
|
+
return {
|
|
1429
|
+
content: [{
|
|
1430
|
+
type: "text",
|
|
1431
|
+
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1432
|
+
}],
|
|
1433
|
+
structuredContent: status
|
|
1434
|
+
};
|
|
1435
|
+
} finally {
|
|
1436
|
+
logToolCall("tinycloud_connect", code, startedAt, 0);
|
|
1437
|
+
}
|
|
1314
1438
|
}
|
|
1315
1439
|
);
|
|
1316
1440
|
}
|