Two new settings pages, MCPs and Tools. Super admins define them; super
admins and admins attach them to bots, to workspaces (every bot there),
or to every bot. Bots get them in chat replies and tasks.
- Custom tools: a JSON-schema of arguments plus an HTTP request, a script
on the bot's computer, or a prompt, templated with {{args.x}} and
{{env.KEY}}, secrets redacted from results.
- MCP servers: remote (URL and headers) or stdio commands run on the host
with a setup command and an isolated env, in a per-process pool. Tools
are listed into a cache and connect on first call.
- Add, edit, access and try open in modals.
- Markdown, JSON and command fields use twui's AceEditor (CodeEditor),
its scripts loaded on demand.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
70 lines
2.8 KiB
TypeScript
70 lines
2.8 KiB
TypeScript
import Input from "@moduletrace/twui/form/Input";
|
|
import Select from "@moduletrace/twui/form/Select";
|
|
import CodeEditor from "@/src/components/general/code-editor";
|
|
import { CustomToolHttpMethods, type CustomToolHttpMethod } from "@/src/dict/custom-tool-dict";
|
|
import type { CustomToolHttpConfig } from "@/src/types";
|
|
import parseTemplateLines, { templateLines } from "@/src/utils/parse-template-lines";
|
|
import TimeoutField from "./timeout-field";
|
|
|
|
type Props = {
|
|
id: string;
|
|
config: CustomToolHttpConfig;
|
|
onChange: (config: CustomToolHttpConfig) => void;
|
|
};
|
|
|
|
/**
|
|
* An HTTP tool's request: method, URL, headers and body, all templates.
|
|
*/
|
|
export default function HttpToolFields({ id, config, onChange }: Props) {
|
|
return (
|
|
<>
|
|
<Select
|
|
id={`${id}-method`}
|
|
title="Method"
|
|
label="Method"
|
|
showLabel
|
|
options={CustomToolHttpMethods.map((method) => ({
|
|
value: method,
|
|
title: method,
|
|
default: method == config.method,
|
|
}))}
|
|
changeHandler={(value) => onChange({ ...config, method: String(value) as CustomToolHttpMethod })}
|
|
/>
|
|
<Input
|
|
id={`${id}-url`}
|
|
title="URL"
|
|
label="URL"
|
|
showLabel
|
|
placeholder="https://api.example.com/weather?city={{args.city}}"
|
|
defaultValue={config.url}
|
|
autoComplete="off"
|
|
info="{{args.name}} fills in an argument (URL-encoded), {{env.KEY}} an env."
|
|
onChange={(e) => onChange({ ...config, url: e.target.value })}
|
|
/>
|
|
<CodeEditor
|
|
mode="properties"
|
|
className="h-24"
|
|
title="Headers"
|
|
placeholder={"Authorization: Bearer {{env.WEATHER_KEY}}"}
|
|
content={templateLines({ map: config.headers, separator: ":" })}
|
|
info="One per line. Put secrets in Settings, under Environment: bots never see them."
|
|
onChange={(value) =>
|
|
onChange({ ...config, headers: parseTemplateLines({ text: value, separator: ":" }) })
|
|
}
|
|
/>
|
|
{config.method == "GET" ? null : (
|
|
<CodeEditor
|
|
mode="json"
|
|
className="h-28"
|
|
title="Body"
|
|
placeholder={'{"city": "{{args.city}}"}'}
|
|
content={config.body || ""}
|
|
info="Empty sends the arguments as JSON."
|
|
onChange={(value) => onChange({ ...config, body: value })}
|
|
/>
|
|
)}
|
|
<TimeoutField id={id} timeout_ms={config.timeout_ms} onChange={(timeout_ms) => onChange({ ...config, timeout_ms })} />
|
|
</>
|
|
);
|
|
}
|