UNPKG

@withpica/mcp-server

Version:

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

304 lines • 15.9 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. export class AgentHygieneTools { pica; constructor(pica) { this.pica = pica; } getTools() { return [ // ADR-247 PR (b) — agent-explicit outcome log + recent read. { definition: { name: "pica_outcome_log", scope: "write:catalog", audiences: ["stdio", "http"], tier: "write", description: "Log an agent-explicit milestone or workflow completion. Use after finishing a meaningful chunk of work — credit attestation shipped, sync placement filed, catalog audit complete — so PICA's dashboards can show the user what their agent has been getting done. " + "Required: `kind` (free-form, typical values: 'workflow_completed', 'milestone_hit'). Optional: `entity_refs` (which works / people / recordings the workflow touched), `summary` (one-line user-facing description), `agent_summary` (structured detail — steps, durations, tool calls). " + "Throws on failure (unlike the auto-fire from write-tier tool success which is fire-and-forget) so you know if the log didn't land.", 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: { kind: { type: "string", description: "Required free-form kind. Typical values: 'workflow_completed', 'milestone_hit'.", }, entity_refs: { type: "array", description: "Optional. Entities the workflow touched — array of {type, id} (e.g. [{type:'work', id:'<uuid>'}]).", items: { type: "object", properties: { type: { type: "string" }, id: { type: "string" }, }, required: ["type", "id"], }, }, summary: { type: "string", description: "Optional. One-line user-facing description of what was completed (e.g. 'credit attestation shipped to 3 collaborators on Midnight Run').", }, agent_summary: { type: "object", description: "Optional. Structured detail the agent wants captured — steps, durations, tool-call counts, etc. JSON object.", }, }, required: ["kind"], }, }, executor: this.logOutcome.bind(this), }, { definition: { name: "pica_outcomes_recent", scope: "read:analytics", audiences: ["stdio", "http"], tier: "read", description: "Read recent workflow outcomes for the current org — what's been getting done. Returns rows ordered by `last_seen_at DESC`, signature-deduped so repeat workflows collapse into one row with `occurrence_count` incremented. " + "Captures both the auto-fire path (every write-tier MCP tool success) and the agent-explicit path (pica_outcome_log calls). Use to summarise activity for the user ('here's what's happened in your catalog this week'), to investigate rate-limit-hits (filter `kind=rate_limit_hit`), or to audit your own past actions. " + "Default limit 20, max 100. Optional `kind` filter (e.g. 'rate_limit_hit', 'gate_shadow_miss', 'workflow_completed'). Optional `source` filter ('auto' for tool-call successes, 'agent_explicit' for pica_outcome_log writes).", 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: { kind: { type: "string", description: "Optional. Filter by outcome kind (e.g. 'rate_limit_hit', 'gate_shadow_miss', 'workflow_completed', 'milestone_hit').", }, source: { type: "string", enum: ["auto", "agent_explicit"], description: "Optional. Filter by source — 'auto' for audit-hook captures from tool successes; 'agent_explicit' for pica_outcome_log writes.", }, limit: { type: "number", description: "Optional. Default 20, max 100.", }, }, }, }, executor: this.recentOutcomes.bind(this), }, { definition: { name: "pica_introduce_self", scope: "always", audiences: ["stdio", "http"], tier: "write", description: "Declare who you are before doing other work. Call this FIRST in a session, before any other tool. " + "Pass: agent_name (e.g. 'Claude Desktop', 'ChatGPT'), optionally agent_role (e.g. 'creator-assistant'), " + "on_behalf_of (the user you're acting for — their handle or workspace name), and model_hint " + "(e.g. 'claude-opus-4-7', 'gpt-5'). " + "PICA uses this for forensic reconstruction and to attribute the work you do to its source — " + "if something goes wrong later, this is the trail we follow. Self-attested provenance, not authentication. " + "Required for Bearer-API-key callers; cookie sessions and pica_grant_* tokens are exempt " + "(their identity is on file). Valid for 24h per credential; re-call to refresh.", 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: { agent_name: { type: "string", description: "Required. The human-readable name of the agent / client (e.g. 'Claude Desktop', 'ChatGPT').", }, agent_role: { type: "string", description: "Optional. The role the agent is playing in this session (e.g. 'creator-assistant', 'catalog-auditor', 'qa').", }, on_behalf_of: { type: "string", description: "Optional but recommended. The end user you're acting for — their handle, email, or workspace name. Helps PICA attribute work correctly when a single agent serves multiple users.", }, model_hint: { type: "string", description: "Optional. The model identifier (e.g. 'claude-opus-4-7', 'gpt-5'). When omitted, PICA falls back to UA-derived best-guess.", }, }, required: ["agent_name"], }, }, executor: this.introduceSelf.bind(this), }, ]; } async introduceSelf( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- args is the MCP tool envelope args) { const agentName = typeof args.agent_name === "string" ? args.agent_name.trim() : ""; if (agentName.length === 0) { return { content: [ { type: "text", text: "Error: agent_name is required — declare who you are (e.g. 'Claude Desktop').", }, ], isError: true, }; } const result = await this.pica.agentIntroductions.declare({ agent_name: agentName, agent_role: typeof args.agent_role === "string" && args.agent_role.trim().length > 0 ? args.agent_role.trim() : undefined, on_behalf_of: typeof args.on_behalf_of === "string" && args.on_behalf_of.trim().length > 0 ? args.on_behalf_of.trim() : undefined, model_hint: typeof args.model_hint === "string" && args.model_hint.trim().length > 0 ? args.model_hint.trim() : undefined, }); // `declare()` resolves to the unwrapped inner payload (the SDK's request() // strips the { success, data } envelope) — read fields directly. Guard the // unexpected-empty case rather than dereferencing undefined. if (!result?.declared_name) { return { content: [ { type: "text", text: "Couldn't confirm the identity declaration landed — the server returned an unexpected response. " + "If you're on a cookie or grant session your identity is already on file, so other tools remain available.", }, ], isError: true, }; } return { content: [ { type: "text", text: `Identity recorded as "${result.declared_name}". Valid for 24h — re-call to refresh. ` + `Other tools are now unblocked for this session.`, }, ], structuredContent: { kind: "pica.card.agent_introduction", version: 1, data: { id: result.id, declared_at: result.declared_at, declared_name: result.declared_name, valid_through: result.valid_through, }, }, }; } // ADR-247 PR (b) — pica_outcome_log executor. async logOutcome( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- args is the MCP tool envelope args) { const kind = typeof args.kind === "string" ? args.kind.trim() : ""; if (kind.length === 0) { return { content: [ { type: "text", text: "Error: kind is required (e.g. 'workflow_completed', 'milestone_hit').", }, ], isError: true, }; } const result = await this.pica.workflowOutcomes.log({ kind, entity_refs: Array.isArray(args.entity_refs) ? args.entity_refs : undefined, summary: typeof args.summary === "string" && args.summary.trim().length > 0 ? args.summary.trim() : undefined, agent_summary: args.agent_summary && typeof args.agent_summary === "object" ? args.agent_summary : undefined, }); // `log()` resolves to the unwrapped inner payload — read fields directly. if (!result?.kind) { return { content: [ { type: "text", text: "Couldn't confirm the outcome was logged — the server returned an unexpected response. " + "No outcome row was recorded; retry if it still matters.", }, ], isError: true, }; } const wasDedup = (result.occurrence_count ?? 1) > 1; const text = wasDedup ? `Outcome recorded (occurrence ${result.occurrence_count} of an existing "${result.kind}" — same signature, count incremented).` : `Outcome recorded as "${result.kind}". This is the first instance of this workflow signature.`; return { content: [{ type: "text", text }], structuredContent: { kind: "pica.card.workflow_outcome_logged", version: 1, data: result, }, }; } // ADR-247 PR (b) — pica_outcomes_recent executor. async recentOutcomes( // eslint-disable-next-line @typescript-eslint/no-explicit-any -- args is the MCP tool envelope args) { const kind = typeof args.kind === "string" ? args.kind : undefined; const source = args.source === "auto" || args.source === "agent_explicit" ? args.source : undefined; const limit = typeof args.limit === "number" ? args.limit : undefined; const result = await this.pica.workflowOutcomes.recent({ kind, source, limit, }); // `recent()` resolves to the unwrapped inner payload — read fields directly. if (!result?.outcomes) { return { content: [ { type: "text", text: "Couldn't read recent outcomes — the server returned an unexpected response.", }, ], isError: true, }; } const count = result.count; const headline = count === 0 ? "No recent outcomes." : `${count} recent outcome${count === 1 ? "" : "s"}.`; const summary = result.outcomes.slice(0, 10).map((o) => { const tag = o.source === "agent_explicit" ? "(logged)" : "(auto)"; const verb = o.kind ?? "unknown"; const subject = o.summary ?? o.tool_name ?? "—"; const count = o.occurrence_count > 1 ? ` ×${o.occurrence_count}` : ""; return `[${verb}] ${subject} ${tag}${count}`; }); const more = result.outcomes.length > 10 ? `\n\n_+${result.outcomes.length - 10} more — pass a higher limit to see them._` : ""; return { content: [ { type: "text", text: `${headline}\n\n${summary.join("\n")}${more}`, }, ], structuredContent: { kind: "pica.card.workflow_outcomes_recent", version: 1, data: result, }, }; } } //# sourceMappingURL=agent-hygiene.js.map