Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions lib/artist/createKnowledgeBase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { uploadToArweave } from "@/lib/arweave/uploadToArweave";
import { uploadTextToArweave } from "@/lib/arweave/uploadTextToArweave";
import { arweaveGatewayUrl } from "@/lib/arweave/arweaveGatewayUrl";
import { selectAccountInfo } from "@/lib/supabase/account_info/selectAccountInfo";
import { updateAccountInfo } from "@/lib/supabase/account_info/updateAccountInfo";
Expand All @@ -22,9 +22,7 @@ export async function createKnowledgeBase(
}

// Upload text to Arweave
const textBuffer = Buffer.from(knowledgeBaseText, "utf-8");
const transaction = await uploadToArweave(textBuffer, "text/plain");
const arweaveUrl = `ar://${transaction.id}`;
const arweaveUrl = await uploadTextToArweave(knowledgeBaseText);
const fetchableUrl = arweaveGatewayUrl(arweaveUrl);

// Generate a name from the first line or a default name
Expand Down
13 changes: 13 additions & 0 deletions lib/arweave/uploadTextToArweave.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { uploadToArweave } from "./uploadToArweave";

/**
* Uploads text content to Arweave as a text/plain file.
*
* @param text - The text content to upload
* @returns The Arweave URL (ar://...)
*/
export async function uploadTextToArweave(text: string): Promise<string> {
const textBuffer = Buffer.from(text, "utf-8");
const transaction = await uploadToArweave(textBuffer, "text/plain");
return `ar://${transaction.id}`;
}
68 changes: 68 additions & 0 deletions lib/files/generateAndStoreTxtFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { uploadTextToArweave } from "@/lib/arweave/uploadTextToArweave";
import uploadJsonToArweave from "@/lib/arweave/uploadJsonToArweave";

export interface GeneratedTxtResponse {
txt: {
base64Data: string;
mimeType: string;
};
arweave?: string | null;
metadataArweave?: string | null;
}

/**
* Generates and stores a TXT file by uploading it to Arweave.
* Creates both the text file and metadata JSON on Arweave.
*
* @param contents - The text contents to store
* @returns The generated TXT response with Arweave URLs
*/
export async function generateAndStoreTxtFile(contents: string): Promise<GeneratedTxtResponse> {
if (!contents) {
throw new Error("Contents are required");
}

const mimeType = "text/plain";

// Upload the TXT file to Arweave
let txtFile: string | null = null;
try {
txtFile = await uploadTextToArweave(contents);
} catch (arweaveError) {
console.error("Error uploading TXT to Arweave:", arweaveError);
// Continue even if Arweave upload fails
}

const image = "ar://EXwe2peizXKxjUMop6W-JPflC5sWyeQR1y0JiRDwUB0";

// Upload metadata JSON to Arweave
let metadataArweave: string | null = null;
try {
const transaction = await uploadJsonToArweave({
image,
animation_url: txtFile || undefined,
content: {
mime: mimeType,
uri: txtFile || "",
},
description: contents,
name: contents.substring(0, 100), // Limit name length
});
metadataArweave = `ar://${transaction.id}`;
} catch (metadataError) {
console.error("Error uploading metadata to Arweave:", metadataError);
}

// Encode contents to base64 for response
const base64Data = Buffer.from(contents, "utf-8").toString("base64");

return {
txt: {
base64Data,
mimeType,
},
arweave: txtFile,
metadataArweave,
};
}

13 changes: 13 additions & 0 deletions lib/mcp/tools/files/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerCreateKnowledgeBaseTool } from "./registerCreateKnowledgeBaseTool";
import { registerGenerateTxtFileTool } from "./registerGenerateTxtFileTool";

/**
* Registers all file-related MCP tools on the server.
*
* @param server - The MCP server instance to register tools on.
*/
export const registerAllFileTools = (server: McpServer): void => {
registerCreateKnowledgeBaseTool(server);
registerGenerateTxtFileTool(server);
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ export function registerCreateKnowledgeBaseTool(server: McpServer): void {
},
);
}

60 changes: 60 additions & 0 deletions lib/mcp/tools/files/registerGenerateTxtFileTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { generateAndStoreTxtFile } from "@/lib/files/generateAndStoreTxtFile";
import { getFetchableUrl } from "@/lib/arweave/getFetchableUrl";
import { getToolResultSuccess } from "@/lib/mcp/getToolResultSuccess";
import { getToolResultError } from "@/lib/mcp/getToolResultError";

const generateTxtFileSchema = z.object({
contents: z.string().min(1, "Contents are required").describe("The contents of the TXT file"),
});

type GenerateTxtFileArgs = z.infer<typeof generateTxtFileSchema>;

/**
* Registers the "generate_txt_file" tool on the MCP server.
* Creates a downloadable TXT file from provided contents and stores it on Arweave.
*
* @param server - The MCP server instance to register the tool on.
*/
export function registerGenerateTxtFileTool(server: McpServer): void {
server.registerTool(
"generate_txt_file",
{
description:
"Create a downloadable TXT file from provided contents. The file will be stored onchain with Arweave and metadata will be created.",
inputSchema: generateTxtFileSchema,
},
async (args: GenerateTxtFileArgs) => {
try {
const result = await generateAndStoreTxtFile(args.contents);

const arweaveUrl = getFetchableUrl(result.arweave || null);

const response = {
success: true,
arweaveUrl,
metadataArweave: result.metadataArweave ? getFetchableUrl(result.metadataArweave) : null,
message: "TXT file successfully generated and stored on Arweave.",
};

return getToolResultSuccess(response);
} catch (error) {
console.error("Error generating TXT file:", error);

// Format helpful error messages based on common issues
let errorMessage = error instanceof Error ? error.message : "An unexpected error occurred";

if (errorMessage.includes("API key")) {
errorMessage = "API key is missing or invalid. Please check your environment variables.";
} else if (errorMessage.includes("content policy")) {
errorMessage = "Your contents may violate content policy. Please try different contents.";
} else if (errorMessage.includes("rate limit")) {
errorMessage = "Rate limit exceeded. Please try again later.";
}

return getToolResultError(`Failed to generate TXT file. ${errorMessage}`);
}
},
);
}
12 changes: 6 additions & 6 deletions lib/mcp/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { registerContactTeamTool } from "./registerContactTeamTool";
import { registerUpdateAccountInfoTool } from "./registerUpdateAccountInfoTool";
import { registerAllArtistSocialsTools } from "./artistSocials";
import { registerSearchWebTool } from "./registerSearchWebTool";
import { registerCreateKnowledgeBaseTool } from "./registerCreateKnowledgeBaseTool";
import { registerAllFileTools } from "./files";

/**
* Registers all MCP tools on the server.
Expand All @@ -19,14 +19,14 @@ import { registerCreateKnowledgeBaseTool } from "./registerCreateKnowledgeBaseTo
*/
export const registerAllTools = (server: McpServer): void => {
registerAllArtistSocialsTools(server);
registerGetLocalTimeTool(server);
registerAllTaskTools(server);
registerAllImageTools(server);
registerAllCatalogTools(server);
registerAllFileTools(server);
registerAllImageTools(server);
registerAllSora2Tools(server);
registerAllSpotifyTools(server);
registerAllTaskTools(server);
registerContactTeamTool(server);
registerUpdateAccountInfoTool(server);
registerGetLocalTimeTool(server);
registerSearchWebTool(server);
registerCreateKnowledgeBaseTool(server);
registerUpdateAccountInfoTool(server);
};