Skip to content

Commit 24ce288

Browse files
author
azeth-sync[bot]
committed
sync: update from monorepo 2026-03-06
1 parent f9a4ec2 commit 24ce288

1 file changed

Lines changed: 56 additions & 22 deletions

File tree

src/tools/registry.ts

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { z } from 'zod';
22
import { hexToString } from 'viem';
33
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
44
import { discoverServicesWithFallback, getRegistryEntry } from '@azeth/sdk';
5-
import { AZETH_CONTRACTS, ERC8004_REGISTRY, formatTokenAmount } from '@azeth/common';
5+
import { AZETH_CONTRACTS, ERC8004_REGISTRY, formatTokenAmount, CATALOG_MAX_ENTRIES, CATALOG_MAX_PATH_LENGTH } from '@azeth/common';
6+
import type { CatalogEntry, CatalogMethod } from '@azeth/common';
67
import { TrustRegistryModuleAbi, ReputationModuleAbi } from '@azeth/common/abis';
78
import { createClient, resolveChain, resolveViemChain, validateAddress } from '../utils/client.js';
89
import { success, error, handleError } from '../utils/response.js';
@@ -41,8 +42,9 @@ const ERC8004_GET_METADATA_ABI = [{
4142
stateMutability: 'view' as const,
4243
}] as const;
4344

44-
/** Metadata keys that can be updated via setMetadata and should overlay tokenURI values */
45-
const OVERLAY_METADATA_KEYS = ['endpoint', 'description', 'name', 'entityType', 'capabilities', 'pricing', 'catalog'];
45+
/** Metadata keys that can be updated via setMetadata and should overlay tokenURI values.
46+
* Note: "catalog" is NOT included — catalogs are off-chain and served from the provider's endpoint. */
47+
const OVERLAY_METADATA_KEYS = ['endpoint', 'description', 'name', 'entityType', 'capabilities', 'pricing'];
4648

