@thehammer/schema-mcp-server 1.0.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/dist/api-client.d.ts +42 -0
- package/dist/api-client.js +168 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +583 -0
- package/package.json +27 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for communicating with the gpt-manager Laravel API.
|
|
3
|
+
*
|
|
4
|
+
* All requests include the Bearer token for SchemaMcpAuthMiddleware
|
|
5
|
+
* and target the schema-scoped MCP endpoints.
|
|
6
|
+
*/
|
|
7
|
+
declare const SCHEMA_ID: string;
|
|
8
|
+
interface ApiResponse {
|
|
9
|
+
data?: unknown;
|
|
10
|
+
schema?: unknown;
|
|
11
|
+
error?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
export declare function mcpPath(endpoint: string): string;
|
|
16
|
+
export declare function getFullContext(): Promise<ApiResponse>;
|
|
17
|
+
export declare function updateModel(data: Record<string, unknown>, message?: string): Promise<ApiResponse>;
|
|
18
|
+
export declare function removeModel(path: string | null, title: string, message?: string): Promise<ApiResponse>;
|
|
19
|
+
export declare function updateRoot(properties: Record<string, unknown>, message?: string): Promise<ApiResponse>;
|
|
20
|
+
export declare function getWorkflowInputs(): Promise<ApiResponse>;
|
|
21
|
+
export declare function getWorkflowInputPages(workflowInputId: number): Promise<ApiResponse>;
|
|
22
|
+
export declare function getChatHistory(limit?: number): Promise<ApiResponse>;
|
|
23
|
+
export declare function listAnnotations(): Promise<ApiResponse>;
|
|
24
|
+
export declare function applyAnnotationAction(id: number | null, action: string, data?: Record<string, unknown>, message?: string): Promise<ApiResponse>;
|
|
25
|
+
export declare function listBlueprints(): Promise<ApiResponse>;
|
|
26
|
+
export declare function listDirectives(): Promise<ApiResponse>;
|
|
27
|
+
export declare function applyDirectiveAction(id: number | null, action: string, data?: Record<string, unknown>, message?: string): Promise<ApiResponse>;
|
|
28
|
+
export declare function listTemplates(): Promise<ApiResponse>;
|
|
29
|
+
export declare function getTemplateDetails(id: number): Promise<ApiResponse>;
|
|
30
|
+
export declare function createTemplate(data: Record<string, unknown>, message?: string): Promise<ApiResponse>;
|
|
31
|
+
export declare function applyTemplateAction(id: number, action: string, data?: Record<string, unknown>, message?: string): Promise<ApiResponse>;
|
|
32
|
+
export declare function patchTemplate(id: number, edits: Array<Record<string, unknown>>, message?: string): Promise<ApiResponse>;
|
|
33
|
+
export declare function getTemplateExamplePages(templateId: number): Promise<ApiResponse>;
|
|
34
|
+
export declare function getTemplateSampleData(templateId: number): Promise<ApiResponse>;
|
|
35
|
+
export declare function setTemplateSampleData(templateId: number, sampleData: Record<string, unknown>, message?: string): Promise<ApiResponse>;
|
|
36
|
+
export declare function getTemplatePreview(templateId: number, teamObjectId?: number, message?: string): Promise<ApiResponse>;
|
|
37
|
+
export declare function getTemplatePreviewHtml(templateId: number, teamObjectId?: number, message?: string): Promise<ApiResponse>;
|
|
38
|
+
export declare function getStyleList(): Promise<ApiResponse>;
|
|
39
|
+
export declare function listQualityGateItems(): Promise<ApiResponse>;
|
|
40
|
+
export declare function submitQualityGateReview(itemId: number, status: string, analysis: string, message?: string): Promise<ApiResponse>;
|
|
41
|
+
export declare function postProgress(progressMessage: string, phase?: string): Promise<ApiResponse>;
|
|
42
|
+
export { SCHEMA_ID };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for communicating with the gpt-manager Laravel API.
|
|
3
|
+
*
|
|
4
|
+
* All requests include the Bearer token for SchemaMcpAuthMiddleware
|
|
5
|
+
* and target the schema-scoped MCP endpoints.
|
|
6
|
+
*/
|
|
7
|
+
const API_URL = process.env.SCHEMA_API_URL || "http://localhost:80";
|
|
8
|
+
const API_TOKEN = process.env.SCHEMA_API_TOKEN || "";
|
|
9
|
+
const SCHEMA_ID = process.env.SCHEMA_DEFINITION_ID || "";
|
|
10
|
+
if (!API_TOKEN) {
|
|
11
|
+
console.error("SCHEMA_API_TOKEN is required");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
if (!SCHEMA_ID) {
|
|
15
|
+
console.error("SCHEMA_DEFINITION_ID is required");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
async function apiRequest(method, path, body) {
|
|
19
|
+
const url = `${API_URL}/api/${path}`;
|
|
20
|
+
const options = {
|
|
21
|
+
method,
|
|
22
|
+
headers: {
|
|
23
|
+
Authorization: `Bearer ${API_TOKEN}`,
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
Accept: "application/json",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
if (body && method !== "GET") {
|
|
29
|
+
options.body = JSON.stringify(body);
|
|
30
|
+
}
|
|
31
|
+
const response = await fetch(url, options);
|
|
32
|
+
const json = (await response.json());
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const errorMessage = json.message || json.error || `HTTP ${response.status}`;
|
|
35
|
+
throw new Error(`API Error: ${errorMessage}`);
|
|
36
|
+
}
|
|
37
|
+
return json;
|
|
38
|
+
}
|
|
39
|
+
// --- Schema MCP Endpoints ---
|
|
40
|
+
export function mcpPath(endpoint) {
|
|
41
|
+
return `schemas/mcp/${SCHEMA_ID}/${endpoint}`;
|
|
42
|
+
}
|
|
43
|
+
export async function getFullContext() {
|
|
44
|
+
return apiRequest("GET", mcpPath("full-context"));
|
|
45
|
+
}
|
|
46
|
+
export async function updateModel(data, message) {
|
|
47
|
+
return apiRequest("POST", mcpPath("update-model"), {
|
|
48
|
+
...data,
|
|
49
|
+
message,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export async function removeModel(path, title, message) {
|
|
53
|
+
return apiRequest("POST", mcpPath("remove-model"), {
|
|
54
|
+
path,
|
|
55
|
+
title,
|
|
56
|
+
message,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function updateRoot(properties, message) {
|
|
60
|
+
return apiRequest("POST", mcpPath("update-root"), { properties, message });
|
|
61
|
+
}
|
|
62
|
+
export async function getWorkflowInputs() {
|
|
63
|
+
return apiRequest("GET", mcpPath("workflow-inputs"));
|
|
64
|
+
}
|
|
65
|
+
export async function getWorkflowInputPages(workflowInputId) {
|
|
66
|
+
return apiRequest("GET", mcpPath(`workflow-input-pages/${workflowInputId}`));
|
|
67
|
+
}
|
|
68
|
+
export async function getChatHistory(limit) {
|
|
69
|
+
const query = limit ? `?limit=${limit}` : "";
|
|
70
|
+
return apiRequest("GET", mcpPath(`chat-history${query}`));
|
|
71
|
+
}
|
|
72
|
+
// --- MCP-Scoped Annotation, Directive, Template Endpoints ---
|
|
73
|
+
export async function listAnnotations() {
|
|
74
|
+
return apiRequest("GET", mcpPath("annotations"));
|
|
75
|
+
}
|
|
76
|
+
export async function applyAnnotationAction(id, action, data, message) {
|
|
77
|
+
return apiRequest("POST", mcpPath("annotations/action"), {
|
|
78
|
+
action,
|
|
79
|
+
annotation_id: id,
|
|
80
|
+
...data,
|
|
81
|
+
message,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export async function listBlueprints() {
|
|
85
|
+
return apiRequest("GET", mcpPath("blueprints"));
|
|
86
|
+
}
|
|
87
|
+
export async function listDirectives() {
|
|
88
|
+
return apiRequest("GET", mcpPath("directives"));
|
|
89
|
+
}
|
|
90
|
+
export async function applyDirectiveAction(id, action, data, message) {
|
|
91
|
+
return apiRequest("POST", mcpPath("directives/action"), {
|
|
92
|
+
action,
|
|
93
|
+
directive_id: id,
|
|
94
|
+
...data,
|
|
95
|
+
message,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
export async function listTemplates() {
|
|
99
|
+
return apiRequest("GET", mcpPath("templates"));
|
|
100
|
+
}
|
|
101
|
+
export async function getTemplateDetails(id) {
|
|
102
|
+
return apiRequest("GET", mcpPath(`templates/${id}`));
|
|
103
|
+
}
|
|
104
|
+
export async function createTemplate(data, message) {
|
|
105
|
+
return apiRequest("POST", mcpPath("templates/action"), {
|
|
106
|
+
...data,
|
|
107
|
+
message,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
export async function applyTemplateAction(id, action, data, message) {
|
|
111
|
+
return apiRequest("POST", mcpPath(`templates/${id}/action`), {
|
|
112
|
+
action,
|
|
113
|
+
...data,
|
|
114
|
+
message,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export async function patchTemplate(id, edits, message) {
|
|
118
|
+
return apiRequest("POST", mcpPath(`templates/${id}/patch`), {
|
|
119
|
+
edits,
|
|
120
|
+
message,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
export async function getTemplateExamplePages(templateId) {
|
|
124
|
+
return apiRequest("GET", mcpPath(`templates/${templateId}/example-pages`));
|
|
125
|
+
}
|
|
126
|
+
export async function getTemplateSampleData(templateId) {
|
|
127
|
+
return apiRequest("GET", mcpPath(`templates/${templateId}/sample-data`));
|
|
128
|
+
}
|
|
129
|
+
export async function setTemplateSampleData(templateId, sampleData, message) {
|
|
130
|
+
return apiRequest("POST", mcpPath(`templates/${templateId}/sample-data`), {
|
|
131
|
+
sample_data: sampleData,
|
|
132
|
+
message,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
export async function getTemplatePreview(templateId, teamObjectId, message) {
|
|
136
|
+
return apiRequest("POST", mcpPath(`templates/${templateId}/preview`), {
|
|
137
|
+
...(teamObjectId ? { team_object_id: teamObjectId } : {}),
|
|
138
|
+
...(message ? { message } : {}),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
export async function getTemplatePreviewHtml(templateId, teamObjectId, message) {
|
|
142
|
+
return apiRequest("POST", mcpPath(`templates/${templateId}/preview-html`), {
|
|
143
|
+
...(teamObjectId ? { team_object_id: teamObjectId } : {}),
|
|
144
|
+
...(message ? { message } : {}),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// --- Style Library Endpoints ---
|
|
148
|
+
export async function getStyleList() {
|
|
149
|
+
return apiRequest("GET", mcpPath("style-list"));
|
|
150
|
+
}
|
|
151
|
+
// --- Quality Gate Endpoints ---
|
|
152
|
+
export async function listQualityGateItems() {
|
|
153
|
+
return apiRequest("GET", mcpPath("quality-gate-items"));
|
|
154
|
+
}
|
|
155
|
+
export async function submitQualityGateReview(itemId, status, analysis, message) {
|
|
156
|
+
return apiRequest("POST", mcpPath(`quality-gate-items/${itemId}/review`), {
|
|
157
|
+
status,
|
|
158
|
+
analysis,
|
|
159
|
+
message,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
export async function postProgress(progressMessage, phase) {
|
|
163
|
+
return apiRequest("POST", mcpPath("progress"), {
|
|
164
|
+
message: progressMessage,
|
|
165
|
+
phase,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
export { SCHEMA_ID };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Schema MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Lightweight stdio MCP server that translates Claude Code tool calls
|
|
6
|
+
* into HTTP requests to the gpt-manager Laravel API.
|
|
7
|
+
*
|
|
8
|
+
* Every mutation tool includes an optional `message` parameter — a human-readable
|
|
9
|
+
* description of what you're doing. This message is logged as an AgentDispatchEvent
|
|
10
|
+
* and shown in the user's real-time activity feed.
|
|
11
|
+
*
|
|
12
|
+
* Environment variables:
|
|
13
|
+
* SCHEMA_API_URL - Laravel API base URL (default: http://localhost:80)
|
|
14
|
+
* SCHEMA_API_TOKEN - Bearer token for SchemaMcpAuthMiddleware
|
|
15
|
+
* SCHEMA_DEFINITION_ID - ID of the schema being built
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Schema MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Lightweight stdio MCP server that translates Claude Code tool calls
|
|
6
|
+
* into HTTP requests to the gpt-manager Laravel API.
|
|
7
|
+
*
|
|
8
|
+
* Every mutation tool includes an optional `message` parameter — a human-readable
|
|
9
|
+
* description of what you're doing. This message is logged as an AgentDispatchEvent
|
|
10
|
+
* and shown in the user's real-time activity feed.
|
|
11
|
+
*
|
|
12
|
+
* Environment variables:
|
|
13
|
+
* SCHEMA_API_URL - Laravel API base URL (default: http://localhost:80)
|
|
14
|
+
* SCHEMA_API_TOKEN - Bearer token for SchemaMcpAuthMiddleware
|
|
15
|
+
* SCHEMA_DEFINITION_ID - ID of the schema being built
|
|
16
|
+
*/
|
|
17
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import * as api from "./api-client.js";
|
|
21
|
+
const server = new McpServer({
|
|
22
|
+
name: "schema-mcp-server",
|
|
23
|
+
version: "1.0.0",
|
|
24
|
+
});
|
|
25
|
+
/** Wrap API response data into MCP tool result format. */
|
|
26
|
+
function jsonResult(data) {
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{ type: "text", text: JSON.stringify(data, null, 2) },
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wrap API response data into MCP tool result format with embedded images.
|
|
35
|
+
*
|
|
36
|
+
* Extracts base64-encoded images from the API response and includes them as
|
|
37
|
+
* MCP image content blocks so the agent can actually see the images (URLs alone
|
|
38
|
+
* are just text strings to the model). Strips the base64 data from the JSON
|
|
39
|
+
* text to avoid duplication.
|
|
40
|
+
*/
|
|
41
|
+
function imageResult(data, base64Key = "images_base64") {
|
|
42
|
+
const content = [];
|
|
43
|
+
// Extract base64 images from top-level or nested data
|
|
44
|
+
// Each entry is {data: string, mime_type: string} from the Laravel API
|
|
45
|
+
const responseData = (data?.data ?? data);
|
|
46
|
+
const base64Images = responseData?.[base64Key] ?? [];
|
|
47
|
+
// Add each image as an MCP image content block
|
|
48
|
+
for (const img of base64Images) {
|
|
49
|
+
content.push({
|
|
50
|
+
type: "image",
|
|
51
|
+
data: img.data,
|
|
52
|
+
mimeType: img.mime_type || "image/jpeg",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Strip base64 data from JSON to avoid bloating the text output
|
|
56
|
+
const cleanData = JSON.parse(JSON.stringify(data));
|
|
57
|
+
if (cleanData?.data?.[base64Key]) {
|
|
58
|
+
delete cleanData.data[base64Key];
|
|
59
|
+
}
|
|
60
|
+
content.push({
|
|
61
|
+
type: "text",
|
|
62
|
+
text: JSON.stringify(cleanData, null, 2),
|
|
63
|
+
});
|
|
64
|
+
return { content };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Wrap an array of pages (each potentially with images) into MCP image+text result.
|
|
68
|
+
*
|
|
69
|
+
* Used for multi-page responses like template_get_example_pages where each page
|
|
70
|
+
* entry may have its own images_base64 array.
|
|
71
|
+
*/
|
|
72
|
+
function multiPageImageResult(data) {
|
|
73
|
+
const content = [];
|
|
74
|
+
const pages = (data?.data ?? []);
|
|
75
|
+
const cleanPages = [];
|
|
76
|
+
for (const page of pages) {
|
|
77
|
+
// Each entry is {data: string, mime_type: string} from the Laravel API
|
|
78
|
+
const base64Images = page?.images_base64 ?? [];
|
|
79
|
+
// Add page label before its images
|
|
80
|
+
if (base64Images.length > 0 && page.filename) {
|
|
81
|
+
content.push({
|
|
82
|
+
type: "text",
|
|
83
|
+
text: `--- ${page.filename} ---`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
for (const img of base64Images) {
|
|
87
|
+
content.push({
|
|
88
|
+
type: "image",
|
|
89
|
+
data: img.data,
|
|
90
|
+
mimeType: img.mime_type || "image/jpeg",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Strip base64 from the clean copy
|
|
94
|
+
const cleanPage = { ...page };
|
|
95
|
+
delete cleanPage.images_base64;
|
|
96
|
+
cleanPages.push(cleanPage);
|
|
97
|
+
}
|
|
98
|
+
// Add text summary at the end
|
|
99
|
+
content.push({
|
|
100
|
+
type: "text",
|
|
101
|
+
text: JSON.stringify({ data: cleanPages }, null, 2),
|
|
102
|
+
});
|
|
103
|
+
return { content };
|
|
104
|
+
}
|
|
105
|
+
/** Filter out undefined values from an object (for optional update params). */
|
|
106
|
+
function definedOnly(data) {
|
|
107
|
+
return Object.fromEntries(Object.entries(data).filter(([, v]) => v !== undefined));
|
|
108
|
+
}
|
|
109
|
+
/** Common message param for all mutation tools. */
|
|
110
|
+
const messageParam = z
|
|
111
|
+
.string()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Human-readable description of what you're doing (shown in the user's activity feed)");
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Progress Tool
|
|
116
|
+
// ============================================================================
|
|
117
|
+
server.tool("post_progress", "Post a progress update to share your current thinking, plans, and discoveries with the user. Call this before making changes to explain your plan, after making changes to explain what you did, and whenever you discover something that affects your approach.", {
|
|
118
|
+
message: z
|
|
119
|
+
.string()
|
|
120
|
+
.describe("Your progress update message for the user"),
|
|
121
|
+
phase: z
|
|
122
|
+
.string()
|
|
123
|
+
.optional()
|
|
124
|
+
.describe("Current phase (e.g., planning, implementing, reviewing)"),
|
|
125
|
+
}, async ({ message, phase }) => {
|
|
126
|
+
const result = await api.postProgress(message, phase);
|
|
127
|
+
return jsonResult(result);
|
|
128
|
+
});
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Schema Tools
|
|
131
|
+
// ============================================================================
|
|
132
|
+
server.tool("schema_get", "Get the full context for the current schema: schema JSON, annotations, directives, templates, and workflow inputs", {}, async () => {
|
|
133
|
+
const result = await api.getFullContext();
|
|
134
|
+
return jsonResult(result);
|
|
135
|
+
});
|
|
136
|
+
server.tool("schema_update_model", "Create or update a model and its fields. Two modes:\n" +
|
|
137
|
+
"**Model mode** (title provided): Creates model if it doesn't exist, then applies all fields. " +
|
|
138
|
+
"Pass fields as {field_name: {type, description, ...}} to set multiple fields in one call.\n" +
|
|
139
|
+
"**Field mode** (field_name provided): Updates a single field's properties via partial merge.\n" +
|
|
140
|
+
"Use dot-path notation for nested models (e.g., 'client' for fields under client).", {
|
|
141
|
+
path: z
|
|
142
|
+
.string()
|
|
143
|
+
.nullable()
|
|
144
|
+
.describe("Dot-path to the parent model (null for root level)"),
|
|
145
|
+
// Model mode params
|
|
146
|
+
title: z
|
|
147
|
+
.string()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("Human-readable title for the model (e.g., 'Medical Providers'). " +
|
|
150
|
+
"The slug name is auto-derived. Required for model mode."),
|
|
151
|
+
type: z
|
|
152
|
+
.enum(["object", "array"])
|
|
153
|
+
.optional()
|
|
154
|
+
.describe("Type of model to create (required when creating a new model, ignored for existing models)"),
|
|
155
|
+
fields: z
|
|
156
|
+
.record(z.string(), z.record(z.string(), z.unknown()))
|
|
157
|
+
.optional()
|
|
158
|
+
.describe("Fields to create/update on the model. Keys are field names, values are property objects " +
|
|
159
|
+
"(e.g., {type: 'string', description: '...', 'x-identity': true}). Each field is merged with existing properties."),
|
|
160
|
+
// Field mode params
|
|
161
|
+
field_name: z
|
|
162
|
+
.string()
|
|
163
|
+
.optional()
|
|
164
|
+
.describe("Name of a single field to update (field mode). Cannot be combined with title."),
|
|
165
|
+
properties: z
|
|
166
|
+
.record(z.string(), z.unknown())
|
|
167
|
+
.optional()
|
|
168
|
+
.describe("Properties to merge into the single field (field mode only)"),
|
|
169
|
+
message: messageParam,
|
|
170
|
+
}, async ({ path, title, type, fields, field_name, properties, message }) => {
|
|
171
|
+
const data = { path };
|
|
172
|
+
if (title !== undefined) {
|
|
173
|
+
data.title = title;
|
|
174
|
+
if (type !== undefined)
|
|
175
|
+
data.type = type;
|
|
176
|
+
if (fields !== undefined)
|
|
177
|
+
data.fields = fields;
|
|
178
|
+
}
|
|
179
|
+
else if (field_name !== undefined) {
|
|
180
|
+
data.field_name = field_name;
|
|
181
|
+
if (properties !== undefined)
|
|
182
|
+
data.properties = properties;
|
|
183
|
+
}
|
|
184
|
+
const result = await api.updateModel(data, message);
|
|
185
|
+
return jsonResult(result);
|
|
186
|
+
});
|
|
187
|
+
server.tool("schema_remove_model", "Remove a model at a path in the schema. Provide the human-readable title — the slug name is auto-derived.", {
|
|
188
|
+
path: z
|
|
189
|
+
.string()
|
|
190
|
+
.nullable()
|
|
191
|
+
.describe("Dot-path to the parent model (null for root level)"),
|
|
192
|
+
title: z
|
|
193
|
+
.string()
|
|
194
|
+
.describe("Human-readable title of the model to remove (e.g., 'Medical Providers'). The slug name is auto-derived."),
|
|
195
|
+
message: messageParam,
|
|
196
|
+
}, async ({ path, title, message }) => {
|
|
197
|
+
const result = await api.removeModel(path, title, message);
|
|
198
|
+
return jsonResult(result);
|
|
199
|
+
});
|
|
200
|
+
server.tool("schema_update_root", "Update root-level schema metadata (title, description, type). WARNING: This modifies the schema root object itself, NOT the fields inside it. To add fields to the root model, use schema_update_model with path=null in field mode. Only use this tool to change the root's title or description.", {
|
|
201
|
+
properties: z
|
|
202
|
+
.record(z.string(), z.unknown())
|
|
203
|
+
.describe("Properties to merge into the root schema definition"),
|
|
204
|
+
message: messageParam,
|
|
205
|
+
}, async ({ properties, message }) => {
|
|
206
|
+
const result = await api.updateRoot(properties, message);
|
|
207
|
+
return jsonResult(result);
|
|
208
|
+
});
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// Annotation Tools
|
|
211
|
+
// ============================================================================
|
|
212
|
+
server.tool("annotation_list", "List all annotations for the current schema", {}, async () => {
|
|
213
|
+
const result = await api.listAnnotations();
|
|
214
|
+
return jsonResult(result);
|
|
215
|
+
});
|
|
216
|
+
server.tool("annotation_create", "Create a new annotation on the schema (note, rule, question, or research)", {
|
|
217
|
+
category: z
|
|
218
|
+
.enum(["note", "rule", "question", "research"])
|
|
219
|
+
.describe("Annotation category"),
|
|
220
|
+
title: z.string().describe("Short title for the annotation"),
|
|
221
|
+
content: z.string().describe("Markdown content of the annotation"),
|
|
222
|
+
target_fragment_selector: z
|
|
223
|
+
.record(z.string(), z.unknown())
|
|
224
|
+
.optional()
|
|
225
|
+
.describe("Fragment selector targeting a specific schema node"),
|
|
226
|
+
message: messageParam,
|
|
227
|
+
}, async ({ category, title, content, target_fragment_selector, message }) => {
|
|
228
|
+
const result = await api.applyAnnotationAction(null, "create", {
|
|
229
|
+
category,
|
|
230
|
+
title,
|
|
231
|
+
content,
|
|
232
|
+
target_fragment_selector,
|
|
233
|
+
}, message);
|
|
234
|
+
return jsonResult(result);
|
|
235
|
+
});
|
|
236
|
+
server.tool("annotation_update", "Update an existing annotation", {
|
|
237
|
+
annotation_id: z.number().describe("ID of the annotation to update"),
|
|
238
|
+
title: z.string().optional().describe("New title"),
|
|
239
|
+
content: z.string().optional().describe("New content"),
|
|
240
|
+
category: z
|
|
241
|
+
.enum(["note", "rule", "question", "research"])
|
|
242
|
+
.optional()
|
|
243
|
+
.describe("New category"),
|
|
244
|
+
message: messageParam,
|
|
245
|
+
}, async ({ annotation_id, message, ...data }) => {
|
|
246
|
+
const updateData = definedOnly(data);
|
|
247
|
+
const result = await api.applyAnnotationAction(annotation_id, "update", updateData, message);
|
|
248
|
+
return jsonResult(result);
|
|
249
|
+
});
|
|
250
|
+
server.tool("annotation_resolve", "Resolve a question annotation by providing an answer", {
|
|
251
|
+
annotation_id: z
|
|
252
|
+
.number()
|
|
253
|
+
.describe("ID of the question annotation to resolve"),
|
|
254
|
+
resolved_content: z.string().describe("The answer to the question"),
|
|
255
|
+
message: messageParam,
|
|
256
|
+
}, async ({ annotation_id, resolved_content, message }) => {
|
|
257
|
+
const result = await api.applyAnnotationAction(annotation_id, "resolve", { resolved_content }, message);
|
|
258
|
+
return jsonResult(result);
|
|
259
|
+
});
|
|
260
|
+
server.tool("annotation_convert_to_rule", "Convert a question or note annotation to a rule", {
|
|
261
|
+
annotation_id: z.number().describe("ID of the annotation to convert"),
|
|
262
|
+
message: messageParam,
|
|
263
|
+
}, async ({ annotation_id, message }) => {
|
|
264
|
+
const result = await api.applyAnnotationAction(annotation_id, "convert-to-rule", undefined, message);
|
|
265
|
+
return jsonResult(result);
|
|
266
|
+
});
|
|
267
|
+
server.tool("annotation_delete", "Soft-delete an annotation", {
|
|
268
|
+
annotation_id: z.number().describe("ID of the annotation to delete"),
|
|
269
|
+
message: messageParam,
|
|
270
|
+
}, async ({ annotation_id, message }) => {
|
|
271
|
+
const result = await api.applyAnnotationAction(annotation_id, "delete", undefined, message);
|
|
272
|
+
return jsonResult(result);
|
|
273
|
+
});
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// Blueprint Tools
|
|
276
|
+
// ============================================================================
|
|
277
|
+
server.tool("blueprint_list", "List all blueprints for the current schema. A blueprint groups behavior directives into an extraction pipeline. One is auto-created when you create your first directive.", {}, async () => {
|
|
278
|
+
const result = await api.listBlueprints();
|
|
279
|
+
return jsonResult(result);
|
|
280
|
+
});
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// Directive Tools
|
|
283
|
+
// ============================================================================
|
|
284
|
+
server.tool("directive_list", "List all behavior directives for the current schema", {}, async () => {
|
|
285
|
+
const result = await api.listDirectives();
|
|
286
|
+
return jsonResult(result);
|
|
287
|
+
});
|
|
288
|
+
server.tool("directive_create", "Create a new behavior directive. IMPORTANT: The 'type' parameter MUST be one of exactly these three shortcut strings: 'DataExtraction', 'InputOrganization', or 'ArtifactGeneration'. Do NOT use full class names (e.g., do NOT use 'ArtifactGenerationSchemaBehaviorDirective'). The blueprint is auto-resolved.", {
|
|
289
|
+
type: z
|
|
290
|
+
.string()
|
|
291
|
+
.describe("Behavior type: 'DataExtraction', 'InputOrganization', or 'ArtifactGeneration'."),
|
|
292
|
+
name: z.string().describe("Unique name for the directive"),
|
|
293
|
+
label: z.string().describe("Display label"),
|
|
294
|
+
target_fragment_selector: z
|
|
295
|
+
.record(z.string(), z.unknown())
|
|
296
|
+
.optional()
|
|
297
|
+
.describe("Fragment selector targeting a schema node. Determines which model in the hierarchy this directive operates on. Null = root level."),
|
|
298
|
+
data_fragment_selector: z
|
|
299
|
+
.record(z.string(), z.unknown())
|
|
300
|
+
.optional()
|
|
301
|
+
.describe("Fragment selector for which fields to extract (DataExtraction only). Auto-generated if omitted."),
|
|
302
|
+
prompt: z
|
|
303
|
+
.string()
|
|
304
|
+
.optional()
|
|
305
|
+
.describe("Prompt text for the directive. Required for ArtifactGeneration (LLM instruction for generating text) and InputOrganization (guidance for page grouping). Optional for DataExtraction (additional extraction guidance)."),
|
|
306
|
+
settings: z
|
|
307
|
+
.record(z.string(), z.unknown())
|
|
308
|
+
.optional()
|
|
309
|
+
.describe("Directive settings object. EXACT key names required: " +
|
|
310
|
+
"For DataExtraction: {'extraction_mode': 'skim'} or {'extraction_mode': 'exhaustive'} — key MUST be 'extraction_mode', NOT 'mode'. " +
|
|
311
|
+
"For IO/Artifact: {'agent_effort': 'high'} or 'very_high' or 'extreme'. " +
|
|
312
|
+
"For Artifact: optionally include 'editable': true, 'deletable': true for user-editable artifacts."),
|
|
313
|
+
message: messageParam,
|
|
314
|
+
}, async ({ type, name, label, target_fragment_selector, data_fragment_selector, prompt, settings, message, }) => {
|
|
315
|
+
// Map type names to FQCNs — accepts shortcuts and common variations
|
|
316
|
+
const typeMap = {
|
|
317
|
+
// Canonical shortcuts
|
|
318
|
+
DataExtraction: "App\\Models\\Schema\\DataExtractionSchemaBehaviorDirective",
|
|
319
|
+
InputOrganization: "App\\Models\\Schema\\InputOrganizationSchemaBehaviorDirective",
|
|
320
|
+
ArtifactGeneration: "App\\Models\\Schema\\ArtifactSchemaBehaviorDirective",
|
|
321
|
+
// Common variations agents try
|
|
322
|
+
Artifact: "App\\Models\\Schema\\ArtifactSchemaBehaviorDirective",
|
|
323
|
+
ArtifactGen: "App\\Models\\Schema\\ArtifactSchemaBehaviorDirective",
|
|
324
|
+
IO: "App\\Models\\Schema\\InputOrganizationSchemaBehaviorDirective",
|
|
325
|
+
Extraction: "App\\Models\\Schema\\DataExtractionSchemaBehaviorDirective",
|
|
326
|
+
// Accept FQCNs directly (agents sometimes pass full class names)
|
|
327
|
+
"App\\Models\\Schema\\DataExtractionSchemaBehaviorDirective": "App\\Models\\Schema\\DataExtractionSchemaBehaviorDirective",
|
|
328
|
+
"App\\Models\\Schema\\InputOrganizationSchemaBehaviorDirective": "App\\Models\\Schema\\InputOrganizationSchemaBehaviorDirective",
|
|
329
|
+
"App\\Models\\Schema\\ArtifactSchemaBehaviorDirective": "App\\Models\\Schema\\ArtifactSchemaBehaviorDirective",
|
|
330
|
+
// Common wrong FQCN the agent guesses
|
|
331
|
+
"App\\Models\\Schema\\ArtifactGenerationSchemaBehaviorDirective": "App\\Models\\Schema\\ArtifactSchemaBehaviorDirective",
|
|
332
|
+
};
|
|
333
|
+
const resolvedType = typeMap[type] || type;
|
|
334
|
+
if (!Object.values(typeMap).includes(resolvedType) &&
|
|
335
|
+
!resolvedType.startsWith("App\\")) {
|
|
336
|
+
return {
|
|
337
|
+
content: [
|
|
338
|
+
{
|
|
339
|
+
type: "text",
|
|
340
|
+
text: JSON.stringify({
|
|
341
|
+
error: `Unknown directive type '${type}'. Use one of: 'DataExtraction', 'InputOrganization', or 'ArtifactGeneration'.`,
|
|
342
|
+
}),
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const result = await api.applyDirectiveAction(null, "create", {
|
|
348
|
+
type: resolvedType,
|
|
349
|
+
name,
|
|
350
|
+
label,
|
|
351
|
+
target_fragment_selector,
|
|
352
|
+
data_fragment_selector,
|
|
353
|
+
prompt,
|
|
354
|
+
settings,
|
|
355
|
+
}, message);
|
|
356
|
+
return jsonResult(result);
|
|
357
|
+
});
|
|
358
|
+
server.tool("directive_update", "Update an existing behavior directive", {
|
|
359
|
+
directive_id: z.number().describe("ID of the directive to update"),
|
|
360
|
+
name: z.string().optional().describe("New name"),
|
|
361
|
+
label: z.string().optional().describe("New label"),
|
|
362
|
+
target_fragment_selector: z
|
|
363
|
+
.record(z.string(), z.unknown())
|
|
364
|
+
.optional()
|
|
365
|
+
.describe("Updated fragment selector targeting a schema node"),
|
|
366
|
+
data_fragment_selector: z
|
|
367
|
+
.record(z.string(), z.unknown())
|
|
368
|
+
.optional()
|
|
369
|
+
.describe("Updated data fragment selector (DataExtraction only)"),
|
|
370
|
+
settings: z
|
|
371
|
+
.record(z.string(), z.unknown())
|
|
372
|
+
.optional()
|
|
373
|
+
.describe("Updated settings"),
|
|
374
|
+
message: messageParam,
|
|
375
|
+
}, async ({ directive_id, message, ...data }) => {
|
|
376
|
+
const updateData = definedOnly(data);
|
|
377
|
+
const result = await api.applyDirectiveAction(directive_id, "update", updateData, message);
|
|
378
|
+
return jsonResult(result);
|
|
379
|
+
});
|
|
380
|
+
server.tool("directive_delete", "Delete a behavior directive", {
|
|
381
|
+
directive_id: z.number().describe("ID of the directive to delete"),
|
|
382
|
+
message: messageParam,
|
|
383
|
+
}, async ({ directive_id, message }) => {
|
|
384
|
+
const result = await api.applyDirectiveAction(directive_id, "delete", undefined, message);
|
|
385
|
+
return jsonResult(result);
|
|
386
|
+
});
|
|
387
|
+
// ============================================================================
|
|
388
|
+
// Template Tools
|
|
389
|
+
// ============================================================================
|
|
390
|
+
server.tool("template_create", "Create a new template definition for the current schema", {
|
|
391
|
+
name: z
|
|
392
|
+
.string()
|
|
393
|
+
.optional()
|
|
394
|
+
.describe("Template name (auto-generated if omitted, must be unique per team)"),
|
|
395
|
+
type: z
|
|
396
|
+
.enum(["document", "table"])
|
|
397
|
+
.optional()
|
|
398
|
+
.describe("Template type (default: document)"),
|
|
399
|
+
description: z
|
|
400
|
+
.string()
|
|
401
|
+
.optional()
|
|
402
|
+
.describe("Human-readable description of the template's purpose"),
|
|
403
|
+
template_json: z
|
|
404
|
+
.record(z.string(), z.unknown())
|
|
405
|
+
.optional()
|
|
406
|
+
.describe("Full template JSON structure with type, meta, styles, and document keys"),
|
|
407
|
+
message: messageParam,
|
|
408
|
+
}, async ({ message, ...data }) => {
|
|
409
|
+
const createData = definedOnly(data);
|
|
410
|
+
const result = await api.createTemplate(createData, message);
|
|
411
|
+
return jsonResult(result);
|
|
412
|
+
});
|
|
413
|
+
server.tool("template_list", "List all template definitions for the current schema", {}, async () => {
|
|
414
|
+
const result = await api.listTemplates();
|
|
415
|
+
return jsonResult(result);
|
|
416
|
+
});
|
|
417
|
+
server.tool("template_get", "Get full details of a template definition including template_json", {
|
|
418
|
+
template_id: z.number().describe("ID of the template to fetch"),
|
|
419
|
+
}, async ({ template_id }) => {
|
|
420
|
+
const result = await api.getTemplateDetails(template_id);
|
|
421
|
+
return jsonResult(result);
|
|
422
|
+
});
|
|
423
|
+
server.tool("template_update", "Update a template definition (e.g., template_json content)", {
|
|
424
|
+
template_id: z.number().describe("ID of the template to update"),
|
|
425
|
+
template_json: z
|
|
426
|
+
.record(z.string(), z.unknown())
|
|
427
|
+
.optional()
|
|
428
|
+
.describe("Updated template JSON structure"),
|
|
429
|
+
name: z.string().optional().describe("Updated template name"),
|
|
430
|
+
message: messageParam,
|
|
431
|
+
}, async ({ template_id, message, ...data }) => {
|
|
432
|
+
const updateData = definedOnly(data);
|
|
433
|
+
const result = await api.applyTemplateAction(template_id, "update", updateData, message);
|
|
434
|
+
return jsonResult(result);
|
|
435
|
+
});
|
|
436
|
+
server.tool("template_patch", "Apply targeted path/value edits to a template's JSON. Preferred over template_update for iterative changes — " +
|
|
437
|
+
"only specify the paths that need to change instead of rewriting the entire template_json. " +
|
|
438
|
+
"Read data-template-path attributes in template_preview HTML to find the correct paths.", {
|
|
439
|
+
template_id: z.number().describe("ID of the template to patch"),
|
|
440
|
+
edits: z
|
|
441
|
+
.array(z.object({
|
|
442
|
+
path: z
|
|
443
|
+
.string()
|
|
444
|
+
.describe("Dot-notation path into template_json (e.g. 'document.body.2.content')"),
|
|
445
|
+
value: z
|
|
446
|
+
.unknown()
|
|
447
|
+
.optional()
|
|
448
|
+
.describe("New value (scalar, element object, or null to delete)"),
|
|
449
|
+
value_array: z
|
|
450
|
+
.array(z.unknown())
|
|
451
|
+
.optional()
|
|
452
|
+
.describe("Replace path with this array (for replacing children, body sections, etc.)"),
|
|
453
|
+
}))
|
|
454
|
+
.describe("Array of path/value edits to apply"),
|
|
455
|
+
message: messageParam,
|
|
456
|
+
}, async ({ template_id, edits, message }) => {
|
|
457
|
+
const result = await api.patchTemplate(template_id, edits, message);
|
|
458
|
+
return jsonResult(result);
|
|
459
|
+
});
|
|
460
|
+
server.tool("template_get_example_pages", "Get example output documents attached to a template. Returns transcribed text content AND image URLs for each file so you can see both the text and visual layout of what the template output should look like.", {
|
|
461
|
+
template_id: z
|
|
462
|
+
.number()
|
|
463
|
+
.describe("ID of the template to get example pages for"),
|
|
464
|
+
}, async ({ template_id }) => {
|
|
465
|
+
const result = await api.getTemplateExamplePages(template_id);
|
|
466
|
+
return multiPageImageResult(result);
|
|
467
|
+
});
|
|
468
|
+
server.tool("template_get_sample_data", "Get the sample data stored on a template. Sample data is used for rendering template previews when no real team object is available.", {
|
|
469
|
+
template_id: z
|
|
470
|
+
.number()
|
|
471
|
+
.describe("ID of the template to get sample data for"),
|
|
472
|
+
}, async ({ template_id }) => {
|
|
473
|
+
const result = await api.getTemplateSampleData(template_id);
|
|
474
|
+
return jsonResult(result);
|
|
475
|
+
});
|
|
476
|
+
server.tool("template_set_sample_data", "Set sample data on a template for preview rendering. Store representative extraction data that matches the schema structure so the template can render a realistic preview. This data is used by template_preview when no team_object_id is provided.", {
|
|
477
|
+
template_id: z
|
|
478
|
+
.number()
|
|
479
|
+
.describe("ID of the template to set sample data on"),
|
|
480
|
+
sample_data: z
|
|
481
|
+
.record(z.string(), z.unknown())
|
|
482
|
+
.describe("Sample data object matching the schema structure. Should contain realistic values " +
|
|
483
|
+
"for all template variables so the preview renders meaningful content."),
|
|
484
|
+
message: messageParam,
|
|
485
|
+
}, async ({ template_id, sample_data, message }) => {
|
|
486
|
+
const result = await api.setTemplateSampleData(template_id, sample_data, message);
|
|
487
|
+
return jsonResult(result);
|
|
488
|
+
});
|
|
489
|
+
server.tool("template_preview", "Capture a screenshot of the rendered template. Returns image URL(s) of the template as it would appear in a document. " +
|
|
490
|
+
"Use this to visually compare your template against the example output from template_get_example_pages. " +
|
|
491
|
+
"If no team_object_id is provided, uses the template's sample_data (set via template_set_sample_data) or auto-generated placeholders.", {
|
|
492
|
+
template_id: z.number().describe("ID of the template to preview"),
|
|
493
|
+
team_object_id: z
|
|
494
|
+
.number()
|
|
495
|
+
.optional()
|
|
496
|
+
.describe("Optional team object ID to render with real extraction data instead of sample data"),
|
|
497
|
+
message: messageParam,
|
|
498
|
+
}, async ({ template_id, team_object_id, message }) => {
|
|
499
|
+
const result = await api.getTemplatePreview(template_id, team_object_id, message);
|
|
500
|
+
return imageResult(result);
|
|
501
|
+
});
|
|
502
|
+
server.tool("template_preview_html", "Get the rendered template HTML for fast structural verification. " +
|
|
503
|
+
"Returns the full HTML of the rendered template as text (~1s, much faster than template_preview). " +
|
|
504
|
+
"Use this during iterative building to verify structure, field references, and layout. " +
|
|
505
|
+
"Use template_preview (screenshot) only once at the end for final visual verification.", {
|
|
506
|
+
template_id: z.number().describe("ID of the template to preview"),
|
|
507
|
+
team_object_id: z
|
|
508
|
+
.number()
|
|
509
|
+
.optional()
|
|
510
|
+
.describe("Optional team object ID to render with real extraction data instead of sample data"),
|
|
511
|
+
message: messageParam,
|
|
512
|
+
}, async ({ template_id, team_object_id, message }) => {
|
|
513
|
+
const result = await api.getTemplatePreviewHtml(template_id, team_object_id, message);
|
|
514
|
+
return jsonResult(result);
|
|
515
|
+
});
|
|
516
|
+
// ============================================================================
|
|
517
|
+
// Quality Gate Tools
|
|
518
|
+
// ============================================================================
|
|
519
|
+
server.tool("quality_gate_list", "List all quality gate items for the current schema. Shows each item's key, category, label, description, status, and reviewer analysis. Use this to see which items are pending review.", {}, async () => {
|
|
520
|
+
const result = await api.listQualityGateItems();
|
|
521
|
+
return jsonResult(result);
|
|
522
|
+
});
|
|
523
|
+
server.tool("quality_gate_submit_review", "Submit a review for a quality gate item. Set status to 'passed' or 'failed' with detailed analysis explaining your reasoning and evidence.", {
|
|
524
|
+
quality_gate_item_id: z
|
|
525
|
+
.number()
|
|
526
|
+
.describe("ID of the quality gate item to review"),
|
|
527
|
+
status: z
|
|
528
|
+
.enum(["passed", "failed"])
|
|
529
|
+
.describe("Review result: 'passed' if the item meets criteria, 'failed' if not"),
|
|
530
|
+
analysis: z
|
|
531
|
+
.string()
|
|
532
|
+
.describe("Detailed review analysis with evidence. Explain what you checked, " +
|
|
533
|
+
"what you found, and why you passed or failed the item."),
|
|
534
|
+
message: messageParam,
|
|
535
|
+
}, async ({ quality_gate_item_id, status, analysis, message }) => {
|
|
536
|
+
const result = await api.submitQualityGateReview(quality_gate_item_id, status, analysis, message);
|
|
537
|
+
return jsonResult(result);
|
|
538
|
+
});
|
|
539
|
+
// ============================================================================
|
|
540
|
+
// Style Library Tools (read-only)
|
|
541
|
+
// ============================================================================
|
|
542
|
+
server.tool("style_list", "List all available style variants organized by element type. Each variant includes a description and structural properties. Call this before building or modifying templates to discover what variants are available. Colors are controlled by the theme, not by variants.", {}, async () => {
|
|
543
|
+
const result = await api.getStyleList();
|
|
544
|
+
return jsonResult(result);
|
|
545
|
+
});
|
|
546
|
+
// ============================================================================
|
|
547
|
+
// Context Tools (read-only)
|
|
548
|
+
// ============================================================================
|
|
549
|
+
server.tool("context_workflow_inputs", "List all workflow inputs associated with this schema, including file metadata", {}, async () => {
|
|
550
|
+
const result = await api.getWorkflowInputs();
|
|
551
|
+
return jsonResult(result);
|
|
552
|
+
});
|
|
553
|
+
server.tool("context_workflow_input_pages", "Get transcribed page content for a specific workflow input (text from document OCR/LLM transcription)", {
|
|
554
|
+
workflow_input_id: z
|
|
555
|
+
.number()
|
|
556
|
+
.describe("ID of the workflow input to get pages for"),
|
|
557
|
+
}, async ({ workflow_input_id }) => {
|
|
558
|
+
const result = await api.getWorkflowInputPages(workflow_input_id);
|
|
559
|
+
return jsonResult(result);
|
|
560
|
+
});
|
|
561
|
+
server.tool("context_chat_history", "Get recent chat messages from the schema's collaboration thread", {
|
|
562
|
+
limit: z
|
|
563
|
+
.number()
|
|
564
|
+
.optional()
|
|
565
|
+
.describe("Maximum number of messages to return (default 50, max 100)"),
|
|
566
|
+
}, async ({ limit }) => {
|
|
567
|
+
const result = await api.getChatHistory(limit);
|
|
568
|
+
return jsonResult(result);
|
|
569
|
+
});
|
|
570
|
+
// ============================================================================
|
|
571
|
+
// Start Server
|
|
572
|
+
// ============================================================================
|
|
573
|
+
async function main() {
|
|
574
|
+
const transport = new StdioServerTransport();
|
|
575
|
+
await server.connect(transport);
|
|
576
|
+
console.error("Schema MCP Server running on stdio");
|
|
577
|
+
console.error(` API URL: ${process.env.SCHEMA_API_URL || "http://localhost:80"}`);
|
|
578
|
+
console.error(` Schema ID: ${api.SCHEMA_ID}`);
|
|
579
|
+
}
|
|
580
|
+
main().catch((error) => {
|
|
581
|
+
console.error("Fatal error:", error);
|
|
582
|
+
process.exit(1);
|
|
583
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thehammer/schema-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Schema Builder - translates Claude Code tool calls into Laravel API requests",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"schema-mcp-server": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"dev": "tsx watch src/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsx": "^4.0.0",
|
|
24
|
+
"typescript": "^5.7.0",
|
|
25
|
+
"@types/node": "^22.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|