Skip to content

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 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.

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. path starts with / and cannot contain .., ? or #. Pass query parameters with query=.
  • Redirects are not followed. The script gets the Location header 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.