4749
/** Overlay per-key metadata updates from getMetadata() onto a parsed registry entry.
4850
* The ERC-8004 tokenURI is immutable after minting, but individual keys can be updated
@@ -71,20 +73,39 @@ async function overlayMetadataUpdates(
7173
}));
7274
}
7375

74-
/** Parse a raw catalog array from metadata */
75-
function parseCatalogFromMeta(raw: unknown): Array<{ name: string; path: string; pricing?: string; description?: string }> | undefined {
76+
/** Parse a raw catalog array from metadata into typed CatalogEntry objects */
77+
function parseCatalogFromMeta(raw: unknown): CatalogEntry[] | undefined {
7678
if (!Array.isArray(raw)) return undefined;
77-
const entries: Array<{ name: string; path: string; pricing?: string; description?: string }> = [];
79+
const entries: CatalogEntry[] = [];
7880
for (const item of raw) {
7981
if (typeof item !== 'object' || item === null) continue;
8082
const rec = item as Record<string, unknown>;
8183
if (typeof rec.name !== 'string' || typeof rec.path !== 'string') continue;
82-
entries.push({
84+
const entry: CatalogEntry = {
8385
name: rec.name,
8486
path: rec.path,
85-
pricing: typeof rec.pricing === 'string' ? rec.pricing : undefined,
86-
description: typeof rec.description === 'string' ? rec.description : undefined,
87-
});
87+
};
88+
if (typeof rec.method === 'string') entry.method = rec.method as CatalogMethod;
89+
if (typeof rec.description === 'string') entry.description = rec.description;
90+
if (typeof rec.pricing === 'string') entry.pricing = rec.pricing;
91+
if (typeof rec.mimeType === 'string') entry.mimeType = rec.mimeType;
92+
if (Array.isArray(rec.capabilities)) {
93+
entry.capabilities = rec.capabilities.filter((c): c is string => typeof c === 'string');
94+
}
95+
if (typeof rec.params === 'object' && rec.params !== null && !Array.isArray(rec.params)) {
96+
entry.params = rec.params as Record<string, string>;
97+
}
98+
if (typeof rec.paid === 'boolean') entry.paid = rec.paid;
99+
if (Array.isArray(rec.accepts)) {
100+
entry.accepts = (rec.accepts as Array<Record<string, unknown>>)
101+
.filter(a => typeof a.network === 'string' && typeof a.asset === 'string')
102+
.map(a => ({
103+
network: a.network as string,
104+
asset: a.asset as `0x${string}`,
105+
...(typeof a.symbol === 'string' ? { symbol: a.symbol } : {}),
106+
}));
107+
}
108+
entries.push(entry);
88109
}
89110
return entries.length > 0 ? entries : undefined;
90111
}
@@ -97,7 +118,7 @@ function parseRegistryDataURI(uri: string): {
97118
capabilities: string[];
98119
endpoint?: string;
99120
pricing?: string;
100-
catalog?: Array<{ name: string; path: string; pricing?: string; description?: string }>;
121+
catalog?: CatalogEntry[];
101122
} | null {
102123
try {
103124
if (!uri.startsWith('data:application/json,')) return null;
@@ -161,11 +182,21 @@ export function registerRegistryTools(server: McpServer): void {
161182
(val) => typeof val === 'string' ? JSON.parse(val) : val,
162183
z.array(z.object({
163184
name: z.string().min(1).max(256),
164-
path: z.string().min(1).max(512),
165-
pricing: z.string().max(256).optional(),
185+
path: z.string().min(1).max(CATALOG_MAX_PATH_LENGTH),
186+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).optional(),
166187
description: z.string().max(1024).optional(),
167-
})).max(20).optional(),
168-
).optional().describe('Service catalog for multi-service providers. Array of offerings, each with name, path (relative to base endpoint), optional pricing, and optional description.'),
188+
pricing: z.string().max(256).optional(),
189+
mimeType: z.string().max(128).optional(),
190+
capabilities: z.array(z.string().max(128)).max(20).optional(),
191+
params: z.record(z.string(), z.string().max(512)).optional(),
192+
paid: z.boolean().optional(),
193+
accepts: z.array(z.object({
194+
network: z.string().min(1).max(64),
195+
asset: z.string().regex(/^0x[0-9a-fA-F]{40}$/) as z.ZodType<`0x${string}`>,
196+
symbol: z.string().max(16).optional(),
197+
})).max(10).optional(),
198+
})).max(CATALOG_MAX_ENTRIES).optional(),
199+
).optional().describe('Off-chain service catalog for multi-service providers. Included in initial registration as a snapshot; providers should serve their live catalog from their endpoint. Each entry: name, path, method (GET/POST/etc), description, pricing, capabilities, params, paid (default true), accepts (multi-chain payment methods).'),
169200
}),
170201
},
171202
async (args) => {
@@ -448,7 +479,7 @@ export function registerRegistryTools(server: McpServer): void {
448479
}
449480

450481
// Try server API first
451-
let entry: { tokenId: string | number; owner: string; name: string; description: string; entityType: string; capabilities: string[]; endpoint?: string; pricing?: string; catalog?: Array<{ name: string; path: string; pricing?: string; description?: string }>; active: boolean } | null = null;
482+
let entry: { tokenId: string | number; owner: string; name: string; description: string; entityType: string; capabilities: string[]; endpoint?: string; pricing?: string; catalog?: CatalogEntry[]; active: boolean } | null = null;
452483
let source: 'server' | 'on-chain' = 'server';
453484

454485
try {
@@ -575,9 +606,11 @@ export function registerRegistryTools(server: McpServer): void {
575606
'Use this when: You need to change your service endpoint, description, capabilities,',
576607
'or other metadata after initial registration with azeth_publish_service.',
577608
'',
578-
'Supported metadata keys: "endpoint", "description", "capabilities", "name", "entityType", "pricing", "catalog".',
609+
'Supported metadata keys: "endpoint", "description", "capabilities", "name", "entityType", "pricing".',
579610
'For capabilities, provide a JSON array string (e.g., \'["translation", "nlp"]\').',
580-
'For catalog, provide a JSON array of objects: \'[{"name":"Chain Data","path":"/v1/chains","pricing":"$0.001/req"}]\'.',
611+
'',
612+
'Note: Catalogs are off-chain and served from your endpoint. Update your catalog by',
613+
'updating the response at your endpoint, not via this tool.',
581614
'',
582615
'Returns: Confirmation with transaction hash.',
583616
'',
@@ -588,7 +621,7 @@ export function registerRegistryTools(server: McpServer): void {
588621
].join('\n'),
589622
inputSchema: z.object({
590623
chain: z.string().optional().describe('Target chain. Defaults to AZETH_CHAIN env var or "baseSepolia". Accepts "base", "baseSepolia", "ethereumSepolia", "ethereum" (and aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet").'),
591-
key: z.enum(['endpoint', 'description', 'capabilities', 'name', 'entityType', 'pricing', 'catalog']).describe(
624+
key: z.enum(['endpoint', 'description', 'capabilities', 'name', 'entityType', 'pricing']).describe(
592625
'Metadata key to update.',
593626
),
594627
value: z.string().min(1).max(2048).describe(
@@ -628,9 +661,10 @@ export function registerRegistryTools(server: McpServer): void {
628661
'Use this when: You need to change several metadata fields at once (e.g., endpoint + description + capabilities).',
629662
'This is more gas-efficient than calling azeth_update_service multiple times.',
630663
'',
631-
'Supported metadata keys: "endpoint", "description", "capabilities", "name", "entityType", "pricing", "catalog".',
664+
'Supported metadata keys: "endpoint", "description", "capabilities", "name", "entityType", "pricing".',
632665
'For capabilities, provide a JSON array string (e.g., \'["translation", "nlp"]\').',
633-
'For catalog, provide a JSON array of objects: \'[{"name":"Chain Data","path":"/v1/chains","pricing":"$0.001/req"}]\'.',
666+
'',
667+
'Note: Catalogs are off-chain. Update your catalog by updating your endpoint response.',
634668
'',
635669
'Returns: Confirmation with a single transaction hash for all updates.',
636670
'',
@@ -644,7 +678,7 @@ export function registerRegistryTools(server: McpServer): void {
644678
updates: z.preprocess(
645679
(val) => typeof val === 'string' ? JSON.parse(val) : val,
646680
z.array(z.object({
647-
key: z.enum(['endpoint', 'description', 'capabilities', 'name', 'entityType', 'pricing', 'catalog']),
681+
key: z.enum(['endpoint', 'description', 'capabilities', 'name', 'entityType', 'pricing']),
648682
value: z.string().min(1).max(2048),
649683
})).min(1).max(5),
650684
).describe('Array of {key, value} pairs to update. Max 5 updates per batch.'),

0 commit comments

Comments
 (0)