@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.js
CHANGED
|
@@ -90,6 +90,17 @@ function firstString(...values) {
|
|
|
90
90
|
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// src/logging.ts
|
|
94
|
+
function logToolCall(tool, code, startedAt, replayedGrants) {
|
|
95
|
+
process.stderr.write(`${JSON.stringify({
|
|
96
|
+
tool,
|
|
97
|
+
code,
|
|
98
|
+
durationMs: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
99
|
+
replayedGrants
|
|
100
|
+
})}
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
// src/remote-store.ts
|
|
94
105
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
95
106
|
import { chmod, mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promises";
|
|
@@ -105,6 +116,7 @@ import {
|
|
|
105
116
|
} from "@tinycloud/node-sdk";
|
|
106
117
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
107
118
|
import { invokeOperation } from "@tinycloud/operations";
|
|
119
|
+
import { tcAppsApprovalMessage, tcAppsApprovalReason } from "@tinycloud/operations/tc-apps";
|
|
108
120
|
var PROFILE = "agent";
|
|
109
121
|
var MAX_CALLBACK_BYTES = 256 * 1024;
|
|
110
122
|
var LEGACY_DEFAULT_NODE_HOST = "https://node.tinycloud.xyz";
|
|
@@ -157,6 +169,10 @@ var RemoteTenantStore = class {
|
|
|
157
169
|
tenantStateRoot(subject) {
|
|
158
170
|
return join(this.#config.stateDir, "tenants", this.#tenantId(subject));
|
|
159
171
|
}
|
|
172
|
+
/** Prepare only this authenticated account's profile before its first operation. */
|
|
173
|
+
async initializeTenant(subject) {
|
|
174
|
+
return (await this.#ensureTenant(subject)).stateRoot;
|
|
175
|
+
}
|
|
160
176
|
async connectStatus(subject, ownerDids = []) {
|
|
161
177
|
const tenant = await this.#ensureTenant(subject);
|
|
162
178
|
if (await this.#hasValidSession(tenant, ownerDids)) {
|
|
@@ -171,7 +187,23 @@ var RemoteTenantStore = class {
|
|
|
171
187
|
};
|
|
172
188
|
}
|
|
173
189
|
async decorateAuthorityResult(subject, ownerDids, result) {
|
|
174
|
-
if (!isRecord(result)
|
|
190
|
+
if (!isRecord(result)) return result;
|
|
191
|
+
if (result.status === "error" && isRecord(result.error) && (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "AUTH_REQUIRED")) {
|
|
192
|
+
await this.#ensureTenant(subject);
|
|
193
|
+
const pending2 = await this.#createPending(subject, ownerDids, "bootstrap", BOOTSTRAP_PERMISSIONS);
|
|
194
|
+
return {
|
|
195
|
+
...result,
|
|
196
|
+
error: {
|
|
197
|
+
...result.error,
|
|
198
|
+
details: {
|
|
199
|
+
...isRecord(result.error.details) ? result.error.details : {},
|
|
200
|
+
approvalUrl: this.#connectUrl(pending2),
|
|
201
|
+
expiresAt: pending2.expiresAt
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (result.status !== "authority_required") return result;
|
|
175
207
|
const request = isRecord(result.request) ? result.request : void 0;
|
|
176
208
|
const requestId = typeof request?.requestId === "string" ? request.requestId : void 0;
|
|
177
209
|
const requested = Array.isArray(request?.requested) ? request.requested.filter(isPermissionEntry) : [];
|
|
@@ -184,7 +216,7 @@ var RemoteTenantStore = class {
|
|
|
184
216
|
kind: "openkey",
|
|
185
217
|
requestId,
|
|
186
218
|
url: this.#connectUrl(pending),
|
|
187
|
-
fallback: "Open the approval URL, then retry the same MCP tool."
|
|
219
|
+
fallback: tcAppsApprovalMessage(requested) ?? "Open the approval URL, then retry the same MCP tool."
|
|
188
220
|
}
|
|
189
221
|
};
|
|
190
222
|
}
|
|
@@ -201,7 +233,7 @@ var RemoteTenantStore = class {
|
|
|
201
233
|
permissions: Buffer.from(JSON.stringify({
|
|
202
234
|
permissions: pending.permissions,
|
|
203
235
|
reason: [
|
|
204
|
-
pending.kind === "bootstrap" ? "Connect this TinyCloud account to the hosted MCP delegate." : "Approve the exact capabilities requested by the TinyCloud MCP operation.",
|
|
236
|
+
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.",
|
|
205
237
|
// Owner DIDs come from the verified OpenKey access token of the MCP
|
|
206
238
|
// client that created this link, never from the link itself.
|
|
207
239
|
...pending.ownerDids.length === 0 ? [] : [`Requested by TinyCloud account ${pending.ownerDids.join(", ")}; approve only if that is you.`]
|
|
@@ -629,15 +661,37 @@ import { addFormats, Ajv, AjvJsonSchemaValidator } from "@modelcontextprotocol/s
|
|
|
629
661
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
630
662
|
|
|
631
663
|
// src/results.ts
|
|
632
|
-
function toMcpToolResult(result, bindings = []) {
|
|
633
|
-
|
|
664
|
+
function toMcpToolResult(result, bindings = [], options = {}) {
|
|
665
|
+
let canonical = isCanonicalOperationResult(result) ? withRecoveryTool(result, bindings) : {
|
|
634
666
|
status: "error",
|
|
635
667
|
operation: {},
|
|
636
668
|
context: {},
|
|
637
669
|
error: { code: "INTERNAL_ERROR" }
|
|
638
670
|
};
|
|
671
|
+
let serialized;
|
|
672
|
+
if (options.surface === "hosted") {
|
|
673
|
+
serialized = JSON.stringify(canonical);
|
|
674
|
+
if (options.readOnly === true && serialized.length > 1e5) {
|
|
675
|
+
const sql = options.operationId?.startsWith("tinycloud.sql.") || options.operationId === "tinycloud.apps.query";
|
|
676
|
+
canonical = {
|
|
677
|
+
status: "error",
|
|
678
|
+
operation: canonical.operation,
|
|
679
|
+
context: canonical.context,
|
|
680
|
+
error: {
|
|
681
|
+
code: sql ? "SQL_RESULT_LIMIT_EXCEEDED" : "KV_RESPONSE_TOO_LARGE",
|
|
682
|
+
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.",
|
|
683
|
+
retryable: false
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
serialized = JSON.stringify(canonical);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
639
689
|
return {
|
|
640
|
-
content: [{
|
|
690
|
+
content: [{
|
|
691
|
+
type: "text",
|
|
692
|
+
text: resultSummary(canonical.status) + (serialized !== void 0 && !(options.operationId === "tinycloud.secrets.get" && canonical.status === "ok") ? `
|
|
693
|
+
${serialized}` : "")
|
|
694
|
+
}],
|
|
641
695
|
structuredContent: canonical
|
|
642
696
|
};
|
|
643
697
|
}
|
|
@@ -685,6 +739,9 @@ var packageRequire = createRequire(
|
|
|
685
739
|
var generatedCatalog = packageRequire(
|
|
686
740
|
"@tinycloud/operations/operations.json"
|
|
687
741
|
);
|
|
742
|
+
var stableErrorCodes = Object.fromEntries(
|
|
743
|
+
generatedCatalog.stableErrors.map((code) => [code, true])
|
|
744
|
+
);
|
|
688
745
|
var ALL_SURFACES = ["local", "hosted"];
|
|
689
746
|
function bindingSurfaces(binding) {
|
|
690
747
|
return binding.surfaces ?? ALL_SURFACES;
|
|
@@ -848,6 +905,46 @@ var TOOL_BINDINGS = [
|
|
|
848
905
|
// Hosted has no persistent reauthorization path for a revoked session
|
|
849
906
|
// yet (TC-896), so status is local/stdio only.
|
|
850
907
|
surfaces: ["local"]
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
name: "tinycloud_apps_context",
|
|
911
|
+
operationId: "tinycloud.apps.context",
|
|
912
|
+
operationVersion: 1,
|
|
913
|
+
readOnlyHint: true,
|
|
914
|
+
idempotentHint: true,
|
|
915
|
+
destructiveHint: false,
|
|
916
|
+
openWorldHint: true,
|
|
917
|
+
surfaces: ["hosted"]
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
name: "tinycloud_apps_read",
|
|
921
|
+
operationId: "tinycloud.apps.read",
|
|
922
|
+
operationVersion: 1,
|
|
923
|
+
readOnlyHint: true,
|
|
924
|
+
idempotentHint: true,
|
|
925
|
+
destructiveHint: false,
|
|
926
|
+
openWorldHint: true,
|
|
927
|
+
surfaces: ["hosted"]
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
name: "tinycloud_apps_write",
|
|
931
|
+
operationId: "tinycloud.apps.write",
|
|
932
|
+
operationVersion: 1,
|
|
933
|
+
readOnlyHint: false,
|
|
934
|
+
idempotentHint: false,
|
|
935
|
+
destructiveHint: true,
|
|
936
|
+
openWorldHint: true,
|
|
937
|
+
surfaces: ["hosted"]
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: "tinycloud_apps_query",
|
|
941
|
+
operationId: "tinycloud.apps.query",
|
|
942
|
+
operationVersion: 1,
|
|
943
|
+
readOnlyHint: true,
|
|
944
|
+
idempotentHint: true,
|
|
945
|
+
destructiveHint: false,
|
|
946
|
+
openWorldHint: true,
|
|
947
|
+
surfaces: ["hosted"]
|
|
851
948
|
}
|
|
852
949
|
];
|
|
853
950
|
function createJsonSchemaValidator() {
|
|
@@ -892,23 +989,50 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
892
989
|
annotations
|
|
893
990
|
},
|
|
894
991
|
async (input) => {
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
992
|
+
const startedAt = performance.now();
|
|
993
|
+
const metrics = { replayedGrants: 0 };
|
|
994
|
+
let code = "INTERNAL_ERROR";
|
|
995
|
+
try {
|
|
996
|
+
const result = await invokeOperation2(
|
|
997
|
+
binding.operationId,
|
|
998
|
+
binding.operationVersion,
|
|
999
|
+
invocationTargetForMcp(selection),
|
|
1000
|
+
input,
|
|
1001
|
+
metrics
|
|
1002
|
+
);
|
|
1003
|
+
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
1004
|
+
const response = toMcpToolResult(projected, TOOL_BINDINGS, {
|
|
1005
|
+
surface: selection.surface,
|
|
1006
|
+
readOnly: binding.readOnlyHint,
|
|
1007
|
+
operationId: binding.operationId
|
|
1008
|
+
});
|
|
1009
|
+
code = outcomeCode(response.structuredContent);
|
|
1010
|
+
return response;
|
|
1011
|
+
} finally {
|
|
1012
|
+
if (selection.surface === "hosted") {
|
|
1013
|
+
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
903
1016
|
}
|
|
904
1017
|
);
|
|
905
1018
|
}
|
|
906
1019
|
}
|
|
1020
|
+
function outcomeCode(result) {
|
|
1021
|
+
if (result.status === "error") {
|
|
1022
|
+
const error = result.error;
|
|
1023
|
+
return typeof error?.code === "string" && Object.hasOwn(stableErrorCodes, error.code) ? error.code : "INTERNAL_ERROR";
|
|
1024
|
+
}
|
|
1025
|
+
if (result.status !== "ok") return result.status;
|
|
1026
|
+
const output = result.output;
|
|
1027
|
+
if (output?.outcome === "COPY_PARTIAL") return "COPY_PARTIAL";
|
|
1028
|
+
if (Array.isArray(output?.ops) && output.ops.some((op) => op.outcome !== "ok")) return "PARTIAL";
|
|
1029
|
+
return "ok";
|
|
1030
|
+
}
|
|
907
1031
|
|
|
908
1032
|
// package.json
|
|
909
1033
|
var package_default = {
|
|
910
1034
|
name: "@tinycloud/mcp",
|
|
911
|
-
version: "0.
|
|
1035
|
+
version: "0.6.0-beta.2",
|
|
912
1036
|
description: "TinyCloud delegated operations MCP server",
|
|
913
1037
|
keywords: [
|
|
914
1038
|
"tinycloud",
|
|
@@ -981,7 +1105,7 @@ var package_default = {
|
|
|
981
1105
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
982
1106
|
"@tinycloud/node-sdk": "3.2.5-beta.0",
|
|
983
1107
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
984
|
-
"@tinycloud/operations": "0.
|
|
1108
|
+
"@tinycloud/operations": "0.6.0-beta.2",
|
|
985
1109
|
jose: "6.1.3"
|
|
986
1110
|
},
|
|
987
1111
|
devDependencies: {
|
|
@@ -1025,13 +1149,13 @@ function createHostedMcpApp(config) {
|
|
|
1025
1149
|
const handler = createMcpHandler(async ({ authInfo: authInfo2 }) => {
|
|
1026
1150
|
const identity = oauthIdentity(authInfo2);
|
|
1027
1151
|
const { subject, ownerDids } = identity;
|
|
1028
|
-
const stateRoot = store.
|
|
1152
|
+
const stateRoot = await store.initializeTenant(subject);
|
|
1029
1153
|
const validator = createJsonSchemaValidator();
|
|
1030
1154
|
const server = new McpServer2(
|
|
1031
1155
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1032
1156
|
{
|
|
1033
1157
|
jsonSchemaValidator: validator,
|
|
1034
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged."
|
|
1158
|
+
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."
|
|
1035
1159
|
}
|
|
1036
1160
|
);
|
|
1037
1161
|
registerTinyCloudTools(server, {
|
|
@@ -1143,14 +1267,21 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1143
1267
|
}
|
|
1144
1268
|
},
|
|
1145
1269
|
async () => {
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1270
|
+
const startedAt = performance.now();
|
|
1271
|
+
let code = "INTERNAL_ERROR";
|
|
1272
|
+
try {
|
|
1273
|
+
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1274
|
+
code = status.connected ? "ok" : "authority_required";
|
|
1275
|
+
return {
|
|
1276
|
+
content: [{
|
|
1277
|
+
type: "text",
|
|
1278
|
+
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1279
|
+
}],
|
|
1280
|
+
structuredContent: status
|
|
1281
|
+
};
|
|
1282
|
+
} finally {
|
|
1283
|
+
logToolCall("tinycloud_connect", code, startedAt, 0);
|
|
1284
|
+
}
|
|
1154
1285
|
}
|
|
1155
1286
|
);
|
|
1156
1287
|
}
|