UNPKG

@withpica/mcp-server

Version:

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

133 lines • 6.18 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { REPORT_ISSUE_WRITE_PROPERTIES, REPORT_ISSUE_REQUIRED, } from "@withpica/mcp-utils"; const VALID_KINDS = [ "workflow_gap", "inconsistent_result", "missing_step", "data_issue", "tool_failure", "ux_confusion", ]; export class ReportIssueTools { pica; constructor(pica) { this.pica = pica; } getTools() { return [ { definition: { name: "pica_report_issue", scope: "write:feedback", audiences: ["stdio", "http", "public"], tier: "write", description: "File a structured issue with the PICA team. Call PROACTIVELY " + "without waiting to be asked when you hit a workflow gap, " + "cross-surface inconsistency, missing step, data issue, or UX " + "confusion. Also call ON-DEMAND when the user says 'this is " + "broken' / 'this is wrong' / 'this didn't work'. The user does " + "NOT need to file a ticket themselves — that's what this tool " + "is for. Do NOT use for raw tool errors: those auto-populate " + "via the audit path and this tool would create a duplicate. " + "Summary is canonicalised + hashed into a signature that " + "dedupes repeat reports across different wording. Operators " + "triage from a queue; you can poll resolution status via " + "pica_my_reported_issues.", 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: REPORT_ISSUE_WRITE_PROPERTIES, required: [...REPORT_ISSUE_REQUIRED], }, }, executor: this.reportIssue.bind(this), }, ]; } async reportIssue(args) { const kind = args.kind; const summary = args.summary; if (!kind || !VALID_KINDS.includes(kind)) { return { content: [ { type: "text", text: `Error: kind must be one of ${VALID_KINDS.join(", ")}.`, }, ], isError: true, }; } if (typeof summary !== "string" || summary.trim().length === 0) { return { content: [{ type: "text", text: "Error: summary is required." }], isError: true, }; } const trimmedSummary = summary.trim().slice(0, 1000); const result = await this.pica.opsIssues.reportIssue({ kind, summary: trimmedSummary, attempted: typeof args.attempted === "string" ? args.attempted : undefined, expected: typeof args.expected === "string" ? args.expected : undefined, actual: typeof args.actual === "string" ? args.actual : undefined, context: args.context && typeof args.context === "object" && !Array.isArray(args.context) ? args.context : undefined, }); // d2c4a5882 tightened the SDK return type to `Promise<ReportIssueResponse>` // without a `.data` wrapper — the old `result?.data || result` fallback // no longer typechecks. `result` IS the unwrapped payload; use directly. const data = result; // Single first-party confirmation — true whether the issue is new or // deduped into an existing signature. We deliberately do NOT surface the // new-vs-dedup distinction or any occurrence/impacted count: those reveal // cross-tenant activity and change nothing the agent can act on. const baseNarrative = `Issue recorded (${kind}). Signature ${data.signature?.slice(0, 8) ?? "unknown"}, severity ${data.severity ?? "info"}. Operators will see it in the intelligence feed.`; // Surface regression context: the upsert RPC flips a resolved issue back // to open and stamps regressed_at — let the agent tell the user this is // a re-occurrence of a previously-fixed bug. The bug's lifecycle is a // first-party fact (the caller is hitting it now); the prior resolution // NOTE is operator free-text about a possibly-foreign occurrence, so it // is not surfaced. const regressionLine = data.is_regression ? this.formatRegressionLine({ previously_resolved_at: data.previously_resolved_at, }) : null; const narrative = regressionLine ? `${baseNarrative}\n\n${regressionLine}` : baseNarrative; const structuredData = { id: data.id, signature: data.signature, status: data.status, severity: data.severity, reported_kind: kind, }; if (data.is_regression) { structuredData.is_regression = true; structuredData.previously_resolved_at = data.previously_resolved_at; } return { content: [{ type: "text", text: narrative }], structuredContent: { kind: "pica.card.ops_issue_reported", version: 1, data: structuredData, }, }; } formatRegressionLine(input) { const resolvedDate = input.previously_resolved_at ? input.previously_resolved_at.slice(0, 10) : "unknown date"; return (`Note: this is a regression of a previously-resolved issue ` + `(fixed on ${resolvedDate}).`); } } //# sourceMappingURL=report-issue.js.map