Files
cobble/src/pages/admin/settings/tools/(partials)/http-tool-fields.tsx
T
tbenandClaude Opus 5.5 c6ec7d77a2 Add MCP servers and custom tools for bots
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]>
2026-10-19 15:01:39 +01:00

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 })} />
</>
);
}