@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
106 lines • 5.12 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
import { parseChangelogText, CHANGELOG_SECTION_NAMES, } from "../lib/changelog.js";
import { CHANGELOG_MD, PACKAGE_NAME, PACKAGE_VERSION, } from "../lib/changelog.generated.js";
const RESOURCE_URI = "release-notes://withpica/mcp-server/latest";
const DEFAULT_LIMIT = 3;
const MAX_LIMIT = 20;
function compareSemver(a, b) {
const parse = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
const [a1, a2, a3] = parse(a);
const [b1, b2, b3] = parse(b);
if (a1 !== b1)
return a1 - b1;
if (a2 !== b2)
return a2 - b2;
return a3 - b3;
}
export class ReleaseNotesTools {
pica;
constructor(pica) {
this.pica = pica;
}
getTools() {
return [
{
definition: {
name: "pica_release_notes",
scope: "read:catalog",
audiences: ["stdio", "http"],
tier: "read",
vernacular_kind: "specialist",
vernacular_reason: "Server self-introspection — surfaces platform release notes for the MCP server itself. Not a catalog tool; not yet covered by the Creator Question Atlas (ADR-226). Disambiguated from the ambiguous 'what's new' phrasing by the NOT-FOR clause in the description.",
description: "Use when the user asks: 'what's new in pica', 'any updates to the server', " +
"'what changed in the MCP', 'what version am I on'. " +
"Returns recent releases of the PICA MCP server (platform updates). " +
"NOT for catalog activity, recent works, or 'what's new in my catalogue' — " +
"use pica_dashboard_briefing or pica_works_query for that. " +
"Returns { package, current_version, releases: [{ version, date, sections, markdown }], resource_uri }. " +
"Defaults to the last 3 releases; use `since_version` to bound the output " +
"or `sections` to filter to Added/Fixed/etc.",
workflows: "infrastructure",
inputSchema: {
type: "object",
properties: {
since_version: {
type: "string",
description: "If set, returns only releases strictly greater than this version " +
"(semver-compared). Example: '2.50.0' returns 2.50.1+, 2.51.0+, etc. " +
"Versions equal to since_version are excluded.",
},
limit: {
type: "number",
description: `Maximum number of releases to return. Default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}.`,
},
sections: {
type: "array",
items: { type: "string", enum: [...CHANGELOG_SECTION_NAMES] },
description: "Filter each release's sections to only these names " +
"(e.g. ['Added', 'Fixed']). Sections not in the list are omitted from output.",
},
},
},
},
executor: this.releaseNotes.bind(this),
},
];
}
releaseNotes = async (args) => {
const sinceVersion = typeof args.since_version === "string" ? args.since_version : null;
const rawLimit = typeof args.limit === "number" ? args.limit : DEFAULT_LIMIT;
const limit = Math.max(1, Math.min(MAX_LIMIT, Math.floor(rawLimit)));
const sectionFilter = Array.isArray(args.sections)
? args.sections.filter((s) => CHANGELOG_SECTION_NAMES.includes(s))
: null;
const all = parseChangelogText(CHANGELOG_MD);
let filtered = all;
if (sinceVersion) {
filtered = all.filter((e) => compareSemver(e.version, sinceVersion) > 0);
}
filtered = filtered.slice(0, limit);
const releases = filtered.map((e) => {
const sections = sectionFilter
? Object.fromEntries(Object.entries(e.sections).filter(([k]) => sectionFilter.includes(k)))
: e.sections;
return {
version: e.version,
date: e.date,
sections,
markdown: e.markdown,
};
});
return {
content: [
{
type: "text",
text: JSON.stringify({
package: PACKAGE_NAME,
current_version: PACKAGE_VERSION,
releases,
resource_uri: RESOURCE_URI,
}, null, 2),
},
],
};
};
}
//# sourceMappingURL=release-notes.js.map