Back to Examples
TypeScript
TypeScript Examples
Complete TypeScript examples using the official MCP SDK to integrate BotEsq legal services into your AI agents.
Installation
bash
# Install the MCP SDKnpm install @modelcontextprotocol/sdk # Or with pnpmpnpm add @modelcontextprotocol/sdkBasic Example
A minimal example showing how to connect and make your first API call:
basic-example.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; interface SessionResponse { session_token: string; operator_name: string; credits_available: number; services_enabled: string[];} interface LegalAnswer { answer: string; complexity: "simple" | "moderate" | "complex"; credits_charged: number; disclaimer: string; attorney_id: string;} async function main() { // Configure the BotEsq MCP server const transport = new StdioClientTransport({ command: "npx", args: ["-y", "@botesq/mcp-server"], env: { BOTESQ_API_KEY: process.env.BOTESQ_API_KEY! }, }); const client = new Client( { name: "typescript-legal-assistant", version: "1.0.0" }, {} ); await client.connect(transport); try { // Start a session const sessionResult = await client.callTool("start_session", { api_key: process.env.BOTESQ_API_KEY!, agent_identifier: "typescript-example", }); const session: SessionResponse = JSON.parse( sessionResult.content[0].text as string ); console.log(`Session started. Credits: ${session.credits_available}`); // Ask a legal question const answerResult = await client.callTool("ask_legal_question", { session_token: session.session_token, question: "What are the key elements of a valid contract?", jurisdiction: "US-CA", }); const answer: LegalAnswer = JSON.parse( answerResult.content[0].text as string ); console.log(`Answer: ${answer.answer}`); console.log(`Complexity: ${answer.complexity}`); console.log(`Credits charged: ${answer.credits_charged}`); } finally { await client.close(); }} main().catch(console.error);BotEsq Client Class
A typed, reusable client class with full error handling:
botesq-client.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; // Type definitionsinterface SessionResponse { session_token: string; operator_name: string; credits_available: number; services_enabled: string[];} interface LegalAnswer { answer: string; complexity: "simple" | "moderate" | "complex"; credits_charged: number; disclaimer: string; attorney_id: string;} interface Credits { credits_available: number; credits_used_this_session: number; credits_used_all_time: number;} interface Matter { matter_id: string; status: string; title: string; matter_type: string;} type MatterType = | "CONTRACT_REVIEW" | "ENTITY_FORMATION" | "COMPLIANCE" | "IP_TRADEMARK" | "IP_COPYRIGHT" | "IP_PATENT" | "EMPLOYMENT" | "LITIGATION_CONSULTATION"; interface BotEsqError extends Error { code: string; details?: Record<string, unknown>; request_id?: string;} class BotEsqClient { private client: Client; private transport: StdioClientTransport | null = null; private sessionToken: string | null = null; constructor(private apiKey: string) { this.client = new Client( { name: "botesq-ts-client", version: "1.0.0" }, {} ); } async connect(): Promise<SessionResponse> { this.transport = new StdioClientTransport({ command: "npx", args: ["-y", "@botesq/mcp-server"], env: { BOTESQ_API_KEY: this.apiKey }, }); await this.client.connect(this.transport); // Auto-start session const session = await this.call<SessionResponse>("start_session", { api_key: this.apiKey, agent_identifier: "botesq-ts-client", }); this.sessionToken = session.session_token; return session; } async disconnect(): Promise<void> { await this.client.close(); } private async call<T>( tool: string, args: Record<string, unknown> ): Promise<T> { const result = await this.client.callTool(tool, args); if (!result.content?.[0]) { throw new Error("Empty response from BotEsq"); } const parsed = JSON.parse(result.content[0].text as string); if (parsed.error) { const error = new Error(parsed.error.message) as BotEsqError; error.code = parsed.error.code; error.details = parsed.error.details; error.request_id = parsed.error.request_id; throw error; } return parsed as T; } private async callWithSession<T>( tool: string, args: Record<string, unknown> ): Promise<T> { if (!this.sessionToken) { throw new Error("Not connected. Call connect() first."); } return this.call<T>(tool, { ...args, session_token: this.sessionToken }); } async askQuestion( question: string, options?: { jurisdiction?: string; context?: string } ): Promise<LegalAnswer> { return this.callWithSession<LegalAnswer>("ask_legal_question", { question, ...options, }); } async checkCredits(): Promise<Credits> { return this.callWithSession<Credits>("check_credits", {}); } async createMatter( type: MatterType, title: string, options?: { description?: string; urgency?: string } ): Promise<Matter> { return this.callWithSession<Matter>("create_matter", { matter_type: type, title, ...options, }); } async listMatters(options?: { status?: string; limit?: number; }): Promise<{ matters: Matter[]; total: number; has_more: boolean }> { return this.callWithSession("list_matters", options || {}); } // Helper to ensure sufficient credits async ensureCredits(required: number): Promise<void> { const credits = await this.checkCredits(); if (credits.credits_available < required) { const deficit = required - credits.credits_available; const amountUsd = Math.max(10, Math.ceil(deficit / 10000) * 10); await this.callWithSession("add_credits", { amount_usd: Math.min(amountUsd, 1000), }); } }} // Usageasync function main() { const client = new BotEsqClient(process.env.BOTESQ_API_KEY!); try { const session = await client.connect(); console.log(`Connected as ${session.operator_name}`); // Check credits const credits = await client.checkCredits(); console.log(`Available credits: ${credits.credits_available}`); // Ask a question const answer = await client.askQuestion( "What is a non-disclosure agreement?", { jurisdiction: "US-CA" } ); console.log(answer.answer); } finally { await client.disconnect(); }} main().catch(console.error); export { BotEsqClient };Consultation Workflow
Request an attorney consultation and handle the async response:
consultation-workflow.ts
interface ConsultationRequest { consultation_id: string; status: string; estimated_completion: string; credits_charged: number;} interface ConsultationResult { consultation_id: string; status: "pending" | "in_progress" | "completed"; response?: string; attorney_id?: string; completed_at?: string; follow_up_available?: boolean;} async function requestConsultation( client: BotEsqClient, question: string, options?: { matterId?: string; context?: string; jurisdiction?: string; urgent?: boolean; }): Promise<string> { // Ensure we have enough credits const creditsCost = options?.urgent ? 10000 : 5000; await client.ensureCredits(creditsCost); // Request the consultation const request = await client["callWithSession"]<ConsultationRequest>( "request_consultation", { question, matter_id: options?.matterId, context: options?.context, jurisdiction: options?.jurisdiction, urgency: options?.urgent ? "urgent" : "standard", } ); console.log(`Consultation requested: ${request.consultation_id}`); console.log(`Estimated completion: ${request.estimated_completion}`); console.log(`Credits charged: ${request.credits_charged}`); // Poll for results (in production, use webhooks) console.log("\nWaiting for attorney response..."); while (true) { await sleep(30000); // Wait 30 seconds const result = await client["callWithSession"]<ConsultationResult>( "get_consultation_result", { consultation_id: request.consultation_id } ); if (result.status === "completed") { console.log("\n=== Consultation Complete ==="); console.log(`\nAttorney Response:\n${result.response}`); console.log(`\nCompleted at: ${result.completed_at}`); if (result.follow_up_available) { console.log("\nFollow-up questions are available for this consultation."); } return result.response!; } console.log(`Status: ${result.status}...`); }} function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms));} // Usageasync function main() { const client = new BotEsqClient(process.env.BOTESQ_API_KEY!); await client.connect(); try { const response = await requestConsultation( client, "We are planning to expand our software business to the European Union. " + "What are the key legal considerations we should be aware of, " + "particularly around GDPR compliance and data transfers?", { context: "We are a B2B SaaS company based in California with " + "customers in multiple countries. We process customer data " + "including email addresses and usage analytics.", jurisdiction: "EU", urgent: false, } ); } finally { await client.disconnect(); }} main().catch(console.error);Vercel AI SDK Integration
Use BotEsq as a tool with the Vercel AI SDK:
vercel-ai-integration.ts
import { generateText, tool } from "ai";import { openai } from "@ai-sdk/openai";import { z } from "zod"; // Create BotEsq tools for AI SDKfunction createBotEsqTools(client: BotEsqClient) { return { askLegalQuestion: tool({ description: "Ask a legal question and get an authoritative answer from licensed attorneys", parameters: z.object({ question: z.string().describe("The legal question to ask"), jurisdiction: z .string() .optional() .describe("Jurisdiction code (e.g., 'US-CA', 'US-NY')"), }), execute: async ({ question, jurisdiction }) => { const answer = await client.askQuestion(question, { jurisdiction }); return { answer: answer.answer, complexity: answer.complexity, disclaimer: answer.disclaimer, }; }, }), checkCredits: tool({ description: "Check the current credit balance", parameters: z.object({}), execute: async () => { return client.checkCredits(); }, }), createMatter: tool({ description: "Create a new legal matter for organizing documents and consultations", parameters: z.object({ matterType: z.enum([ "CONTRACT_REVIEW", "ENTITY_FORMATION", "COMPLIANCE", "IP_TRADEMARK", "IP_COPYRIGHT", "IP_PATENT", "EMPLOYMENT", "LITIGATION_CONSULTATION", ]), title: z.string().describe("Brief title for the matter"), description: z.string().optional(), }), execute: async ({ matterType, title, description }) => { return client.createMatter(matterType, title, { description }); }, }), };} // Usage with AI SDKasync function main() { const botesq = new BotEsqClient(process.env.BOTESQ_API_KEY!); await botesq.connect(); try { const result = await generateText({ model: openai("gpt-4-turbo"), tools: createBotEsqTools(botesq), maxSteps: 5, prompt: `I'm starting a tech company in California with two co-founders. We want to build an AI product. Can you help me understand: 1. What type of business entity we should form? 2. What legal agreements we need between founders? 3. Any IP considerations for AI products?`, }); console.log(result.text); } finally { await botesq.disconnect(); }} main().catch(console.error);Error Handling
Robust error handling with TypeScript:
error-handling.ts
interface BotEsqError extends Error { code: string; details?: Record<string, unknown>; request_id?: string;} function isBotEsqError(error: unknown): error is BotEsqError { return ( error instanceof Error && "code" in error && typeof (error as BotEsqError).code === "string" );} async function withRetry<T>( fn: () => Promise<T>, options: { maxRetries?: number; baseDelay?: number } = {}): Promise<T> { const { maxRetries = 3, baseDelay = 1000 } = options; let lastError: Error; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (isBotEsqError(error)) { // Don't retry client errors (except rate limits) if (error.code !== "RATE_LIMITED" && !error.code.includes("500")) { throw error; } } // Calculate delay with jitter const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000; await new Promise((resolve) => setTimeout(resolve, delay)); } } throw lastError!;} // Usageasync function safeLegalQuery(client: BotEsqClient, question: string) { try { return await withRetry(() => client.askQuestion(question)); } catch (error) { if (isBotEsqError(error)) { switch (error.code) { case "INSUFFICIENT_CREDITS": console.error("Not enough credits. Please add more."); break; case "SESSION_EXPIRED": // Reconnect and retry await client.connect(); return client.askQuestion(question); case "RATE_LIMITED": console.error("Rate limited. Please slow down requests."); break; default: console.error(`BotEsq error [${error.code}]: ${error.message}`); } if (error.request_id) { console.error(`Request ID for support: ${error.request_id}`); } } throw error; }}TypeScript-Specific Tips
Best Practices
- Define interfaces for all API responses to get full type safety
- Use
zodfor runtime validation of API responses - Create a singleton client instance for connection reuse
- Use async/await consistently throughout your codebase
- Implement proper cleanup with
try/finallyblocks - Type guard functions help with error handling