@withpica/mcp-server 2.39.0 → 2.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +109 -11
- package/dist/resources/agent-guide.d.ts +15 -0
- package/dist/resources/agent-guide.d.ts.map +1 -0
- package/dist/resources/agent-guide.js +120 -0
- package/dist/resources/agent-guide.js.map +1 -0
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +64 -0
- package/dist/resources/index.js.map +1 -1
- package/dist/resources/required-schemas.generated.d.ts +128 -0
- package/dist/resources/required-schemas.generated.d.ts.map +1 -0
- package/dist/resources/required-schemas.generated.js +344 -0
- package/dist/resources/required-schemas.generated.js.map +1 -0
- package/dist/resources/required-schemas.source.d.ts +53 -0
- package/dist/resources/required-schemas.source.d.ts.map +1 -0
- package/dist/resources/required-schemas.source.js +127 -0
- package/dist/resources/required-schemas.source.js.map +1 -0
- package/dist/tools/agent-identity.d.ts.map +1 -1
- package/dist/tools/agent-identity.js +5 -0
- package/dist/tools/agent-identity.js.map +1 -1
- package/dist/tools/agreement-types.d.ts.map +1 -1
- package/dist/tools/agreement-types.js +8 -0
- package/dist/tools/agreement-types.js.map +1 -1
- package/dist/tools/agreements.d.ts.map +1 -1
- package/dist/tools/agreements.js +6 -0
- package/dist/tools/agreements.js.map +1 -1
- package/dist/tools/analytics.d.ts.map +1 -1
- package/dist/tools/analytics.js +6 -0
- package/dist/tools/analytics.js.map +1 -1
- package/dist/tools/app-tools.d.ts.map +1 -1
- package/dist/tools/app-tools.js +3 -0
- package/dist/tools/app-tools.js.map +1 -1
- package/dist/tools/assets.d.ts.map +1 -1
- package/dist/tools/assets.js +11 -0
- package/dist/tools/assets.js.map +1 -1
- package/dist/tools/audio-files.d.ts.map +1 -1
- package/dist/tools/audio-files.js +6 -0
- package/dist/tools/audio-files.js.map +1 -1
- package/dist/tools/audit.d.ts +1 -1
- package/dist/tools/audit.d.ts.map +1 -1
- package/dist/tools/audit.js +2 -1
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js +2 -0
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/bulk.d.ts.map +1 -1
- package/dist/tools/bulk.js +2 -0
- package/dist/tools/bulk.js.map +1 -1
- package/dist/tools/calendar.d.ts.map +1 -1
- package/dist/tools/calendar.js +1 -0
- package/dist/tools/calendar.js.map +1 -1
- package/dist/tools/collaborators.d.ts.map +1 -1
- package/dist/tools/collaborators.js +7 -0
- package/dist/tools/collaborators.js.map +1 -1
- package/dist/tools/comparisons.d.ts.map +1 -1
- package/dist/tools/comparisons.js +2 -0
- package/dist/tools/comparisons.js.map +1 -1
- package/dist/tools/credits.d.ts.map +1 -1
- package/dist/tools/credits.js +3 -0
- package/dist/tools/credits.js.map +1 -1
- package/dist/tools/custody.d.ts.map +1 -1
- package/dist/tools/custody.js +6 -0
- package/dist/tools/custody.js.map +1 -1
- package/dist/tools/dashboard.d.ts.map +1 -1
- package/dist/tools/dashboard.js +8 -0
- package/dist/tools/dashboard.js.map +1 -1
- package/dist/tools/directory.d.ts.map +1 -1
- package/dist/tools/directory.js +1 -0
- package/dist/tools/directory.js.map +1 -1
- package/dist/tools/discovery.d.ts.map +1 -1
- package/dist/tools/discovery.js +3 -0
- package/dist/tools/discovery.js.map +1 -1
- package/dist/tools/disputes.d.ts.map +1 -1
- package/dist/tools/disputes.js +1 -0
- package/dist/tools/disputes.js.map +1 -1
- package/dist/tools/documents.d.ts.map +1 -1
- package/dist/tools/documents.js +1 -0
- package/dist/tools/documents.js.map +1 -1
- package/dist/tools/duplicates.d.ts.map +1 -1
- package/dist/tools/duplicates.js +2 -0
- package/dist/tools/duplicates.js.map +1 -1
- package/dist/tools/enrichment.d.ts.map +1 -1
- package/dist/tools/enrichment.js +11 -0
- package/dist/tools/enrichment.js.map +1 -1
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/exports.js +5 -0
- package/dist/tools/exports.js.map +1 -1
- package/dist/tools/feedback.d.ts.map +1 -1
- package/dist/tools/feedback.js +1 -0
- package/dist/tools/feedback.js.map +1 -1
- package/dist/tools/import-documents.d.ts.map +1 -1
- package/dist/tools/import-documents.js +3 -0
- package/dist/tools/import-documents.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +8 -0
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/index.d.ts +21 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/integrations.d.ts.map +1 -1
- package/dist/tools/integrations.js +1 -0
- package/dist/tools/integrations.js.map +1 -1
- package/dist/tools/labels.d.ts.map +1 -1
- package/dist/tools/labels.js +1 -0
- package/dist/tools/labels.js.map +1 -1
- package/dist/tools/licensing.d.ts.map +1 -1
- package/dist/tools/licensing.js +5 -0
- package/dist/tools/licensing.js.map +1 -1
- package/dist/tools/memory.d.ts.map +1 -1
- package/dist/tools/memory.js +4 -0
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/multimedia.d.ts.map +1 -1
- package/dist/tools/multimedia.js +5 -0
- package/dist/tools/multimedia.js.map +1 -1
- package/dist/tools/my-reported-issues.d.ts.map +1 -1
- package/dist/tools/my-reported-issues.js +1 -0
- package/dist/tools/my-reported-issues.js.map +1 -1
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +4 -0
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notifications.d.ts.map +1 -1
- package/dist/tools/notifications.js +6 -0
- package/dist/tools/notifications.js.map +1 -1
- package/dist/tools/onboarding.d.ts.map +1 -1
- package/dist/tools/onboarding.js +1 -0
- package/dist/tools/onboarding.js.map +1 -1
- package/dist/tools/people.d.ts.map +1 -1
- package/dist/tools/people.js +5 -0
- package/dist/tools/people.js.map +1 -1
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +6 -0
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/publishers.d.ts.map +1 -1
- package/dist/tools/publishers.js +2 -0
- package/dist/tools/publishers.js.map +1 -1
- package/dist/tools/recordings.d.ts.map +1 -1
- package/dist/tools/recordings.js +5 -0
- package/dist/tools/recordings.js.map +1 -1
- package/dist/tools/release-rich.d.ts.map +1 -1
- package/dist/tools/release-rich.js +1 -0
- package/dist/tools/release-rich.js.map +1 -1
- package/dist/tools/releases.d.ts.map +1 -1
- package/dist/tools/releases.js +14 -0
- package/dist/tools/releases.js.map +1 -1
- package/dist/tools/report-issue.d.ts.map +1 -1
- package/dist/tools/report-issue.js +1 -0
- package/dist/tools/report-issue.js.map +1 -1
- package/dist/tools/royalties.d.ts.map +1 -1
- package/dist/tools/royalties.js +5 -0
- package/dist/tools/royalties.js.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +3 -0
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/send.d.ts.map +1 -1
- package/dist/tools/send.js +3 -0
- package/dist/tools/send.js.map +1 -1
- package/dist/tools/sessions.d.ts.map +1 -1
- package/dist/tools/sessions.js +4 -0
- package/dist/tools/sessions.js.map +1 -1
- package/dist/tools/settings.d.ts.map +1 -1
- package/dist/tools/settings.js +9 -0
- package/dist/tools/settings.js.map +1 -1
- package/dist/tools/share-links.d.ts.map +1 -1
- package/dist/tools/share-links.js +5 -0
- package/dist/tools/share-links.js.map +1 -1
- package/dist/tools/signup.d.ts.map +1 -1
- package/dist/tools/signup.js +1 -0
- package/dist/tools/signup.js.map +1 -1
- package/dist/tools/split-sheets.d.ts.map +1 -1
- package/dist/tools/split-sheets.js +7 -0
- package/dist/tools/split-sheets.js.map +1 -1
- package/dist/tools/subscription.d.ts.map +1 -1
- package/dist/tools/subscription.js +2 -0
- package/dist/tools/subscription.js.map +1 -1
- package/dist/tools/team.d.ts.map +1 -1
- package/dist/tools/team.js +5 -0
- package/dist/tools/team.js.map +1 -1
- package/dist/tools/telegram.d.ts.map +1 -1
- package/dist/tools/telegram.js +3 -0
- package/dist/tools/telegram.js.map +1 -1
- package/dist/tools/uploads.d.ts.map +1 -1
- package/dist/tools/uploads.js +2 -0
- package/dist/tools/uploads.js.map +1 -1
- package/dist/tools/works.d.ts.map +1 -1
- package/dist/tools/works.js +7 -0
- package/dist/tools/works.js.map +1 -1
- package/package.json +7 -3
- package/scripts/build-required-schemas.ts +233 -0
- package/scripts/refresh-schema-mirror.ts +182 -0
- package/server.json +2 -2
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ADR-214 — generator for `required-schemas.generated.ts`.
|
|
5
|
+
*
|
|
6
|
+
* Reads three inputs:
|
|
7
|
+
* 1. ToolRegistry (instantiated from src) → tool inputSchemas
|
|
8
|
+
* 2. mcp-server/src/resources/required-schemas.source.ts → narrative + companion_calls
|
|
9
|
+
* 3. mcp-server/src/resources/schema-mirror.json → CHECK constraint enums
|
|
10
|
+
*
|
|
11
|
+
* Joins them and emits `mcp-server/src/resources/required-schemas.generated.ts`,
|
|
12
|
+
* a TypeScript module exporting `REQUIRED_SCHEMAS` consumed by ResourceRegistry.
|
|
13
|
+
*
|
|
14
|
+
* Determinism: object keys sorted alphabetically, arrays preserved in source
|
|
15
|
+
* order. Re-running with no source changes produces a byte-identical file.
|
|
16
|
+
*
|
|
17
|
+
* Wired into mcp-server/package.json as part of `prebuild` (after
|
|
18
|
+
* bundle-apps.ts). Runs via tsx; no build step required.
|
|
19
|
+
*
|
|
20
|
+
* Failure modes:
|
|
21
|
+
* - source.ts references a tool not in ToolRegistry → exit 1, name file to fix
|
|
22
|
+
* - companion_calls[].tool not in ToolRegistry → exit 1
|
|
23
|
+
* - enum_tables references missing table/column in mirror → exit 1
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { dirname, join } from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
29
|
+
import {
|
|
30
|
+
REQUIRED_SCHEMAS_SOURCE,
|
|
31
|
+
type RequiredSchemaSource,
|
|
32
|
+
} from "../src/resources/required-schemas.source.js";
|
|
33
|
+
import { ToolRegistry, type ToolDefinition } from "../src/tools/index.js";
|
|
34
|
+
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
+
const __dirname = dirname(__filename);
|
|
37
|
+
const MCP_SERVER_ROOT = join(__dirname, "..");
|
|
38
|
+
const MIRROR_PATH = join(
|
|
39
|
+
MCP_SERVER_ROOT,
|
|
40
|
+
"src/resources/schema-mirror.json",
|
|
41
|
+
);
|
|
42
|
+
const OUTPUT_PATH = join(
|
|
43
|
+
MCP_SERVER_ROOT,
|
|
44
|
+
"src/resources/required-schemas.generated.ts",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
interface SchemaMirror {
|
|
48
|
+
[table: string]: string[] | Record<string, string[]> | string | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadMirror(): SchemaMirror {
|
|
52
|
+
const raw = readFileSync(MIRROR_PATH, "utf-8");
|
|
53
|
+
return JSON.parse(raw) as SchemaMirror;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function loadRegistry(): Map<string, ToolDefinition> {
|
|
57
|
+
const stubPica = {} as never;
|
|
58
|
+
const stubConfig = {
|
|
59
|
+
picaApiKey: "stub",
|
|
60
|
+
picaApiUrl: "https://stub.example.com/api",
|
|
61
|
+
serverName: "schema-generator",
|
|
62
|
+
version: "0.0.0",
|
|
63
|
+
debug: false,
|
|
64
|
+
lobbyMode: false,
|
|
65
|
+
credentialsPath: "",
|
|
66
|
+
discoveryMode: false,
|
|
67
|
+
} as never;
|
|
68
|
+
|
|
69
|
+
const registry = new ToolRegistry(
|
|
70
|
+
stubPica,
|
|
71
|
+
stubConfig,
|
|
72
|
+
() => {},
|
|
73
|
+
{ callerIdentity: "schema-generator", transport: "stdio" } as never,
|
|
74
|
+
() => {},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const map = new Map<string, ToolDefinition>();
|
|
78
|
+
// ToolRegistry.tools is private but read-accessible from sibling code in
|
|
79
|
+
// the same workspace (matches existing pattern in scripts/lint-mcp-tools.ts).
|
|
80
|
+
const tools = (registry as unknown as {
|
|
81
|
+
tools: Map<string, { definition: ToolDefinition }>;
|
|
82
|
+
}).tools;
|
|
83
|
+
for (const [name, entry] of tools) {
|
|
84
|
+
map.set(name, entry.definition);
|
|
85
|
+
}
|
|
86
|
+
return map;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function fail(msg: string): never {
|
|
90
|
+
console.error(`[build-required-schemas] ERROR: ${msg}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getRequiredAndProperties(
|
|
95
|
+
tools: Map<string, ToolDefinition>,
|
|
96
|
+
toolName: string,
|
|
97
|
+
source: string,
|
|
98
|
+
): { required: string[]; properties: string[] } {
|
|
99
|
+
const def = tools.get(toolName);
|
|
100
|
+
if (!def) {
|
|
101
|
+
fail(
|
|
102
|
+
`${source} references tool '${toolName}' which is not in ToolRegistry. ` +
|
|
103
|
+
`Edit mcp-server/src/resources/required-schemas.source.ts and remove or rename the reference.`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const schema = def.inputSchema;
|
|
107
|
+
const required = Array.isArray(schema.required) ? [...schema.required] : [];
|
|
108
|
+
const properties = Object.keys(schema.properties ?? {});
|
|
109
|
+
return { required, properties };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function buildEnums(
|
|
113
|
+
mirror: SchemaMirror,
|
|
114
|
+
tables: RequiredSchemaSource["enum_tables"],
|
|
115
|
+
workflow: string,
|
|
116
|
+
): Record<string, string[]> {
|
|
117
|
+
if (!tables || tables.length === 0) return {};
|
|
118
|
+
const out: Record<string, string[]> = {};
|
|
119
|
+
for (const { table, columns } of tables) {
|
|
120
|
+
const tableEntry = mirror[table];
|
|
121
|
+
if (!tableEntry || typeof tableEntry !== "object" || Array.isArray(tableEntry)) {
|
|
122
|
+
fail(
|
|
123
|
+
`Workflow '${workflow}' references table '${table}' missing from schema-mirror.json. ` +
|
|
124
|
+
`Run \`tsx mcp-server/scripts/refresh-schema-mirror.ts\` against staging to regenerate.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const tableMap = tableEntry as Record<string, string[]>;
|
|
128
|
+
for (const col of columns) {
|
|
129
|
+
const values = tableMap[col];
|
|
130
|
+
if (!Array.isArray(values)) {
|
|
131
|
+
fail(
|
|
132
|
+
`Workflow '${workflow}' references ${table}.${col} missing from schema-mirror.json.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
out[col] = [...values].sort();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildCompanion(
|
|
142
|
+
tools: Map<string, ToolDefinition>,
|
|
143
|
+
companion: RequiredSchemaSource["companion_calls"][number],
|
|
144
|
+
workflow: string,
|
|
145
|
+
): Record<string, unknown> {
|
|
146
|
+
// Verify the tool exists; we don't pull required[] from its inputSchema
|
|
147
|
+
// since companion_calls already declare what the call requires (often a
|
|
148
|
+
// wrapper/batch shape rather than the raw entity required-fields).
|
|
149
|
+
if (!tools.has(companion.tool)) {
|
|
150
|
+
fail(
|
|
151
|
+
`Workflow '${workflow}' companion '${companion.tool}' not in ToolRegistry. ` +
|
|
152
|
+
`Edit required-schemas.source.ts.`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const out: Record<string, unknown> = {
|
|
156
|
+
tool: companion.tool,
|
|
157
|
+
when: companion.when,
|
|
158
|
+
required: companion.required,
|
|
159
|
+
};
|
|
160
|
+
if (companion.per_row_required) {
|
|
161
|
+
out.per_row_required = companion.per_row_required;
|
|
162
|
+
}
|
|
163
|
+
if (companion.constraint) {
|
|
164
|
+
out.constraint = companion.constraint;
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function buildResource(
|
|
170
|
+
tools: Map<string, ToolDefinition>,
|
|
171
|
+
mirror: SchemaMirror,
|
|
172
|
+
source: RequiredSchemaSource,
|
|
173
|
+
): Record<string, unknown> {
|
|
174
|
+
const { required, properties } = getRequiredAndProperties(
|
|
175
|
+
tools,
|
|
176
|
+
source.primary_tool,
|
|
177
|
+
`Workflow '${source.workflow}' primary_tool`,
|
|
178
|
+
);
|
|
179
|
+
const recommended = properties.filter((p) => !required.includes(p));
|
|
180
|
+
const enums = buildEnums(mirror, source.enum_tables, source.workflow);
|
|
181
|
+
const companions = source.companion_calls.map((c) =>
|
|
182
|
+
buildCompanion(tools, c, source.workflow),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
workflow: source.workflow,
|
|
187
|
+
summary: source.summary,
|
|
188
|
+
primary_tool: source.primary_tool,
|
|
189
|
+
primary_required: required,
|
|
190
|
+
primary_recommended: recommended,
|
|
191
|
+
enums,
|
|
192
|
+
companion_calls: companions,
|
|
193
|
+
example: source.example,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function emit(resources: Record<string, Record<string, unknown>>): string {
|
|
198
|
+
// Sort keys deterministically so re-runs produce byte-identical output.
|
|
199
|
+
const sortedKeys = Object.keys(resources).sort();
|
|
200
|
+
const sortedResources: Record<string, Record<string, unknown>> = {};
|
|
201
|
+
for (const k of sortedKeys) sortedResources[k] = resources[k];
|
|
202
|
+
|
|
203
|
+
const json = JSON.stringify(sortedResources, null, 2);
|
|
204
|
+
return [
|
|
205
|
+
"// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.",
|
|
206
|
+
"//",
|
|
207
|
+
"// AUTO-GENERATED by mcp-server/scripts/build-required-schemas.ts.",
|
|
208
|
+
"// Do not edit by hand. Re-run via `cd mcp-server && npm run prebuild`.",
|
|
209
|
+
"// Source: required-schemas.source.ts + schema-mirror.json + ToolRegistry inputSchemas.",
|
|
210
|
+
"// ADR-214 — required-fields contracts per workflow.",
|
|
211
|
+
"",
|
|
212
|
+
"export const REQUIRED_SCHEMAS = " + json + " as const;",
|
|
213
|
+
"",
|
|
214
|
+
"export type RequiredSchemaKey = keyof typeof REQUIRED_SCHEMAS;",
|
|
215
|
+
"",
|
|
216
|
+
].join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function main(): void {
|
|
220
|
+
const tools = loadRegistry();
|
|
221
|
+
const mirror = loadMirror();
|
|
222
|
+
const out: Record<string, Record<string, unknown>> = {};
|
|
223
|
+
for (const [key, source] of Object.entries(REQUIRED_SCHEMAS_SOURCE)) {
|
|
224
|
+
out[key] = buildResource(tools, mirror, source);
|
|
225
|
+
}
|
|
226
|
+
const text = emit(out);
|
|
227
|
+
writeFileSync(OUTPUT_PATH, text, "utf-8");
|
|
228
|
+
console.log(
|
|
229
|
+
`[build-required-schemas] wrote ${Object.keys(out).length} schemas to ${OUTPUT_PATH}`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
main();
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ADR-214 — refresh schema-mirror.json against staging or prod.
|
|
5
|
+
*
|
|
6
|
+
* Connects to the configured Postgres URL (env var SUPABASE_DB_URL or
|
|
7
|
+
* DATABASE_URL) and queries pg_constraint for CHECK constraints on the
|
|
8
|
+
* tables backing Phase 1 schema resources. Parses the constraint
|
|
9
|
+
* definitions, extracts enum values, writes a deterministic JSON file at
|
|
10
|
+
* mcp-server/src/resources/schema-mirror.json.
|
|
11
|
+
*
|
|
12
|
+
* NOT run by CI — humans run this when they suspect or know that a CHECK
|
|
13
|
+
* constraint changed in production. The mirror is committed to git so PR
|
|
14
|
+
* diffs surface drift.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* SUPABASE_DB_URL=postgres://... npx tsx mcp-server/scripts/refresh-schema-mirror.ts
|
|
18
|
+
*
|
|
19
|
+
* Optional dep: `pg` (added as devDependency on mcp-server).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { dirname, join } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = dirname(__filename);
|
|
28
|
+
const MIRROR_PATH = join(
|
|
29
|
+
__dirname,
|
|
30
|
+
"..",
|
|
31
|
+
"src/resources/schema-mirror.json",
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
interface MirrorContent {
|
|
35
|
+
_generated_at: string;
|
|
36
|
+
_source: string;
|
|
37
|
+
_note: string;
|
|
38
|
+
[table: string]:
|
|
39
|
+
| string
|
|
40
|
+
| Record<string, string[]>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const TABLES_TO_MIRROR: Array<{ table: string; columns: string[] }> = [
|
|
44
|
+
{ table: "audio_files", columns: ["classification", "file_type"] },
|
|
45
|
+
{ table: "recording_credits", columns: ["role"] },
|
|
46
|
+
{ table: "recordings", columns: ["version_type"] },
|
|
47
|
+
{
|
|
48
|
+
table: "work_credits",
|
|
49
|
+
columns: ["attestation_status", "credit_type", "rights_type"],
|
|
50
|
+
},
|
|
51
|
+
{ table: "works", columns: ["work_status", "work_type"] },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
function fail(msg: string): never {
|
|
55
|
+
console.error(`[refresh-schema-mirror] ERROR: ${msg}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse a CHECK constraint definition string for ANY-of-array shapes.
|
|
61
|
+
* Postgres formats vary; common shapes:
|
|
62
|
+
* CHECK ((work_type = ANY (ARRAY['song'::text, 'instrumental'::text, ...])))
|
|
63
|
+
* CHECK ((work_type::text = ANY (ARRAY['song'::text, ...])))
|
|
64
|
+
* CHECK (work_type IN ('song', 'instrumental', ...))
|
|
65
|
+
* Returns the literal values in source order; caller sorts for determinism.
|
|
66
|
+
*/
|
|
67
|
+
function parseEnumFromCheckDef(def: string): string[] | null {
|
|
68
|
+
// Match ARRAY[...] form
|
|
69
|
+
const arrayMatch = def.match(/ARRAY\[([^\]]+)\]/);
|
|
70
|
+
if (arrayMatch) {
|
|
71
|
+
const inside = arrayMatch[1];
|
|
72
|
+
const literals = [...inside.matchAll(/'([^']+)'/g)].map((m) => m[1]);
|
|
73
|
+
return literals.length > 0 ? literals : null;
|
|
74
|
+
}
|
|
75
|
+
// Match IN (...) form
|
|
76
|
+
const inMatch = def.match(/IN\s*\(([^)]+)\)/i);
|
|
77
|
+
if (inMatch) {
|
|
78
|
+
const inside = inMatch[1];
|
|
79
|
+
const literals = [...inside.matchAll(/'([^']+)'/g)].map((m) => m[1]);
|
|
80
|
+
return literals.length > 0 ? literals : null;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function main(): Promise<void> {
|
|
86
|
+
const dbUrl = process.env.SUPABASE_DB_URL ?? process.env.DATABASE_URL;
|
|
87
|
+
if (!dbUrl) {
|
|
88
|
+
fail(
|
|
89
|
+
"SUPABASE_DB_URL (or DATABASE_URL) env var not set. Provide a Postgres connection string for staging or prod.",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Lazy-import pg so the file type-checks even if pg isn't installed.
|
|
94
|
+
// Generator is the hot path; refresh is operator-only.
|
|
95
|
+
let Client: typeof import("pg").Client;
|
|
96
|
+
try {
|
|
97
|
+
({ Client } = await import("pg"));
|
|
98
|
+
} catch {
|
|
99
|
+
fail(
|
|
100
|
+
"pg npm package not installed. Run: cd mcp-server && npm install --save-dev pg @types/pg",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const client = new Client({ connectionString: dbUrl });
|
|
105
|
+
await client.connect();
|
|
106
|
+
|
|
107
|
+
const out: Record<string, Record<string, string[]>> = {};
|
|
108
|
+
|
|
109
|
+
for (const { table, columns } of TABLES_TO_MIRROR) {
|
|
110
|
+
out[table] = {};
|
|
111
|
+
for (const col of columns) {
|
|
112
|
+
// Find a CHECK constraint on this column. Look for any constraint
|
|
113
|
+
// whose pg_get_constraintdef text references the column name and
|
|
114
|
+
// contains an ANY/IN enum list.
|
|
115
|
+
const result = await client.query<{ def: string }>(
|
|
116
|
+
`
|
|
117
|
+
SELECT pg_get_constraintdef(oid) AS def
|
|
118
|
+
FROM pg_constraint
|
|
119
|
+
WHERE conrelid = $1::regclass
|
|
120
|
+
AND contype = 'c'
|
|
121
|
+
AND pg_get_constraintdef(oid) ILIKE $2
|
|
122
|
+
`,
|
|
123
|
+
[`public.${table}`, `%${col}%`],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
let values: string[] | null = null;
|
|
127
|
+
for (const row of result.rows) {
|
|
128
|
+
const parsed = parseEnumFromCheckDef(row.def);
|
|
129
|
+
if (parsed && parsed.length > 0) {
|
|
130
|
+
values = parsed;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!values) {
|
|
135
|
+
console.warn(
|
|
136
|
+
`[refresh-schema-mirror] warn: no enum CHECK found for ${table}.${col}; leaving empty array`,
|
|
137
|
+
);
|
|
138
|
+
values = [];
|
|
139
|
+
}
|
|
140
|
+
out[table][col] = [...values].sort();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await client.end();
|
|
145
|
+
|
|
146
|
+
// Preserve metadata from existing mirror if present.
|
|
147
|
+
let existingNote =
|
|
148
|
+
"CHECK constraint enums for tables backing ADR-214 Phase 1 schemas. Refresh via `npx tsx mcp-server/scripts/refresh-schema-mirror.ts` against staging. Committed so PR diffs surface schema drift. See ADR-214 Decision section, Drift Prevention #3.";
|
|
149
|
+
try {
|
|
150
|
+
const prev = JSON.parse(
|
|
151
|
+
readFileSync(MIRROR_PATH, "utf-8"),
|
|
152
|
+
) as MirrorContent;
|
|
153
|
+
if (typeof prev._note === "string") existingNote = prev._note;
|
|
154
|
+
} catch {
|
|
155
|
+
// first run — fine
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const sortedTables = Object.keys(out).sort();
|
|
159
|
+
const sortedOut: Record<string, Record<string, string[]>> = {};
|
|
160
|
+
for (const t of sortedTables) {
|
|
161
|
+
const cols = Object.keys(out[t]).sort();
|
|
162
|
+
sortedOut[t] = {};
|
|
163
|
+
for (const c of cols) sortedOut[t][c] = out[t][c];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const final: MirrorContent = {
|
|
167
|
+
_generated_at: new Date().toISOString(),
|
|
168
|
+
_source: dbUrl.replace(/:[^:@]+@/, ":***@"),
|
|
169
|
+
_note: existingNote,
|
|
170
|
+
...sortedOut,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
writeFileSync(MIRROR_PATH, JSON.stringify(final, null, 2) + "\n", "utf-8");
|
|
174
|
+
console.log(
|
|
175
|
+
`[refresh-schema-mirror] wrote ${sortedTables.length} tables to ${MIRROR_PATH}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
main().catch((err) => {
|
|
180
|
+
console.error(err);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
});
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/withpica/pica",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.40.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@withpica/mcp-server",
|
|
14
|
-
"version": "2.
|
|
14
|
+
"version": "2.40.0",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|