{
  "data": {
    "tool": {
      "category": "",
      "endpoint": "https://mcp.pinappai.com/mcp",
      "jsonUrl": "https://www.anchorterminal.com/api/v1/tools/pinappai-mcp.json",
      "kind": "mcp",
      "listed": "indexed",
      "liveUrl": "https://www.anchorterminal.com/api/v1/live/pinappai-mcp.json",
      "markdownUrl": "https://www.anchorterminal.com/tools/pinappai-mcp.md",
      "mcpTools": {
        "check": {
          "checker": "anchor-check/1.0",
          "totalTokens": 7665,
          "counts": {
            "error": 0,
            "note": 1,
            "warn": 4
          },
          "findings": [
            {
              "rule": "TC16",
              "severity": "warn",
              "tool": "restore_api_key",
              "message": "no readOnlyHint or destructiveHint",
              "fix": "Set readOnlyHint: true if it only reads; otherwise set destructiveHint and idempotentHint."
            },
            {
              "rule": "TC16",
              "severity": "warn",
              "tool": "unarchive_project",
              "message": "no readOnlyHint or destructiveHint",
              "fix": "Set readOnlyHint: true if it only reads; otherwise set destructiveHint and idempotentHint."
            },
            {
              "rule": "TC16",
              "severity": "warn",
              "tool": "update_me_profile",
              "message": "no readOnlyHint or destructiveHint",
              "fix": "Set readOnlyHint: true if it only reads; otherwise set destructiveHint and idempotentHint."
            },
            {
              "rule": "TC16",
              "severity": "warn",
              "tool": "update_project",
              "message": "no readOnlyHint or destructiveHint",
              "fix": "Set readOnlyHint: true if it only reads; otherwise set destructiveHint and idempotentHint."
            },
            {
              "rule": "TC24",
              "severity": "note",
              "message": "38 of 38 tools have no outputSchema",
              "fix": "Declare outputSchema for tools that return structured data, and return structuredContent that matches it."
            }
          ]
        },
        "checkedAt": "2026-10-04T22:23:34Z",
        "count": 38,
        "schemaTokens": 7665,
        "status": "ok",
        "tools": [
          {
            "name": "list_projects",
            "description": "List PinAppAI projects this API key can access. Returns one entry per project in the key's workspace, with slug, name, derived domain, and created_at. Call this first to see what projects are available before drilling into specific ones.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {},
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "get_review_summary",
            "description": "Cheap one-call summary of change-request state for a project. Returns totals + by-page + by-reviewer breakdowns + oldest/newest unresolved timestamps. Call this BEFORE drilling into individual CRs to frame the work.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "list_change_requests",
            "description": "List change requests for a project with filters + cursor pagination. Defaults to include=['change_requested'] (highest-signal subset). Returns digest fields per row (including element_kind=text|mixed|field|null and suggested_text — a literal text replacement on text/field picks); no inline images at this tier (token-cheap by design). Use get_change_request for full detail + screenshots.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "cursor": {
                  "description": "Opaque pagination cursor from a previous response's next_cursor. Pass it to fetch the next page.",
                  "type": "string"
                },
                "include": {
                  "description": "Which feedback kinds to include: change_requested (a decision requesting a change), rejected (a decision rejecting the pick), approved (a decision approving the pick), comment (a comment-only pin, no decision). Defaults to ['change_requested'].",
                  "items": {
                    "enum": [
                      "change_requested",
                      "rejected",
                      "approved",
                      "comment"
                    ],
                    "type": "string"
                  },
                  "type": "array"
                },
                "limit": {
                  "description": "Max rows to return per page. Defaults to 50, maximum 200.",
                  "maximum": 200,
                  "minimum": 1,
                  "type": "integer"
                },
                "page_url": {
                  "description": "Filter to change requests captured on this exact page URL.",
                  "type": "string"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                },
                "reviewer": {
                  "description": "Filter by reviewer name or email (matches either field).",
                  "type": "string"
                },
                "since": {
                  "description": "ISO 8601 timestamp. Only return change requests created after this time.",
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "get_change_request",
            "description": "Full detail for one change request. Returns the CR (including element_kind=text|mixed|field|null and suggested_text — a literal text-replacement the reviewer typed when picking a text/field element), plus related[] (other CRs on the same selector + same page) so you can detect patterns from a single fetch. Set include_image=true to embed the screenshot inline as an MCP image content block. When suggested_text is non-null on a text/field pick, paste it verbatim — do not interpret or rewrite. When element_kind=mixed, the element contains more than text (icons/images/structure) — rely on the comment + screenshot, not just the visible text.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "id": {
                  "description": "The FEEDBACK id (fb_*), NOT a change-request id (cr_*). When you have a CR row from pinappai_list_apply_inbox / list_change_requests, pass its `source_feedback_id` (or just `id` in list_change_requests' response — same field) here. Passing a cr_* value returns 404.",
                  "minLength": 1,
                  "type": "string"
                },
                "include_image": {
                  "description": "When true, also returns the change request's screenshot inline as an MCP image content block, in addition to the JSON text response. Defaults to false (JSON only).",
                  "type": "boolean"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project",
                "id"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "analyze_patterns",
            "description": "Server-side deterministic groupings (by selector / page / reviewer / keyword) plus heuristic suggested_clusters of change requests. Set ai_clusters=\"auto\" (default) to also request semantic clustering via the host's MCP sampling capability — returns ai_clustering_hint for the host AI to apply inline if sampling is unsupported. Use this BEFORE drilling into individual CRs to find patterns across the dataset.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "ai_clusters": {
                  "description": "Controls semantic clustering via the host's MCP sampling capability. off skips it and returns only the deterministic groupings. auto (default) attempts it and falls back to an ai_clustering_hint for the calling AI to apply inline if the host doesn't support sampling. on requires sampling support and returns a sampling_unsupported error if the host doesn't provide it.",
                  "enum": [
                    "off",
                    "auto",
                    "on"
                  ],
                  "type": "string"
                },
                "ai_model_preference": {
                  "description": "Optional model preference hint for the sampling request. Currently accepted but not forwarded to the host's createMessage call (the sampling request always uses a fixed speed/intelligence preference); reserved for future use.",
                  "type": "string"
                },
                "group_by": {
                  "description": "Which deterministic groupings to compute: selector (by CSS selector), page (by page URL), reviewer (by reviewer). Defaults to ['selector', 'page']. Keyword extraction (common_keywords) is always computed regardless of this setting.",
                  "items": {
                    "enum": [
                      "selector",
                      "page",
                      "reviewer",
                      "keyword"
                    ],
                    "type": "string"
                  },
                  "type": "array"
                },
                "include": {
                  "description": "Which feedback kinds to pull into the analysis: change_requested (a decision requesting a change), rejected (a decision rejecting the pick), approved (a decision approving the pick), comment (a comment-only pin, no decision). Defaults to ['change_requested'].",
                  "items": {
                    "enum": [
                      "change_requested",
                      "rejected",
                      "approved",
                      "comment"
                    ],
                    "type": "string"
                  },
                  "type": "array"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "openWorldHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "get_screenshot",
            "description": "Return the screenshot for a change request as an inline MCP image content block. Use when you want vision context without the full JSON envelope from get_change_request.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "id": {
                  "description": "The FEEDBACK id (fb_*), NOT a change-request id (cr_*). When you have a CR row from pinappai_list_apply_inbox, pass its `source_feedback_id` here. Passing a cr_* value returns 404.",
                  "minLength": 1,
                  "type": "string"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project",
                "id"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "get_project",
            "description": "Get a single project by slug — name, allowed_origins, archive state, and the embeddable widget api_key. Scoped to the API key's workspace.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "create_project",
            "description": "Create a new PinAppAI project inside the API key's workspace. Returns the project plus its widget api_key for embedding. Requires editor role on the workspace.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "allowed_origins": {
                  "description": "Origins where the widget will load (e.g. https://example.com or *.example.com). Up to 20 entries.",
                  "items": {
                    "maxLength": 255,
                    "minLength": 1,
                    "type": "string"
                  },
                  "maxItems": 20,
                  "type": "array"
                },
                "name": {
                  "description": "Human-readable project name.",
                  "maxLength": 120,
                  "minLength": 1,
                  "type": "string"
                },
                "slug": {
                  "description": "Project slug (lowercase, hyphens). Must be unique across all PinAppAI projects.",
                  "maxLength": 64,
                  "minLength": 2,
                  "pattern": "^[a-z0-9][a-z0-9-]*$",
                  "type": "string"
                }
              },
              "required": [
                "slug",
                "name"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": false,
              "idempotentHint": false
            }
          },
          {
            "name": "update_project",
            "description": "Update editable fields on a project — slug, name, owner_email, allowed_origins, allow_anonymous_reviewers, primary_origin (review link), preview_origin (preview review link). Requires editor role.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "allow_anonymous_reviewers": {
                  "description": "When true, any verified email on a CORS-allowed origin can submit feedback. When false, the email must be on the workspace or project_reviewers list.",
                  "type": "boolean"
                },
                "allowed_origins": {
                  "description": "REPLACES the existing list. Pass an empty array [] to clear all allowed origins.",
                  "items": {
                    "maxLength": 255,
                    "minLength": 1,
                    "type": "string"
                  },
                  "maxItems": 20,
                  "type": "array"
                },
                "name": {
                  "description": "New display name for the project.",
                  "maxLength": 120,
                  "minLength": 1,
                  "type": "string"
                },
                "owner_email": {
                  "description": "Reassign project ownership to a different email (for display only — does not change permissions).",
                  "format": "email",
                  "maxLength": 254,
                  "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
                  "type": "string"
                },
                "preview_origin": {
                  "anyOf": [
                    {
                      "format": "uri",
                      "maxLength": 255,
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "Optional branch/preview deploy origin. review.pinappai.com/p/\u003cslug\u003e~preview/ serves it so reviewers can check APPLIED changes before they ship — records still write against primary_origin. Set this after publishing an apply round to a preview deploy; null (or an empty string) clears it."
                },
                "primary_origin": {
                  "anyOf": [
                    {
                      "format": "uri",
                      "maxLength": 255,
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ],
                  "description": "Canonical site origin powering the no-install review link (review.pinappai.com/p/\u003cslug\u003e/). Any URL is normalized down to its origin. null (or an empty string) clears it and disables the link."
                },
                "project": {
                  "description": "Current project slug (the lookup key).",
                  "minLength": 1,
                  "type": "string"
                },
                "slug": {
                  "description": "New slug (renames the project; widget snippets keep working since they reference api_key, not slug).",
                  "maxLength": 64,
                  "minLength": 2,
                  "pattern": "^[a-z0-9][a-z0-9-]*$",
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true
            }
          },
          {
            "name": "archive_project",
            "description": "Soft-archive a project — the widget stops accepting new feedback but the project + history is preserved. Reversible via unarchive_project. Owner role required.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "project": {
                  "description": "The project slug to archive.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": false
            }
          },
          {
            "name": "unarchive_project",
            "description": "Restore an archived project — the widget starts accepting feedback again on pages that still embed the snippet. Owner role required.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "project": {
                  "description": "The project slug to unarchive.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true
            }
          },
          {
            "name": "list_workspaces",
            "description": "List all workspaces the API key's user is a member of, with role per workspace. The api-key remains bound to its original workspace — to operate on another, mint a new key for that workspace in the admin UI.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {},
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "create_workspace",
            "description": "Create a new workspace owned by the API key's user. Free/Pro plans cap at 1 workspace; Team/Business unlimited. To use the new workspace from MCP, mint a fresh key for it in the admin UI.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "name": {
                  "description": "Human-readable workspace name.",
                  "maxLength": 120,
                  "minLength": 1,
                  "type": "string"
                },
                "slug": {
                  "description": "Workspace slug (lowercase, hyphens). Globally unique.",
                  "maxLength": 64,
                  "minLength": 2,
                  "pattern": "^[a-z0-9][a-z0-9-]*$",
                  "type": "string"
                }
              },
              "required": [
                "slug",
                "name"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": false,
              "idempotentHint": false
            }
          },
          {
            "name": "get_workspace",
            "description": "Get the API key's workspace details (slug, name, plan, tier) plus the user's role in that workspace. Useful as a permissions probe.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {},
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "update_workspace",
            "description": "Rename or re-slug the API key's workspace. Owner role required. Slug changes break existing dashboard URLs.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "name": {
                  "description": "New display name for the workspace.",
                  "maxLength": 120,
                  "minLength": 1,
                  "type": "string"
                },
                "slug": {
                  "description": "Renaming slug breaks every URL pointing at the old slug — use sparingly.",
                  "maxLength": 64,
                  "minLength": 2,
                  "pattern": "^[a-z0-9][a-z0-9-]*$",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": true
            }
          },
          {
            "name": "delete_workspace",
            "description": "Schedule the API key's workspace for permanent deletion (7-day grace window). Reversible only via the email magic-link sent to the owner. Subsequent MCP calls with this key will fail not_found until cancelled.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {},
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": true
            }
          },
          {
            "name": "list_members",
            "description": "List members of the API key's workspace with their roles, sorted owner → admin → editor → viewer. The user_id field is the lookup key for update_member_role and remove_member.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {},
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "invite_member",
            "description": "Invite a user to the API key's workspace as admin / editor / viewer (default editor). Admin role required to call. Sends a welcome email.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "email": {
                  "description": "Invitee's email address.",
                  "format": "email",
                  "maxLength": 254,
                  "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
                  "type": "string"
                },
                "name": {
                  "description": "Optional display name; defaults to the email's local-part.",
                  "maxLength": 120,
                  "type": "string"
                },
                "role": {
                  "description": "Defaults to editor. Admins can't invite owners — that requires an existing owner.",
                  "enum": [
                    "admin",
                    "editor",
                    "viewer"
                  ],
                  "type": "string"
                }
              },
              "required": [
                "email"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": false,
              "idempotentHint": false
            }
          },
          {
            "name": "update_member_role",
            "description": "Change a workspace member's role. Caller must outrank both the current role and the new role; promoting to owner requires owner role. Last-owner demotion is rejected with last_owner.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "role": {
                  "description": "The new role.",
                  "enum": [
                    "owner",
                    "admin",
                    "editor",
                    "viewer"
                  ],
                  "type": "string"
                },
                "user_id": {
                  "description": "The member's user_id (from list_members).",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "user_id",
                "role"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": true
            }
          },
          {
            "name": "remove_member",
            "description": "Remove a member from the API key's workspace. Self-removal works for any role; cross-removal requires admin+ and outranking the target. Last-owner removal returns last_owner.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "user_id": {
                  "description": "The member's user_id (from list_members).",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "user_id"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": false
            }
          },
          {
            "name": "list_reviewers",
            "description": "List external reviewers on a project (people invited via invite_reviewer). Workspace members are implicitly authorized and not listed here. Status: pending / verified / revoked.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "invite_reviewer",
            "description": "Invite an external reviewer to a project. They'll get a welcome email; status starts pending and flips to verified when they OTP through the widget. Editor role required.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "email": {
                  "description": "Reviewer's email address.",
                  "format": "email",
                  "maxLength": 254,
                  "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
                  "type": "string"
                },
                "name": {
                  "description": "Optional display name; defaults to the email's local-part.",
                  "maxLength": 120,
                  "type": "string"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project",
                "email"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": false,
              "idempotentHint": false
            }
          },
          {
            "name": "revoke_reviewer",
            "description": "Revoke an external reviewer's access. First call soft-revokes (status=revoked, audit trail preserved); second call hard-deletes (status=removed). Editor role required.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                },
                "reviewer_id": {
                  "description": "The project_reviewers row id (from list_reviewers).",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project",
                "reviewer_id"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": false
            }
          },
          {
            "name": "list_audit_events",
            "description": "Read audit log entries for the API key's workspace. Owner role + Business tier required. Returns newest-first; supports filters action / actor (email substring) / from / to (Unix ms).",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "action": {
                  "description": "Exact dotted action name (e.g. \"feedback.update\", \"member.invite\").",
                  "type": "string"
                },
                "actor": {
                  "description": "Substring match on actor_email.",
                  "type": "string"
                },
                "from": {
                  "description": "Start of time range (Unix ms, inclusive).",
                  "maximum": 9007199254740991,
                  "minimum": 0,
                  "type": "integer"
                },
                "limit": {
                  "description": "Default 100, max 500.",
                  "maximum": 500,
                  "minimum": 1,
                  "type": "integer"
                },
                "offset": {
                  "description": "Default 0.",
                  "maximum": 9007199254740991,
                  "minimum": 0,
                  "type": "integer"
                },
                "to": {
                  "description": "End of time range (Unix ms, exclusive).",
                  "maximum": 9007199254740991,
                  "minimum": 0,
                  "type": "integer"
                }
              },
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "list_api_keys",
            "description": "List API keys (active + recently-revoked) in the calling key's workspace. Plaintext / hash never returned. Owner or admin role required.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {},
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "get_api_key",
            "description": "Get a single API key's metadata by id (no plaintext / hash). Owner or admin role required. Wrong-role and nonexistent-id both surface as not_found.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "id": {
                  "description": "The api_keys.id (from list_api_keys).",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "id"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "revoke_api_key",
            "description": "Soft-revoke an API key (reversible via restore_api_key for 7 days). Owner or admin role required. Refuses to revoke the calling key with cannot_revoke_self.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "id": {
                  "description": "The api_keys.id to revoke.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "id"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": true
            }
          },
          {
            "name": "restore_api_key",
            "description": "Restore a soft-revoked API key (within 7 days of revocation). Owner or admin role required. Returns restore_window_expired if the row was never revoked or was revoked \u003e 7 days ago.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "id": {
                  "description": "The api_keys.id to restore.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "id"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true
            }
          },
          {
            "name": "get_me",
            "description": "Whoami probe — returns the user behind the API key, the workspace it's bound to, the user's role in that workspace, and the API key's metadata. Run this first to establish context.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {},
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "update_me_profile",
            "description": "Update the API key user's display name. Email is identity and not editable.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "name": {
                  "description": "The new display name.",
                  "maxLength": 120,
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true
            }
          },
          {
            "name": "export_my_data",
            "description": "GDPR Art. 15 right-of-access export — every row PinAppAI holds about the API key's user. Sensitive columns (password hash, OAuth tokens) are server-side redacted. Personal data of OTHER reviewers is NOT included.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {},
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "pinappai_get_iteration_coverage",
            "description": "Read coverage metrics for an iteration: items_decided / manifest_size + items_undecided + per-reviewer decided_count + disagreement_count. Useful for the AI to summarize \"you have N items still waiting on M reviewers\" and identify CRs needing admin attention. Read-only.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "iteration_id": {
                  "description": "The iteration id (it_*).",
                  "minLength": 1,
                  "type": "string"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project",
                "iteration_id"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "pinappai_list_iterations",
            "description": "Paginated history of iterations for a project, newest first. Each entry includes seq_no, opened_at, applied_at (NULL while open), bundle_summary, and the frozen manifest_cr_ids_json + manifest_reviewer_ids_json. Pair with pinappai_get_iteration_coverage to summarize outcomes per iteration.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "before_seq": {
                  "description": "Pagination cursor — return iterations with seq_no strictly less than this.",
                  "maximum": 9007199254740991,
                  "minimum": 1,
                  "type": "integer"
                },
                "limit": {
                  "description": "Max iterations to return (default 50, max 200).",
                  "maximum": 200,
                  "minimum": 1,
                  "type": "integer"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "pinappai_list_apply_inbox",
            "description": "v3 Apply-inbox listing. Returns every change request in an Apply-eligible inbox substate (inbox_new / inbox_rejected / inbox_change_requested) with the prior cr_marked_applied event payload AND the latest reviewer/admin decision payload inlined per row. Used by /pinappai:apply to drive substate-specific edits (new = standard apply; rejected = revert via swap; change_requested = apply with new wording) in one round-trip.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "limit": {
                  "description": "Max rows to return (default 100).",
                  "maximum": 500,
                  "minimum": 1,
                  "type": "integer"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "readOnlyHint": true
            }
          },
          {
            "name": "pinappai_apply_change_requests",
            "description": "v3 atomic apply primitive. Takes a list of {cr_id, before_text, after_text} after the AI has edited source for each. Creates one iteration record (audit trail visible in the Project history page) and transitions every eligible CR from inbox_* state to 'in_review' atomically. Per-CR errors (CR not in eligible state, e.g. someone deferred it mid-session) are returned in the response without failing the whole batch. Editor role required.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "applies": {
                  "description": "List of CRs to mark applied in one atomic batch.",
                  "items": {
                    "additionalProperties": false,
                    "properties": {
                      "after_text": {
                        "description": "The source-file content AFTER your edit.",
                        "maxLength": 4000,
                        "type": "string"
                      },
                      "before_text": {
                        "description": "The source-file content this CR targeted BEFORE you edited it. Stored on the cr_marked_applied event payload so admin Revert can swap byte-for-byte later. For 'inbox_rejected' CRs the AI should have already done the swap (after_text → before_text from the CR's previous cr_marked_applied event); the new before_text is the pre-revert source and after_text is the post-revert source.",
                        "maxLength": 4000,
                        "type": "string"
                      },
                      "cr_id": {
                        "description": "The change-request id (cr_*).",
                        "minLength": 1,
                        "type": "string"
                      }
                    },
                    "required": [
                      "cr_id",
                      "before_text",
                      "after_text"
                    ],
                    "type": "object"
                  },
                  "minItems": 1,
                  "type": "array"
                },
                "bundle_summary": {
                  "description": "Short human-readable summary of this Apply batch — shows up in the project history page. e.g. \"applied 7 CRs across 3 pages\".",
                  "maxLength": 2000,
                  "type": "string"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project",
                "applies"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": false
            }
          },
          {
            "name": "pinappai_reset_review_data",
            "description": "Permanently deletes a project's review data: all change requests (+ their event history), iterations, reviewer decisions, ALL freeform pins/comments with their screenshots, and the /changes/-page registration tables. NOT recoverable — no grace window. Requires admin role. Ask the user for explicit confirmation before calling, and prefer the /pinappai:reset-project prompt, which also offers to clean up the repo's static /changes/ page and .pinappai/last-applied.json.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "confirm": {
                  "const": true,
                  "description": "Must be literally true. The caller must have shown the user what will be deleted and received explicit confirmation first — there is no grace window; this is not recoverable.",
                  "type": "boolean"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project",
                "confirm"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": true
            }
          },
          {
            "name": "pinappai_register_change_items",
            "description": "Register the items of a freshly written /changes/ review page with the server — REQUIRED after generating or regenerating the page, or the in-page review chip and decision bars have no data. Pass every [data-pp-item] in document order with the same attribute values you put in the markup. This replaces the removed client-side self-registration: the page itself can no longer register anything. Editor role required.",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "items": {
                  "description": "Every [data-pp-item] on the page you just wrote, in DOCUMENT ORDER (order drives the review queue). The FULL active set — items you omit are deactivated server-side.",
                  "items": {
                    "additionalProperties": false,
                    "properties": {
                      "after_text": {
                        "description": "The AFTER text, verbatim from the page.",
                        "maxLength": 4000,
                        "type": "string"
                      },
                      "applied_cr_id": {
                        "description": "The originating CR id (data-pp-applied-cr-id). Items whose CR no longer exists are dropped server-side; omit only for hand-authored items.",
                        "maxLength": 64,
                        "minLength": 1,
                        "type": "string"
                      },
                      "before_text": {
                        "description": "The BEFORE text, verbatim from the page.",
                        "maxLength": 4000,
                        "type": "string"
                      },
                      "id": {
                        "description": "The data-pp-item value — e.g. \"pricing:hero-title\".",
                        "maxLength": 200,
                        "minLength": 1,
                        "type": "string"
                      },
                      "target_page_url": {
                        "description": "Root-relative URL of the ACTUAL page the change lives on (data-pp-page-url).",
                        "maxLength": 2048,
                        "type": "string"
                      },
                      "target_source_files": {
                        "description": "Comma-separated source-repo file paths (data-pp-source-files).",
                        "maxLength": 2048,
                        "type": "string"
                      },
                      "target_source_selector": {
                        "description": "CSS selector to the element on the source page (data-pp-source-selector).",
                        "maxLength": 2048,
                        "type": "string"
                      },
                      "title": {
                        "description": "The data-pp-title value.",
                        "maxLength": 300,
                        "type": "string"
                      }
                    },
                    "required": [
                      "id"
                    ],
                    "type": "object"
                  },
                  "maxItems": 2000,
                  "minItems": 1,
                  "type": "array"
                },
                "page_path": {
                  "description": "Path of the /changes/ page on the site. Default '/changes'.",
                  "maxLength": 2048,
                  "minLength": 1,
                  "pattern": "^\\/[\\w\\-./]*$",
                  "type": "string"
                },
                "project": {
                  "description": "The project slug.",
                  "minLength": 1,
                  "type": "string"
                }
              },
              "required": [
                "project",
                "items"
              ],
              "type": "object"
            },
            "annotations": {
              "destructiveHint": true,
              "idempotentHint": true
            }
          },
          {
            "name": "pinappai_get_workflow",
            "description": "Return the full procedure for a PinAppAI guided workflow, rendered exactly as the\n/pinappai:\u003cname\u003e prompt delivers it. Call this when the user asks for one of these\nand this client does not surface MCP prompts. Follow the returned text verbatim and\ndo not improvise a workflow from its name or description.\n\nAvailable workflows:\n  analyze                Read-only pattern analysis of change requests\n  apply                  v3 PinAppAI Apply — drain the project Inbox by editing source for every Apply-eligible change…\n  audit-review           Pull recent audit-log events for the workspace and summarize unusual activity\n  auth-help              Walk through PinAppAI MCP authentication — recommend the browser-flow login first, fall back…\n  embed-widget           Embed the PinAppAI widget snippet into an EXISTING project's site, then bootstrap the…\n  generate-changes-page  Generate a temporary /changes/ review page for a multi-page rewrite\n  remove                 Remove ALL PinAppAI integration from this repository (widget script, /changes/ pages, optional…\n  reset-project          Reset a project's review data to zero — permanently delete its change requests, iterations,…\n  setup-project          Walk the user through creating a new PinAppAI project from scratch — pick or create a…\n  summarize              Generate a PR description / changelog / customer email from completed work, referencing the…",
            "inputSchema": {
              "additionalProperties": false,
              "properties": {
                "name": {
                  "description": "The workflow to fetch. See the description for what each does.",
                  "enum": [
                    "analyze",
                    "apply",
                    "audit-review",
                    "auth-help",
                    "embed-widget",
                    "generate-changes-page",
                    "remove",
                    "reset-project",
                    "setup-project",
                    "summarize"
                  ],
                  "type": "string"
                }
              },
              "required": [
                "name"
              ],
              "type": "object"
            },
            "annotations": {
              "idempotentHint": true,
              "openWorldHint": false,
              "readOnlyHint": true,
              "title": "Get PinAppAI workflow"
            }
          }
        ]
      },
      "name": "PinAppAI",
      "note": "Indexed from the official MCP registry: facts and our own checks, not reviewed, so no score, grade or rank.",
      "packages": [
        {
          "registryType": "npm",
          "identifier": "@pinappai/mcp",
          "version": "0.9.9",
          "transport": "stdio"
        }
      ],
      "pageJsonUrl": "https://www.anchorterminal.com/tools/pinappai-mcp.json",
      "popularity": {
        "npmWeekly": 72
      },
      "registryName": "com.pinappai/mcp",
      "remotes": [
        {
          "type": "streamable-http",
          "url": "https://mcp.pinappai.com/mcp"
        }
      ],
      "repository": "",
      "reviewed": false,
      "slug": "pinappai-mcp",
      "source": "the official MCP registry",
      "sourceUrl": "https://registry.modelcontextprotocol.io/v0.1/servers?search=com.pinappai/mcp",
      "summary": "Visual website feedback your coding agent applies: pin change requests on the live site over MCP.",
      "updatedAt": "2026-07-25T22:26:59Z",
      "url": "https://www.anchorterminal.com/tools/pinappai-mcp",
      "vendor": "pinappai.com",
      "vendorUrl": "https://pinappai.com/docs/mcp/",
      "version": "0.9.9",
      "websiteUrl": "https://pinappai.com/docs/mcp/",
      "where": "both",
      "why": [
        "vendor"
      ]
    }
  },
  "kind": "anchor.page",
  "links": {
    "api": "https://www.anchorterminal.com/api/v1/index.json",
    "html": "https://www.anchorterminal.com/tools/pinappai-mcp",
    "json": "https://www.anchorterminal.com/tools/pinappai-mcp.json",
    "llms": "https://www.anchorterminal.com/llms.txt",
    "markdown": "https://www.anchorterminal.com/tools/pinappai-mcp.md",
    "slim": "https://www.anchorterminal.com/tools/pinappai-mcp.min.md"
  },
  "markdown": "# PinAppAI\n\n\u003e Indexed, not reviewed: facts from the official MCP registry and our own checks. No score, grade or rank, and not in the rankings until the panel reviews it. How the index works: https://www.anchorterminal.com/indexed/\n\n- Kind: MCP server, by pinappai.com (https://pinappai.com/docs/mcp/)\n- Listed because: It's published in the registry under pinappai.com, a namespace the registry only gives to whoever proves they control that domain.\n- What the official MCP registry says: Visual website feedback your coding agent applies: pin change requests on the live site over MCP.\n\n## Facts\n\n- MCP registry: `com.pinappai/mcp` 0.9.9\n- Endpoint: https://mcp.pinappai.com/mcp (streamable HTTP)\n- Package: npm `@pinappai/mcp` (stdio)\n- Website: https://pinappai.com/docs/mcp/\n- npm downloads a week: 72\n- Registry entry updated: 2026-07-25\n\n## Tools\n\n- Tools it lists (38, about 7,665 tokens of context, `tools/list` without credentials over MCP 2025-11-25, checked 2026-10-04 22:23 UTC):\n  - `list_projects` (read-only): List PinAppAI projects this API key can access. Returns one entry per project in the key's workspace, with slug, name, derived domain, and created_at. Call…\n  - `get_review_summary` (read-only): Cheap one-call summary of change-request state for a project. Returns totals + by-page + by-reviewer breakdowns + oldest/newest unresolved timestamps. Call…\n  - `list_change_requests` (read-only): List change requests for a project with filters + cursor pagination. Defaults to include=['change_requested'] (highest-signal subset). Returns digest fields…\n  - `get_change_request` (read-only): Full detail for one change request. Returns the CR (including element_kind=text|mixed|field|null and suggested_text — a literal text-replacement the reviewer…\n  - `analyze_patterns` (read-only): Server-side deterministic groupings (by selector / page / reviewer / keyword) plus heuristic suggested_clusters of change requests. Set ai_clusters=\"auto\"…\n  - `get_screenshot` (read-only): Return the screenshot for a change request as an inline MCP image content block. Use when you want vision context without the full JSON envelope from…\n  - `get_project` (read-only): Get a single project by slug — name, allowed_origins, archive state, and the embeddable widget api_key. Scoped to the API key's workspace.\n  - `create_project`: Create a new PinAppAI project inside the API key's workspace. Returns the project plus its widget api_key for embedding. Requires editor role on the workspace.\n  - `update_project`: Update editable fields on a project — slug, name, owner_email, allowed_origins, allow_anonymous_reviewers, primary_origin (review link), preview_origin…\n  - `archive_project`: Soft-archive a project — the widget stops accepting new feedback but the project + history is preserved. Reversible via unarchive_project. Owner role required.\n  - `unarchive_project`: Restore an archived project — the widget starts accepting feedback again on pages that still embed the snippet. Owner role required.\n  - `list_workspaces` (read-only): List all workspaces the API key's user is a member of, with role per workspace. The api-key remains bound to its original workspace — to operate on another,…\n  - `create_workspace`: Create a new workspace owned by the API key's user. Free/Pro plans cap at 1 workspace; Team/Business unlimited. To use the new workspace from MCP, mint a fresh…\n  - `get_workspace` (read-only): Get the API key's workspace details (slug, name, plan, tier) plus the user's role in that workspace. Useful as a permissions probe.\n  - `update_workspace`: Rename or re-slug the API key's workspace. Owner role required. Slug changes break existing dashboard URLs.\n  - `delete_workspace`: Schedule the API key's workspace for permanent deletion (7-day grace window). Reversible only via the email magic-link sent to the owner. Subsequent MCP calls…\n  - `list_members` (read-only): List members of the API key's workspace with their roles, sorted owner → admin → editor → viewer. The user_id field is the lookup key for update_member_role…\n  - `invite_member`: Invite a user to the API key's workspace as admin / editor / viewer (default editor). Admin role required to call. Sends a welcome email.\n  - `update_member_role`: Change a workspace member's role. Caller must outrank both the current role and the new role; promoting to owner requires owner role. Last-owner demotion is…\n  - `remove_member`: Remove a member from the API key's workspace. Self-removal works for any role; cross-removal requires admin+ and outranking the target. Last-owner removal…\n  - `list_reviewers` (read-only): List external reviewers on a project (people invited via invite_reviewer). Workspace members are implicitly authorized and not listed here. Status: pending /…\n  - `invite_reviewer`: Invite an external reviewer to a project. They'll get a welcome email; status starts pending and flips to verified when they OTP through the widget. Editor…\n  - `revoke_reviewer`: Revoke an external reviewer's access. First call soft-revokes (status=revoked, audit trail preserved); second call hard-deletes (status=removed). Editor role…\n  - `list_audit_events` (read-only): Read audit log entries for the API key's workspace. Owner role + Business tier required. Returns newest-first; supports filters action / actor (email…\n  - `list_api_keys` (read-only): List API keys (active + recently-revoked) in the calling key's workspace. Plaintext / hash never returned. Owner or admin role required.\n  - `get_api_key` (read-only): Get a single API key's metadata by id (no plaintext / hash). Owner or admin role required. Wrong-role and nonexistent-id both surface as not_found.\n  - `revoke_api_key`: Soft-revoke an API key (reversible via restore_api_key for 7 days). Owner or admin role required. Refuses to revoke the calling key with cannot_revoke_self.\n  - `restore_api_key`: Restore a soft-revoked API key (within 7 days of revocation). Owner or admin role required. Returns restore_window_expired if the row was never revoked or was…\n  - `get_me` (read-only): Whoami probe — returns the user behind the API key, the workspace it's bound to, the user's role in that workspace, and the API key's metadata. Run this first…\n  - `update_me_profile`: Update the API key user's display name. Email is identity and not editable.\n  - `export_my_data` (read-only): GDPR Art. 15 right-of-access export — every row PinAppAI holds about the API key's user. Sensitive columns (password hash, OAuth tokens) are server-side…\n  - `pinappai_get_iteration_coverage` (read-only): Read coverage metrics for an iteration: items_decided / manifest_size + items_undecided + per-reviewer decided_count + disagreement_count. Useful for the AI to…\n  - `pinappai_list_iterations` (read-only): Paginated history of iterations for a project, newest first. Each entry includes seq_no, opened_at, applied_at (NULL while open), bundle_summary, and the…\n  - `pinappai_list_apply_inbox` (read-only): v3 Apply-inbox listing. Returns every change request in an Apply-eligible inbox substate (inbox_new / inbox_rejected / inbox_change_requested) with the prior…\n  - `pinappai_apply_change_requests`: v3 atomic apply primitive. Takes a list of {cr_id, before_text, after_text} after the AI has edited source for each. Creates one iteration record (audit trail…\n  - `pinappai_reset_review_data`: Permanently deletes a project's review data: all change requests (+ their event history), iterations, reviewer decisions, ALL freeform pins/comments with their…\n  - `pinappai_register_change_items`: Register the items of a freshly written /changes/ review page with the server — REQUIRED after generating or regenerating the page, or the in-page review chip…\n  - `pinappai_get_workflow` (read-only): Return the full procedure for a PinAppAI guided workflow, rendered exactly as the /pinappai:\u003cname\u003e prompt delivers it. Call this when the user asks for one of…\n- How its tools read to an agent (0 errors, 4 warnings, 1 note, about 7,665 tokens; rules at https://www.anchorterminal.com/check.md; not part of the score):\n  - warn TC16 restore_api_key: no readOnlyHint or destructiveHint\n  - warn TC16 unarchive_project: no readOnlyHint or destructiveHint\n  - warn TC16 update_me_profile: no readOnlyHint or destructiveHint\n  - warn TC16 update_project: no readOnlyHint or destructiveHint\n  - note TC24 server: 38 of 38 tools have no outputSchema\n\n- JSON: https://www.anchorterminal.com/api/v1/tools/pinappai-mcp.json\n- Being indexed says nothing about quality, and nobody can pay for it. Ask for a review: https://www.anchorterminal.com/builders/#claiming\n",
  "meta": {
    "attribution": "Anchor Terminal (https://www.anchorterminal.com)",
    "docs": "https://www.anchorterminal.com/docs/",
    "generatedAt": "2026-10-05",
    "license": "CC-BY-4.0",
    "method": "https://www.anchorterminal.com/benchmark/",
    "methodology": "0.3",
    "openapi": "https://www.anchorterminal.com/openapi.json",
    "preview": false,
    "run": "2026-10-01",
    "runLabel": "October 2026 research run"
  },
  "page": {
    "breadcrumbs": [
      {
        "name": "Home",
        "url": "https://www.anchorterminal.com/"
      },
      {
        "name": "Terminal",
        "url": "https://www.anchorterminal.com/tools/"
      },
      {
        "name": "Indexed",
        "url": "https://www.anchorterminal.com/indexed/"
      },
      {
        "name": "PinAppAI",
        "url": ""
      }
    ],
    "description": "PinAppAI, an MCP server by pinappai.com, listed from the official MCP registry. Indexed, not reviewed: facts and our own checks, no score or ranking. Visual website feedback your coding agent applies: pin change requests on the live site over MCP.",
    "facts": [
      "not reviewed",
      "not ranked",
      "facts only"
    ],
    "h1": "PinAppAI",
    "image": "https://www.anchorterminal.com/assets/og/indexed.png",
    "path": "/tools/pinappai-mcp",
    "published": "",
    "section": "indexed",
    "title": "PinAppAI: MCP server, indexed from the official MCP registry",
    "toc": null,
    "updated": "2026-10-05",
    "url": "https://www.anchorterminal.com/tools/pinappai-mcp"
  },
  "tokens": {
    "markdown": 2350,
    "slim": 2280
  },
  "version": 1
}
