@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
106 lines • 4.78 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
/**
* The scope of a portfolio relationship, as the assistant tools accept it
* (ADR-333 D3). The main MCP server cannot import the app, so this mirrors
* `relationshipScopeSchema` in lib/services/portfolio-relationship-model.ts
* with snake_case names. lib/services/__tests__/relationship-scope-mcp-
* parity.test.ts fails if a field, an allowed value or a limit drifts.
*
* Every field is optional: the app fills the defaults (whole catalogue, both
* halves, worldwide, no end date, money on only for an administrator,
* directory off). The rules that join two fields (for example "worldwide
* cannot be combined with other territories") are enforced by the app, which
* refuses with a plain reason; the descriptions below state them.
*
* Deliberately has no imports, so the parity test can load it directly.
*/
export const SCOPE_HALVES = ["both", "composition", "master"];
export const SCOPE_CATALOGUES = ["whole", "chosen"];
export const SCOPE_MAX_TERRITORIES = 300;
export const SCOPE_MAX_ITEMS = 5000;
export const SCOPE_TERRITORY_PATTERN = "^[A-Z]{2}$";
export const SCOPE_DATE_PATTERN = "^\\d{4}-\\d{2}-\\d{2}$";
/** The tool argument name for each field of the app's scope. */
export const SCOPE_FIELD_NAMES = {
half: "half",
territories: "territories",
term_starts_on: "termStartsOn",
term_ends_on: "termEndsOn",
money: "money",
directory: "directory",
catalogue: "catalogue",
work_ids: "workIds",
recording_ids: "recordingIds",
};
/** JSON Schema for the optional `scope` argument of grant, invite and answer. */
export const RELATIONSHIP_SCOPE_INPUT_SCHEMA = {
type: "object",
description: "What the relationship covers. Leave out any field to use the default: the whole catalogue, " +
"both halves, worldwide, until revoked, statements hidden (shown by default only to an " +
"administrator), directory off.",
additionalProperties: false,
properties: {
half: {
type: "string",
enum: [...SCOPE_HALVES],
description: "Which half of the songs: both, composition (the song and its publishing) or master (the recordings).",
},
territories: {
type: "array",
items: { type: "string", pattern: SCOPE_TERRITORY_PATTERN },
minItems: 1,
maxItems: SCOPE_MAX_TERRITORIES,
description: 'Countries covered. Only ["WW"] (worldwide) is accepted today: limiting a relationship to some countries is not available yet, and any other value is refused.',
},
term_starts_on: {
type: ["string", "null"],
pattern: SCOPE_DATE_PATTERN,
description: "First day covered, YYYY-MM-DD (UTC). Leave out or null to start when it is accepted.",
},
term_ends_on: {
type: ["string", "null"],
pattern: SCOPE_DATE_PATTERN,
description: "Last day covered, YYYY-MM-DD (UTC), inclusive. Leave out or null for until revoked. On that date access ends by itself.",
},
money: {
type: "boolean",
description: "true lets the organisation see statements and earnings in scope.",
},
directory: {
type: "boolean",
description: "true lets an administrator list or hide songs in the public directory and answer enquiries. Administrators only.",
},
catalogue: {
type: "string",
enum: [...SCOPE_CATALOGUES],
description: "whole for the whole catalogue, or chosen for only the songs and recordings named in work_ids and recording_ids.",
},
work_ids: {
type: "array",
items: { type: "string", format: "uuid" },
maxItems: SCOPE_MAX_ITEMS,
description: "Songs covered when catalogue is chosen (ids from pica_works_query). Not allowed for a master-only scope.",
},
recording_ids: {
type: "array",
items: { type: "string", format: "uuid" },
maxItems: SCOPE_MAX_ITEMS,
description: "Recordings covered when catalogue is chosen (ids from pica_recordings_query). Not allowed for a composition-only scope.",
},
},
};
/**
* Convert the tool's `scope` argument to the app's field names. Unknown keys
* are dropped here; the schema above already refuses them at the MCP layer.
*/
export function scopeArgToApi(scope) {
if (!scope)
return undefined;
const out = {};
for (const [arg, api] of Object.entries(SCOPE_FIELD_NAMES)) {
if (scope[arg] !== undefined)
out[api] = scope[arg];
}
return out;
}
//# sourceMappingURL=relationship-scope.js.map