|
| 1 | +import { Tool } from "@modelcontextprotocol/sdk/types.js"; |
| 2 | +import { z } from "zod"; |
| 3 | +import * as path from "path"; |
| 4 | +import { FormattedIssue, ParseHedSidecarResult } from "../types/index.js"; |
| 5 | +import { formatIssue, formatIssues, separateIssuesBySeverity } from "../utils/issueFormatter.js"; |
| 6 | +import { readFileFromPath } from "../utils/fileReader.js"; |
| 7 | +import { schemaCache } from '../utils/schemaCache.js'; |
| 8 | +import { mcpToZod } from '../utils/mcpToZod.js'; |
| 9 | + |
| 10 | +// Import HED validation functions |
| 11 | +import { buildSchemasFromVersion, BidsSidecar } from "hed-validator"; |
| 12 | + |
| 13 | +// Define the MCP inputSchema first |
| 14 | +const parseHedSidecarInputSchema = { |
| 15 | + type: "object" as const, |
| 16 | + properties: { |
| 17 | + filePath: { |
| 18 | + type: "string" as const, |
| 19 | + description: "The absolute path to the sidecar file to parse" |
| 20 | + }, |
| 21 | + hedVersion: { |
| 22 | + type: "string" as const, |
| 23 | + description: "The HED schema version to use (e.g., 8.4.0 or lang_1.1.0, score_2.1.0)" |
| 24 | + }, |
| 25 | + checkForWarnings: { |
| 26 | + type: "boolean" as const, |
| 27 | + description: "Whether to check for warnings in addition to errors", |
| 28 | + default: false |
| 29 | + }, |
| 30 | + fileData: { |
| 31 | + type: "string" as const, |
| 32 | + description: "Optional JSON string containing the sidecar data to use instead of reading from filePath" |
| 33 | + } |
| 34 | + }, |
| 35 | + required: ["filePath", "hedVersion"] |
| 36 | +}; |
| 37 | + |
| 38 | +// Generate Zod schema from MCP schema |
| 39 | +const ParseHedSidecarSchema = mcpToZod(parseHedSidecarInputSchema); |
| 40 | + |
| 41 | +export type ParseHedSidecarArgs = z.infer<typeof ParseHedSidecarSchema>; |
| 42 | + |
| 43 | +/** |
| 44 | + * Tool definition for parsing HED sidecar files |
| 45 | + */ |
| 46 | +export const parseHedSidecar: Tool = { |
| 47 | + name: "parseHedSidecar", |
| 48 | + description: "Parses a HED sidecar file using the specified HED schema version", |
| 49 | + inputSchema: parseHedSidecarInputSchema |
| 50 | +}; |
| 51 | + |
| 52 | +/** |
| 53 | + * Parse a HED sidecar file using the specified HED schema version. |
| 54 | + */ |
| 55 | +export async function handleParseHedSidecar(args: ParseHedSidecarArgs): Promise<ParseHedSidecarResult> { |
| 56 | + const { filePath, hedVersion, checkForWarnings = false, fileData } = args; |
| 57 | + |
| 58 | + try { |
| 59 | + // Use schema cache to get or create the HED schemas |
| 60 | + const hedSchemas = await schemaCache.getOrCreateSchema(hedVersion); |
| 61 | + |
| 62 | + // Get the file data if not provided |
| 63 | + let data = fileData; |
| 64 | + if (!data) { |
| 65 | + data = await readFileFromPath(filePath); |
| 66 | + } |
| 67 | + |
| 68 | + // Parse JSON data (fileData is always a string now) |
| 69 | + const jsonData = JSON.parse(data); |
| 70 | + |
| 71 | + const fileName = path.basename(filePath) || "sidecar.json"; // Properly extract filename using path.basename |
| 72 | + const sidecar = new BidsSidecar(fileName, { path: filePath, name: fileName }, jsonData); |
| 73 | + const validationIssues = sidecar.validate(hedSchemas); |
| 74 | + |
| 75 | + // Format all validation issues |
| 76 | + const allFormattedIssues = formatIssues(validationIssues); |
| 77 | + |
| 78 | + // Separate issues by severity |
| 79 | + const { errors: formattedErrors, others: formattedWarnings } = separateIssuesBySeverity(allFormattedIssues); |
| 80 | + |
| 81 | + // Only include warnings if checkForWarnings is true |
| 82 | + const finalWarnings = checkForWarnings ? formattedWarnings : []; |
| 83 | + |
| 84 | + return { |
| 85 | + parsedHedSidecar: JSON.stringify(jsonData), |
| 86 | + errors: formattedErrors, |
| 87 | + warnings: finalWarnings |
| 88 | + }; |
| 89 | + |
| 90 | + } catch (error) { |
| 91 | + return {parsedHedSidecar: "", errors: [formatIssue(error)], warnings: []}; |
| 92 | + } |
| 93 | +} |
0 commit comments