Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
100 changes: 100 additions & 0 deletions plugins/airtable/skills/agent-activity-log/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
name: agent-activity-log
description: Scaffold and operate an opt-in `Agent activity log` table that records what the agent did, decided, and got blocked on across a long-running or multi-session Airtable workflow. Use whenever a workflow skill (product-ops, sales-ops, marketing-ops, etc.) is being set up for an agent-driven motion (recurring triage, multi-step plan, automated monitoring, agentic workflow), or when the user explicitly asks for "agent activity tracking," "audit log of agent decisions," "agent memory," "track what the agent did," or similar. The pattern is opt-in (front-load the offer, frame as auditability for the user's benefit, not surveillance). Composes into workflow skills the same way `show-airtable-link` does — workflow skills point at this skill rather than re-implementing the schema inline.
license: MIT
metadata:
version: '1.0.0'
author: airtable
---

# Agent activity log

A typed audit log of agent decisions, blockers, and outcomes — opt-in for users who are explicitly building an agent-driven workflow. The selling point: humans (and the next agent session) can read back what the agent did and why, without trusting a context-window summary that may be gone tomorrow. Pairs naturally with Airtable's role as a persistent agent substrate.

This skill owns the schema and disclosure language. Workflow skills (product-ops, sales-ops, marketing-ops, etc.) compose this skill rather than re-implementing the pattern; they trigger it when the user's intent surfaces an agent-driven workflow and pass through to this skill for the scaffolding + ongoing use.

## When this fires

Trigger phrases — workflow skills should offer this to the user when any of these surface:

- _"track what the agent is doing"_, _"agent activity log,"_ _"audit log of agent decisions"_
- _"long-running workflow,"_ _"agent memory,"_ _"persistent state for the agent"_
- _"keep a record of what changed and why"_
- Setup-mode invocations describing an agent-driven motion: _"I want the agent to triage feedback every morning,"_ _"the agent should propose changes for me to approve,"_ _"set up a self-running workflow"_

Surface proactively when the user's language signals they're building something the agent will run repeatedly or autonomously — not for one-shot interactions.

## Disclosure (opt-in, not surveillance)

Front-load the offer. The user agrees up front or declines; either is fine.

> _"I can also set up a log of my own activity in a table called `Agent activity log` so you can audit what I've done and why. It tracks every record I create or modify with the reasoning. It's opt-in — if you'd rather not have it, we'll skip it. Want me to include it?"_

Frame as **auditability for the user's benefit** — they can see what the agent decided, what got changed, and where the agent got stuck. Not as monitoring the agent for its own sake.

## Schema

A single `Agent activity log` table. Keep it out of stakeholder-facing Interfaces — this is internal audit data for the agent's operator, not content the broader team should browse in the app.

### Core fields

- **`Summary`** (singleLineText, primary) — one-line what-happened. This is the **primary field**.
- **`Timestamp`** (createdTime) — when the event happened.
- **`Action`** or **`Event type`** (singleSelect: `Read`, `Create`, `Update`, `Delete`, `Decision`, `Blocker`, `Question`, `Completion`, `Error`) — adapt the choices to the workflow's grain.
- **`Reasoning`** or **`Detail`** (multilineText) — what the agent intended and why, including inputs considered and alternatives rejected
- **`Outcome`** (singleSelect: `Completed`, `Partial`, `Failed`, `Blocked`)
- **`Status`** (singleSelect: `Open`, `Acknowledged`, `Resolved`, `Stale`) — for blockers and questions that need human follow-up
- **`Session ID`** (singleLineText) — tie events from one agent invocation together

### Linking to the records the agent touched

**Airtable's `multipleRecordLinks` field is bound to a single target table at field-creation time** — there is no polymorphic linked-record field that spans multiple tables. Two viable patterns:

1. **Per-target linked-record fields (recommended)** — one `multipleRecordLinks` field per table the agent might touch. For a product-ops base: `Linked Roadmap item`, `Linked Customer feedback`, `Linked Release`, `Linked OKR`. For a sales-ops base: `Linked Account`, `Linked Contact`, `Linked Opportunity`, `Linked Activity`. The agent populates whichever field matches the touched record's table; the others stay empty. **Gives reverse-link navigation** — opening a touched record shows all `Agent activity log` entries that touched it, automatically. Slightly more schema overhead per added table the agent touches.
2. **URL-only fallback** — a single `Target record URL` (URL field) holding the deep-link to the touched record. No reverse-link navigation, no rollups across the log, but simpler schema. Reasonable when the agent touches many tables and per-table linked fields would get unwieldy, or for early-stage setups where browser-driven inspection is fine.

Most builds use **pattern 1 for the 3-5 tables the agent touches most often + pattern 2 as fallback for ad-hoc touches** — add a `Target record URL` field alongside the per-table linked-record fields and populate it whenever the touched record's table isn't one of the wired-up linked fields.

Add a `Target table` (singleSelect of the workflow's tables) so a viewer can quickly see which kind of record an entry touched, even before clicking through.

### Schema variants per workflow domain

The shape is the same across workflow skills; the per-target linked-record fields change to match the parent base's tables. The workflow skill that's invoking `agent-activity-log` knows its own table inventory and should pass them through.

## Use guidance

Throughout any agent-driven session, write events to the log as decisions are made or blockers surface. Pattern:

1. **At session start**: write an event with `Action = Completion`, `Outcome = Completed`, and `Summary` describing what the session is starting on. The `Session ID` for this entry becomes the tie-thread for the rest of the session's writes.
2. **On each meaningful decision**: write a `Decision` event with the full reasoning in `Reasoning`. Include alternatives considered and why they were rejected.
3. **On blockers**: write a `Blocker` event with `Status = Open`, link to the affected records via the per-table linked-record fields.
4. **On questions for the human**: write a `Question` event with `Status = Open`, link to the affected records. The human can answer by updating the record (e.g., adding a comment or moving status to `Acknowledged`).
5. **On errors**: write an `Error` event with `Outcome = Failed` and the error context in `Reasoning`.
6. **At session end**: write a `Completion` event summarizing the session's outputs and any unresolved items.

Don't over-write — log meaningful decisions and changes, not every tool call. Reads in particular usually don't need to be logged unless the workflow's audit value depends on it.

## Composition into workflow skills

The workflow skill (product-ops, sales-ops, marketing-ops, etc.) should:

1. **Surface this pattern to the user** when the trigger phrases above appear, framing it as opt-in.
2. **Compose `agent-activity-log`** — don't re-implement the schema inline; point at this skill the same way workflow skills point at `show-airtable-link` for the URL-handoff pattern.
3. **Pass through workflow-specific context** — the tables the agent will be touching, so the per-target linked-record fields can be scaffolded correctly for that workflow.
4. **Hand off at session end** via `show-airtable-link` — link to the `Agent activity log` table or to a "Recent agent activity" Interface view so the user can inspect.

Suggested workflow-skill body language:

> _"When the user wants agent-activity tracking (`'audit log of agent decisions,' 'long-running workflow,' 'agent memory,'` etc.), compose the `agent-activity-log` skill. Frame as opt-in — disclose first, scaffold after the user agrees. Pass the workflow's record-touching tables through so the per-target linked-record fields get scaffolded for the right tables."_

## Composition with `show-airtable-link`

At session end (or when surfacing what the agent did), hand off to the user with a `show-airtable-link` to the `Agent activity log` table — or to a dedicated Interface view filtered to the current `Session ID` so the user sees only this session's activity. Both are valid; pick based on the user's stated preference for browsing vs. session-by-session review.

## Anti-patterns

- **Don't auto-create `Agent activity log` without disclosure.** This pattern earns trust when offered; it erodes trust when imposed. Always disclose first.
- **Don't claim "polymorphic" linked-record fields.** Airtable's `multipleRecordLinks` field is bound to a single target table — schema-design accordingly with the per-table linked-record + URL-fallback pattern above.
- **Don't conflate the agent log with the workflow's normal tables.** `Agent activity log` records what the agent did while helping; the workflow tables (Opportunities, Roadmap items, Campaigns, Projects, etc.) record what the team is working on. Keep them parallel, linked, but distinct.
- **Don't surveillance-frame.** Disclosure language frames as auditability for the user's benefit, not monitoring the agent for its own sake.
- **Don't over-log.** Meaningful decisions and changes, not every tool call. Reads usually don't need logging unless the audit story specifically depends on it.
148 changes: 148 additions & 0 deletions plugins/airtable/skills/airtable-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
name: airtable-cli
description: Lists bases, reads and writes records, manages tables and fields, filters and searches data in Airtable via the `airtable-mcp` CLI. Use when the task involves Airtable data or the user mentions airtable-mcp, bases, tables, records, or fields.
license: MIT
metadata:
version: '1.0.0'
author: airtable
---

# airtable-mcp

## Self-discovery

Tools are fetched from the MCP server at runtime, so the CLI never has a hardcoded command list. Discover what's available:

```sh
airtable-mcp tools # human-readable list
airtable-mcp tools --json # machine-parseable list
airtable-mcp <tool> --help # show flags and descriptions for a tool
```

Run `airtable-mcp tools` before assuming a tool exists. Tool names, arguments, and output shapes can change between server releases without a CLI update.

## Install

```sh
npm install -g @airtable/mcp-cli
```

## Auth

The CLI needs an Airtable personal access token (PAT). Two paths:

**Environment variable (preferred for scripts/agents):**

```sh
export AIRTABLE_TOKEN=pat_xxx
```

**Interactive configure (stores token in `~/.airtable/cli.json` with 0600 permissions):**

```sh
airtable-mcp configure
```

Create tokens at https://airtable.com/create/tokens. Ensure the token has the scopes required by the tools being called.

`AIRTABLE_TOKEN` takes precedence over saved profiles when no `--profile` flag is set. Never log or echo tokens.

## Quick reference

| Task | Command |
| ---------------------- | ------------------------------------------------------- |
| Set up credentials | `airtable-mcp configure` |
| Add a named profile | `airtable-mcp configure --profile work` |
| Check auth status | `airtable-mcp whoami` |
| Remove credentials | `airtable-mcp logout` |
| Remove all profiles | `airtable-mcp logout --all` |
| List available tools | `airtable-mcp tools` |
| Run a tool | `airtable-mcp <tool> --flagName value` |
| Get tool help | `airtable-mcp <tool> --help` |
| Pass args via stdin | `echo '{"key":"val"}' \| airtable-mcp <tool> --input -` |
| Bypass tool cache | `airtable-mcp <tool> --refresh` |
| Suppress status msgs | `airtable-mcp <tool> -q` |
| Raw text output | `airtable-mcp <tool> --output raw` |
| Use a specific profile | `airtable-mcp <tool> --profile work` |

Tool names use hyphens on the CLI (`list-records`) but underscores in MCP (`list_records`). The CLI translates automatically.

## Workflow

1. **Auth** — set `AIRTABLE_TOKEN` or run `airtable-mcp configure`
2. **Discover** — run `airtable-mcp tools` to see available tools
3. **Inspect** — run `airtable-mcp <tool> --help` for flags and descriptions
4. **Check access** — in `tools --json` output, check the `access` field: `read-only`, `write`, or `destructive`. Confirm with the user before running `destructive` tools.
5. **Execute** — run `airtable-mcp <tool> --flagName value`

## Output & automation

- Default output is formatted JSON to stdout. Status messages go to stderr.
- `--json` on `tools` gives a JSON array of `{name, title, access}`.
- `-q` / `--quiet` suppresses stderr status messages (cache warnings, etc).
- `--output raw` returns the raw server response text instead of parsed JSON.
- `--input -` reads tool arguments as a JSON object from stdin, bypassing flag parsing.
- Exit codes: `0` success, `1` error (auth, tool failure, not found), `2` usage error (bad flags, bad input).

## Common tasks

**Find a base and list its tables:**

```sh
airtable-mcp search-bases --searchQuery "Project Tracker" -q
airtable-mcp list-tables-for-base --baseId appEXAMPLEbase001 -q
```

**List records with specific fields:**

```sh
airtable-mcp list-records-for-table \
--baseId appEXAMPLEbase001 --tableId tblEXAMPLEtable01 \
--fieldIds '["Name","Status"]' --pageSize 10 -q
```

**Filter records** — filters use structured JSON, not formula strings. Wrap conditions in an `operands` array; the top-level `operator` defaults to `and` if omitted:

```sh
airtable-mcp list-records-for-table \
--baseId appEXAMPLEbase001 --tableId tblEXAMPLEtable01 \
--filters '{"operator":"and","operands":[{"operator":"=","operands":["Status","Done"]}]}' -q
```

For select fields, filter by choice ID (from `get-table-schema`), not the display name. The `airtable-filters` skill covers compound filters, date filters, and operator-by-field-type details.

**Search records** — use `search-records` for free-text/fuzzy queries on large tables. Use `list-records-for-table` with `--filters` when filtering by exact field values:

```sh
airtable-mcp search-records \
--baseId appEXAMPLEbase001 --table tblEXAMPLEtable01 \
--query "acme" --fields '["Name","Notes"]' -q
```

Pass `--fields ALL_SEARCHABLE_FIELDS` to search across every indexed field. Date, rating, checkbox, and button fields are not searchable.

**Update records** — complex args are easier via `--input -`:

```sh
echo '{"baseId":"appEXAMPLEbase001","tableId":"tblEXAMPLEtable01","records":[{"id":"recEXAMPLErecord1","fields":{"fldEXAMPLEfield01":"Done"}}]}' \
| airtable-mcp update-records-for-table --input - -q
```

Select field values are returned as objects (`{"id":"sel...","name":"Done"}`) but must be written as plain strings (`"Done"`). Record field keys in create/update currently require field IDs (`fldEXAMPLEfield02`) — use `get-table-schema` to resolve names to IDs before writing. Note that `fieldIds`, `sort`, and `filters` accept both names and IDs.

## Gotchas

| Problem | Cause | Fix |
| -------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `Unknown tool: X` | Tool name doesn't exist on the server or cache is stale | Run `airtable-mcp tools --refresh` to refresh, then retry |
| `Authentication failed` | Token expired, revoked, or wrong | Run `airtable-mcp configure` or check `AIRTABLE_TOKEN` |
| `Access denied` | Token missing required scopes | Add scopes at https://airtable.com/create/tokens |
| `Connection timed out` | Server unreachable (10s timeout) | Check network; CLI falls back to stale cache if available |
| Boolean flags take no value | `--dryRun true` passes `"true"` as next arg | Use `--dryRun` alone (booleans are presence-based) |
| Array/object args fail | Value isn't valid JSON | Pass as JSON string: `--fieldMappings '{"a":"b"}'` |
| Filter rejected at top level | Single condition passed without `operands` wrapper | Wrap in `{"operands":[...]}` (`operator` defaults to `and`) |
| Sort key is `fieldId` not `field` | `--sort '[{"field":"Name"}]'` silently ignored | Use `{"fieldId":"Name","direction":"asc"}` — accepts field IDs or names |
| Select filter returns no matches | Filtering by display name instead of choice ID | Run `get-table-schema` first to get `sel...` choice IDs |
| `INVALID_RECORDS` on batch write | Batch limit is 10 records per request (default; varies by account) | Split into chunks of ≤10 and check `<tool> --help` for the current limit |
| Permission error on `list-records-for-table` | User has interface-only access to the base | Use `list-records-for-page` / `get-record-for-page` instead |
| Endpoints restricted | CLI only allows HTTPS on `*.airtable.com` | Cannot point at arbitrary servers (security constraint) |
Loading