@socialproof/memory-mcp 0.1.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.
@@ -0,0 +1,671 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { MEMORY_TYPESCRIPT_COMPATIBILITY_VERSION } from "@socialproof/memory";
3
+ import { PRODUCTION_ACTION_CATALOG, PRODUCTION_ACTION_CATALOG_VERSION, SOCIAL_ACTION_REGISTRY_VERSION, getSocialActionDescriptor, } from "@socialproof/social";
4
+ import { McpRuntimeError, redactSensitiveText } from "./errors.js";
5
+ const SUPPORTED_SOCIAL_TOOLS = [
6
+ "chain_list_actions",
7
+ "social_create_post",
8
+ "social_create_comment",
9
+ "social_react_post",
10
+ "social_react_comment",
11
+ "social_create_repost",
12
+ "social_remove_post_reaction",
13
+ "social_remove_comment_reaction",
14
+ "social_edit_post",
15
+ "social_edit_comment",
16
+ "social_remove_repost",
17
+ "social_follow_profile",
18
+ "social_unfollow_profile",
19
+ "social_block_profile",
20
+ "social_unblock_profile",
21
+ "messaging_send_message",
22
+ "messaging_create_group",
23
+ "messaging_list_inbox",
24
+ "messaging_wait_for_message",
25
+ "organization_get_control",
26
+ "organization_accept_invitation",
27
+ "organization_decline_invitation",
28
+ "agent_register_child",
29
+ "agent_update_child",
30
+ "agent_deactivate_child",
31
+ "agent_revoke_child",
32
+ "organization_create",
33
+ "organization_update_metadata",
34
+ "organization_update_category",
35
+ "organization_deactivate",
36
+ "organization_ensure_memory_group",
37
+ "organization_define_role",
38
+ "organization_assign_role",
39
+ "organization_revoke_role",
40
+ "organization_create_invitation",
41
+ "agent_provision_signer",
42
+ "agent_register_root",
43
+ "chain_get_action_status",
44
+ "chain_request_action_approval",
45
+ "chain_approve_action",
46
+ "chain_prepare_approved_action",
47
+ "chain_submit_approved_action",
48
+ ];
49
+ function bytesToHex(value) {
50
+ return Buffer.from(value).toString("hex");
51
+ }
52
+ function normalizeAddress(value) {
53
+ const hex = value.trim().toLowerCase().replace(/^0x/, "");
54
+ if (!/^[0-9a-f]{1,64}$/.test(hex))
55
+ return "";
56
+ return `0x${hex.padStart(64, "0")}`;
57
+ }
58
+ function normalizeRpcUrl(value) {
59
+ let parsed;
60
+ try {
61
+ parsed = new URL(value);
62
+ }
63
+ catch {
64
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context contains an invalid RPC URL.");
65
+ }
66
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
67
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context RPC URL must use HTTP or HTTPS.");
68
+ }
69
+ const localHostnames = new Set(["localhost", "127.0.0.1", "::1"]);
70
+ if (parsed.protocol === "http:" && !localHostnames.has(parsed.hostname)) {
71
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context RPC URL must use HTTPS unless it targets localhost.");
72
+ }
73
+ return parsed.toString().replace(/\/$/, "");
74
+ }
75
+ function parseNetwork(value) {
76
+ if (value === "mainnet" || value === "testnet" || value === "devnet" || value === "localnet") {
77
+ return value;
78
+ }
79
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context contains an unsupported network.");
80
+ }
81
+ function parseChainObject(value) {
82
+ const chain = requireJsonObject(value, "Agent context social chain");
83
+ const fields = [
84
+ "packageId",
85
+ "usernameRegistryId",
86
+ "platformRegistryId",
87
+ "platformObjectId",
88
+ "blockListRegistryId",
89
+ "postConfigId",
90
+ "memoryConfigId",
91
+ "mydataRegistryId",
92
+ ];
93
+ const parsed = {};
94
+ for (const field of fields) {
95
+ const objectId = chain[field];
96
+ if (typeof objectId !== "string" || !normalizeAddress(objectId)) {
97
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", `Agent context social chain is missing a valid ${field}.`);
98
+ }
99
+ parsed[field] = objectId;
100
+ }
101
+ const clockId = chain.clockId;
102
+ if (clockId !== undefined && (typeof clockId !== "string" || !normalizeAddress(clockId))) {
103
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context social chain contains an invalid clockId.");
104
+ }
105
+ parsed.clockId = typeof clockId === "string" ? clockId : "0x6";
106
+ for (const field of [
107
+ "socialGraphId",
108
+ "messagingPackageId",
109
+ "messagingVersionId",
110
+ "messagingConfigId",
111
+ "messagingNamespaceId",
112
+ "messagingGroupManagerId",
113
+ "messagingGroupLeaverId",
114
+ ]) {
115
+ const objectId = chain[field];
116
+ if (objectId !== undefined && (typeof objectId !== "string" || !normalizeAddress(objectId))) {
117
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", `Agent context social chain contains an invalid ${field}.`);
118
+ }
119
+ if (typeof objectId === "string")
120
+ parsed[field] = objectId;
121
+ }
122
+ return parsed;
123
+ }
124
+ function statusError(status, body) {
125
+ const safeDetail = redactSensitiveText(body.trim());
126
+ const suffix = safeDetail ? ` ${safeDetail}` : "";
127
+ if (status === 401) {
128
+ return new McpRuntimeError("AUTHENTICATION_FAILED", `Gateway authentication failed.${suffix}`);
129
+ }
130
+ if (status === 403) {
131
+ if (body.includes("action_approval_required")) {
132
+ return new McpRuntimeError("APPROVAL_FLOW_NOT_AVAILABLE", `Owner wallet approval is required.${suffix}`, { approvalRequired: true });
133
+ }
134
+ return new McpRuntimeError("CAPABILITY_DENIED", `The agent is not permitted to perform this action.${suffix}`);
135
+ }
136
+ if (status === 409) {
137
+ return new McpRuntimeError("CONFLICT", `The action conflicts with current chain state.${suffix}`);
138
+ }
139
+ if (status === 429) {
140
+ return new McpRuntimeError("RATE_LIMITED", "The sponsorship rate limit was reached.", {
141
+ retryable: true,
142
+ });
143
+ }
144
+ return new McpRuntimeError(status >= 500 ? "UPSTREAM_UNAVAILABLE" : "SOCIAL_GATEWAY_UNAVAILABLE", `The gateway rejected the request with status ${status}.${suffix}`, { retryable: status >= 500 });
145
+ }
146
+ function requireJsonObject(value, label) {
147
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
148
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", `${label} returned an invalid response.`);
149
+ }
150
+ return value;
151
+ }
152
+ function parseAgentContext(value) {
153
+ const context = requireJsonObject(value, "Agent context");
154
+ const requiredStrings = ["memoryAccountId", "agentObjectId", "derivedAddress", "network", "rpcUrl"];
155
+ for (const key of requiredStrings) {
156
+ if (typeof context[key] !== "string" || !context[key]) {
157
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", `Agent context is missing ${key}.`);
158
+ }
159
+ }
160
+ if (!normalizeAddress(context.memoryAccountId) ||
161
+ !normalizeAddress(context.derivedAddress)) {
162
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context contains an invalid account or signer address.");
163
+ }
164
+ if (!Number.isSafeInteger(context.capabilities) || !Number.isSafeInteger(context.approvalRequiredCapabilities)) {
165
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context contains an unsafe capability bitmap.");
166
+ }
167
+ const network = parseNetwork(context.network);
168
+ const rpcUrl = normalizeRpcUrl(context.rpcUrl);
169
+ const socialChain = parseChainObject(context.socialChain);
170
+ if (context.packageId !== undefined &&
171
+ (typeof context.packageId !== "string" ||
172
+ normalizeAddress(context.packageId) !== normalizeAddress(socialChain.packageId))) {
173
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context packageId does not match its social chain packageId.");
174
+ }
175
+ if (context.permittedRegistryActions !== undefined &&
176
+ (!Array.isArray(context.permittedRegistryActions) ||
177
+ !context.permittedRegistryActions.every((action) => typeof action === "string"))) {
178
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context contains an invalid action catalog.");
179
+ }
180
+ if (context.platformScope !== undefined &&
181
+ context.platformScope !== null &&
182
+ (typeof context.platformScope !== "string" || !normalizeAddress(context.platformScope))) {
183
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Agent context contains an invalid platform scope.");
184
+ }
185
+ return {
186
+ memoryAccountId: context.memoryAccountId,
187
+ agentObjectId: context.agentObjectId,
188
+ derivedAddress: context.derivedAddress,
189
+ capabilities: context.capabilities,
190
+ approvalRequiredCapabilities: context.approvalRequiredCapabilities,
191
+ platformScope: context.platformScope,
192
+ network,
193
+ rpcUrl,
194
+ packageId: typeof context.packageId === "string" ? context.packageId : undefined,
195
+ socialChain,
196
+ permittedRegistryActions: context.permittedRegistryActions,
197
+ };
198
+ }
199
+ function parsePreparedAction(value, expectedAction, expectedIdempotencyKey) {
200
+ const prepared = requireJsonObject(value, "Registered action gateway");
201
+ if (prepared.registryAction !== expectedAction ||
202
+ prepared.registryVersion !== SOCIAL_ACTION_REGISTRY_VERSION ||
203
+ prepared.idempotencyKey !== expectedIdempotencyKey ||
204
+ typeof prepared.bytes !== "string" ||
205
+ typeof prepared.digest !== "string" ||
206
+ typeof prepared.parameterHash !== "string" ||
207
+ typeof prepared.transactionKindHash !== "string" ||
208
+ typeof prepared.packageId !== "string" ||
209
+ typeof prepared.packageVersion !== "string" ||
210
+ typeof prepared.status !== "string" ||
211
+ !Number.isSafeInteger(prepared.expiresAtMs)) {
212
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Registered action gateway returned incomplete metadata.");
213
+ }
214
+ const decoded = Buffer.from(prepared.bytes, "base64");
215
+ if (decoded.length < 10 || decoded.length > 128 * 1024) {
216
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Registered action gateway returned invalid transaction bytes.");
217
+ }
218
+ if (!/^[1-9A-HJ-NP-Za-km-z]{43,44}$/.test(prepared.digest)) {
219
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Registered action gateway returned an invalid digest.");
220
+ }
221
+ return prepared;
222
+ }
223
+ function parseExecution(value, expectedDigest) {
224
+ const execution = requireJsonObject(value, "Sponsor execution");
225
+ if (execution.digest !== expectedDigest) {
226
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "Sponsor execution returned an unexpected digest.");
227
+ }
228
+ return { digest: expectedDigest };
229
+ }
230
+ function capabilityPresent(bitmap, capability) {
231
+ return (BigInt(bitmap) & BigInt(capability)) === BigInt(capability);
232
+ }
233
+ function assertAddressPin(label, configured, authenticated) {
234
+ if (configured === undefined)
235
+ return;
236
+ if (!normalizeAddress(configured)) {
237
+ throw new McpRuntimeError("INVALID_CONFIGURATION", `${label} is not a valid object ID.`);
238
+ }
239
+ if (normalizeAddress(configured) !== normalizeAddress(authenticated)) {
240
+ throw new McpRuntimeError("CONFLICT", `${label} does not match the authenticated agent context. Refresh or remove the local deployment pin.`);
241
+ }
242
+ }
243
+ function resolveExecutionContext(authenticated, pins) {
244
+ if (pins.network !== undefined && pins.network !== authenticated.network) {
245
+ throw new McpRuntimeError("CONFLICT", "mysoNetwork does not match the authenticated agent context. Refresh or remove the local deployment pin.");
246
+ }
247
+ if (pins.rpcUrl !== undefined && normalizeRpcUrl(pins.rpcUrl) !== authenticated.rpcUrl) {
248
+ throw new McpRuntimeError("CONFLICT", "mysoRpcUrl does not match the authenticated agent context. Refresh or remove the local deployment pin.");
249
+ }
250
+ const chainPins = pins.chain;
251
+ if (chainPins) {
252
+ const fields = [
253
+ "packageId",
254
+ "usernameRegistryId",
255
+ "platformRegistryId",
256
+ "platformObjectId",
257
+ "blockListRegistryId",
258
+ "postConfigId",
259
+ "memoryConfigId",
260
+ "mydataRegistryId",
261
+ "clockId",
262
+ "socialGraphId",
263
+ "messagingPackageId",
264
+ "messagingVersionId",
265
+ "messagingConfigId",
266
+ "messagingNamespaceId",
267
+ "messagingGroupManagerId",
268
+ "messagingGroupLeaverId",
269
+ ];
270
+ for (const field of fields) {
271
+ const authenticatedValue = authenticated.socialChain[field];
272
+ if (chainPins[field] !== undefined && authenticatedValue === undefined) {
273
+ throw new McpRuntimeError("CONFLICT", `socialChain.${field} is not configured by the gateway.`);
274
+ }
275
+ assertAddressPin(`socialChain.${field}`, chainPins[field], authenticatedValue ?? (field === "clockId" ? "0x6" : ""));
276
+ }
277
+ }
278
+ return {
279
+ network: authenticated.network,
280
+ rpcUrl: authenticated.rpcUrl,
281
+ chain: authenticated.socialChain,
282
+ };
283
+ }
284
+ /**
285
+ * Executes only hard-coded, versioned Tier 1A/1B action-registry entries.
286
+ * There is no arbitrary action name, Move target, PTB, or direct-sign fallback
287
+ * in the public MCP path. Tier 3 owner actions remain disabled until the
288
+ * wallet-approval workflow is available.
289
+ */
290
+ export class SponsoredSocialGateway {
291
+ supportedToolNames = SUPPORTED_SOCIAL_TOOLS;
292
+ signer;
293
+ accountId;
294
+ serverUrl;
295
+ platformId;
296
+ networkPin;
297
+ rpcUrlPin;
298
+ chainPins;
299
+ fetchImpl;
300
+ constructor(options) {
301
+ if (!normalizeAddress(options.accountId)) {
302
+ throw new McpRuntimeError("INVALID_CONFIGURATION", "accountId must be a valid MySo object ID.");
303
+ }
304
+ this.signer = options.signer;
305
+ this.accountId = options.accountId;
306
+ this.serverUrl = options.serverUrl.replace(/\/$/, "");
307
+ this.platformId = options.platformId;
308
+ this.networkPin = options.network;
309
+ this.rpcUrlPin = options.rpcUrl;
310
+ this.chainPins = options.chain;
311
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
312
+ }
313
+ async listActions() {
314
+ await this.ensureDeploymentPlatform();
315
+ const context = parseAgentContext(await this.signedJson("GET", "/api/agent/context"));
316
+ const permitted = new Set(context.permittedRegistryActions ?? []);
317
+ return {
318
+ catalogVersion: PRODUCTION_ACTION_CATALOG_VERSION,
319
+ registryVersion: SOCIAL_ACTION_REGISTRY_VERSION,
320
+ actions: PRODUCTION_ACTION_CATALOG.map((action) => ({
321
+ ...action,
322
+ permitted: action.availability === "enabled" &&
323
+ action.requiredCapability !== null &&
324
+ capabilityPresent(context.capabilities, action.requiredCapability) &&
325
+ (!context.permittedRegistryActions || permitted.has(action.id)),
326
+ approvalRequired: action.tier !== "1" ||
327
+ (action.requiredCapability !== null &&
328
+ capabilityPresent(context.approvalRequiredCapabilities, action.requiredCapability)),
329
+ })),
330
+ };
331
+ }
332
+ async reactToPost(input) {
333
+ const { idempotencyKey, ...parameters } = input;
334
+ return this.executeRegisteredAction("social.react_to_post.v1", parameters, idempotencyKey);
335
+ }
336
+ async createPost(input) {
337
+ const { idempotencyKey, ...parameters } = input;
338
+ return this.executeRegisteredAction("social.create_post.v1", parameters, idempotencyKey);
339
+ }
340
+ async createComment(input) {
341
+ const { idempotencyKey, ...parameters } = input;
342
+ return this.executeRegisteredAction("social.create_comment.v1", parameters, idempotencyKey);
343
+ }
344
+ async reactToComment(input) {
345
+ const { idempotencyKey, ...parameters } = input;
346
+ return this.executeRegisteredAction("social.react_to_comment.v1", parameters, idempotencyKey);
347
+ }
348
+ async createRepost(input) {
349
+ const { idempotencyKey, ...parameters } = input;
350
+ return this.executeRegisteredAction("social.create_repost.v1", parameters, idempotencyKey);
351
+ }
352
+ executeInput(action, input) {
353
+ const { idempotencyKey, ...parameters } = input;
354
+ return this.executeRegisteredAction(action, parameters, idempotencyKey);
355
+ }
356
+ async removePostReaction(input) {
357
+ return this.executeInput("social.remove_post_reaction.v1", input);
358
+ }
359
+ async removeCommentReaction(input) {
360
+ return this.executeInput("social.remove_comment_reaction.v1", input);
361
+ }
362
+ async editPost(input) {
363
+ return this.executeInput("social.edit_post.v1", input);
364
+ }
365
+ async editComment(input) {
366
+ return this.executeInput("social.edit_comment.v1", input);
367
+ }
368
+ async removeRepost(input) {
369
+ return this.executeInput("social.remove_repost.v1", input);
370
+ }
371
+ async followProfile(input) {
372
+ return this.executeInput("social.follow_profile.v1", input);
373
+ }
374
+ async unfollowProfile(input) {
375
+ return this.executeInput("social.unfollow_profile.v1", input);
376
+ }
377
+ async blockProfile(input) {
378
+ return this.executeInput("social.block_profile.v1", input);
379
+ }
380
+ async unblockProfile(input) {
381
+ return this.executeInput("social.unblock_profile.v1", input);
382
+ }
383
+ async sendMessage(input) {
384
+ return this.executeInput("messaging.send_message.v1", input);
385
+ }
386
+ async createMessagingGroup(input) {
387
+ return this.executeInput("messaging.create_group.v1", input);
388
+ }
389
+ async acceptOrganizationInvitation(input) {
390
+ return this.executeInput("organization.accept_invitation.v1", input);
391
+ }
392
+ async declineOrganizationInvitation(input) {
393
+ return this.executeInput("organization.decline_invitation.v1", input);
394
+ }
395
+ async registerChildAgent(input) {
396
+ await this.ensureDeploymentPlatform();
397
+ const context = parseAgentContext(await this.signedJson("GET", "/api/agent/context"));
398
+ return this.executeInput("agent.register_child.v1", {
399
+ ...input,
400
+ parentAgentObjectId: input.parentAgentObjectId ?? context.agentObjectId,
401
+ });
402
+ }
403
+ async updateChildAgent(input) {
404
+ return this.executeInput("agent.update_child.v1", input);
405
+ }
406
+ async deactivateChildAgent(input) {
407
+ return this.executeInput("agent.deactivate_child.v1", input);
408
+ }
409
+ async revokeChildAgent(input) {
410
+ return this.executeInput("agent.revoke_child.v1", input);
411
+ }
412
+ async getOrganizationControl(organizationId) {
413
+ if (!normalizeAddress(organizationId)) {
414
+ throw new McpRuntimeError("INVALID_ARGUMENT", "organizationId must be an object ID.");
415
+ }
416
+ await this.ensureDeploymentPlatform();
417
+ return this.signedJson("GET", `/api/organizations/${encodeURIComponent(organizationId)}/control`);
418
+ }
419
+ async listInbox(input) {
420
+ await this.ensureDeploymentPlatform();
421
+ const query = new URLSearchParams();
422
+ if (input.limit !== undefined)
423
+ query.set("limit", String(input.limit));
424
+ if (input.offset !== undefined)
425
+ query.set("offset", String(input.offset));
426
+ if (input.groupId)
427
+ query.set("groupId", input.groupId);
428
+ if (input.afterCreatedAtMs !== undefined)
429
+ query.set("afterCreatedAtMs", String(input.afterCreatedAtMs));
430
+ if (input.afterSeq !== undefined)
431
+ query.set("afterSeq", String(input.afterSeq));
432
+ const suffix = query.size > 0 ? `?${query}` : "";
433
+ return this.signedJson("GET", `/api/messaging/inbox${suffix}`);
434
+ }
435
+ async waitForMessage(input) {
436
+ await this.ensureDeploymentPlatform();
437
+ const query = new URLSearchParams();
438
+ if (input.timeoutMs !== undefined)
439
+ query.set("timeoutMs", String(input.timeoutMs));
440
+ if (input.groupId)
441
+ query.set("groupId", input.groupId);
442
+ if (input.afterCreatedAtMs !== undefined)
443
+ query.set("afterCreatedAtMs", String(input.afterCreatedAtMs));
444
+ if (input.afterSeq !== undefined)
445
+ query.set("afterSeq", String(input.afterSeq));
446
+ const suffix = query.size > 0 ? `?${query}` : "";
447
+ return this.signedJson("GET", `/api/messaging/wait${suffix}`);
448
+ }
449
+ async deletePost(_postId) {
450
+ throw this.approvalUnavailable("Post deletion");
451
+ }
452
+ async deleteComment(_input) {
453
+ throw this.approvalUnavailable("Comment deletion");
454
+ }
455
+ async getActionStatus(digest) {
456
+ if (!/^[1-9A-HJ-NP-Za-km-z]{43,44}$/.test(digest)) {
457
+ throw new McpRuntimeError("INVALID_ARGUMENT", "digest must be a 43- or 44-character base58 transaction digest.");
458
+ }
459
+ await this.ensureDeploymentPlatform();
460
+ return this.signedJson("GET", `/api/chain/actions/${encodeURIComponent(digest)}`);
461
+ }
462
+ async requestActionApproval(input) {
463
+ this.validateApprovalInput(input);
464
+ await this.assertActionInAuthenticatedCatalog(input.registryAction);
465
+ return this.signedJson("POST", "/api/chain/approvals/request", {
466
+ registryAction: input.registryAction,
467
+ registryVersion: SOCIAL_ACTION_REGISTRY_VERSION,
468
+ idempotencyKey: input.idempotencyKey,
469
+ parameters: input.parameters,
470
+ expiresInSeconds: input.expiresInSeconds,
471
+ });
472
+ }
473
+ async approveAction(input) {
474
+ if (!/^[0-9a-f-]{36}$/i.test(input.approvalId)) {
475
+ throw new McpRuntimeError("INVALID_ARGUMENT", "approvalId must be a UUID.");
476
+ }
477
+ if (input.walletSignature.length < 80 || input.walletSignature.length > 4096) {
478
+ throw new McpRuntimeError("INVALID_ARGUMENT", "A serialized wallet personal-message signature is required.");
479
+ }
480
+ return this.requestJson("POST", `/api/chain/approvals/${encodeURIComponent(input.approvalId)}/approve`, { "content-type": "application/json" }, JSON.stringify({ walletSignature: input.walletSignature }));
481
+ }
482
+ async prepareApprovedAction(input) {
483
+ this.validateApprovalInput(input);
484
+ if (!/^[0-9a-f-]{36}$/i.test(input.approvalId)) {
485
+ throw new McpRuntimeError("INVALID_ARGUMENT", "approvalId must be a UUID.");
486
+ }
487
+ await this.assertActionInAuthenticatedCatalog(input.registryAction);
488
+ return this.signedJson("POST", "/api/chain/actions/prepare", {
489
+ registryAction: input.registryAction,
490
+ registryVersion: SOCIAL_ACTION_REGISTRY_VERSION,
491
+ idempotencyKey: input.idempotencyKey,
492
+ parameters: input.parameters,
493
+ approvalId: input.approvalId,
494
+ });
495
+ }
496
+ async submitApprovedAction(input) {
497
+ getSocialActionDescriptor(input.registryAction);
498
+ if (!/^[A-Za-z0-9._:/-]{8,128}$/.test(input.idempotencyKey)) {
499
+ throw new McpRuntimeError("INVALID_ARGUMENT", "idempotencyKey must be 8-128 URL-safe characters.");
500
+ }
501
+ if (!/^[0-9a-f-]{36}$/i.test(input.approvalId) || !/^[1-9A-HJ-NP-Za-km-z]{43,44}$/.test(input.digest)) {
502
+ throw new McpRuntimeError("INVALID_ARGUMENT", "approvalId or digest is invalid.");
503
+ }
504
+ if (!input.walletSignature.trim()) {
505
+ throw new McpRuntimeError("INVALID_ARGUMENT", "walletSignature is required.");
506
+ }
507
+ await this.assertActionInAuthenticatedCatalog(input.registryAction);
508
+ return this.signedJson("POST", "/api/chain/actions/submit", {
509
+ registryAction: input.registryAction,
510
+ registryVersion: SOCIAL_ACTION_REGISTRY_VERSION,
511
+ idempotencyKey: input.idempotencyKey,
512
+ approvalId: input.approvalId,
513
+ digest: input.digest,
514
+ signature: input.walletSignature,
515
+ });
516
+ }
517
+ validateApprovalInput(input) {
518
+ const validation = getSocialActionDescriptor(input.registryAction).validate(input.parameters);
519
+ if (!validation.success) {
520
+ throw new McpRuntimeError("INVALID_ARGUMENT", `Registered action parameters are invalid: ${validation.issues.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`);
521
+ }
522
+ if (!/^[A-Za-z0-9._:/-]{8,128}$/.test(input.idempotencyKey)) {
523
+ throw new McpRuntimeError("INVALID_ARGUMENT", "idempotencyKey must be 8-128 URL-safe characters.");
524
+ }
525
+ }
526
+ async assertActionInAuthenticatedCatalog(action) {
527
+ await this.ensureDeploymentPlatform();
528
+ const descriptor = getSocialActionDescriptor(action);
529
+ const context = parseAgentContext(await this.signedJson("GET", "/api/agent/context"));
530
+ if (!capabilityPresent(context.capabilities, descriptor.requiredCapability)) {
531
+ throw new McpRuntimeError("CAPABILITY_DENIED", "The agent lacks the action capability.");
532
+ }
533
+ if (context.permittedRegistryActions && !context.permittedRegistryActions.includes(action)) {
534
+ throw new McpRuntimeError("CAPABILITY_DENIED", "The action is not in the authenticated registry catalog.");
535
+ }
536
+ }
537
+ async executeRegisteredAction(action, parameters, idempotencyKey) {
538
+ const descriptor = getSocialActionDescriptor(action);
539
+ if (descriptor.riskTier !== "1A" && descriptor.riskTier !== "1B") {
540
+ throw new McpRuntimeError("CAPABILITY_DENIED", "Only registered Tier 1A and Tier 1B actions may execute automatically.");
541
+ }
542
+ await this.ensureDeploymentPlatform();
543
+ const context = parseAgentContext(await this.signedJson("GET", "/api/agent/context"));
544
+ resolveExecutionContext(context, {
545
+ network: this.networkPin,
546
+ rpcUrl: this.rpcUrlPin,
547
+ chain: this.chainPins,
548
+ });
549
+ const signerAddress = normalizeAddress(await this.signer.getMySoAddress());
550
+ const contextAccount = normalizeAddress(context.memoryAccountId);
551
+ const configuredAccount = normalizeAddress(this.accountId);
552
+ const contextSigner = normalizeAddress(context.derivedAddress);
553
+ if (!contextAccount ||
554
+ !configuredAccount ||
555
+ !contextSigner ||
556
+ !signerAddress ||
557
+ contextAccount !== configuredAccount ||
558
+ contextSigner !== signerAddress) {
559
+ throw new McpRuntimeError("AUTHENTICATION_FAILED", "The signer does not match the authenticated agent context.");
560
+ }
561
+ if (!capabilityPresent(context.capabilities, descriptor.requiredCapability)) {
562
+ throw new McpRuntimeError("CAPABILITY_DENIED", "The agent lacks the capability required by the registered action.");
563
+ }
564
+ if (capabilityPresent(context.approvalRequiredCapabilities, descriptor.requiredCapability)) {
565
+ throw new McpRuntimeError("APPROVAL_FLOW_NOT_AVAILABLE", `The registered action ${action} requires owner approval. Use the chain request/approve/prepare/submit tools.`, { approvalRequired: true });
566
+ }
567
+ if (context.permittedRegistryActions &&
568
+ !context.permittedRegistryActions.includes(action)) {
569
+ throw new McpRuntimeError("CAPABILITY_DENIED", "The authenticated action catalog does not expose this registered action.");
570
+ }
571
+ if (context.platformScope &&
572
+ normalizeAddress(context.platformScope) !== normalizeAddress(this.platformId ?? "")) {
573
+ throw new McpRuntimeError("CAPABILITY_DENIED", "The configured platform is outside the agent's scope.");
574
+ }
575
+ if (!/^[A-Za-z0-9._:/-]{8,128}$/.test(idempotencyKey)) {
576
+ throw new McpRuntimeError("INVALID_ARGUMENT", "idempotencyKey must be 8-128 URL-safe characters.");
577
+ }
578
+ const prepared = parsePreparedAction(await this.signedJson("POST", "/api/chain/actions/prepare", {
579
+ registryAction: action,
580
+ registryVersion: SOCIAL_ACTION_REGISTRY_VERSION,
581
+ idempotencyKey,
582
+ parameters,
583
+ }), action, idempotencyKey);
584
+ const sponsoredBytes = Uint8Array.from(Buffer.from(prepared.bytes, "base64"));
585
+ const signature = await this.signer.signTransaction(sponsoredBytes);
586
+ const executed = parseExecution(await this.signedJson("POST", "/api/chain/actions/submit", {
587
+ registryAction: action,
588
+ registryVersion: SOCIAL_ACTION_REGISTRY_VERSION,
589
+ idempotencyKey,
590
+ digest: prepared.digest,
591
+ signature,
592
+ }), prepared.digest);
593
+ return {
594
+ registryAction: action,
595
+ registryVersion: SOCIAL_ACTION_REGISTRY_VERSION,
596
+ idempotencyKey,
597
+ digest: executed.digest,
598
+ parameterHash: prepared.parameterHash,
599
+ transactionKindHash: prepared.transactionKindHash,
600
+ packageId: prepared.packageId,
601
+ packageVersion: prepared.packageVersion,
602
+ chain: { status: "submitted", digest: executed.digest },
603
+ indexer: { status: "pending" },
604
+ };
605
+ }
606
+ approvalUnavailable(label) {
607
+ return new McpRuntimeError("APPROVAL_FLOW_NOT_AVAILABLE", `${label} requires the chain request/approve/prepare/submit wallet flow.`, { approvalRequired: true });
608
+ }
609
+ async ensureDeploymentPlatform() {
610
+ if (this.platformId)
611
+ return;
612
+ const config = requireJsonObject(await this.requestJson("GET", "/config", {}, undefined), "Deployment config");
613
+ const chain = parseChainObject(config.socialChain);
614
+ this.platformId = chain.platformObjectId;
615
+ }
616
+ async signedJson(method, requestPath, body) {
617
+ const bodyString = method === "GET" ? "" : JSON.stringify(body ?? {});
618
+ const bodyHash = createHash("sha256").update(bodyString).digest("hex");
619
+ const timestamp = Math.floor(Date.now() / 1000).toString();
620
+ const nonce = randomUUID();
621
+ const canonical = `${timestamp}.${method}.${requestPath}.${bodyHash}.${nonce}.${this.accountId}`;
622
+ const message = new TextEncoder().encode(canonical);
623
+ const [signature, publicKey] = await Promise.all([
624
+ this.signer.sign(message),
625
+ this.signer.getPublicKey(),
626
+ ]);
627
+ const headers = {
628
+ "x-public-key": bytesToHex(publicKey),
629
+ "x-signature": bytesToHex(signature),
630
+ "x-timestamp": timestamp,
631
+ "x-nonce": nonce,
632
+ "x-account-id": this.accountId,
633
+ "x-sdk-compatibility": MEMORY_TYPESCRIPT_COMPATIBILITY_VERSION,
634
+ };
635
+ if (bodyString)
636
+ headers["content-type"] = "application/json";
637
+ if (this.platformId)
638
+ headers["x-platform-id"] = this.platformId;
639
+ return this.requestJson(method, requestPath, headers, bodyString || undefined);
640
+ }
641
+ async requestJson(method, requestPath, headers, body) {
642
+ let response;
643
+ try {
644
+ response = await this.fetchImpl(`${this.serverUrl}${requestPath}`, {
645
+ method,
646
+ headers,
647
+ body,
648
+ });
649
+ }
650
+ catch (error) {
651
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "The transaction gateway is unavailable.", {
652
+ retryable: true,
653
+ cause: error,
654
+ });
655
+ }
656
+ const responseText = await response.text();
657
+ if (!response.ok)
658
+ throw statusError(response.status, responseText);
659
+ if (!responseText)
660
+ return {};
661
+ try {
662
+ return JSON.parse(responseText);
663
+ }
664
+ catch (error) {
665
+ throw new McpRuntimeError("UPSTREAM_UNAVAILABLE", "The transaction gateway returned invalid JSON.", {
666
+ retryable: true,
667
+ cause: error,
668
+ });
669
+ }
670
+ }
671
+ }