Skip to main content
Glama
deepwriterClient.ts12.5 kB
// Using native fetch and FormData (available in Node.js 18+) // TODO: Make base URL configurable (e.g., via environment variables) const DEEPWRITER_API_BASE_URL = 'https://app.deepwriter.com'; interface ApiErrorResponse { message: string; // Add other potential error fields if known } // Generic function to handle API requests async function makeApiRequest<T>( endpoint: string, apiKey: string, method: 'GET' | 'POST' | 'PATCH' | 'DELETE' = 'GET', body?: object ): Promise<T> { const url = `${DEEPWRITER_API_BASE_URL}${endpoint}`; const headers = { 'x-api-key': apiKey, 'Content-Type': 'application/json', 'Accept': 'application/json', }; console.error(`Making API request: ${method} ${url}`); // Log request details (excluding body/key for security) try { const response = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : undefined, }); if (!response.ok) { let errorBodyText = await response.text(); // Get raw response text first let errorMessage = `API request failed: ${response.status} ${response.statusText}`; console.error(`API Error ${response.status}: Raw Body ->`, errorBodyText); // Log raw body try { // Try parsing as JSON to get a structured message if available const errorJson = JSON.parse(errorBodyText) as ApiErrorResponse; if (errorJson && errorJson.message) { errorMessage += ` - ${errorJson.message}`; } else if (errorBodyText) { errorMessage += ` - ${errorBodyText}`; // Use raw text if no JSON message } } catch (jsonError) { // If JSON parsing fails, use the raw text if available if (errorBodyText) { errorMessage += ` - ${errorBodyText}`; } } // TODO: Implement more specific error mapping based on status codes (401, 403, 404, etc.) throw new Error(errorMessage); } // Handle cases where the response might be empty (e.g., DELETE success) if (response.status === 204 || response.headers.get('content-length') === '0') { return {} as T; // Or return a specific success indicator if needed } return await response.json() as T; } catch (error) { console.error(`Network or fetch error: ${error}`); throw new Error(`Failed to connect to DeepWriter API: ${error instanceof Error ? error.message : String(error)}`); } } // --- API Client Functions --- // Define proper types for API responses interface ProjectListItem { id: string; title: string; created_at: string; // Assuming ISO string format } interface ListProjectsResponse { projects: ProjectListItem[]; // Add other potential fields like pagination info if applicable } export async function listProjects(apiKey: string): Promise<ListProjectsResponse> { console.error("Calling actual listProjects API"); if (!apiKey) { throw new Error("API key is required for listProjects"); } // Actual implementation: return makeApiRequest<ListProjectsResponse>('/api/listProjects', apiKey, 'GET'); } // --- getProjectDetails --- interface ProjectDetails { id: string; title: string; author?: string; // Optional fields based on schema email?: string; model?: string; outline_text?: string; prompt?: string; style_text?: string; supplemental_info?: string; work_description?: string; work_details?: string; work_vision?: string; created_at: string; updated_at: string; } export interface GetProjectDetailsResponse { project: ProjectDetails; } export async function getProjectDetails(apiKey: string, projectId: string): Promise<GetProjectDetailsResponse> { console.error(`Calling actual getProjectDetails API for project ID: ${projectId}`); if (!apiKey) { throw new Error("API key is required for getProjectDetails"); } if (!projectId) { throw new Error("Project ID is required for getProjectDetails"); } const endpoint = `/api/getProjectDetails?projectId=${encodeURIComponent(projectId)}`; return makeApiRequest<GetProjectDetailsResponse>(endpoint, apiKey, 'GET'); } // --- createProject --- // Input body structure expected by the API (only newProjectName based on demo script) interface CreateProjectApiInput { newProjectName: string; // API expects only project name } export interface CreateProjectResponse { id: string; // ID of the newly created project } export async function createProject(apiKey: string, title: string, email: string): Promise<CreateProjectResponse> { console.error(`Calling actual createProject API with title: ${title}`); if (!apiKey) { throw new Error("API key is required for createProject"); } if (!title || title.trim() === '') { throw new Error("Project title is required for createProject"); } // Note: email parameter accepted for MCP interface compatibility but not sent to API // Use the correct field name 'newProjectName' for the API request body (email not needed) const body: CreateProjectApiInput = { newProjectName: title }; return makeApiRequest<CreateProjectResponse>('/api/createProject', apiKey, 'POST', body); } // --- updateProject --- // Interface for the 'updates' object based on the schema interface ProjectUpdates { author?: string; email?: string; model?: string; outline_text?: string; prompt?: string; style_text?: string; supplemental_info?: string; title?: string; work_description?: string; work_details?: string; work_vision?: string; } export interface UpdateProjectResponse { id: string; // ID of the updated project } export async function updateProject( apiKey: string, projectId: string, updates: ProjectUpdates ): Promise<UpdateProjectResponse> { console.error(`Calling actual updateProject API for project ID: ${projectId}`); if (!apiKey) { throw new Error("API key is required for updateProject"); } if (!projectId) { throw new Error("Project ID is required for updateProject"); } if (!updates || Object.keys(updates).length === 0) { throw new Error("Updates object cannot be empty for updateProject"); } const endpoint = `/api/updateProject?projectId=${encodeURIComponent(projectId)}`; return makeApiRequest<UpdateProjectResponse>(endpoint, apiKey, 'PATCH', updates); } // --- deleteProject --- export interface DeleteProjectResponse { message: string; // Success message } export async function deleteProject(apiKey: string, projectId: string): Promise<DeleteProjectResponse> { console.error(`Calling actual deleteProject API for project ID: ${projectId}`); if (!apiKey) { throw new Error("API key is required for deleteProject"); } if (!projectId) { throw new Error("Project ID is required for deleteProject"); } const endpoint = `/api/deleteProject?projectId=${encodeURIComponent(projectId)}`; // Use a temporary type that allows for an empty object from makeApiRequest on 204 type TempDeleteResponse = DeleteProjectResponse | {}; const response = await makeApiRequest<TempDeleteResponse>(endpoint, apiKey, 'DELETE'); // If API returns 204 (empty object), construct the standard success message if (typeof response === 'object' && Object.keys(response).length === 0) { return { message: `Project ${projectId} deleted successfully.` }; } // Otherwise, assume the API returned the expected { message: ... } structure return response as DeleteProjectResponse; } // --- generateWizardWork --- interface GenerateWizardWorkInputBody { projectId: string; prompt: string; author: string; email: string; outline_text?: string; has_technical_diagrams?: 'auto' | 'on' | 'off'; has_tableofcontents?: 'auto' | 'on' | 'off'; use_web_research?: 'auto' | 'on' | 'off'; page_length?: string; questions_and_answers?: string; // JSON string mode?: 'deepwriter' | 'benchmark' | 'romance' | 'homework' | 'deepseek' | 'skunkworks'; isDefault?: boolean; max_pages?: number; free_trial_mode?: 'true' | 'false'; } export interface GenerateWizardWorkResponse { message: string; jobId: string; // ID of the generated job } export async function generateWizardWork( apiKey: string, params: GenerateWizardWorkInputBody ): Promise<GenerateWizardWorkResponse> { console.error(`Calling actual generateWizardWork API for project ID: ${params.projectId}`); if (!apiKey) { throw new Error("API key is required for generateWizardWork"); } if (!params.projectId) { throw new Error("Project ID is required for generateWizardWork"); } if (!params.prompt) { throw new Error("Prompt is required for generateWizardWork"); } if (!params.author) { throw new Error("Author is required for generateWizardWork"); } if (!params.email) { throw new Error("Email is required for generateWizardWork"); } // Add isDefault: true as required by the API (from demo script) const requestBody = { ...params, isDefault: true }; return makeApiRequest<GenerateWizardWorkResponse>('/api/generateWizardWork', apiKey, 'POST', requestBody); } // --- formatPrompt --- interface FormatPromptInputBody { prompt: string; projectId?: string; } export interface FormatPromptResponse { enhanced_prompt: string; formatted_prompt?: string; // Legacy field name for compatibility questions?: string[]; } export async function formatPrompt( apiKey: string, prompt: string, projectId?: string ): Promise<FormatPromptResponse> { console.error(`Calling actual formatPrompt API`); if (!apiKey) { throw new Error("API key is required for formatPrompt"); } if (!prompt) { throw new Error("Prompt is required for formatPrompt"); } const body: FormatPromptInputBody = { prompt: prompt, ...(projectId && { projectId: projectId }) // Note: API expects projectId not project_id }; return makeApiRequest<FormatPromptResponse>('/api/formatPrompt', apiKey, 'POST', body); } // --- uploadProjectFiles --- export interface ProjectFileResponse { id: string; file_name: string; file_type: string; file_size: number; file_url?: string; storage_path: string; uploaded_at: string; } export interface UploadProjectFilesResponse { success: boolean; files: ProjectFileResponse[]; summary: { total_files: number; successful_uploads: number; failed_uploads: number; errors: Array<{ file_name: string; error: string; }>; }; } export async function uploadProjectFiles( apiKey: string, projectId: string, files: File[] ): Promise<UploadProjectFilesResponse> { console.error(`Calling actual uploadProjectFiles API for project ID: ${projectId}`); if (!apiKey) { throw new Error("API key is required for uploadProjectFiles"); } if (!projectId) { throw new Error("Project ID is required for uploadProjectFiles"); } if (!files || files.length === 0) { throw new Error("At least one file is required for uploadProjectFiles"); } const formData = new FormData(); formData.append('projectId', projectId); // Add all files to the FormData files.forEach((file) => { formData.append('file', file); }); const url = `${DEEPWRITER_API_BASE_URL}/api/uploadProjectFiles`; const headers = { 'x-api-key': apiKey, 'Origin': DEEPWRITER_API_BASE_URL.replace('/api', ''), // Don't set Content-Type header - let browser set it with boundary for multipart/form-data }; console.error(`Making file upload request: POST ${url}`); try { const response = await fetch(url, { method: 'POST', headers, body: formData, }); if (!response.ok) { let errorBodyText = await response.text(); let errorMessage = `File upload failed: ${response.status} ${response.statusText}`; console.error(`Upload Error ${response.status}: Raw Body ->`, errorBodyText); try { const errorJson = JSON.parse(errorBodyText) as ApiErrorResponse; if (errorJson && errorJson.message) { errorMessage += ` - ${errorJson.message}`; } else if (errorBodyText) { errorMessage += ` - ${errorBodyText}`; } } catch (jsonError) { if (errorBodyText) { errorMessage += ` - ${errorBodyText}`; } } throw new Error(errorMessage); } return await response.json() as UploadProjectFilesResponse; } catch (error) { console.error(`Network or fetch error during file upload: ${error}`); throw new Error(`Failed to upload files to DeepWriter API: ${error instanceof Error ? error.message : String(error)}`); } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/deepwriter-ai/Deepwriter-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server