@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
133 lines • 6.18 kB
JavaScript
// 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