Browse your Val Town account from Raycast, and allow Raycast AI to run the vals you choose.
Create a token at val.town/settings/api and paste it into the extension's preferences.
Run Val (⌘R) on any val runs it from Raycast. A val that takes nothing runs immediately; one with inputs gets a form generated from its argument schema.
Configure Val (⌘T) is that config screen: the entrypoint file to call, and the arguments the val takes as a JSON Schema. With Raycast AI available, ⌘G reads the entrypoint's code and drafts the schema. Correct anything it got wrong before saving. Nothing changes until you save, and saving never runs the val.
SKILL.md guides by request.Raycast AI can only see and run vals you have allowed. That is the Allow AI Access checkbox on the config screen, or Enable / Disable AI Agent Access (⇧⌘A) directly. Running a val yourself never needs it. Require Confirm (⇧⌘C) makes Raycast AI stop and ask before each run. It's off by default.
Beyond running, Raycast AI can also read an allowed val's files and blobs, and check its recent runs and failures. All on your request.
You can ask Raycast AI to load a skill of yours: a skills/<name>/SKILL.md file (with frontmatter) in any of your vals, whose instructions name vals to run. It searches all skills regardless of allow list, but if the skill mentions a val that is not allowed, Raycast AI will not be able to run it.
The Arguments box on a val's config screen takes a JSON Schema describing the request body the val's http handler reads. Raycast AI sends an object matching it, and the extension POSTs that object as the body. Leave the box empty and the val is called with GET and no body at all.
Give every property a description. That is what the model reads when it works out what to pass, and it is the difference between a val the model calls correctly and one it guesses at.
One required argument:
{
"type": "object",
"properties": {
"city": { "type": "string", "description": "City to look up, for example Berlin" }
},
"required": ["city"]
}
Optional arguments. Anything left out of required is optional, so say what happens without it:
{
"type": "object",
"properties": {
"query": { "type": "string", "description": "Search term" },
"limit": { "type": "integer", "description": "How many results to return. Defaults to 10." }
},
"required": ["query"]
}
A fixed set of choices. enum keeps the model from inventing a value:
{
"type": "object",
"properties": {
"status": { "type": "string", "enum": ["todo", "doing", "done"], "description": "The new status" }
},
"required": ["status"]
}
Lists and nested objects:
{
"type": "object",
"properties": {
"recipients": {
"type": "array",
"items": { "type": "string" },
"description": "Email addresses to notify"
},
"message": {
"type": "object",
"properties": {
"subject": { "type": "string", "description": "Subject line" },
"body": { "type": "string", "description": "Plain text body" }
},
"required": ["subject", "body"],
"description": "The email to send"
}
},
"required": ["recipients", "message"]
}
The extension does not check the body against the schema before sending it — the schema steers the model, it does not police it. required and enum are instructions to Raycast AI, not validation.
In the val's own blob storage, under the key raycast:tool.json:
{
"version": 1,
"inputSchema": { "type": "object", "properties": {} },
// the file to call. null finds a runnable file automatically
"entrypoint": null,
// Raycast AI specific, null = use val's description
"description": null,
// whether Raycast AI is allowed to run this val
"active": true,
// stop and ask before running
"confirm": false
}
It lives with the val so it survives forking, sharing, and reinstalling the extension. A missing config fails.
Which vals you allowed are stored as raycast:tools.json in your account-global blob storage.