Creating Custom Functions
This guide covers the ways to create a function from Resources → Functions. For MCP servers, see Connecting an MCP Server.
Before you start, search the Function Library for an existing function that already does the job, and check the Marketplace for a ready-made one (see Marketplace). Editing an existing function is usually better than adding a near-duplicate.
You need the Editor role or higher in the project to create, save or test functions.
Open the chooser
- Go to Resources → Functions.
- Click + at the top of the Function Library.
- The New function chooser lists the ways to build a tool, safest first:
| Card | Use it when |
|---|---|
| Wrap an integration operation (recommended) | The call you need is an operation declared on an integration. |
| Start from a template | You want a platform template. Currently this opens a blank Local Python form; install templates from the Marketplace instead. |
| Local Python | You need custom logic. |
| MCP server | You want to run an MCP server as a subprocess. |
| MCP over HTTP | You want to connect a remote MCP endpoint. |
| Import a Skill | You want to write a SKILL.md skill by hand. To upload an existing skill, use Import Skill instead (below). |
Option 1: Wrap an integration operation
This generates a brokered function with no credential in it. The integration must already declare at least one operation. See Declaring Integration Operations.
- Choose Wrap an integration operation.
- Pick the Integration. Its allowed URLs are shown under the field.
- In the Operation table, filter and click the operation to wrap. Write methods (anything other than GET, HEAD or OPTIONS) are highlighted.
- Check the Tool name (what the model calls it, defaulting to
<integration>_<operation>) and the Description for the model. - Review the Generated contract: the parameters taken from the operation's path, the declared operation, how the secret is handled ("resolved by the broker, never in the worker"), approval (inherit) and the proposed risk (medium · external-read for reads, high · external-write for writes). A preview of the generated
capabilities.call(...)is shown. - Click Create function.
The new function is linked to the integration and has the operation already granted under Allowed Operations.
Option 2: Write Local Python
- Choose Local Python.
- Fill in Function Name and Description. Both are required.
- Write the code in Code. The code must define a function whose name matches Function Name exactly.
- Optionally set Approval Override and Tags (comma-separated).
- Click Link Integration to attach the integrations the function needs, then choose the operations it may call under Allowed Operations.
- Tick Internal only if the function calls FYAI's own API (see below).
- Click Create.
After creation, the Definition tab shows the code beside a live console. See Testing Functions.
How Local Python runs
- Each call runs in a fresh, single-use process in an isolated sandbox worker.
- Both
defandasync deffunctions are supported. - The parameter schema the model sees is read from the function signature without running your code. Use
Annotated[type, "description"]to give each parameter a type and description.Optional[...]andT | Noneare understood. Unannotated parameters are advertised as strings. Parameters with a default are optional. - A parameter named
jwt_claimsis filled in by the platform with the caller's context. Do not ask the model for it. - Return something JSON-serialisable. Anything else is converted to a string.
Calling an external API: use the broker
Call a declared operation instead of handling a secret:
from typing import Annotated, Any, Dict, Optional
from app.utils import capabilities
def github_list_open_pulls(
owner: Annotated[str, "Repository owner, e.g. 'octo-org'"],
repo: Annotated[str, "Repository name"],
jwt_claims: Annotated[Optional[Dict[str, Any]], "System injected OIDC credentials."] = None,
) -> Any:
"""List open pull requests for a repository."""
return capabilities.call(
"list_open_pull_requests",
integration=42,
owner=owner,
repo=repo,
)
operationmust be declared on the integration, and listed in the function's Allowed Operations. A call to an operation that is not granted is refused at runtime.integrationis the numeric integration ID. The integration must be linked to the function.- Keyword arguments are the operation's declared path, query and body parameters. Anything else is dropped.
capabilities.callraisesCapabilityCallErrorwhen a call is refused or fails, andCapabilityUnavailableErrorwhen the run has no capability (for example, outside a run).
Calling FYAI's own API: Internal
Tick Internal when the function authenticates to the FYAI platform itself as the caller (for example, a tool that lists your agents). The platform then passes the caller's identity to the function through jwt_claims. Internal does not grant access to third-party integrations: brokered calls are still limited to linked integrations and allowed operations.
What the platform checks when you save
Local Python runs in a sandbox, so code that imports other app.* modules, queries the platform database, reads platform environment variables or shells out to cluster tooling cannot work, however it behaves on your machine. The platform reads the code when you save it and reports each problem with its line and what to use instead (an agent.* dataset or platform_api.call('agentapi', 'dataset_query', ...) for platform data, IntegrationWrapper or an integration operation for anything with a credential). Fix every error finding; where the platform enforces the check, a function with one is not saved. The rules are listed in What a Local Python function can and cannot do.
What the Definition tab checks
Under the editor, a strip of chips summarises what the platform detected in the code on the last save:
| Chip | Meaning |
|---|---|
N parameters | How many arguments the model can fill in. |
N secret-shaped | Parameters whose names look like credentials (for example API_TOKEN). A prompt could put a credential in them. |
| brokered / direct egress / MCP / no network / unreadable | How the code reaches the network, with detected hosts in the tooltip. |
| env fallback | The code falls back to an environment variable for a credential. |
N declared op / N undeclared op / no declared ops | Operations called by the code compared with Allowed Operations. An undeclared operation will be refused at runtime. |
A Detected in this body panel points at the lines that triggered a warning. These checks are heuristics: they never block a save.
Option 3: Import a skill
Skills package instructions (and optionally scripts and references) that the model activates when relevant.
- On the Function Library, click the Import Skill button at the top of the tree.
- Either Choose archive (.zip / .tar.gz) or enter a public Git repository URL.
- Click Import.
The format is detected automatically. Supported sources are Agent Skills (SKILL.md), Cursor rules and GitHub Copilot agent, prompt or instructions files. Tools a skill declares in its frontmatter (allowed-tools) are recorded as requests only and are not granted automatically.
To write a skill by hand, choose Import a Skill in the chooser and paste the Markdown into SKILL.md. Skill names must be lowercase letters, digits and single hyphens (at most 64 characters), and descriptions at most 1,024 characters. The description is shown to the model up front; the body is added to the conversation when the model activates the skill.
Copy, publish and delete
- Copy Function duplicates the function, including its linked integrations and allowed operations, under a new name.
- Publish Template (platform administrators only) publishes the current editor values as a platform template.
- Delete removes the function after confirmation.
Next steps
- Assign the function to an agent: Adding Tools to Agents.
- Decide whether calls need approval: Using Approval Gates.
- Every field: Function Settings.