@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.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,36 @@ 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"]
|
|
851
938
|
}
|
|
852
939
|
];
|
|
853
940
|
function createJsonSchemaValidator() {
|
|
@@ -892,23 +979,50 @@ function registerTinyCloudTools(server, selection, validator) {
|
|
|
892
979
|
annotations
|
|
893
980
|
},
|
|
894
981
|
async (input) => {
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
982
|
+
const startedAt = performance.now();
|
|
983
|
+
const metrics = { replayedGrants: 0 };
|
|
984
|
+
let code = "INTERNAL_ERROR";
|
|
985
|
+
try {
|
|
986
|
+
const result = await invokeOperation2(
|
|
987
|
+
binding.operationId,
|
|
988
|
+
binding.operationVersion,
|
|
989
|
+
invocationTargetForMcp(selection),
|
|
990
|
+
input,
|
|
991
|
+
metrics
|
|
992
|
+
);
|
|
993
|
+
const projected = selection.transformOperationResult === void 0 ? result : await selection.transformOperationResult(result);
|
|
994
|
+
const response = toMcpToolResult(projected, TOOL_BINDINGS, {
|
|
995
|
+
surface: selection.surface,
|
|
996
|
+
readOnly: binding.readOnlyHint,
|
|
997
|
+
operationId: binding.operationId
|
|
998
|
+
});
|
|
999
|
+
code = outcomeCode(response.structuredContent);
|
|
1000
|
+
return response;
|
|
1001
|
+
} finally {
|
|
1002
|
+
if (selection.surface === "hosted") {
|
|
1003
|
+
logToolCall(binding.name, code, startedAt, metrics.replayedGrants);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
903
1006
|
}
|
|
904
1007
|
);
|
|
905
1008
|
}
|
|
906
1009
|
}
|
|
1010
|
+
function outcomeCode(result) {
|
|
1011
|
+
if (result.status === "error") {
|
|
1012
|
+
const error = result.error;
|
|
1013
|
+
return typeof error?.code === "string" && Object.hasOwn(stableErrorCodes, error.code) ? error.code : "INTERNAL_ERROR";
|
|
1014
|
+
}
|
|
1015
|
+
if (result.status !== "ok") return result.status;
|
|
1016
|
+
const output = result.output;
|
|
1017
|
+
if (output?.outcome === "COPY_PARTIAL") return "COPY_PARTIAL";
|
|
1018
|
+
if (Array.isArray(output?.ops) && output.ops.some((op) => op.outcome !== "ok")) return "PARTIAL";
|
|
1019
|
+
return "ok";
|
|
1020
|
+
}
|
|
907
1021
|
|
|
908
1022
|
// package.json
|
|
909
1023
|
var package_default = {
|
|
910
1024
|
name: "@tinycloud/mcp",
|
|
911
|
-
version: "0.
|
|
1025
|
+
version: "0.6.0-beta.1",
|
|
912
1026
|
description: "TinyCloud delegated operations MCP server",
|
|
913
1027
|
keywords: [
|
|
914
1028
|
"tinycloud",
|
|
@@ -981,7 +1095,7 @@ var package_default = {
|
|
|
981
1095
|
"@modelcontextprotocol/server": "2.0.0-beta.4",
|
|
982
1096
|
"@tinycloud/node-sdk": "3.2.5-beta.0",
|
|
983
1097
|
"@tinycloud/node-sdk-wasm": "1.7.7",
|
|
984
|
-
"@tinycloud/operations": "0.
|
|
1098
|
+
"@tinycloud/operations": "0.6.0-beta.1",
|
|
985
1099
|
jose: "6.1.3"
|
|
986
1100
|
},
|
|
987
1101
|
devDependencies: {
|
|
@@ -1025,13 +1139,13 @@ function createHostedMcpApp(config) {
|
|
|
1025
1139
|
const handler = createMcpHandler(async ({ authInfo: authInfo2 }) => {
|
|
1026
1140
|
const identity = oauthIdentity(authInfo2);
|
|
1027
1141
|
const { subject, ownerDids } = identity;
|
|
1028
|
-
const stateRoot = store.
|
|
1142
|
+
const stateRoot = await store.initializeTenant(subject);
|
|
1029
1143
|
const validator = createJsonSchemaValidator();
|
|
1030
1144
|
const server = new McpServer2(
|
|
1031
1145
|
{ name: MCP_SERVER_NAME, version: MCP_VERSION },
|
|
1032
1146
|
{
|
|
1033
1147
|
jsonSchemaValidator: validator,
|
|
1034
|
-
instructions: "Call tinycloud_connect first. If an operation returns authority_required, open approval.url, then retry that operation unchanged."
|
|
1148
|
+
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
1149
|
}
|
|
1036
1150
|
);
|
|
1037
1151
|
registerTinyCloudTools(server, {
|
|
@@ -1143,14 +1257,21 @@ function registerConnectTool(server, validator, store, identity) {
|
|
|
1143
1257
|
}
|
|
1144
1258
|
},
|
|
1145
1259
|
async () => {
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1260
|
+
const startedAt = performance.now();
|
|
1261
|
+
let code = "INTERNAL_ERROR";
|
|
1262
|
+
try {
|
|
1263
|
+
const status = await store.connectStatus(identity.subject, identity.ownerDids);
|
|
1264
|
+
code = status.connected ? "ok" : "authority_required";
|
|
1265
|
+
return {
|
|
1266
|
+
content: [{
|
|
1267
|
+
type: "text",
|
|
1268
|
+
text: status.connected ? "TinyCloud is connected." : `TinyCloud approval is required. Open ${status.approvalUrl}, then call this tool again. This link expires at ${status.expiresAt}.`
|
|
1269
|
+
}],
|
|
1270
|
+
structuredContent: status
|
|
1271
|
+
};
|
|
1272
|
+
} finally {
|
|
1273
|
+
logToolCall("tinycloud_connect", code, startedAt, 0);
|
|
1274
|
+
}
|
|
1154
1275
|
}
|
|
1155
1276
|
);
|
|
1156
1277
|
}
|