diff --git a/.changeset/skill-document-action-tools.md b/.changeset/skill-document-action-tools.md new file mode 100644 index 0000000000..f2338ce197 --- /dev/null +++ b/.changeset/skill-document-action-tools.md @@ -0,0 +1,17 @@ +--- +'@objectstack/mcp': patch +--- + +fix(mcp): the generated SKILL.md now documents the business-action tools + +`renderSkillMarkdown()` listed only the 7 object-CRUD tools; the MCP surface +exposes 9 — `list_actions` / `run_action` (business actions) were missing, so +agents installing the skill never learned they can run approvals, conversions, +or flow triggers directly. The skill now covers the full native tool surface +and teaches action preference: when `list_actions` offers a matching action, +call it instead of hand-editing the records it would have touched (actions +carry the app's validation and side effects), confirming destructive or +confirmation-flagged actions with the user first. + +Prerequisite for the distribution shells (#2714 Phase 0): every shell repo +copies this rendered content, so the gap had to close before fan-out. diff --git a/packages/mcp/src/skill.test.ts b/packages/mcp/src/skill.test.ts index 56f11dbc60..30055caf24 100644 --- a/packages/mcp/src/skill.test.ts +++ b/packages/mcp/src/skill.test.ts @@ -51,7 +51,7 @@ describe('renderSkillMarkdown', () => { expect(md).toContain('Authorization: ApiKey'); }); - it('lists the object-CRUD tools and a discover-first instruction', () => { + it('lists the full native tool surface (CRUD + business actions) and a discover-first instruction', () => { const md = renderSkillMarkdown(); for (const tool of [ 'list_objects', @@ -61,12 +61,20 @@ describe('renderSkillMarkdown', () => { 'create_record', 'update_record', 'delete_record', + 'list_actions', + 'run_action', ]) { expect(md).toContain(tool); } expect(md.toLowerCase()).toContain('discover'); }); + it('teaches action preference — run a matching business action instead of hand-editing records', () => { + const md = renderSkillMarkdown(); + expect(md).toContain('Prefer actions over hand-edits'); + expect(md).toContain('run_action({ actionName, objectName?, recordId?, params? })'); + }); + it('is generic — it does not enumerate any concrete schema', () => { // The skill must not bake in tenant object/field names; it points to live // discovery instead. Sanity: no stray "objectName: ". diff --git a/packages/mcp/src/skill.ts b/packages/mcp/src/skill.ts index dde6d9b136..b10c72d93f 100644 --- a/packages/mcp/src/skill.ts +++ b/packages/mcp/src/skill.ts @@ -58,8 +58,9 @@ description: ${OBJECTSTACK_SKILL_DESCRIPTION} # ObjectStack -${intro} An ObjectStack environment exposes its data **objects** (tables) as -tools over the Model Context Protocol (MCP). Every operation runs **as you** — +${intro} An ObjectStack environment exposes its data **objects** (tables) and +its business **actions** (registered app logic: approvals, conversions, flow +triggers) as tools over the Model Context Protocol (MCP). Every operation runs **as you** — under your account's permissions and row-level security — so you may see a subset of rows, or get a permission error on a write. That is expected governance, not a failure. @@ -67,9 +68,11 @@ governance, not a failure. ## When to use Use these tools whenever the user wants to **inspect or change data** in their -ObjectStack app: look up records, filter/report, create or update entries, or -clean up data. Prefer these tools over guessing — the environment is the source -of truth. +ObjectStack app — look up records, filter/report, create or update entries, +clean up data — or **run a business action** the app defines (approve a +request, convert a lead, kick off a flow). Prefer these tools over guessing — +the environment is the source of truth, and an action is always better than +hand-editing the records it would have touched. ## Connect @@ -112,6 +115,8 @@ always current even as the app evolves: 1. \`list_objects\` — see what objects exist. 2. \`describe_object({ objectName })\` — get an object's fields (name, type, required) before querying or writing it. +3. \`list_actions\` — see what business actions you may run (each entry + includes its parameters and whether it needs a \`recordId\`). Always discover the relevant object's shape before constructing a filter or a create/update payload. @@ -128,6 +133,14 @@ create/update payload. - **update_record({ objectName, recordId, data })** — change fields on a record. - **delete_record({ objectName, recordId })** — delete a record (destructive — confirm with the user first). +- **list_actions()** — list the business actions you are permitted to run, with + each action's declared parameters, whether it operates on a record, and + whether it is flagged destructive. +- **run_action({ actionName, objectName?, recordId?, params? })** — invoke a + business action by name. This executes the app's registered logic (can + mutate data or trigger flows) under your permissions and RLS. Pass + \`recordId\` for record-scoped actions and \`params\` for declared inputs; + \`objectName\` only disambiguates a name shared by multiple objects. ## Conventions & gotchas @@ -139,13 +152,20 @@ create/update payload. - **Writes are real and immediate.** There is no implicit dry-run. Confirm destructive actions (\`delete_record\`, bulk updates) with the user. - **Page large reads.** Use \`limit\`/\`offset\` rather than asking for everything. +- **Prefer actions over hand-edits.** When \`list_actions\` offers an action for + the task (approve, convert, close, …), call it instead of updating the + records yourself — actions carry the app's validation and side effects. + Confirm first when an action is record-destructive or flagged for + confirmation. ## Recommended workflow -1. \`list_objects\` to orient. +1. \`list_objects\` (and \`list_actions\` when the task sounds like a business + operation) to orient. 2. \`describe_object\` on the target object. 3. \`query_records\` to read / verify current state. -4. \`create_record\` / \`update_record\` / \`delete_record\` to make changes, - confirming destructive steps with the user. +4. \`run_action\` when a matching business action exists; otherwise + \`create_record\` / \`update_record\` / \`delete_record\` — confirming + destructive steps with the user. `; }