diff --git a/src/contract/get_contract_download.ts b/src/contract/get_contract_download.ts new file mode 100644 index 0000000..660eb7e --- /dev/null +++ b/src/contract/get_contract_download.ts @@ -0,0 +1,54 @@ +import { Tool } from '@modelcontextprotocol/sdk/types.js'; +import { SpotDraftClient } from '../spotdraft_client.js'; + +const getContractDownloadTool: Tool = { + name: 'get_contract_download', + description: `Downloads a copy of the contract file directly from SpotDraft using API v2.1. + +Returns the contract file as base64-encoded data along with metadata such as filename and content type. + +## Parameters +- \`composite_id\`: ID of the Contract. Should be of the form T-123 or H-123. T stands for Template contracts and H stands for Historical contracts. + +## Response +Returns an object containing: +- \`data\`: Base64-encoded file content +- \`filename\`: Original filename (if available from response headers) +- \`contentType\`: MIME type of the file (if available from response headers) + +### How the Model Should Use the Output +The \`data\` field contains the raw binary content of the contract file. To help the user: +- If the platform supports file uploads (e.g., Slack, email, chat app), use \`data\`, \`filename\`, and \`contentType\` to send the contract as a file attachment. +- If direct file sharing is not supported, respond with a message indicating the file was downloaded and provide its name and type. +- Do not attempt to display or summarize the file content. +- Do not try to decode or transform the binary string. + +### Usage Example +Given \`composite_id = "T-332728"\`, the model can call this tool to retrieve and forward the finalized contract to the user as a downloadable file.`, + + inputSchema: { + type: 'object', + properties: { + composite_id: { + type: 'string', + description: + 'ID of the Contract (e.g., T-123 or H-123). T stands for Template contracts and H stands for Historical contracts.', + }, + }, + required: ['composite_id'], + }, +}; + +interface GetContractDownloadRequest { + composite_id: string; +} + +const getContractDownload = async ( + request: GetContractDownloadRequest, + spotdraftClient: SpotDraftClient, +): Promise<{ data: string; filename?: string; contentType?: string }> => { + const endpoint = `/v2.1/public/contracts/${request.composite_id}/download`; + return spotdraftClient.downloadFile(endpoint); +}; + +export { getContractDownload, GetContractDownloadRequest, getContractDownloadTool }; diff --git a/src/index.ts b/src/index.ts index 5af576c..d66a118 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,11 @@ import { getContractApprovals, getContractApprovalsTool, } from './contract/get_contract_approvals.js'; +import { + GetContractDownloadRequest, + getContractDownload, + getContractDownloadTool, +} from './contract/get_contract_download.js'; import { GetContractDownloadLinkRequest, getContractDownloadLink, @@ -69,6 +74,7 @@ async function handleListToolsRequest() { tools: [ getContractListTool, getContractDownloadLinkTool, + getContractDownloadTool, getContractStatusTool, getContractActivityLogTool, getContractApprovalsTool, @@ -119,6 +125,9 @@ async function handleCallToolRequest(request: CallToolRequest, spotdraftClient: case getContractDownloadLinkTool.name: result = await getContractDownloadLink(args as unknown as GetContractDownloadLinkRequest, spotdraftClient); break; + case getContractDownloadTool.name: + result = await getContractDownload(args as unknown as GetContractDownloadRequest, spotdraftClient); + break; case getContractStatusTool.name: result = await getContractStatus(args as unknown as GetContractStatusRequest, spotdraftClient); break; diff --git a/src/spotdraft_client.ts b/src/spotdraft_client.ts index d34040a..489dcf1 100644 --- a/src/spotdraft_client.ts +++ b/src/spotdraft_client.ts @@ -48,6 +48,35 @@ export class SpotDraftClient { } return response.json(); } + + async downloadFile(endpoint: string): Promise<{ data: string; filename?: string; contentType?: string }> { + const response = await this.post(endpoint, {}); + + if (!response.ok) { + const errorBody = await response.text(); + throw new Error(`SpotDraft API Error: ${response.status} ${response.statusText} - ${errorBody}`); + } + + const arrayBuffer = await response.arrayBuffer(); + const base64Data = Buffer.from(arrayBuffer).toString('base64'); + + const contentDisposition = response.headers.get('content-disposition'); + let filename: string | undefined; + if (contentDisposition) { + const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); + if (filenameMatch && filenameMatch[1]) { + filename = decodeURIComponent(filenameMatch[1].replace(/['"]/g, '')); + } + } + + const contentType = response.headers.get('content-type') || undefined; + + return { + data: base64Data, + filename, + contentType, + }; + } } export default SpotDraftClient;