{"name":"com.pinappai/mcp","slug":"pinappai-mcp","title":"PinAppAI","description":"Visual website feedback your coding agent applies: pin change requests on the live site over MCP.","url":"https://mcp.market/server/pinappai-mcp","rating":null,"grade":"B","score":75,"certified":false,"status":"active","category":"ai","tags":["ai"],"presence":{"score":17,"stars":null,"forks":null,"downloads_week":52,"last_push_at":null,"license":"SEE LICENSE IN LICENSE"},"uptime":{"percent":100,"checks":28,"ok":28,"last_checked_at":"2026-11-04T13:15:32.455Z","last_ok_at":"2026-11-04T13:15:32.455Z","latency_ms":311},"claimed":false,"transport":"mixed","callable_via_gateway":true,"default_price_micros":0,"repository":null,"website":"https://pinappai.com/docs/mcp/","version":"0.9.9","remotes":[{"type":"streamable-http","url":"https://mcp.pinappai.com/mcp","headers":[{"description":"Bearer ppk_... workspace API key. Omit for keyless discovery; required for tool calls.","format":"string","isSecret":true,"name":"Authorization"}]}],"packages":[{"registryType":"npm","identifier":"@pinappai/mcp","version":"0.9.9","transport":{"type":"stdio"},"environmentVariables":[{"description":"PinAppAI workspace API key (ppk_...). Optional: interactive setups use `npx @pinappai/mcp login` instead; set this only for CI/headless.","format":"string","isSecret":true,"name":"PINAPPAI_API_KEY"}]}],"tools":[{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"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'].","type":"array","items":{"type":"string","enum":["change_requested","rejected","approved","comment"]}},"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.","type":"array","items":{"type":"string","enum":["selector","page","reviewer","keyword"]}},"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.","type":"string","enum":["off","auto","on"]},"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"}},"required":["project"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug to archive."}},"required":["project"],"additionalProperties":false}},{"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.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"slug":{"type":"string","minLength":2,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Project slug (lowercase, hyphens). Must be unique across all PinAppAI projects."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Human-readable project name."},"allowed_origins":{"description":"Origins where the widget will load (e.g. https://example.com or *.example.com). Up to 20 entries.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":255}}},"required":["slug","name"],"additionalProperties":false}},{"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.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"slug":{"type":"string","minLength":2,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-]*$","description":"Workspace slug (lowercase, hyphens). Globally unique."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Human-readable workspace name."}},"required":["slug","name"],"additionalProperties":false}},{"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.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{},"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"The api_keys.id (from list_api_keys)."}},"required":["id"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"id":{"type":"string","minLength":1,"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."},"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"}},"required":["project","id"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."}},"required":["project"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."}},"required":["project"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"id":{"type":"string","minLength":1,"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."}},"required":["project","id"],"additionalProperties":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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Invitee's email address."},"name":{"description":"Optional display name; defaults to the email's local-part.","type":"string","maxLength":120},"role":{"description":"Defaults to editor. Admins can't invite owners — that requires an existing owner.","type":"string","enum":["admin","editor","viewer"]}},"required":["email"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Reviewer's email address."},"name":{"description":"Optional display name; defaults to the email's local-part.","type":"string","maxLength":120}},"required":["project","email"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"additionalProperties":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).","write_action":false,"price_micros":0,"input_schema":{"type":"object","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).","type":"integer","minimum":0,"maximum":9007199254740991},"to":{"description":"End of time range (Unix ms, exclusive).","type":"integer","minimum":0,"maximum":9007199254740991},"limit":{"description":"Default 100, max 500.","type":"integer","minimum":1,"maximum":500},"offset":{"description":"Default 0.","type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"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'].","type":"array","items":{"type":"string","enum":["change_requested","rejected","approved","comment"]}},"page_url":{"description":"Filter to change requests captured on this exact page URL.","type":"string"},"since":{"description":"ISO 8601 timestamp. Only return change requests created after this time.","type":"string"},"reviewer":{"description":"Filter by reviewer name or email (matches either field).","type":"string"},"limit":{"description":"Max rows to return per page. Defaults to 50, maximum 200.","type":"integer","minimum":1,"maximum":200},"cursor":{"description":"Opaque pagination cursor from a previous response's next_cursor. Pass it to fetch the next page.","type":"string"}},"required":["project"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"additionalProperties":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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."}},"required":["project"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"applies":{"minItems":1,"type":"array","items":{"type":"object","properties":{"cr_id":{"type":"string","minLength":1,"description":"The change-request id (cr_*)."},"before_text":{"type":"string","maxLength":4000,"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."},"after_text":{"type":"string","maxLength":4000,"description":"The source-file content AFTER your edit."}},"required":["cr_id","before_text","after_text"],"additionalProperties":false},"description":"List of CRs to mark applied in one atomic batch."},"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\".","type":"string","maxLength":2000}},"required":["project","applies"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"iteration_id":{"type":"string","minLength":1,"description":"The iteration id (it_*)."}},"required":["project","iteration_id"],"additionalProperties":false}},{"name":"pinappai_get_workflow","description":"Return the full procedure for a PinAppAI guided workflow, rendered exactly as the\n/pinappai:<name> 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…","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"name":{"type":"string","enum":["analyze","apply","audit-review","auth-help","embed-widget","generate-changes-page","remove","reset-project","setup-project","summarize"],"description":"The workflow to fetch. See the description for what each does."}},"required":["name"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"limit":{"description":"Max rows to return (default 100).","type":"integer","minimum":1,"maximum":500}},"required":["project"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"limit":{"description":"Max iterations to return (default 50, max 200).","type":"integer","minimum":1,"maximum":200},"before_seq":{"description":"Pagination cursor — return iterations with seq_no strictly less than this.","type":"integer","minimum":1,"maximum":9007199254740991}},"required":["project"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"page_path":{"description":"Path of the /changes/ page on the site. Default '/changes'.","type":"string","minLength":1,"maxLength":2048,"pattern":"^\\/[\\w\\-./]*$"},"items":{"minItems":1,"maxItems":2000,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":200,"description":"The data-pp-item value — e.g. \"pricing:hero-title\"."},"title":{"description":"The data-pp-title value.","type":"string","maxLength":300},"target_page_url":{"description":"Root-relative URL of the ACTUAL page the change lives on (data-pp-page-url).","type":"string","maxLength":2048},"target_source_selector":{"description":"CSS selector to the element on the source page (data-pp-source-selector).","type":"string","maxLength":2048},"target_source_files":{"description":"Comma-separated source-repo file paths (data-pp-source-files).","type":"string","maxLength":2048},"before_text":{"description":"The BEFORE text, verbatim from the page.","type":"string","maxLength":4000},"after_text":{"description":"The AFTER text, verbatim from the page.","type":"string","maxLength":4000},"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.","type":"string","minLength":1,"maxLength":64}},"required":["id"],"additionalProperties":false},"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."}},"required":["project","items"],"additionalProperties":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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"confirm":{"type":"boolean","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."}},"required":["project","confirm"],"additionalProperties":false}},{"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.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"user_id":{"type":"string","minLength":1,"description":"The member's user_id (from list_members)."}},"required":["user_id"],"additionalProperties":false}},{"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 > 7 days ago.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"The api_keys.id to restore."}},"required":["id"],"additionalProperties":false}},{"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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"The api_keys.id to revoke."}},"required":["id"],"additionalProperties":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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug."},"reviewer_id":{"type":"string","minLength":1,"description":"The project_reviewers row id (from list_reviewers)."}},"required":["project","reviewer_id"],"additionalProperties":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.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"The project slug to unarchive."}},"required":["project"],"additionalProperties":false}},{"name":"update_me_profile","description":"Update the API key user's display name. Email is identity and not editable.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"The new display name."}},"required":["name"],"additionalProperties":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.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"user_id":{"type":"string","minLength":1,"description":"The member's user_id (from list_members)."},"role":{"type":"string","enum":["owner","admin","editor","viewer"],"description":"The new role."}},"required":["user_id","role"],"additionalProperties":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.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","minLength":1,"description":"Current project slug (the lookup key)."},"slug":{"description":"New slug (renames the project; widget snippets keep working since they reference api_key, not slug).","type":"string","minLength":2,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-]*$"},"name":{"description":"New display name for the project.","type":"string","minLength":1,"maxLength":120},"owner_email":{"description":"Reassign project ownership to a different email (for display only — does not change permissions).","type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"allowed_origins":{"description":"REPLACES the existing list. Pass an empty array [] to clear all allowed origins.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":255}},"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"},"primary_origin":{"description":"Canonical site origin powering the no-install review link (review.pinappai.com/p/<slug>/). Any URL is normalized down to its origin. null (or an empty string) clears it and disables the link.","anyOf":[{"type":"string","maxLength":255,"format":"uri"},{"type":"null"}]},"preview_origin":{"description":"Optional branch/preview deploy origin. review.pinappai.com/p/<slug>~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.","anyOf":[{"type":"string","maxLength":255,"format":"uri"},{"type":"null"}]}},"required":["project"],"additionalProperties":false}},{"name":"update_workspace","description":"Rename or re-slug the API key's workspace. Owner role required. Slug changes break existing dashboard URLs.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"name":{"description":"New display name for the workspace.","type":"string","minLength":1,"maxLength":120},"slug":{"description":"Renaming slug breaks every URL pointing at the old slug — use sparingly.","type":"string","minLength":2,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-]*$"}},"additionalProperties":false}}],"scan":{"score":75,"grade":"B","scanned_at":"2026-09-24T21:42:15.899Z","report":{"scannerVersion":"0.1.9","scannedAt":"2026-09-24T21:42:15.865Z","components":{"code":{"score":25,"max":25,"notes":["16 source files scanned"]},"reliability":{"score":20,"max":20,"notes":["remote reachable in 651ms"]},"poisoning":{"score":15,"max":15,"notes":["38 tool descriptions checked"]},"auth":{"score":8,"max":15,"notes":["API key sent as a header"]},"maintenance":{"score":3,"max":15,"notes":["no repository listed"]},"identity":{"score":4,"max":10,"notes":["verified namespace with website, no repo"]}},"findings":[{"id":"maint.no-repo","severity":"low","component":"maintenance","title":"No source repository listed"}],"inputs":{"probes":[{"url":"https://mcp.pinappai.com/mcp","reachable":true,"authRequired":false,"latencyMs":651,"serverInfo":{"name":"pinappai","version":"0.1.0"}}],"packages":[{"registryType":"npm","identifier":"@pinappai/mcp","version":"0.9.9","found":true,"license":"SEE LICENSE IN LICENSE","hasInstallScripts":false,"dependencyCount":4,"publishedAt":"2026-07-25T22:26:01.954Z","weeklyDownloads":52}],"repo":{"found":false},"icon":{"url":"https://pinappai.com/favicon.svg","source":"site"},"presence":{"stars":null,"forks":null,"downloadsWeek":52,"license":"SEE LICENSE IN LICENSE","lastPushAt":null,"score":17}}}},"grade_history":[],"reviews":[]}