@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
306 lines • 16 kB
JavaScript
// 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",
minimum: 1,
maximum: 100,
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