@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/http-cli.js
CHANGED
|
@@ -107,6 +107,17 @@ function firstString(...values) {
|
|
|
107
107
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// src/logging.ts
|
|
111
|
+
function logToolCall(tool, code, startedAt, replayedGrants) {
|
|
112
|
+
process.stderr.write(`${JSON.stringify({
|
|
113
|
+
tool,
|
|
114
|
+
code,
|
|
115
|
+
durationMs: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
116
|
+
replayedGrants
|
|
117
|
+
})}
|
|
118
|
+
`);
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
// src/remote-store.ts
|
|
111
122
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
112
123
|
import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promises";
|
|
@@ -122,6 +133,7 @@ import {
|
|
|
122
133
|
} from "@tinycloud/node-sdk";
|
|
123
134
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
124
135
|
import { invokeOperation } from "@tinycloud/operations";
|
|
136
|
+
import { tcAppsApprovalMessage, tcAppsApprovalReason } from "@tinycloud/operations/tc-apps";
|
|
125
137
|
var PROFILE = "agent";
|
|
126
138
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
127
139
|
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
@@ -174,6 +186,10 @@ var RemoteTenantStore = class {
|
|
|
174
186
|
tenantStateRoot(subject) {
|
|
175
187
|
return join(this.#config.stateDir, "tenants", this.#tenantId(subject));
|
|
176
188
|
}
|
|
189
|
+
/** Prepare only this authenticated account's profile before its first operation. */
|
|
190
|
+
async initializeTenant(subject) {
|
|
191
|
+
return (await this.#ensureTenant(subject)).stateRoot;
|
|
192
|
+
}
|
|
177
193
|
async connectStatus(subject, ownerDids = []) {
|
|
178
194
|
const tenant = await this.#ensureTenant(subject);
|
|
179
195
|
if (await this.#hasValidSession(tenant, ownerDids)) {
|
|
@@ -188,7 +204,23 @@ var RemoteTenantStore = class {
|
|
|
188
204
|
};
|
|
189
205
|
}
|
|
190
206
|
async decorateAuthorityResult(subject, ownerDids, result) {
|
|
191
|
-
if (!isRecord(result)
|
|
207
|
+
if (!isRecord(result)) return result;
|
|
208
|
+
if (result.status === "error" && isRecord(result.error) && (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "AUTH_REQUIRED")) {
|
|
209
|
+
await this.#ensureTenant(subject);
|
|
210
|
+
const pending2 = await this.#createPending(subject, ownerDids, "bootstrap", BOOTSTRAP_PERMISSIONS);
|
|
211
|
+
return {
|
|
212
|
+
...result,
|
|
213
|
+
error: {
|
|
214
|
+
...result.error,
|
|
215
|
+
details: {
|
|
216
|
+
...isRecord(result.error.details) ? result.error.details : {},
|
|
217
|
+
approvalUrl: this.#connectUrl(pending2),
|
|
218
|
+
expiresAt: pending2.expiresAt
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (result.status !== "authority_required") return result;
|
|
192
224
|
const request = isRecord(result.request) ? result.request : void 0;
|
|
193
225
|
const requestId = typeof request?.requestId === "string" ? request.requestId : void 0;
|
|
194
226
|
const requested = Array.isArray(request?.requested) ? request.requested.filter(isPermissionEntry) : [];
|
|
@@ -201,7 +233,7 @@ var RemoteTenantStore = class {
|
|
|
201
233
|
kind: "openkey",
|
|
202
234
|
requestId,
|
|
203
235
|
url: this.#connectUrl(pending),
|
|
204
|
-
fallback: "Open the approval URL, then retry the same MCP tool."
|
|
236
|
+
fallback: tcAppsApprovalMessage(requested) ?? "Open the approval URL, then retry the same MCP tool."
|
|
205
237
|
}
|
|
206
238
|
};
|
|
207
239
|
}
|
|
@@ -218,7 +250,7 @@ var RemoteTenantStore = class {
|
|
|
218
250
|
permissions: Buffer.from(JSON.stringify({
|
|
219
251
|
permissions: pending.permissions,
|
|
220
252
|
reason: [
|
|
221
|
-
pending.kind === "bootstrap" ? "Connect this TinyCloud account to the hosted MCP delegate." : "Approve the exact capabilities requested by the TinyCloud MCP operation.",
|
|
253
|
+
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.",
|
|
222
254
|
// Owner DIDs come from the verified OpenKey access token of the MCP
|
|
223
255
|
// client that created this link, never from the link itself.
|
|
224
256
|
...pending.ownerDids.length === 0 ? [] : [`Requested by TinyCloud account ${pending.ownerDids.join(", ")}; approve only if that is you.`]
|
|
@@ -646,15 +678,37 @@ import { addFormats, Ajv, AjvJsonSchemaValidator } from "@modelcontextprotocol/s
|
|
|
646
678
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
647
679
|
|
|
648
680
|
// src/results.ts
|
|
649
|
-
function toMcpToolResult(result, bindings = []) {
|
|
650
|
-
|
|
681
|
+
function toMcpToolResult(result, bindings = [], options = {}) {
|
|
682
|
+
let canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
651
683
|
status: "error",
|
|
652
684
|
operation: {},
|
|
653
685
|
context: {},
|
|
654
686
|
error: { code: "INTERNAL_ERROR" }
|
|
655
687
|
};
|
|
688
|
+
let serialized;
|
|
689
|
+
if (options.surface === "hosted") {
|
|
690
|
+
serialized = JSON.stringify(canonical);
|
|
691
|
+
if (options.readOnly === true && serialized.length > 1e5) {
|
|
692
|
+
const sql = options.operationId?.startsWith("tinycloud.sql.") || options.operationId === "tinycloud.apps.query";
|
|
693
|
+
canonical = {
|
|
694
|
+
status: "error",
|
|
695
|
+
operation: canonical.operation,
|
|
696
|
+
context: canonical.context,
|
|
697
|
+
error: {
|
|
698
|
+
code: sql ? "SQL_RESULT_LIMIT_EXCEEDED" : "KV_RESPONSE_TOO_LARGE",
|
|
699
|
+
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.",
|
|
700
|
+
retryable: false
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
serialized = JSON.stringify(canonical);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
656
706
|
return {
|
|
657
|
-
content: [{
|
|
707
|
+
content: [{
|
|
708
|
+
type: "text",
|
|
709
|
+
text: resultSummary(canonical.status) + (serialized !== void 0 && !(options.operationId === "tinycloud.secrets.get" && canonical.status === "ok") ? `
|
|
710
|
+
${serialized}` : "")
|
|
711
|
+
}],
|
|
658
712
|
structuredContent: canonical
|
|
659
713
|
};
|
|
660
714
|
}
|
|
@@ -702,6 +756,9 @@ var packageRequire = createRequire(
|
|
|
702
756
|
var generatedCatalog = packageRequire(
|
|
703
757
|
"@tinycloud/operations/operations.json"
|
|
704
758
|
);
|
|
759
|
+
var stableErrorCodes = Object.fromEntries(
|
|
760
|
+
generatedCatalog.stableErrors.map((code) => [code, true])
|
|
761
|
+
);
|
|
705
762
|
var ALL_SURFACES = ["local", "hosted"];
|
|
706
763
|
function bindingSurfaces(binding) {
|
|
707
764
|
return binding.surfaces ?? ALL_SURFACES;
|
|
@@ -865,6 +922,46 @@ var TOOL_BINDINGS = [
|
|
|
865
922
|
// Hosted has no persistent reauthorization path for a revoked session
|
|
866
923
|
// yet (TC-896), so status is local/stdio only.
|
|
867
924
|
surfaces: ["local"]
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
name: "tinycloud_apps_context",
|
|
928
|
+
operationId: "tinycloud.apps.context",
|
|
929
|
+
operationVersion: 1,
|
|
930
|
+
readOnlyHint: true,
|
|
931
|
+
idempotentHint: true,
|
|
932
|
+
destructiveHint: false,
|
|
933
|
+
openWorldHint: true,
|
|
934
|
+
surfaces: ["hosted"]
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
name: "tinycloud_apps_read",
|
|
938
|
+
operationId: "tinycloud.apps.read",
|
|
939
|
+
operationVersion: 1,
|
|
940
|
+
readOnlyHint: true,
|
|
941
|
+
idempotentHint: true,
|
|
942
|
+
destructiveHint: false,
|
|
943
|
+
openWorldHint: true,
|
|
944
|
+
surfaces: ["hosted"]
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
name: "tinycloud_apps_write",
|
|
948
|
+
operationId: "tinycloud.apps.write",
|
|
949
|
+
operationVersion: 1,
|
|
950
|
+
readOnlyHint: false,
|
|
951
|
+
idempotentHint: false,
|
|
952
|
+
destructiveHint: true,
|
|
953
|
+
openWorldHint: true,
|
|
954
|
+
surfaces: ["hosted"]
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
name: "tinycloud_apps_query",
|
|
958
|
+
operationId: "tinycloud.apps.query",
|
|
959
|
+
operationVersion: 1,
|
|
960
|
+
readOnlyHint: true,
|
|
961
|
+
idempotentHint: true,
|
|
962
|
+
destructiveHint: false,
|
|
963
|
+
openWorldHint: true,
|
|
964
|
+
surfaces: ["hosted"]
|
|
868
965
|
}
|
|
869
966
|
];
|
|
870
967
|
function createJsonSchemaValidator() {
|
|
@@ -909,23 +1006,50 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
909
1006
|
annotations
|
|
910
1007
|
},
|
|
911
1008
|
async (input) => {
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1009
|
+
const startedAt = performance.now();
|
|
1010
|
+
const metrics = { replayedGrants: 0 };
|
|
1011
|
+
let code = "INTERNAL_ERROR";
|
|
1012
|
+
try {
|
|
1013
|
+
const result = await invokeOperation2(
|
|
1014
|
+
binding.operationId,
|
|
1015
|
+
binding.operationVersion,
|
|
1016
|
+
invocationTargetForMcp(selection),
|
|
1017
|
+
input,
|
|
1018
|
+
metrics
|
|
1019
|
+
);
|
|
1020
|
+
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
1021
|
+
const response = toMcpToolResult(projected, TOOL_BINDINGS, {
|
|
1022
|
+
surface: selection.surface,
|
|
1023
|
+
readOnly: binding.readOnlyHint,
|
|
1024
|
+
operationId: binding.operationId
|
|
1025
|
+
});
|
|
1026
|
+
code = outcomeCode(response.structuredContent);
|
|
1027
|
+
return response;
|
|
1028
|
+
} finally {
|
|
1029
|
+
if (selection.surface === "hosted") {
|
|
1030
|
+
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
920
1033
|
}
|
|
921
1034
|
);
|
|
922
1035
|
}
|
|
923
1036
|
}
|
|
1037
|
+
function outcomeCode(result) {
|
|
1038
|
+
if (result.status === "error") {
|
|
1039
|
+
const error = result.error;
|
|
1040
|
+
return typeof error?.code === "string" && Object.hasOwn(stableErrorCodes, error.code) ? error.code : "INTERNAL_ERROR";
|
|
1041
|
+
}
|
|
1042
|
+
if (result.status !== "ok") return result.status;
|
|
1043
|
+
const output = result.output;
|
|
1044
|
+
if (output?.outcome === "COPY_PARTIAL") return "COPY_PARTIAL";
|
|
1045
|
+
if (Array.isArray(output?.ops) && output.ops.some((op) => op.outcome !== "ok")) return "PARTIAL";
|
|
1046
|
+
return "ok";
|
|
1047
|
+
}
|
|
924
1048
|
|
|
925
1049
|
// package.json
|
|
926
1050
|
var package_default = {
|
|
927
1051
|
name: "@tinycloud/mcp",
|
|
928
|
-
version: "0.
|
|
1052
|
+
version: "0.6.0-beta.2",
|
|
929
1053
|
description: "TinyCloud delegated operations MCP server",
|
|
930
1054
|
keywords: [
|
|
931
1055
|
"tinycloud",
|
|
@@ -998,7 +1122,7 @@ var package_default = {
|
|
|
998
1122
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
999
1123
|
"@tinycloud/node-sdk": "3.2.5-beta.0",
|
|
1000
1124
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
1001
|
-
"@tinycloud/operations": "0.
|
|
1125
|
+
"@tinycloud/operations": "0.6.0-beta.2",
|
|
1002
1126
|
jose: "6.1.3"
|
|
1003
1127
|
},
|
|
1004
1128
|
devDependencies: {
|
|
@@ -1042,13 +1166,13 @@ function createHostedMcpApp(config) {
|
|
|
1042
1166
|
const handler = createMcpHandler(async ({ authInfo: authInfo2 }) => {
|
|
1043
1167
|
const identity = oauthIdentity(authInfo2);
|
|
1044
1168
|
const { subject, ownerDids } = identity;
|
|
1045
|
-
const stateRoot = store.
|
|
1169
|
+
const stateRoot = await store.initializeTenant(subject);
|
|
1046
1170
|
const validator = createJsonSchemaValidator();
|
|
1047
1171
|
const server = new McpServer2(
|
|
1048
1172
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1049
1173
|
{
|
|
1050
1174
|
jsonSchemaValidator: validator,
|
|
1051
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged."
|
|
1175
|
+
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."
|
|
1052
1176
|
}
|
|
1053
1177
|
);
|
|
1054
1178
|
registerTinyCloudTools(server, {
|
|
@@ -1160,14 +1284,21 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1160
1284
|
}
|
|
1161
1285
|
},
|
|
1162
1286
|
async () => {
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1287
|
+
const startedAt = performance.now();
|
|
1288
|
+
let code = "INTERNAL_ERROR";
|
|
1289
|
+
try {
|
|
1290
|
+
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1291
|
+
code = status.connected ? "ok" : "authority_required";
|
|
1292
|
+
return {
|
|
1293
|
+
content: [{
|
|
1294
|
+
type: "text",
|
|
1295
|
+
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1296
|
+
}],
|
|
1297
|
+
structuredContent: status
|
|
1298
|
+
};
|
|
1299
|
+
} finally {
|
|
1300
|
+
logToolCall("tinycloud_connect", code, startedAt, 0);
|
|
1301
|
+
}
|
|
1171
1302
|
}
|
|
1172
1303
|
);
|
|
1173
1304
|
}
|