@kaminaduck/scryfall-mcp-server
Version:
A Model Context Protocol (MCP) server that provides access to the Scryfall API for Magic: The Gathering card data
335 lines • 14.2 kB
JavaScript
/**
* File-serving MCP resources for the Scryfall server.
*/
import { readFile } from 'node:fs/promises';
import { extname } from 'node:path';
import { existsSync } from 'node:fs';
import { createFileManager } from '../fileManager.js';
import { createCardDatabase } from '../database.js';
import { logger } from '../logger.js';
/**
* Serve a downloaded card image by file ID.
*/
export async function serveCardImage(fileId) {
logger.info(`[Resource] Serving card image for file ID: ${fileId}`);
try {
const fm = await createFileManager();
try {
// Check if this might be a transform card by looking for face 0 by default
const db = await createCardDatabase();
let cardInfo;
try {
cardInfo = db.getCardByFileId(fileId);
if (!cardInfo) {
// Check if there's a face 0 for this base file ID
const result = await fm.readFileContent(fileId);
if (result) {
const [content, mimeType] = result;
return [content, mimeType];
}
else {
const errorMsg = { status: 'error', message: `File not found for ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
}
// Check if this card is part of a transform card
const isTransform = db.isTransformCard(cardInfo.card_name);
if (isTransform) {
// For transform cards, default to face 0
return await serveCardImageFace(fileId, 0);
}
else {
// Single-faced card - use existing behavior
const result = await fm.readFileContent(fileId);
if (result) {
const [content, mimeType] = result;
return [content, mimeType];
}
else {
const errorMsg = { status: 'error', message: `File not found for ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
}
}
finally {
db.close();
}
}
finally {
fm.close();
}
}
catch (error) {
logger.error('[Error] Failed to serve card image:', error);
const errorMsg = {
status: 'error',
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
return [JSON.stringify(errorMsg), 'application/json'];
}
}
/**
* Serve a downloaded art crop image by file ID.
*/
export async function serveArtCrop(fileId) {
logger.info(`[Resource] Serving art crop for file ID: ${fileId}`);
try {
const fm = await createFileManager();
try {
// Check if this might be a transform card by looking for face 0 by default
const db = await createCardDatabase();
let cardInfo;
try {
cardInfo = db.getCardByFileId(fileId);
if (!cardInfo) {
// Try direct file access
const result = await fm.readFileContent(fileId);
if (result) {
const [content, mimeType] = result;
return [content, mimeType];
}
else {
const errorMsg = { status: 'error', message: `File not found for ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
}
// Check if this card is part of a transform card
const isTransform = db.isTransformCard(cardInfo.card_name);
if (isTransform) {
// For transform cards, default to face 0
return await serveArtCropFace(fileId, 0);
}
else {
// Single-faced card - use existing behavior
const result = await fm.readFileContent(fileId);
if (result) {
const [content, mimeType] = result;
return [content, mimeType];
}
else {
const errorMsg = { status: 'error', message: `File not found for ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
}
}
finally {
db.close();
}
}
finally {
fm.close();
}
}
catch (error) {
logger.error('[Error] Failed to serve art crop:', error);
const errorMsg = {
status: 'error',
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
return [JSON.stringify(errorMsg), 'application/json'];
}
}
/**
* Serve metadata JSON file for a downloaded card.
*/
export async function serveMetadata(fileId) {
logger.info(`[Resource] Serving metadata for file ID: ${fileId}`);
try {
const fm = await createFileManager();
try {
const db = await createCardDatabase();
try {
const cardInfo = db.getCardByFileId(fileId);
if (!cardInfo) {
const errorMsg = { status: 'error', message: `Metadata not found for ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
// Check if this card is part of a transform card
const isTransform = db.isTransformCard(cardInfo.card_name);
if (isTransform) {
// For transform cards, default to face 0
return await serveMetadataFace(fileId, 0);
}
else {
// Single-faced card - check if it's an art crop
if (cardInfo.card_name.includes('_art_crop')) {
// This is an art crop, try to find the JSON file
const imagePath = cardInfo.filename;
const jsonPath = imagePath.replace(extname(imagePath), '.json');
if (existsSync(jsonPath)) {
const content = await readFile(jsonPath, 'utf-8');
return [content, 'application/json'];
}
}
const errorMsg = { status: 'error', message: `Metadata file not found for ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
}
finally {
db.close();
}
}
finally {
fm.close();
}
}
catch (error) {
logger.error('[Error] Failed to serve metadata:', error);
const errorMsg = {
status: 'error',
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
return [JSON.stringify(errorMsg), 'application/json'];
}
}
/**
* Serve a specific face of a transform card image by file ID and face index.
*/
export async function serveCardImageFace(fileId, faceIndex) {
logger.info(`[Resource] Serving card image face ${faceIndex} for file ID: ${fileId}`);
try {
const db = await createCardDatabase();
try {
// Get the base card info to determine the card name
const baseCardInfo = db.getCardByFileId(fileId);
if (!baseCardInfo) {
const errorMsg = { status: 'error', message: `Card not found for file ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
// Get the face-specific card info
const faceCardInfo = db.getCardFaceInfo(baseCardInfo.card_name, faceIndex);
if (!faceCardInfo) {
const errorMsg = { status: 'error', message: `Face ${faceIndex} not found for card: ${baseCardInfo.card_name}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
// Use the face-specific file ID to get the content
const fm = await createFileManager();
try {
const result = await fm.readFileContent(faceCardInfo.file_id);
if (result) {
const [content, mimeType] = result;
return [content, mimeType];
}
else {
const errorMsg = { status: 'error', message: `File not found for face ${faceIndex} of card: ${baseCardInfo.card_name}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
}
finally {
fm.close();
}
}
finally {
db.close();
}
}
catch (error) {
logger.error(`[Error] Failed to serve card image face ${faceIndex}:`, error);
const errorMsg = {
status: 'error',
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
return [JSON.stringify(errorMsg), 'application/json'];
}
}
/**
* Serve a specific face of a transform card art crop by file ID and face index.
*/
export async function serveArtCropFace(fileId, faceIndex) {
logger.info(`[Resource] Serving art crop face ${faceIndex} for file ID: ${fileId}`);
try {
const db = await createCardDatabase();
try {
// Get the base card info to determine the card name
const baseCardInfo = db.getCardByFileId(fileId);
if (!baseCardInfo) {
const errorMsg = { status: 'error', message: `Card not found for file ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
// Get the face-specific card info (look for art crop variant)
const faceCardInfo = db.getCardFaceInfo(baseCardInfo.card_name, faceIndex);
if (!faceCardInfo) {
const errorMsg = { status: 'error', message: `Art crop face ${faceIndex} not found for card: ${baseCardInfo.card_name}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
// Use the face-specific file ID to get the content
const fm = await createFileManager();
try {
const result = await fm.readFileContent(faceCardInfo.file_id);
if (result) {
const [content, mimeType] = result;
return [content, mimeType];
}
else {
const errorMsg = { status: 'error', message: `Art crop file not found for face ${faceIndex} of card: ${baseCardInfo.card_name}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
}
finally {
fm.close();
}
}
finally {
db.close();
}
}
catch (error) {
logger.error(`[Error] Failed to serve art crop face ${faceIndex}:`, error);
const errorMsg = {
status: 'error',
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
return [JSON.stringify(errorMsg), 'application/json'];
}
}
/**
* Serve face-specific metadata for a transform card by file ID and face index.
*/
export async function serveMetadataFace(fileId, faceIndex) {
logger.info(`[Resource] Serving metadata face ${faceIndex} for file ID: ${fileId}`);
try {
const db = await createCardDatabase();
try {
// Get the base card info to determine the card name
const baseCardInfo = db.getCardByFileId(fileId);
if (!baseCardInfo) {
const errorMsg = { status: 'error', message: `Card not found for file ID: ${fileId}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
// Get the face-specific card info
const faceCardInfo = db.getCardFaceInfo(baseCardInfo.card_name, faceIndex);
if (!faceCardInfo) {
const errorMsg = { status: 'error', message: `Metadata face ${faceIndex} not found for card: ${baseCardInfo.card_name}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
// Check if this is an art crop (metadata only exists for art crops)
if (!faceCardInfo.card_name.includes('_art_crop')) {
const errorMsg = { status: 'error', message: `Metadata is only available for art crop images. Face ${faceIndex} is not an art crop.` };
return [JSON.stringify(errorMsg), 'application/json'];
}
// Find the corresponding JSON file for this face
const imagePath = faceCardInfo.filename;
const jsonPath = imagePath.replace(extname(imagePath), '.json');
if (existsSync(jsonPath)) {
const content = await readFile(jsonPath, 'utf-8');
return [content, 'application/json'];
}
else {
const errorMsg = { status: 'error', message: `Metadata file not found for face ${faceIndex} of card: ${baseCardInfo.card_name}` };
return [JSON.stringify(errorMsg), 'application/json'];
}
}
finally {
db.close();
}
}
catch (error) {
logger.error(`[Error] Failed to serve metadata face ${faceIndex}:`, error);
const errorMsg = {
status: 'error',
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
return [JSON.stringify(errorMsg), 'application/json'];
}
}
//# sourceMappingURL=fileResources.js.map