@@ -2,7 +2,8 @@ import { z } from 'zod';
22import { hexToString } from 'viem' ;
33import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' ;
44import { 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' ;
67import { TrustRegistryModuleAbi , ReputationModuleAbi } from '@azeth/common/abis' ;
78import { createClient , resolveChain , resolveViemChain , validateAddress } from '../utils/client.js' ;
89import { 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 ( / ^ 0 x [ 0 - 9 a - f A - 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