#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import Anthropic from "@anthropic-ai/sdk";
// === Types ===
interface Party {
name: string;
role: "company" | "user" | "provider";
address?: string;
email?: string;
}
interface GenerateInput {
documentType: "privacy" | "terms" | "cookie";
parties: Party[];
terms: Record<string, string>;
jurisdiction: string;
}
// === Prompt Templates ===
function buildPrompt(input: GenerateInput): string {
const { documentType, parties, terms, jurisdiction } = input;
const company = parties.find(p => p.role === "company");
const companyName = company?.name || "[COMPANY NAME]";
const companyEmail = company?.email || "[CONTACT EMAIL]";
const websiteUrl = terms.websiteUrl || "[WEBSITE URL]";
const siteName = terms.siteName || companyName;
const today = new Date().toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
const baseRequirements = `
Requirements:
- Write in clear, professional legal language
- Use HTML formatting (h2 headings, paragraphs, lists)
- Include last updated date: ${today}
- Reference jurisdiction: ${jurisdiction}
- Company: ${companyName}
- Contact email: ${companyEmail}
- Do NOT include markdown — use only HTML tags
- Output ONLY the HTML content, starting from the first <h2> tag
`;
const servicesInfo = terms.services
? `\nServices/Technologies used:\n${terms.services}`
: "";
if (documentType === "privacy") {
return `You are a legal document expert. Generate a comprehensive Privacy Policy for "${siteName}" (${websiteUrl}).
${servicesInfo}
${baseRequirements}
Include sections for:
1. Introduction and scope
2. Information we collect
3. How we use your information
4. Cookies and tracking technologies
5. Third-party services
6. Data sharing and disclosure
7. Data retention
8. Your rights (GDPR, CCPA as applicable for ${jurisdiction})
9. Children's privacy
10. International data transfers
11. Security measures
12. Changes to this policy
13. Contact information
Be thorough but readable.`;
}
if (documentType === "terms") {
return `You are a legal document expert. Generate comprehensive Terms of Service for "${siteName}" (${websiteUrl}).
${servicesInfo}
${baseRequirements}
Include sections for:
1. Acceptance of terms
2. Description of service
3. User accounts and registration
4. User responsibilities and acceptable use
5. Intellectual property rights
6. Payment terms (if applicable)
7. Third-party services and links
8. Disclaimers and limitations of liability
9. Indemnification
10. Termination
11. Governing law (${jurisdiction})
12. Dispute resolution
13. Changes to terms
14. Contact information
Be thorough but readable.`;
}
// Cookie policy
return `You are a legal document expert. Generate a comprehensive Cookie Policy for "${siteName}" (${websiteUrl}).
${servicesInfo}
${baseRequirements}
Include sections for:
1. What are cookies
2. How we use cookies
3. Types of cookies we use:
a. Strictly necessary cookies
b. Performance/analytics cookies
c. Functionality cookies
d. Targeting/advertising cookies
e. Third-party cookies
4. Cookie table with name, provider, purpose, duration, type
5. How to manage cookies (browser-specific instructions)
6. Cookie consent
7. Changes to this policy
8. Contact information
Be specific and compliant with ${jurisdiction} regulations.`;
}
// === Input Schema ===
const PartySchema = z.object({
name: z.string().describe("Name of the party (company, user, etc.)"),
role: z.enum(["company", "user", "provider"]).describe("Role of the party"),
address: z.string().optional().describe("Physical address"),
email: z.string().optional().describe("Contact email"),
});
const InputSchema = {
documentType: z.enum(["privacy", "terms", "cookie"]).describe(
"Type of legal document to generate"
),
parties: z.array(PartySchema).describe("Parties involved in the document"),
terms: z.record(z.string(), z.string()).describe(
"Key-value pairs for document terms. Supported keys: websiteUrl, siteName, services (comma-separated list of technologies/services used)"
),
jurisdiction: z.string().describe(
"Legal jurisdiction (e.g., 'United States', 'European Union', 'California, USA')"
),
};
// === Main Server ===
async function main() {
const anthropic = new Anthropic();
const server = new McpServer({
name: "legalforge-mcp",
version: "1.0.0",
});
// Register the generate_legal_document tool
server.tool(
"generate_legal_document",
"Generate professional legal documents (Privacy Policy, Terms of Service, Cookie Policy) using AI",
InputSchema,
async ({ documentType, parties, terms, jurisdiction }) => {
try {
const input: GenerateInput = {
documentType,
parties: parties as Party[],
terms: terms as Record<string, string>,
jurisdiction,
};
const prompt = buildPrompt(input);
const message = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 8000,
messages: [{ role: "user", content: prompt }],
});
const content = message.content[0];
if (content.type !== "text") {
return {
content: [{ type: "text" as const, text: "Error: Unexpected response format from AI" }],
isError: true,
};
}
// Clean up the response - remove code fences if present
let document = content.text;
document = document.replace(/^```html?\s*\n?/i, "").replace(/\n?```\s*$/i, "");
return {
content: [
{
type: "text" as const,
text: document,
},
],
};
} catch (error) {
const msg = error instanceof Error ? error.message : "Unknown error";
return {
content: [{ type: "text" as const, text: `Error generating document: ${msg}` }],
isError: true,
};
}
}
);
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("LegalForge MCP server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});