diff --git a/tools/server/public_simplechat/toolmcp.mjs b/tools/server/public_simplechat/toolmcp.mjs index 3933489923..b10487a6e1 100644 --- a/tools/server/public_simplechat/toolmcp.mjs +++ b/tools/server/public_simplechat/toolmcp.mjs @@ -1,18 +1,14 @@ //@ts-check // ALERT - Simple Stupid flow - Using from a discardable VM is better -// Helpers to handle tools/functions calling related to local/web access, pdf, etal -// which work in sync with the bundled simpleproxy.py server logic. -// Uses the js specific web worker path. +// simple mcpish client to handle tool/function calling provided by bundled simplemcp.py server logic. +// Currently it provides tool calls related to local/web access, pdf, etal // by Humans for All // // -// The simpleproxy.py server is expected to provide the below services -// urlraw - fetch the request url content as is -// htmltext - fetch the requested html content and provide plain text version -// after stripping it of tag blocks like head, script, style, header, footer, nav, ... -// pdftext - fetch the requested pdf and provide the plain text version -// xmlfiltered - fetch the requested xml content and provide a optionally filtered version of same +// The simplemcp.py mcpish server is expected to provide the below on /mcp service path +// tools/list - to get the meta of list of functions supported through simplemcp +// tools/call - to run the specified tool call // @@ -75,140 +71,51 @@ async function proxyserver_get_anyargs(chatid, toolcallid, toolname, objSearchPa /** - * Setup a proxy server dependent tool call - * NOTE: Currently the logic is setup for the bundled simpleproxy.py + * fetch supported tool calls meta data. + * NOTE: Currently the logic is setup for the bundled simplemcp.py * @param {string} tag * @param {string} chatId - * @param {string} tcPath - * @param {string} tcName - * @param {{ [x: string]: any; }} tcsData * @param {mToolsMgr.TCSwitch} tcs */ -async function proxyserver_tc_setup(tag, chatId, tcPath, tcName, tcsData, tcs) { +async function mcpserver_toolslist(tag, chatId, tcs) { tag = `${tag}:${chatId}` - let chat = gMe.multiChat.simpleChats[chatId] - await fetch(`${chat.cfg.tools.proxyUrl}/aum?url=${tcPath}.jambudweepe.akashaganga.multiverse.987654321123456789`).then(resp=>{ - if (resp.statusText != 'bharatavarshe') { - console.log(`WARN:ToolWeb:${tag}:Dont forget to run the bundled local.tools/simpleproxy.py to enable me`) + try { + let chat = gMe.multiChat.simpleChats[chatId] + + let id = new Date().getTime() + let ibody = { + jsonrpc: "2.0", + id: id, + method: "tools/list" + } + let headers = new Headers(); + headers.append("Content-Type", "application/json") + let resp = await fetch(`${chat.cfg.tools.proxyUrl}/mcp`, { + method: "POST", + headers: headers, + body: JSON.stringify(ibody), + }); + if (resp.status != 200) { + console.log`WARN:${tag}:ToolsList:MCP server says:${resp.status}:${resp.statusText}` return - } else { - console.log(`INFO:ToolWeb:${tag}:Enabling...`) } - tcs[tcName] = tcsData; - }).catch(err=>console.log(`WARN:ToolWeb:${tag}:ProxyServer missing?:${err}\nDont forget to run the bundled local.tools/simpleproxy.py`)) -} - - -// -// Fetch Url Raw -// - - -let fetchurlraw_meta = { - "type": "function", - "function": { - "name": "fetch_url_raw", - "description": "Fetch contents of the requested url (local file path / web based) through a proxy server and return the got content as is, in few seconds. Mainly useful for getting textual non binary contents", - "parameters": { - "type": "object", - "properties": { - "url":{ - "type":"string", - "description":"url of the local file / web content to fetch" - } - }, - "required": ["url"] + let obody = await resp.json() + if ((obody.results) && (obody.results.tools)) { + for(const tcmeta of obody.results.tools) { + if (!tcmeta.function) { + continue + } + console.log`INFO:${tag}:ToolsList:${tcmeta.function.name}` + tcs[tcmeta.function.name] = { + "handler": mcpserver_toolcall, + "meta": tcmeta, + "result": "" + } } } + } catch (err) { + console.log(`ERRR:${tag}:ToolsList:MCP server hs failed:${err}\nDont forget to run bundled local.tools/simplemcp.py`) } - - -/** - * Implementation of the fetch url raw logic. - * Expects a simple minded proxy server to be running locally - * * listening on a configured port - * * expecting http requests - * * with a query token named url wrt the path urlraw - * which gives the actual url to fetch - * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful - * @param {string} chatid - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function fetchurlraw_run(chatid, toolcallid, toolname, obj) { - // maybe filter out any key other than 'url' in obj - return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'urlraw'); -} - - -/** - * Setup fetch_url_raw for tool calling - * NOTE: Currently the logic is setup for the bundled simpleproxy.py - * @param {mToolsMgr.TCSwitch} tcs - * @param {string} chatId - */ -async function fetchurlraw_setup(tcs, chatId) { - return proxyserver_tc_setup('FetchUrlRaw', chatId, 'urlraw', 'fetch_url_raw', { - "handler": fetchurlraw_run, - "meta": fetchurlraw_meta, - "result": "" - }, tcs); -} - - -// -// Fetch html Text -// - - -let fetchhtmltext_meta = { - "type": "function", - "function": { - "name": "fetch_html_text", - "description": "Fetch html content from given url through a proxy server and return its text content after stripping away the html tags as well as head, script, style, header, footer, nav blocks, in few seconds", - "parameters": { - "type": "object", - "properties": { - "url":{ - "type":"string", - "description":"url of the html page that needs to be fetched and inturn unwanted stuff stripped from its contents to some extent" - } - }, - "required": ["url"] - } - } - } - - -/** - * Implementation of the fetch html text logic. - * Expects the simple minded simpleproxy server to be running locally, - * providing service for htmltext path. - * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful - * @param {string} chatid - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function fetchhtmltext_run(chatid, toolcallid, toolname, obj) { - // maybe filter out any key other than 'url' in obj - return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'htmltext'); -} - - -/** - * Setup fetch_html_text for tool calling - * NOTE: Currently the logic is setup for the bundled simpleproxy.py - * @param {mToolsMgr.TCSwitch} tcs - * @param {string} chatId - */ -async function fetchhtmltext_setup(tcs, chatId) { - return proxyserver_tc_setup('FetchHtmlText', chatId, 'htmltext', 'fetch_html_text', { - "handler": fetchhtmltext_run, - "meta": fetchhtmltext_meta, - "result": "" - }, tcs); } @@ -272,52 +179,6 @@ async function searchwebtext_setup(tcs, chatId) { } -// -// FetchPdfText -// - - -let fetchpdftext_meta = { - "type": "function", - "function": { - "name": "fetch_pdf_as_text", - "description": "Fetch pdf from requested local file path / web url through a proxy server and return its text content after converting pdf to text, in few seconds. One is allowed to get a part of the pdf by specifying the starting and ending page numbers", - "parameters": { - "type": "object", - "properties": { - "url":{ - "type":"string", - "description":"local file path (file://) / web (http/https) based url of the pdf that will be got and inturn converted to text" - }, - "startPageNumber":{ - "type":"integer", - "description":"Specify the starting page number within the pdf, this is optional. If not specified set to first page." - }, - "endPageNumber":{ - "type":"integer", - "description":"Specify the ending page number within the pdf, this is optional. If not specified set to the last page." - }, - }, - "required": ["url"] - } - } - } - - -/** - * Implementation of the fetch pdf as text logic. - * Expects a simple minded proxy server to be running locally - * * listening on a configured port - * * expecting http requests - * * with a query token named url wrt pdftext path, - * which gives the actual url to fetch - * * gets the requested pdf and converts to text, before returning same. - * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful - * @param {string} chatid - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ function fetchpdftext_run(chatid, toolcallid, toolname, obj) { return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'pdftext'); } @@ -338,86 +199,14 @@ async function fetchpdftext_setup(tcs, chatId) { } -// -// Fetch XML Filtered -// - - -let gRSSTagDropsDefault = [ - "^rss:channel:item:guid:.*", - "^rss:channel:item:link:.*", - "^rss:channel:item:description:.*", - ".*:image:.*", - ".*:enclosure:.*" -]; - -let fetchxmlfiltered_meta = { - "type": "function", - "function": { - "name": "fetch_xml_filtered", - "description": "Fetch requested xml url through a proxy server that can optionally filter out unwanted tags and their contents. Will take few seconds", - "parameters": { - "type": "object", - "properties": { - "url":{ - "type":"string", - "description":"url of the xml file that will be fetched" - }, - "tagDropREs":{ - "type":"string", - "description":`Optionally specify a json stringified list of xml tag heirarchies to drop. - For each tag that needs to be dropped, one needs to specify regular expression of the heirarchy of tags involved, - where the tag names are always mentioned in lower case along with a : as suffix. - For example for rss feeds one could use ${JSON.stringify(gRSSTagDropsDefault)} and so...` - } - }, - "required": ["url"] - } - } - } - - -/** - * Implementation of the fetch xml filtered logic. - * Expects simpleproxy to be running at specified url and providing xmltext service - * ALERT: Accesses a seperate/external web proxy/caching server, be aware and careful - * @param {string} chatid - * @param {string} toolcallid - * @param {string} toolname - * @param {any} obj - */ -function fetchxmlfiltered_run(chatid, toolcallid, toolname, obj) { - let tagDropREs = obj.tagDropREs - if (tagDropREs == undefined) { - tagDropREs = JSON.stringify([]) // JSON.stringify(gRSSTagDropsDefault) - } - let headers = { 'xmlfiltered-tagdrop-res': tagDropREs } - return proxyserver_get_anyargs(chatid, toolcallid, toolname, obj, 'xmlfiltered', headers); -} - - -/** - * Setup fetch_xml_filtered for tool calling - * NOTE: Currently the logic is setup for the bundled simpleproxy.py - * @param {mToolsMgr.TCSwitch} tcs - * @param {string} chatId - */ -async function fetchxmlfiltered_setup(tcs, chatId) { - return proxyserver_tc_setup('FetchXmlFiltered', chatId, 'xmlfiltered', 'fetch_xml_filtered', { - "handler": fetchxmlfiltered_run, - "meta": fetchxmlfiltered_meta, - "result": "" - }, tcs); -} - - // // Entry point // /** - * Used to get hold of the web worker to use for running tool/function call related code. + * Used to get hold of the global Me instance, and through it + * the toolsManager and chat settings ... * @param {mChatMagic.Me} me */ export async function init(me) { @@ -429,7 +218,7 @@ export async function init(me) { * Return the tool call switch with supported / enabled / available tool calls * Allows to verify / setup tool calls, which need to cross check things at runtime * before getting allowed, like maybe bcas they depend on a config wrt specified - * chat session. + * chat session or handshake with mcpish server in this case and so... * @param {string} chatId */ export async function setup(chatId) { @@ -437,10 +226,6 @@ export async function setup(chatId) { * @type {mToolsMgr.TCSwitch} tcs */ let tc_switch = {} - await fetchurlraw_setup(tc_switch, chatId) - await fetchhtmltext_setup(tc_switch, chatId) - await searchwebtext_setup(tc_switch, chatId) - await fetchpdftext_setup(tc_switch, chatId) - await fetchxmlfiltered_setup(tc_switch, chatId) + await mcpserver_toolslist("ToolMCP", chatId, tc_switch) return tc_switch }