UNPKG

@withpica/mcp-server

Version:

MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog

149 lines • 6.13 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { SUBMIT_FEEDBACK_WRITE_PROPERTIES, SUBMIT_FEEDBACK_REQUIRED, } from "@withpica/mcp-utils"; const VALID_CATEGORIES = ["bug_report", "feature_request", "question", "other"]; const ROUTING = { bug_report: "devops", feature_request: "ceo", question: "comms", other: "category-mapped", }; export class FeedbackTools { pica; constructor(pica) { this.pica = pica; } getTools() { return [ { definition: { name: "pica_submit_feedback", scope: "write:feedback", audiences: ["stdio", "http", "public"], tier: "write", description: "File a bug report, feature request, question, or general note " + "without leaving the conversation. Routes to the correct team " + "role (bug_report→devops, feature_request→ceo, question→comms, " + "other→category-mapped) and creates a team_tasks row the " + "founder reviews directly. Subject is always " + "the authenticated user; no user_id parameter accepted.", workflows: "infrastructure", vernacular_kind: "specialist", vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.", inputSchema: { type: "object", properties: SUBMIT_FEEDBACK_WRITE_PROPERTIES, required: SUBMIT_FEEDBACK_REQUIRED, }, }, executor: this.submitFeedback.bind(this), }, ]; } async submitFeedback(args) { const category = args.category; const description = args.description; if (!category) { return { content: [{ type: "text", text: "Error: category is required." }], isError: true, }; } if (!VALID_CATEGORIES.includes(category)) { return { content: [ { type: "text", text: `Error: category must be one of ${VALID_CATEGORIES.join(", ")}.`, }, ], isError: true, }; } if (typeof description !== "string" || description.trim().length === 0) { return { content: [{ type: "text", text: "Error: description is required." }], isError: true, }; } // ADR-184 rule 2: reject caller-supplied user_id defensively. The // subject is always the authenticated session/API-key user. const sentiment = args.sentiment === "positive" || args.sentiment === "negative" ? args.sentiment : category === "bug_report" ? "negative" : "positive"; try { const result = await this.pica.feedback.submit({ actionType: `general_${category}`, sentiment, comment: description.trim(), portal: "mcp", }); const data = result?.data || result; const id = data?.id ?? "unknown"; const routed = ROUTING[category]; return { content: [ { type: "text", text: `Feedback submitted as ${category.replace("_", " ")}; ` + `routed to ${routed} team. Reference ID: ${id}.`, }, ], structuredContent: { kind: "pica.card.feedback_submitted", version: 1, data: { id, category, routed_to: routed, sentiment, }, }, }; } catch (e) { // Surface the underlying error to the agent and to the audit log. // Prior behaviour returned a generic neutral string which caused // mcp_audit_log rows with error_code=null / error_message=null, // making triage impossible (ops_issue 808e5fa0, 2026-05-15). // // We still report to ops_issues (fire-and-forget) for visibility, // but the agent now receives the real error message so it can // decide whether to retry, adjust the payload, or escalate. // // structuredContent.error_code + .error_message are read by the // audit-logger (index.ts:1891-1895) to populate mcp_audit_log. const msg = e instanceof Error ? e.message : String(e); const status = e?.status; const errorCode = status ? `HTTP_${status}` : "UNKNOWN"; void this.pica.opsIssues .reportIssue({ kind: "tool_failure", summary: `pica_submit_feedback failed${status ? ` (${status})` : ""}: ${msg}`, context: { tool_name: "pica_submit_feedback", error_code: errorCode, parameters_shape: Object.keys(args).sort().join(","), }, }) .catch(() => { // never block on the ops report — it's fire-and-forget }); return { content: [ { type: "text", text: `Error submitting feedback: ${msg}`, }, ], structuredContent: { error_code: errorCode, error_message: msg, }, isError: true, }; } } } //# sourceMappingURL=feedback.js.map