@@ -263,10 +263,12 @@ private AMap<AString, ACell> listTools() {
263263 return Maps .of (Strings .create ("tools" ), listToolsVector ());
264264 }
265265
266+ /* JSON-RPC protocol result */
266267 private AMap <AString , ACell > protocolResult (AMap <AString , ACell > result ) {
267268 return BASE_RESPONSE .assoc (FIELD_RESULT , result );
268269 }
269270
271+ /* JSON-RPC protocol error */
270272 private AMap <AString , ACell > protocolError (int code , String message ) {
271273 AMap <AString , ACell > error = Maps .of (
272274 FIELD_CODE , CVMLong .create (code ),
@@ -295,9 +297,16 @@ private AMap<AString, ACell> toolCall(ACell paramsCell) throws InterruptedExcept
295297 return protocolError (-32601 , "Unknown tool: " + toolName );
296298 }
297299
298- return tool .handle (RT .ensureMap (params .get (FIELD_ARGUMENTS )));
300+ AMap <AString ,ACell > arguments =RT .ensureMap (params .get (FIELD_ARGUMENTS ));
301+
302+ if (arguments == null ) {
303+ return protocolError (-32602 , toolName +" requires arguments" );
304+ }
305+
306+ return tool .handle (arguments );
299307 }
300308
309+ /* Create a Result from a CVM Result */
301310 private AMap <AString , ACell > toolResult (Result result ) {
302311 AMap <AString , ACell > structured = EMPTY_MAP ;
303312 ACell value = result .getValue ();
@@ -321,6 +330,7 @@ private AMap<AString, ACell> toolSuccess(ACell structuredResult) {
321330 return protocolResult (buildMcpResult (payload , false ));
322331 }
323332
333+ /* Create an error result for a tool call (but protocol valid) */
324334 private AMap <AString , ACell > toolError (String message ) {
325335 AMap <AString , ACell > payload = Maps .of (
326336 Strings .create ("message" ), Strings .create (message )
@@ -356,6 +366,7 @@ private void registerTools() {
356366 registerTool (new ValidateTool ());
357367 registerTool (new CreateAccountTool ());
358368 registerTool (new DescribeAccountTool ());
369+ registerTool (new LookupTool ());
359370 }
360371
361372 private void registerTool (McpTool tool ) {
@@ -378,9 +389,6 @@ private class QueryTool extends McpTool {
378389
379390 @ Override
380391 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) throws InterruptedException {
381- if (arguments == null ) {
382- return protocolError (-32602 , "Query requires arguments" );
383- }
384392 AString sourceCell = RT .ensureString (arguments .get (ARG_SOURCE ));
385393 if (sourceCell == null ) {
386394 return protocolError (-32602 , "Query requires 'source' string" );
@@ -412,9 +420,6 @@ private class TransactTool extends McpTool {
412420
413421 @ Override
414422 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) throws InterruptedException {
415- if (arguments == null ) {
416- return protocolError (-32602 , "Transact requires arguments" );
417- }
418423 AString sourceCell = RT .ensureString (arguments .get (ARG_SOURCE ));
419424 if (sourceCell == null ) {
420425 return protocolError (-32602 , "Transact requires 'source' string" );
@@ -519,9 +524,6 @@ private class HashTool extends McpTool {
519524
520525 @ Override
521526 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) {
522- if (arguments == null ) {
523- return protocolError (-32602 , "Hash tool requires arguments" );
524- }
525527 AString valueCell = RT .ensureString (arguments .get (ARG_VALUE ));
526528 if (valueCell == null ) {
527529 return protocolError (-32602 , "Hash tool requires 'value' string" );
@@ -554,9 +556,6 @@ private class SignTool extends McpTool {
554556
555557 @ Override
556558 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) {
557- if (arguments == null ) {
558- return protocolError (-32602 , "Sign tool requires arguments" );
559- }
560559 AString valueCell = RT .ensureString (arguments .get (ARG_VALUE ));
561560 if (valueCell == null ) {
562561 return toolError ("Sign tool requires a 'value' hex string" );
@@ -597,9 +596,6 @@ private class SubmitTool extends McpTool {
597596
598597 @ Override
599598 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) throws InterruptedException {
600- if (arguments == null ) {
601- return protocolError (-32602 , "Submit requires arguments" );
602- }
603599 AString hashCell = RT .ensureString (arguments .get (Strings .create ("hash" )));
604600 if (hashCell == null ) {
605601 return protocolError (-32602 , "Submit requires 'hash' string" );
@@ -660,9 +656,6 @@ private class EncodeTool extends McpTool {
660656
661657 @ Override
662658 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) {
663- if (arguments == null ) {
664- return protocolError (-32602 , "Encode requires arguments" );
665- }
666659 AString cvxCell = RT .ensureString (arguments .get (Strings .create ("cvx" )));
667660 if (cvxCell == null ) {
668661 return protocolError (-32602 , "Encode requires 'cvx' string" );
@@ -688,9 +681,6 @@ private class DecodeTool extends McpTool {
688681
689682 @ Override
690683 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) {
691- if (arguments == null ) {
692- return protocolError (-32602 , "Decode requires arguments" );
693- }
694684 AString cad3Cell = RT .ensureString (arguments .get (Strings .create ("cad3" )));
695685 if (cad3Cell == null ) {
696686 return protocolError (-32602 , "Decode requires 'cad3' string" );
@@ -758,9 +748,6 @@ private class ValidateTool extends McpTool {
758748
759749 @ Override
760750 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) {
761- if (arguments == null ) {
762- return protocolError (-32602 , "Validate requires arguments" );
763- }
764751 AString publicKeyCell = RT .ensureString (arguments .get (ARG_PUBLIC_KEY ));
765752 if (publicKeyCell == null ) {
766753 return protocolError (-32602 , "Validate requires 'publicKey' string" );
@@ -832,9 +819,6 @@ private class CreateAccountTool extends McpTool {
832819
833820 @ Override
834821 public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) throws InterruptedException {
835- if (arguments == null ) {
836- return protocolError (-32602 , "CreateAccount requires arguments" );
837- }
838822 AString accountKeyCell = RT .ensureString (arguments .get (ARG_ACCOUNT_KEY ));
839823 if (accountKeyCell == null ) {
840824 return protocolError (-32602 , "CreateAccount requires 'accountKey' string" );
@@ -932,6 +916,95 @@ public AMap<AString, ACell> handle(AMap<AString, ACell> arguments) throws Interr
932916 }
933917 }
934918
919+ private class LookupTool extends McpTool {
920+ LookupTool () {
921+ super (McpTool .loadMetadata ("convex/restapi/mcp/tools/lookup.json" ));
922+ }
923+
924+ @ Override
925+ public AMap <AString , ACell > handle (AMap <AString , ACell > arguments ) throws InterruptedException {
926+ try {
927+ // Parse address
928+ ACell addressCell = arguments .get (ARG_ADDRESS );
929+ if (addressCell == null ) {
930+ return toolError ("Lookup requires 'address' parameter, e.g. '#5675' or '@convex.core'" );
931+ }
932+ Address address = Address .parse (addressCell );
933+ if (address == null ) {
934+ return toolError ("Invalid address format" );
935+ }
936+
937+ // Parse symbol
938+ AString symbolCell = RT .ensureString (arguments .get (Strings .SYMBOL ));
939+ if (symbolCell == null ) {
940+ return toolError ("Lookup requires 'symbol' parameter" );
941+ }
942+ Symbol symbol = RT .ensureSymbol (Reader .read (symbolCell ));
943+
944+ // Get account status
945+ AccountStatus accountStatus = server .getPeer ().getConsensusState ().getAccount (address );
946+ if (accountStatus == null ) {
947+ return toolError ("Account not found: " + address );
948+ }
949+
950+ // Check if symbol exists in environment
951+ AHashMap <Symbol , ACell > env = accountStatus .getEnvironment ();
952+ boolean exists = (env != null ) && env .containsKey (symbol );
953+
954+ // Get the value
955+ ACell value = null ;
956+ if (exists && env != null ) {
957+ value = env .get (symbol );
958+
959+ // Apply path if provided
960+ AString pathCell = RT .ensureString (arguments .get (Strings .create ("getPath" )));
961+ if (pathCell != null && value != null ) {
962+ String pathStr = pathCell .toString ();
963+ try {
964+ // Parse the path as a sequence
965+ ACell pathForm = Reader .read (pathStr );
966+ AVector <ACell > pathSeq = RT .ensureVector (pathForm );
967+ if (pathSeq != null ) {
968+ // Convert sequence to array for RT.getIn
969+ long pathLen = pathSeq .count ();
970+ ACell [] pathKeys = new ACell [(int )pathLen ];
971+ for (long i = 0 ; i < pathLen ; i ++) {
972+ pathKeys [(int )i ] = pathSeq .get (i );
973+ }
974+ value = RT .getIn (value , pathKeys );
975+ } else {
976+ // If not a vector, try as a single key
977+ value = RT .getIn (value , pathForm );
978+ }
979+ } catch (Exception e ) {
980+ return toolError ("Failed to parse getPath: " + e .getMessage ());
981+ }
982+ }
983+ }
984+
985+ // Get metadata for the symbol (only if it exists)
986+ AHashMap <ACell , ACell > meta = null ;
987+ if (exists ) {
988+ AHashMap <ACell , ACell > symbolMeta = accountStatus .getMetadata (symbol );
989+ // Return metadata only if it's not empty
990+ if (symbolMeta != null && !symbolMeta .isEmpty ()) {
991+ meta = symbolMeta ;
992+ }
993+ }
994+
995+ // Build result
996+ AMap <AString , ACell > resultMap = Maps .of (
997+ Strings .create ("exists" ), exists ? CVMBool .TRUE : CVMBool .FALSE ,
998+ Strings .create ("value" ), value != null ? value : RT .cvm (null ),
999+ Strings .create ("meta" ), meta != null ? meta : RT .cvm (null )
1000+ );
1001+ return toolSuccess (resultMap );
1002+ } catch (Exception e ) {
1003+ return toolError ("Lookup failed: " + e .getMessage ());
1004+ }
1005+ }
1006+ }
1007+
9351008 private AMap <AString ,ACell > WELL_KNOWN =JSON .parse ("""
9361009 {
9371010 "mcp_version": "1.0",
0 commit comments