- api/mcp_apps.py + api/mcp_app/task_card.html (MCP Apps, io.modelcontextprotocol/ui): apps that support it (Claude, ChatGPT, VS Code…) show Anomalous's own card under begin_task, show_panel, report and run_workflow results: the task's steps, latest thumbnails (inline), the AI's open questions answered with a click (app_answer, plus a chat message so the AI goes on). The card reads through app_task_view; both tools are for the card only (visibility "app"), are never steps and never take the user's news. The card never answers a gate: an OK is only given in Anomalous, so no tool an AI can reach grants one. - api/mcp_prompts.py: prompts/list and prompts/get, four ready-made requests (slash commands): workbench_task, pick_best, fix_missing_models, explain_canvas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
4.0 KiB
Python
86 lines
4.0 KiB
Python
"""What each thing an AI app can do through Anomalous's MCP endpoint is, in one place.
|
|
|
|
Every tool (mcp_tools.py reads, mcp_actions.py acts, mcp_task_tools.py the AI's own) has an
|
|
entry here: its kind (the AI workbench's colour, words and counts; None = not a step of its
|
|
own), the stage of the work it belongs to (the flow over a task: models, combo, prompt,
|
|
canvas, run, pictures, voice, library) and its gate (what it touches or costs, for the user's
|
|
rules on what needs their OK: canvas, gpu, download, network; None = nothing to guard).
|
|
tests/test_action_catalog.py keeps this catalog and the tools in step.
|
|
|
|
The user's own changes in Anomalous (activity log actions) get stages too, so the ones made
|
|
while an AI works sit in the same flow as its steps (ai_tasks.note_activity).
|
|
"""
|
|
|
|
from collections import namedtuple
|
|
|
|
Entry = namedtuple("Entry", "kind stage gate")
|
|
|
|
STAGES = ("model", "combo", "prompt", "canvas", "run", "picture", "voice", "library")
|
|
GATES = ("canvas", "gpu", "download", "network")
|
|
|
|
TOOLS = {
|
|
# reading
|
|
"library_overview": Entry("look", "library", None),
|
|
"search_models": Entry("look", "model", None),
|
|
"get_model": Entry("look", "model", None),
|
|
"scan_report": Entry("look", "library", None),
|
|
"search_images": Entry("look", "picture", None),
|
|
"get_image_info": Entry("look", "picture", None),
|
|
"list_combos": Entry("look", "combo", None),
|
|
"list_workflows": Entry("look", "canvas", None),
|
|
"list_saved_prompts": Entry("look", "prompt", None),
|
|
"search_audio": Entry("look", "voice", None),
|
|
"recent_activity": Entry("look", "library", None),
|
|
"describe_canvas": Entry("look", "canvas", None),
|
|
"check_workflow_models": Entry("look", "model", None),
|
|
"download_status": Entry("look", "model", None),
|
|
"list_voices": Entry("look", "voice", None),
|
|
# acting
|
|
"set_prompt": Entry("canvas", "prompt", "canvas"),
|
|
"set_model": Entry("canvas", "model", "canvas"),
|
|
"add_lora": Entry("canvas", "model", "canvas"),
|
|
"place_combo": Entry("canvas", "combo", "canvas"),
|
|
"fix_workflow_models": Entry("canvas", "model", "canvas"),
|
|
"run_workflow": Entry("run", "run", "gpu"),
|
|
"download_missing_models": Entry("download", "model", "download"),
|
|
"download_model": Entry("download", "model", "download"),
|
|
"scan_models": Entry("scan", "library", "network"),
|
|
"speak": Entry("speech", "voice", "gpu"),
|
|
"open_in_anomalous": Entry("show", "model", None),
|
|
# the AI's own: notes and panels are steps; the rest only steer the task
|
|
"report": Entry("note", None, None),
|
|
"show_panel": Entry("panel", None, None),
|
|
"begin_task": Entry(None, None, None),
|
|
"update_panel": Entry(None, None, None),
|
|
"get_answers": Entry(None, None, None),
|
|
# Anomalous's card in the conversation (mcp_apps.py): its own calls, not the AI's steps
|
|
"app_task_view": Entry(None, None, None),
|
|
"app_answer": Entry(None, None, None),
|
|
}
|
|
|
|
# Activity log actions (activity_log.py ROUTE_ACTIONS, and "canvas") the user makes in Anomalous
|
|
# that belong in the task an AI is working on, with their stage. Downloads, imports, moves, scans
|
|
# and backups are left out: an AI's own calls can cause those too.
|
|
USER_ACTIONS = {
|
|
"canvas": "canvas",
|
|
"model_edit": "library", "model_cover": "library", "model_delete": "library",
|
|
"recipe_save": "canvas", "recipe_edit": "canvas", "recipe_delete": "canvas", "recipe_restore": "canvas",
|
|
"material_save": "prompt", "material_edit": "prompt", "material_delete": "prompt",
|
|
"preset_save": "run", "preset_rename": "run", "preset_delete": "run",
|
|
"image_delete": "picture", "audio_delete": "voice", "audio_save": "voice", "voice_settings": "voice",
|
|
"note_save": "library", "note_delete": "library",
|
|
}
|
|
|
|
|
|
def entry(tool, read_only=True):
|
|
"""The catalog entry of `tool`; one not listed is a read, or an act when it is not read-only."""
|
|
found = TOOLS.get(tool)
|
|
if found:
|
|
return found
|
|
return Entry("look" if read_only else "act", None, None)
|
|
|
|
|
|
def is_step(tool):
|
|
"""Whether a call of `tool` is a step of its own in a task."""
|
|
return entry(tool).kind is not None
|