forked from EduCraft/curriculum-project-hub
feat(hub): expose pdf_to_md_bundle as MCP tool to agent + skill (ADR-0027) (#9)
Co-authored-by: Hong Jiarong <me@jrhim.com> Co-committed-by: Hong Jiarong <me@jrhim.com>
This commit is contained in:
@@ -7,11 +7,16 @@ import { readFeishuContext } from "./read.js";
|
||||
import type { ApprovalManager } from "./approval.js";
|
||||
import { CPH_HUB_MCP_TOOL_IDS, type CphHubMcpToolId } from "../agent/roleTools.js";
|
||||
import { WorkspaceFileBoundaryError } from "../security/workspaceFiles.js";
|
||||
import type { PrismaClient } from "@prisma/client";
|
||||
import type { LocalSecretEnvelope } from "../security/secretEnvelope.js";
|
||||
import { createPdfToMdBundleAdapter } from "../capability/pdfToMdBundle.js";
|
||||
import { AliyunDocmindClient } from "../capability/docmindClient.js";
|
||||
|
||||
export interface FileDeliveryToolOptions {
|
||||
readonly rt: FeishuRuntime;
|
||||
readonly chatId: string;
|
||||
readonly projectId: string;
|
||||
readonly organizationId: string;
|
||||
readonly runId: string;
|
||||
readonly workspaceRoot?: string | undefined;
|
||||
readonly workspaceDir: string;
|
||||
@@ -20,6 +25,8 @@ export interface FileDeliveryToolOptions {
|
||||
readonly approvalManager: ApprovalManager;
|
||||
readonly onDelivered?: (path: string) => void;
|
||||
readonly tools?: readonly CphHubMcpToolId[] | undefined;
|
||||
readonly prisma: PrismaClient;
|
||||
readonly secretEnvelope: LocalSecretEnvelope;
|
||||
}
|
||||
|
||||
export function createFileDeliveryMcpServer(options: FileDeliveryToolOptions): McpSdkServerConfigWithInstance {
|
||||
@@ -230,6 +237,51 @@ export function createFileDeliveryMcpServer(options: FileDeliveryToolOptions): M
|
||||
);
|
||||
}
|
||||
|
||||
if (enabledTools.has("convert_pdf_to_md")) {
|
||||
const adapter = createPdfToMdBundleAdapter({
|
||||
secrets: options.secretEnvelope,
|
||||
client: new AliyunDocmindClient(),
|
||||
prisma: options.prisma,
|
||||
});
|
||||
tools.push(
|
||||
tool(
|
||||
"convert_pdf_to_md",
|
||||
"Convert a PDF file in the workspace to a Markdown bundle (markdown + extracted images) using Alibaba Cloud Document Mind. The PDF must already be in the workspace (use feishu_download_resource first if it came from Feishu). Returns the path to the generated markdown file and the list of extracted image paths. Mathematical formulas are converted to LaTeX.",
|
||||
{
|
||||
input_path: z.string().describe("Relative path to the input PDF within the workspace."),
|
||||
output_dir: z.string().describe("Relative directory within the workspace to write the markdown and images into. Will be created if it does not exist."),
|
||||
},
|
||||
async (args) => {
|
||||
try {
|
||||
const result = await adapter.invoke({
|
||||
runId: options.runId,
|
||||
organizationId: options.organizationId,
|
||||
projectId: options.projectId,
|
||||
workspaceDir: options.workspaceDir,
|
||||
inputPath: args.input_path,
|
||||
outputDir: args.output_dir,
|
||||
prisma: options.prisma,
|
||||
});
|
||||
const lines = [`Converted PDF to markdown. ${result.artifacts.length} files written:`];
|
||||
for (const artifact of result.artifacts) {
|
||||
lines.push(` - ${artifact.path} (${artifact.kind})`);
|
||||
}
|
||||
lines.push(`Pages: ${result.consumption.quantity}, Cost: $${(result.consumption.costUsd ?? 0).toFixed(4)}`);
|
||||
return {
|
||||
content: [{ type: "text", text: lines.join("\n") }],
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
isError: true,
|
||||
content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }],
|
||||
};
|
||||
}
|
||||
},
|
||||
{ alwaysLoad: true },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const instructions = mcpInstructions(enabledTools);
|
||||
return createSdkMcpServer({
|
||||
name: "cph_hub",
|
||||
@@ -260,5 +312,12 @@ function mcpInstructions(enabledTools: ReadonlySet<CphHubMcpToolId>): string {
|
||||
if (enabledTools.has("request_approval")) {
|
||||
instructions.push("Use request_approval when explicit human approval or confirmation is required before continuing.");
|
||||
}
|
||||
if (enabledTools.has("convert_pdf_to_md")) {
|
||||
instructions.push(
|
||||
"Use convert_pdf_to_md when the user asks to convert a PDF to Markdown.",
|
||||
"If the PDF came from a Feishu message, first use feishu_download_resource to save it to the workspace, then call convert_pdf_to_md.",
|
||||
"Do NOT attempt to parse PDFs yourself with Read or Bash — always use convert_pdf_to_md for accurate text, formula, and image extraction.",
|
||||
);
|
||||
}
|
||||
return instructions.join(" ");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user