Scripts
Each tool of a custom connector is a script: a small function in Starlark, a dialect of Python. Your tunnel runs it when an agent calls the tool.
@tool.script(intent="read")def ticket_text(ticket_id: Annotated[str, "The ticket's id."]): """The readable text of a ticket.""" answer = request("GET", "/api/tickets/" + ticket_id) if answer.status == 404: fail("no such ticket") return {"text": dig(ok(answer), "body")}- The function name is the tool name.
- The docstring is the description the agent reads.
- The arguments, with
Annotated[type, "description"], become the tool’s input schema. - The body is the script.
In a .star file, the same tool is script_tool("ticket_text", "The readable text of a ticket.", ticket_text, intent = "read", params = [param("ticket_id", required = True)]).
Intent
Section titled “Intent”intent says what the tool does, and decides which permission applies:
intent |
Meaning |
|---|---|
"read" |
Only looks things up. Its requests are held to GET and HEAD. |
"write" (the default) |
Creates or changes something. |
"delete" |
Deletes something. |
A tool’s requests may do no more than its intent: a read tool cannot write, whatever its script tries. ask=True marks a tool that should ask first.
What a script can use
Section titled “What a script can use”| Name | |
|---|---|
request(method, path, query=None, body=None, headers=None) |
One call to your app. path starts with /. A dict or list body is sent as JSON, a string as it is. |
ok(response) |
The decoded JSON of a 2xx answer. Otherwise the call ends with one plain sentence built from your errors hints. |
fail(message) |
End the call with this message, which the agent reads. |
attempt(fn, ...) |
Call fn and get {ok, value, error} instead of ending the call when it fails. |
json, re, text |
Encode and decode JSON, regular expressions, text helpers. |
html, xml |
Parse a page or an XML answer and select elements. |
A response has status, headers (lower-case names), text and json.
Starlark is a small Python: functions, for loops, comprehensions, dicts, lists, % and .format() are there. There are no classes, try, while, f-strings or imports.
- A script can only reach its own app.
pathstarts with/and cannot contain..,?or#. Pass query parameters withquery=. - Redirects are not followed. The script gets the
Locationheader back. - The app’s address must be on the tunnel’s allow list.
- The script never sees the login, and cannot set the login’s header. Flowplane adds it to each request.
- One call may make up to 10 requests to the app and run for up to 20 seconds. Past a limit, the call ends with an error that says so.
- Socket tools (IMAP, SMTP) do not run in the tunnel yet.