@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/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,36 @@ 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"]
|
|
868
955
|
}
|
|
869
956
|
];
|
|
870
957
|
function createJsonSchemaValidator() {
|
|
@@ -909,23 +996,50 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
909
996
|
annotations
|
|
910
997
|
},
|
|
911
998
|
async (input) => {
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
999
|
+
const startedAt = performance.now();
|
|
1000
|
+
const metrics = { replayedGrants: 0 };
|
|
1001
|
+
let code = "INTERNAL_ERROR";
|
|
1002
|
+
try {
|
|
1003
|
+
const result = await invokeOperation2(
|
|
1004
|
+
binding.operationId,
|
|
1005
|
+
binding.operationVersion,
|
|
1006
|
+
invocationTargetForMcp(selection),
|
|
1007
|
+
input,
|
|
1008
|
+
metrics
|
|
1009
|
+
);
|
|
1010
|
+
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
1011
|
+
const response = toMcpToolResult(projected, TOOL_BINDINGS, {
|
|
1012
|
+
surface: selection.surface,
|
|
1013
|
+
readOnly: binding.readOnlyHint,
|
|
1014
|
+
operationId: binding.operationId
|
|
1015
|
+
});
|
|
1016
|
+
code = outcomeCode(response.structuredContent);
|
|
1017
|
+
return response;
|
|
1018
|
+
} finally {
|
|
1019
|
+
if (selection.surface === "hosted") {
|
|
1020
|
+
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
920
1023
|
}
|
|
921
1024
|
);
|
|
922
1025
|
}
|
|
923
1026
|
}
|
|
1027
|
+
function outcomeCode(result) {
|
|
1028
|
+
if (result.status === "error") {
|
|
1029
|
+
const error = result.error;
|
|
1030
|
+
return typeof error?.code === "string" && Object.hasOwn(stableErrorCodes, error.code) ? error.code : "INTERNAL_ERROR";
|
|
1031
|
+
}
|
|
1032
|
+
if (result.status !== "ok") return result.status;
|
|
1033
|
+
const output = result.output;
|
|
1034
|
+
if (output?.outcome === "COPY_PARTIAL") return "COPY_PARTIAL";
|
|
1035
|
+
if (Array.isArray(output?.ops) && output.ops.some((op) => op.outcome !== "ok")) return "PARTIAL";
|
|
1036
|
+
return "ok";
|
|
1037
|
+
}
|
|
924
1038
|
|
|
925
1039
|
// package.json
|
|
926
1040
|
var package_default = {
|
|
927
1041
|
name: "@tinycloud/mcp",
|
|
928
|
-
version: "0.
|
|
1042
|
+
version: "0.6.0-beta.1",
|
|
929
1043
|
description: "TinyCloud delegated operations MCP server",
|
|
930
1044
|
keywords: [
|
|
931
1045
|
"tinycloud",
|
|
@@ -998,7 +1112,7 @@ var package_default = {
|
|
|
998
1112
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
999
1113
|
"@tinycloud/node-sdk": "3.2.5-beta.0",
|
|
1000
1114
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
1001
|
-
"@tinycloud/operations": "0.
|
|
1115
|
+
"@tinycloud/operations": "0.6.0-beta.1",
|
|
1002
1116
|
jose: "6.1.3"
|
|
1003
1117
|
},
|
|
1004
1118
|
devDependencies: {
|
|
@@ -1042,13 +1156,13 @@ function createHostedMcpApp(config) {
|
|
|
1042
1156
|
const handler = createMcpHandler(async ({ authInfo: authInfo2 }) => {
|
|
1043
1157
|
const identity = oauthIdentity(authInfo2);
|
|
1044
1158
|
const { subject, ownerDids } = identity;
|
|
1045
|
-
const stateRoot = store.
|
|
1159
|
+
const stateRoot = await store.initializeTenant(subject);
|
|
1046
1160
|
const validator = createJsonSchemaValidator();
|
|
1047
1161
|
const server = new McpServer2(
|
|
1048
1162
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1049
1163
|
{
|
|
1050
1164
|
jsonSchemaValidator: validator,
|
|
1051
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged."
|
|
1165
|
+
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
1166
|
}
|
|
1053
1167
|
);
|
|
1054
1168
|
registerTinyCloudTools(server, {
|
|
@@ -1160,14 +1274,21 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1160
1274
|
}
|
|
1161
1275
|
},
|
|
1162
1276
|
async () => {
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1277
|
+
const startedAt = performance.now();
|
|
1278
|
+
let code = "INTERNAL_ERROR";
|
|
1279
|
+
try {
|
|
1280
|
+
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1281
|
+
code = status.connected ? "ok" : "authority_required";
|
|
1282
|
+
return {
|
|
1283
|
+
content: [{
|
|
1284
|
+
type: "text",
|
|
1285
|
+
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1286
|
+
}],
|
|
1287
|
+
structuredContent: status
|
|
1288
|
+
};
|
|
1289
|
+
} finally {
|
|
1290
|
+
logToolCall("tinycloud_connect", code, startedAt, 0);
|
|
1291
|
+
}
|
|
1171
1292
|
}
|
|
1172
1293
|
);
|
|
1173
1294
|
}
|