@@ -374,7 +374,8 @@ <h1 class="serif text-4xl md:text-5xl mb-2 gradient-text">
374374 geminiKey : localStorage . getItem ( "geminiApiKey" ) || "" ,
375375 busy : false ,
376376 conversationHistory : [ ] ,
377- settingsExpanded : false
377+ settingsExpanded : false ,
378+ availableTools : [ ]
378379 } ;
379380
380381 elements . endpoint . value = state . endpoint ;
@@ -469,6 +470,40 @@ <h1 class="serif text-4xl md:text-5xl mb-2 gradient-text">
469470 return card ;
470471 } ;
471472
473+ const createOrganizationCard = ( org ) => {
474+ const card = document . createElement ( "div" ) ;
475+ card . className = "dataset-card rounded-xl p-5 cursor-pointer" ;
476+ card . style . cssText = "background: var(--bg-secondary);" ;
477+
478+ const title = document . createElement ( "h3" ) ;
479+ title . className = "text-base font-semibold mb-2" ;
480+ title . style . color = "var(--text-primary)" ;
481+ title . textContent = org . title || org . display_name || org . name || "Organization" ;
482+
483+ const desc = document . createElement ( "p" ) ;
484+ desc . className = "text-sm mb-3 line-clamp-2" ;
485+ desc . style . color = "var(--text-secondary)" ;
486+ desc . textContent = org . description || "No description available." ;
487+
488+ const meta = document . createElement ( "div" ) ;
489+ meta . className = "flex items-center gap-4 text-xs" ;
490+ meta . style . color = "var(--text-muted)" ;
491+
492+ const packageCount = org . package_count || org . packages ?. length || 0 ;
493+
494+ meta . innerHTML = `
495+ <span class="flex items-center gap-1">
496+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
497+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
498+ </svg>
499+ ${ packageCount . toLocaleString ( ) } dataset${ packageCount !== 1 ? 's' : '' }
500+ </span>
501+ ` ;
502+
503+ card . append ( title , desc , meta ) ;
504+ return card ;
505+ } ;
506+
472507 const extractStructured = ( result ) => {
473508 if ( ! result ) return null ;
474509 if ( result . structuredContent ) return result . structuredContent ;
@@ -519,10 +554,12 @@ <h1 class="serif text-4xl md:text-5xl mb-2 gradient-text">
519554 const checkMcp = async ( ) => {
520555 setStatus ( "checking" , "Checking..." ) ;
521556 try {
522- await callMcp ( "tools/list" , { } ) ;
523- setStatus ( "ok" , "Online" ) ;
557+ const result = await callMcp ( "tools/list" , { } ) ;
558+ state . availableTools = result . tools || [ ] ;
559+ setStatus ( "ok" , `Online (${ state . availableTools . length } tools)` ) ;
524560 } catch ( error ) {
525561 setStatus ( "error" , "Offline" ) ;
562+ state . availableTools = [ ] ;
526563 }
527564 } ;
528565
@@ -549,14 +586,40 @@ <h1 class="serif text-4xl md:text-5xl mb-2 gradient-text">
549586 if ( ! state . geminiKey ) {
550587 throw new Error ( "Missing Gemini API key" ) ;
551588 }
552- const systemPrompt = "Convert requests to Solr queries for CKAN. Respond only with JSON: {\"q\":\"...\"}. Use conversation context to refine searches." ;
589+
590+ // Build tool list for prompt
591+ const toolList = state . availableTools . map ( t =>
592+ `- ${ t . name } : ${ t . description ?. substring ( 0 , 100 ) || 'No description' } `
593+ ) . join ( '\n' ) ;
594+
595+ const systemPrompt = `You are an assistant for CKAN open data queries.
596+ Available MCP tools:
597+ ${ toolList }
598+
599+ Choose the most appropriate tool and generate its arguments.
600+ Respond ONLY with JSON in this format:
601+ {
602+ "tool": "tool_name",
603+ "arguments": {
604+ "server_url": "CKAN_SERVER",
605+ ...other params based on tool schema
606+ }
607+ }
608+
609+ For dataset searches use ckan_package_search or ckan_find_relevant_datasets.
610+ For organization queries use ckan_organization_list, ckan_organization_show, or ckan_organization_search.
611+ For tags use ckan_tag_list.
612+ Always include server_url parameter.
613+ Use conversation context to refine queries.` ;
614+
553615 const contents = [
554616 ...history ,
555617 {
556618 role : "user" ,
557- parts : [ { text : `${ systemPrompt } \nRequest : ${ userQuery } ` } ]
619+ parts : [ { text : `${ systemPrompt } \n\nCKAN Server: ${ state . ckanServer } \nUser request : ${ userQuery } ` } ]
558620 }
559621 ] ;
622+
560623 const response = await fetch (
561624 `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${ state . geminiKey } ` ,
562625 {
@@ -567,42 +630,72 @@ <h1 class="serif text-4xl md:text-5xl mb-2 gradient-text">
567630 } ,
568631 body : JSON . stringify ( {
569632 contents : contents ,
570- generationConfig : { temperature : 0.2 , maxOutputTokens : 200 }
633+ generationConfig : { temperature : 0.2 , maxOutputTokens : 500 }
571634 } )
572635 }
573636 ) ;
637+
574638 if ( ! response . ok ) {
575639 throw new Error ( `Gemini HTTP ${ response . status } ` ) ;
576640 }
641+
577642 const data = await response . json ( ) ;
578643 const text = data . candidates ?. [ 0 ] ?. content ?. parts ?. [ 0 ] ?. text || "" ;
579644 const jsonStart = text . indexOf ( "{" ) ;
580645 const jsonEnd = text . lastIndexOf ( "}" ) ;
581646 if ( jsonStart === - 1 || jsonEnd === - 1 ) {
582647 throw new Error ( "Invalid Gemini response" ) ;
583648 }
649+
584650 const parsed = JSON . parse ( text . slice ( jsonStart , jsonEnd + 1 ) ) ;
585- if ( ! parsed . q ) {
586- throw new Error ( "Gemini did not return q " ) ;
651+ if ( ! parsed . tool || ! parsed . arguments ) {
652+ throw new Error ( "Gemini did not return tool and arguments " ) ;
587653 }
588- return parsed . q ;
654+
655+ return parsed ;
589656 } ;
590657
591658 const runSearch = async ( query ) => {
592- const normalizedQuery = normalizeQuery ( query ) ;
593- const solrQuery = await callGemini ( query , state . conversationHistory ) . catch ( ( ) => normalizedQuery ) ;
594- setToolStatus ( "⚡ Searching datasets..." ) ;
659+ // Try Gemini to select tool and arguments
660+ let toolCall ;
661+ try {
662+ toolCall = await callGemini ( query , state . conversationHistory ) ;
663+ } catch ( error ) {
664+ // Fallback: use ckan_package_search with normalized query
665+ const normalizedQuery = normalizeQuery ( query ) ;
666+ toolCall = {
667+ tool : "ckan_package_search" ,
668+ arguments : {
669+ server_url : state . ckanServer ,
670+ q : normalizedQuery ,
671+ rows : 5 ,
672+ response_format : "json"
673+ }
674+ } ;
675+ }
676+
677+ // Ensure server_url is set
678+ if ( ! toolCall . arguments . server_url ) {
679+ toolCall . arguments . server_url = state . ckanServer ;
680+ }
681+
682+ // Ensure response_format is json for programmatic parsing
683+ if ( ! toolCall . arguments . response_format ) {
684+ toolCall . arguments . response_format = "json" ;
685+ }
686+
687+ setToolStatus ( `⚡ Using ${ toolCall . tool } ...` ) ;
595688 const result = await callMcp ( "tools/call" , {
596- name : "ckan_package_search" ,
597- arguments : {
598- server_url : state . ckanServer ,
599- q : solrQuery ,
600- rows : 5 ,
601- response_format : "json"
602- }
689+ name : toolCall . tool ,
690+ arguments : toolCall . arguments
603691 } ) ;
604692 setToolStatus ( "" ) ;
605- return extractStructured ( result ) ;
693+
694+ const data = extractStructured ( result ) ;
695+ return {
696+ tool : toolCall . tool ,
697+ data : data
698+ } ;
606699 } ;
607700
608701 const handleSend = async ( ) => {
@@ -622,8 +715,10 @@ <h1 class="serif text-4xl md:text-5xl mb-2 gradient-text">
622715 } ) ;
623716
624717 try {
625- const data = await runSearch ( query ) ;
626- if ( ! data || ! Array . isArray ( data . results ) ) {
718+ const response = await runSearch ( query ) ;
719+ const { tool, data } = response ;
720+
721+ if ( ! data ) {
627722 addMessage ( "assistant" , "No results found." ) ;
628723 state . conversationHistory . push ( {
629724 role : "model" ,
@@ -635,19 +730,40 @@ <h1 class="serif text-4xl md:text-5xl mb-2 gradient-text">
635730 const container = document . createElement ( "div" ) ;
636731 container . className = "flex flex-col gap-3" ;
637732
638- const summary = document . createElement ( "p" ) ;
639- summary . className = "text-sm font-medium mb-1" ;
640- summary . style . color = "var(--primary)" ;
641- summary . textContent = `📊 Found ${ data . count ?? data . results . length } dataset${ ( data . count ?? data . results . length ) !== 1 ? 's' : '' } ` ;
642- container . appendChild ( summary ) ;
733+ // Handle different tool types
734+ if ( tool . includes ( "organization" ) ) {
735+ // Organization tools
736+ const items = data . results || data || [ ] ;
737+ const summary = document . createElement ( "p" ) ;
738+ summary . className = "text-sm font-medium mb-1" ;
739+ summary . style . color = "var(--primary)" ;
740+ summary . textContent = `🏢 Found ${ data . count ?? items . length } organization${ ( data . count ?? items . length ) !== 1 ? 's' : '' } ` ;
741+ container . appendChild ( summary ) ;
643742
644- data . results . forEach ( ( dataset ) => container . appendChild ( createDatasetCard ( dataset ) ) ) ;
645- addMessage ( "assistant" , container ) ;
743+ items . slice ( 0 , 10 ) . forEach ( ( org ) => container . appendChild ( createOrganizationCard ( org ) ) ) ;
744+ addMessage ( "assistant" , container ) ;
646745
647- state . conversationHistory . push ( {
648- role : "model" ,
649- parts : [ { text : `Found ${ data . count ?? data . results . length } datasets for "${ query } "` } ]
650- } ) ;
746+ state . conversationHistory . push ( {
747+ role : "model" ,
748+ parts : [ { text : `Found ${ data . count ?? items . length } organizations for "${ query } "` } ]
749+ } ) ;
750+ } else {
751+ // Dataset tools (package_search, find_relevant_datasets, etc.)
752+ const items = data . results || data || [ ] ;
753+ const summary = document . createElement ( "p" ) ;
754+ summary . className = "text-sm font-medium mb-1" ;
755+ summary . style . color = "var(--primary)" ;
756+ summary . textContent = `📊 Found ${ data . count ?? items . length } dataset${ ( data . count ?? items . length ) !== 1 ? 's' : '' } ` ;
757+ container . appendChild ( summary ) ;
758+
759+ items . slice ( 0 , 10 ) . forEach ( ( dataset ) => container . appendChild ( createDatasetCard ( dataset ) ) ) ;
760+ addMessage ( "assistant" , container ) ;
761+
762+ state . conversationHistory . push ( {
763+ role : "model" ,
764+ parts : [ { text : `Found ${ data . count ?? items . length } datasets for "${ query } "` } ]
765+ } ) ;
766+ }
651767 } catch ( error ) {
652768 addMessage ( "assistant" , `❌ Error: ${ error . message || "Could not contact MCP" } ` ) ;
653769 state . conversationHistory . push ( {
0 commit comments