173 lines
5.5 KiB
JavaScript
173 lines
5.5 KiB
JavaScript
//@ts-check
|
|
// ALERT - Simple minded flow - Using from a discardable VM is better.
|
|
// 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 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
|
|
//
|
|
|
|
|
|
import * as mChatMagic from './simplechat.js'
|
|
import * as mToolsMgr from './tools.mjs'
|
|
|
|
|
|
/**
|
|
* @type {mChatMagic.Me}
|
|
*/
|
|
let gMe = /** @type{mChatMagic.Me} */(/** @type {unknown} */(null));
|
|
|
|
|
|
/**
|
|
* For now hash the shared secret with the year.
|
|
* @param {mChatMagic.SimpleChat} chat
|
|
*/
|
|
async function bearer_transform(chat) {
|
|
let data = `${new Date().getUTCFullYear()}${chat.cfg.tools.mcpServerAuth}`
|
|
const ab = await crypto.subtle.digest('sha-256', new TextEncoder().encode(data));
|
|
return Array.from(new Uint8Array(ab)).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
|
|
|
|
/**
|
|
* Implements tool call execution through a mcpish server. Initial go.
|
|
* NOTE: Currently only uses textual contents in the result.
|
|
* NOTE: Currently the logic is setup to work with bundled simplemcp.py
|
|
* ALERT: Accesses a seperate/external mcpish server, be aware and careful
|
|
* @param {string} chatid
|
|
* @param {string} toolcallid
|
|
* @param {string} toolname
|
|
* @param {any} obj
|
|
*/
|
|
async function mcpserver_toolcall(chatid, toolcallid, toolname, obj) {
|
|
let chat = gMe.multiChat.simpleChats[chatid]
|
|
if (gMe.toolsMgr.workers.js.onmessage == null) {
|
|
return
|
|
}
|
|
try {
|
|
let newUrl = `${chat.cfg.tools.mcpServerUrl}`
|
|
let headers = new Headers();
|
|
let btoken = await bearer_transform(chat)
|
|
headers.append('Authorization', `Bearer ${btoken}`)
|
|
headers.append("Content-Type", "application/json")
|
|
let ibody = {
|
|
jsonrpc: "2.0",
|
|
id: toolcallid,
|
|
method: "tools/call",
|
|
params: {
|
|
name: toolname,
|
|
arguments: obj
|
|
}
|
|
}
|
|
let resp = await fetch(newUrl, {
|
|
method: "POST",
|
|
headers: headers,
|
|
body: JSON.stringify(ibody),
|
|
});
|
|
if (!resp.ok) {
|
|
throw new Error(`${resp.status}:${resp.statusText}`);
|
|
}
|
|
let obody = await resp.json()
|
|
let textResult = ""
|
|
if ((obody.result) && (obody.result.content)) {
|
|
for(const tcr of obody.result.content) {
|
|
if (!tcr.text) {
|
|
continue
|
|
}
|
|
textResult += `\n\n${tcr.text}`
|
|
}
|
|
}
|
|
gMe.toolsMgr.workers_postmessage_for_main(gMe.toolsMgr.workers.js, chatid, toolcallid, toolname, textResult);
|
|
} catch (err) {
|
|
gMe.toolsMgr.workers_postmessage_for_main(gMe.toolsMgr.workers.js, chatid, toolcallid, toolname, `Error:${err}`);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetch supported tool calls meta data from a mcpish server.
|
|
* NOTE: Currently the logic is setup to work with bundled simplemcp.py
|
|
* ALERT: Accesses a seperate/external mcpish server, be aware and careful
|
|
* @param {string} tag
|
|
* @param {string} chatId
|
|
* @param {mToolsMgr.TCSwitch} tcs
|
|
*/
|
|
async function mcpserver_toolslist(tag, chatId, tcs) {
|
|
tag = `${tag}:${chatId}`
|
|
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();
|
|
let btoken = await bearer_transform(chat)
|
|
headers.append('Authorization', `Bearer ${btoken}`)
|
|
headers.append("Content-Type", "application/json")
|
|
let resp = await fetch(`${chat.cfg.tools.mcpServerUrl}`, {
|
|
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
|
|
}
|
|
let obody = await resp.json()
|
|
if ((obody.result) && (obody.result.tools)) {
|
|
for(const tcmeta of obody.result.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`)
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Entry point
|
|
//
|
|
|
|
|
|
/**
|
|
* 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) {
|
|
gMe = 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 or handshake with mcpish server in this case and so...
|
|
* @param {string} chatId
|
|
*/
|
|
export async function setup(chatId) {
|
|
/**
|
|
* @type {mToolsMgr.TCSwitch}
|
|
*/
|
|
let tc_switch = {}
|
|
await mcpserver_toolslist("ToolMCP", chatId, tc_switch)
|
|
return tc_switch
|
|
}
|