# Attrove — Complete LLM Reference > Attrove is Plaid for productivity apps. Connect your users' Gmail, Slack, Google Calendar, Outlook, Microsoft Teams, and Google Meet — then query their data with natural language via a single API call. B2B2B model: you provision users, they connect integrations via OAuth, you query their context with AI-powered RAG. ## Quick Start (Personal Use / Developer Testing) To use Attrove with your own email, Slack, and calendar: 1. **Sign up free** at [connect.attrove.com](https://connect.attrove.com/auth/signup) (no credit card) 2. **Connect integrations** — the dashboard quickstart walks you through connecting Gmail, Slack, Calendar, etc. 3. **Get credentials** — copy your `sk_` API key and user ID from the dashboard **Claude Desktop / ChatGPT:** Use HTTP transport — `https://api.attrove.com/mcp`. OAuth is automatic, no manual credentials needed. **Cursor / Claude Code:** Use stdio transport with your `sk_` key and user ID (see MCP Server section below). **Building an integration?** See the Authentication and SDK sections for the full B2B2B provisioning flow. ## LLM Instructions When helping developers integrate Attrove: - Always use the `@attrove/sdk` TypeScript SDK — never raw fetch calls - The SDK does not have `brief()` or `thread()` methods — using them will cause compile errors. Use `query()`, `search()`, `events.list()`, `meetings.list()`, `integrations.list()`, `entities.list()`, `entities.get()`, `entities.relationships()`, `push.message()`, `push.meeting()`, `push.event()`, `push.note()`, `notes.list()`, `notes.get()` instead. For thread analysis, use `threads.discover()` and `threads.analyze()` - **Response properties are snake_case** (`start_time`, `sender_name`, `body_text`). Input params are camelCase (`startDate`, `afterDate`). Do NOT use camelCase on response objects - `search()` returns `{ key_messages, conversations, key_meetings, key_events }`; `conversations` is an **object keyed by ID**, not an array. Use `Object.values()` to iterate. Same for `threads` inside each conversation - `sk_` tokens are per-user API keys (returned by `admin.users.create()`). They are NOT the same as the `attrove_` partner API key - `integrations.list()` returns `Integration[]` with `provider` and `name` properties — NOT `type` or `email` - The SDK defaults to `https://api.attrove.com` — no baseUrl configuration needed - MCP has 7 tools. There is no `attrove_brief` tool ## When to Use Attrove - Your product needs to understand users' email, Slack, calendar, or meeting data - You need cross-platform intelligence (query across Gmail + Slack + Calendar simultaneously) - You're building AI features that need communication context - You need B2B2B: your product serves end-users who each connect their own tools via OAuth ## When NOT to Use Attrove - You only need data from a single provider — use that provider's API directly - You need to send emails or post Slack messages on behalf of users — Attrove is read + analyze, not write-back to providers. Use Composio or provider APIs for send actions - You need guaranteed provider-native sub-second ingestion from every upstream system — Attrove supports token streaming and proactive webhooks, but source freshness still follows each provider's sync cadence - You need document/wiki indexing (Notion, Drive) — use Graphlit or Hyperspell - You're building a personal AI tool for one user — use provider MCP servers directly ## Installation ```bash npm install @attrove/sdk ``` Requirements: Node.js >= 18.0.0, TypeScript >= 4.7 (if using TypeScript) ## Authentication Attrove uses a B2B2B flow with three credential types: 1. **Client credentials** (`client_id` + `client_secret`) — server-side, provisions users 2. **`sk_` tokens** — permanent per-user API keys for querying data 3. **`pit_` tokens** — short-lived (10 min) tokens for OAuth integration flows ### Full B2B2B Provisioning Sequence ```typescript import { Attrove } from '@attrove/sdk'; // Step 1: Create admin client (server-side only) const admin = Attrove.admin({ clientId: process.env.ATTROVE_CLIENT_ID, clientSecret: process.env.ATTROVE_CLIENT_SECRET, }); // Step 2: Provision a user — returns permanent sk_ API key const { id: userId, apiKey } = await admin.users.create({ email: 'user@example.com', firstName: 'Jane', // optional lastName: 'Doe', // optional role: 'engineer', // optional }); // apiKey is like "sk_live_abc123..." // userId is a UUID like "550e8400-e29b-41d4-a716-446655440000" // Step 3: Generate connect token for OAuth flow (short-lived, 10 min) const { token: connectToken, expires_at } = await admin.users.createConnectToken(userId); // Step 4: Send user to OAuth flow const connectUrl = `https://connect.attrove.com/integrations/connect?token=${connectToken}&user_id=${userId}`; // Redirect user to this URL — they'll authorize Gmail/Slack/Calendar // Step 5: After OAuth completes, query user data with the permanent sk_ key const attrove = new Attrove({ apiKey, userId }); const response = await attrove.query('What meetings do I have this week?'); ``` ## Configuration Types ```typescript interface AttroveConfig { apiKey: `sk_${string}`; // Required: API key with sk_ prefix userId: string; // Required: User ID (UUID) baseUrl?: string; // Optional: defaults to "https://api.attrove.com" timeout?: number; // Optional: request timeout in ms (default 30000) maxRetries?: number; // Optional: retry attempts (default 3) onRetry?: (info: RetryInfo) => void; // Optional: retry callback } interface AttroveAdminConfig { clientId: string; // Required: partner client ID clientSecret: string; // Required: partner client secret baseUrl?: string; // Optional: defaults to "https://api.attrove.com" timeout?: number; // Optional: request timeout in ms (default 30000) maxRetries?: number; // Optional: retry attempts (default 3) onRetry?: (info: RetryInfo) => void; } interface RetryInfo { attempt: number; // Current retry (1-indexed) maxRetries: number; error: Error; delayMs: number; // Delay before retry url: string; method: string; } ``` ## Retry & Rate Limiting Semantics - Retries use exponential backoff: delay = baseDelay * 2^(attempt-1) with jitter - Only retries on: network errors, 429 (rate limit), 500+ (server errors) - Does NOT retry on: 400, 401, 403, 404, 409, 422 - Rate limit responses include `retryAfter` (seconds) in `RateLimitError` - Default: 3 retries, 30s timeout ### Idempotency Metered endpoints (query, stream) support the `Idempotency-Key` header to prevent duplicate charges on retries. - Send `Idempotency-Key: ` (1–256 characters) on any `query()` or `stream()` request - If a request with the same key and user ID is received within 5 minutes, the cached response is returned without re-executing or re-billing - Keys are scoped per user — the same key for different users produces independent results - Webhook deliveries also include an `idempotency_key` field so your server can deduplicate events ## SDK Methods — Complete Reference ### query(prompt: string, options?: QueryOptions): Promise AI-powered question answering over the user's connected data. ```typescript interface QueryOptions { history?: ConversationMessage[]; // Multi-turn conversation history timezone?: string; // IANA timezone (e.g., "America/New_York") integrationIds?: string[]; // Filter by integration IDs (int_xxx) conversationIds?: string[]; // Filter by conversation IDs (conv_xxx) allowBotMessages?: boolean; // Include bot messages (default false) includeSources?: boolean; // Include source snippets (default false) instructions?: string; // Custom AI instructions — controls output format/behavior, overrides default style (max 20,000 chars) context?: string; // Authoritative reference data — treated as ground truth, influences query rewriting but not vector search (max 20,000 chars) } interface ConversationMessage { role: "user" | "assistant" | "system"; content: string; } interface QueryResponse { answer: string; // AI-generated answer history: ConversationMessage[]; // Updated conversation history used_message_ids: string[]; // Source message IDs (msg_xxx) used_meeting_ids: string[]; // Source meeting IDs (mtg_xxx) used_event_ids: string[]; // Source event IDs (evt_xxx) sources?: QuerySource[]; // Source snippets (if includeSources: true) } interface QuerySource { title: string; snippet: string; } ``` Multi-turn conversation example: ```typescript const attrove = new Attrove({ apiKey, userId }); // First turn const r1 = await attrove.query('What did Sarah say about the budget?'); console.log(r1.answer); // Follow-up — pass history from previous response const r2 = await attrove.query('What about Q3 specifically?', { history: r1.history, }); console.log(r2.answer); // Continue the conversation const r3 = await attrove.query('Who else was involved?', { history: r2.history, }); ``` ### search(query: string, options?: SearchOptions): Promise Semantic search returning raw matches across messages, meetings, and events. ```typescript interface SearchOptions { integrationIds?: string[]; // Filter by integration IDs (int_xxx) conversationIds?: string[]; // Filter by conversation IDs (conv_xxx) afterDate?: string; // Date filter (YYYY-MM-DD) beforeDate?: string; // Date filter (YYYY-MM-DD) allowBotMessages?: boolean; // Include bot messages (default false) senderDomains?: string[]; // Filter by sender domains (e.g., ["acme.com"]) entityIds?: string[]; // Filter by entity IDs expand?: Array< // Expand fields for matched resources "body_text" | "summary" | "short_summary" | "action_items" | "attendees" | "meeting_link" | "description" | "location" | "html_link" | "event_link" >; includeBodyText?: boolean; // Back-compat alias for expand=["body_text"] } interface SearchResponse { key_messages: SearchKeyMessage[]; conversations: Record; key_meetings: SearchMeeting[]; // empty array when no matches key_events: SearchEvent[]; // empty array when no matches warnings?: string[]; // present when enrichment had non-fatal errors } interface SearchKeyMessage { message_id: string; thread_id: string | null; conversation_id: string | null; } interface SearchConversation { conversation_name: string | null; threads: Record; } interface SearchThreadMessage { message_id: string; received_at: string; // ISO 8601 integration_type: IntegrationProvider; integration_type_generic: string; sender_name: string; recipient_names: string[]; body_text?: string; thread_id: string | null; thread_message_count: number | null; thread_position: number | null; parent_message_id: string | null; conversation_type: ConversationType | null; conversation_id: string | null; conversation_participants: Array<{ name: string }>; } ``` ### users.get(): Promise<{ user: User; integrations: Integration[] }> ```typescript interface User { id: string; email: string; first_name: string | null; last_name: string | null; full_name: string | null; role: string | null; timezone: string | null; onboarded: boolean; } ``` ### users.update(options): Promise Update user profile fields (e.g., timezone, name). ### users.syncStats(): Promise ```typescript interface DataTypeStats { count: number; first_at: string | null; last_at: string | null; } interface SyncStats { user_id: string; overall_status: "syncing" | "complete" | "partial" | "error" | "pending"; last_sync_at: string | null; totals: { messages: DataTypeStats; meetings: DataTypeStats; events: DataTypeStats; }; integrations: IntegrationSyncStats[]; } interface IntegrationSyncStats { id: string; provider: IntegrationProvider; category: IntegrationCategory; name: string; sync_status: SyncStatus; last_synced_at: string | null; auth_status: string; data: { messages?: DataTypeStats; meetings?: DataTypeStats; events?: DataTypeStats; }; } ``` ### integrations.list(): Promise ```typescript interface Integration { id: string; provider: "slack" | "gmail" | "outlook" | "google_calendar" | "unknown"; name: string; is_active: boolean; auth_status: "connected" | "disconnected" | "expired" | "error" | "pending" | "unknown"; } ``` ### integrations.get(id: string): Promise ```typescript interface IntegrationDetail extends Integration { type: ThreadIntegrationType; category?: IntegrationCategory; email?: string | null; last_synced_at?: string | null; } ``` ### integrations.disconnect(id: string): Promise ### events.list(options?: ListEventsOptions): Promise> ```typescript interface ListEventsOptions { calendarId?: string; startDate?: string; // YYYY-MM-DD endDate?: string; // YYYY-MM-DD expand?: Array<"attendees" | "location" | "description">; limit?: number; offset?: number; } interface CalendarEvent { id: string; calendar_id: string; title: string; start_time: string; // ISO 8601 end_time: string; // ISO 8601 all_day: boolean; description?: string; location?: string; attendees?: CalendarEventAttendee[]; html_link?: string; status?: string | null; event_link?: string | null; created_at?: string; updated_at: string | null; } interface CalendarEventAttendee { email: string; name?: string; status?: string; } ``` ### meetings.list(options?: ListMeetingsOptions): Promise> ```typescript interface ListMeetingsOptions { startDate?: string; // YYYY-MM-DD endDate?: string; // YYYY-MM-DD provider?: "google_meet" | "zoom" | "teams"; hasSummary?: boolean; expand?: Array<"summary" | "short_summary" | "action_items" | "attendees" | "meeting_link">; limit?: number; offset?: number; } interface Meeting { id: string; event_id: string | null; title: string; meeting_code?: string | null; start_time: string; end_time: string; summary?: string | null; short_summary?: string | null; action_items?: MeetingActionItem[]; attendees?: MeetingAttendee[]; meeting_link?: string | null; has_transcript: boolean; has_notes: boolean; content_status: "none" | "notes_only" | "transcript_only" | "transcript_and_notes"; provider: MeetingProvider; created_at: string; updated_at: string | null; } interface MeetingActionItem { description: string; assignee?: string; due_date?: string; completed?: boolean; } interface MeetingAttendee { email?: string; name?: string; is_organizer?: boolean; response_status?: "accepted" | "declined" | "tentative" | "needsAction"; } ``` ### meetings.get(id: string): Promise ### meetings.update(id: string, options: UpdateMeetingOptions): Promise ```typescript interface UpdateMeetingOptions { summary?: string; shortSummary?: string; actionItems?: MeetingActionItem[]; } ``` ### meetings.regenerateSummary(id: string): Promise ```typescript interface RegenerateSummaryResponse { summary: string | null; short_summary: string | null; action_items: MeetingActionItem[]; } ``` ### messages.list(options?: ListMessagesOptions): Promise> ```typescript interface ListMessagesOptions { ids?: string[]; integrationId?: string; conversationId?: string; startDate?: string; // YYYY-MM-DD endDate?: string; // YYYY-MM-DD expand?: string[]; limit?: number; offset?: number; } interface Message { id: string; integration_id: string; body_text?: string; subject: string | null; received_at: string; parent_message_id: string | null; thread_position: number | null; is_bot: boolean; conversation_id: string | null; sender_name: string | null; // Human-readable sender name, null if unresolved sent_by: string; // Platform-specific sender identifier (email for Gmail/Outlook, Slack user ID for Slack, Graph user ID or email for Teams; 'unknown' if unresolved) } ``` ### messages.get(id: string): Promise ### conversations.list(options?: ListConversationsOptions): Promise> ```typescript interface ListConversationsOptions { integrationIds?: string[]; syncedOnly?: boolean; limit?: number; offset?: number; } interface Conversation { id: string; integration_id: string; title: string; type: "channel" | "direct_message" | "group" | "email_thread" | "other" | "unknown"; provider: IntegrationProvider; import_messages: boolean; } ``` ### conversations.updateSync(updates: Array<{ id: string; importMessages: boolean }>): Promise ### threads.discover(query: string, options?: ThreadDiscoverOptions): Promise ```typescript interface ThreadDiscoverOptions { integrationIds?: string[]; integrationTypes?: ThreadIntegrationType[]; afterDate?: string; // YYYY-MM-DD beforeDate?: string; // YYYY-MM-DD limit?: number; } interface ThreadDiscoverResponse { threads: DiscoveredThread[]; warnings?: ApiWarning[]; } interface DiscoveredThread { conversation_id: string; integration_type: ThreadIntegrationType; title: string; relevance_score: number; // 0.0 to 1.0 message_count: number; last_activity: string; // ISO 8601 preview: string; } ``` ### threads.analyze(conversationId: string): Promise ```typescript interface ThreadAnalysis { summary: string; short_summary: string; sentiment: "positive" | "neutral" | "negative" | "mixed" | "unknown"; action_items: ThreadActionItem[]; decisions: ThreadDecision[]; blockers: ThreadBlocker[]; participants: string[]; message_count: number; date_range: { start: string; end: string }; warnings?: ApiWarning[]; } interface ThreadActionItem { description: string; owner?: string; deadline?: string; } interface ThreadDecision { description: string; made_by?: string; } interface ThreadBlocker { description: string; owner?: string; } ``` ### calendars.list(options?: ListCalendarsOptions): Promise> ```typescript interface ListCalendarsOptions { integrationId?: string; active?: boolean; expand?: Array<"description">; limit?: number; offset?: number; } interface Calendar { id: string; integration_id: string; title: string; description?: string | null; active: boolean; created_at: string; updated_at: string; } ``` ### calendars.update(id: string, options: { active: boolean }): Promise ### entities.list(options?: ListEntitiesOptions): Promise List contacts (people and bots) that the user has communicated with across connected integrations. ```typescript interface ListEntitiesOptions { search?: string; // Substring match on name or exact match on email entityType?: string; // "person" | "company" | "other" isBot?: boolean; // Filter by bot status limit?: number; // 1-100, default 20 offset?: number; } interface EntityContact { id: string; // Opaque ID (ent_xxx format) name: string; entity_type: "person" | "company" | "other" | "bot" | "user"; external_ids: string[]; // Email addresses and platform identifiers is_bot: boolean; avatar_uri: string | null; } ``` ### entities.get(id: string): Promise Retrieve a single contact by ID. Accepts both `ent_xxx` opaque format and raw UUID. ### entities.relationships(options?: ListRelationshipsOptions): Promise Get entity co-occurrence graph — pairs of contacts that appear together on messages and meetings. ```typescript interface ListRelationshipsOptions { limit?: number; // 1-500, default 200 minInteractions?: number; // Minimum co-occurrence count, default 1 includeBots?: boolean; // Include bot entities, default false } interface EntityRelationship { entity_a: EntityRelationshipNode; entity_b: EntityRelationshipNode; co_occurrence_count: number; last_interaction_at: string | null; // ISO 8601 } interface EntityRelationshipNode { id: string; name: string; entity_type: "person" | "company" | "other" | "bot" | "user"; external_ids: string[]; is_bot: boolean; avatar_uri: string | null; } ``` ## Push API (No OAuth Required) Push data directly into Attrove without requiring users to connect OAuth integrations. Data is queued for async processing and becomes searchable via AI queries once indexed. ### push.message(input: PushMessageInput): Promise ```typescript interface PushMessageInput { source: 'email' | 'chat' | 'alert' | 'custom'; bodyText: string; // 1–100,000 chars subject?: string; // max 500 chars senderName?: string; senderEmail?: string; recipientEmails?: string[]; receivedAt?: string; // ISO 8601 externalId?: string; // for idempotent upserts threadId?: string; metadata?: Record; // max ~50KB } ``` ### push.meeting(input: PushMeetingInput): Promise ```typescript interface PushMeetingInput { title: string; // 1–500 chars startTime: string; // ISO 8601 endTime: string; // ISO 8601 transcript?: string; // max 500,000 chars summary?: string; // max 50,000 chars shortSummary?: string; // max 5,000 chars actionItems?: { text: string; assignee?: string }[]; // max 50 attendees?: { name: string; email?: string }[]; // max 100 externalId?: string; metadata?: Record; } ``` ### push.event(input: PushEventInput): Promise ```typescript interface PushEventInput { title: string; // 1–500 chars startTime: string; // ISO 8601 endTime?: string; // defaults to startTime description?: string; // max 10,000 chars location?: string; // max 1,000 chars allDay?: boolean; // default false externalId?: string; metadata?: Record; } ``` ### push.note(input: PushNoteInput): Promise ```typescript interface PushNoteInput { body: string; // 1–10,000 chars title?: string; // max 500 chars refType?: 'message' | 'meeting' | 'event' | 'entity'; refId?: string; // must match refType prefix: msg_, mtg_, evt_, ent_ externalId?: string; metadata?: Record; } // Raw HTTP response wraps in { success, data }; the SDK unwraps automatically. // SDK methods return PushResponse directly: interface PushResponse { id: string; // prefixed ID (msg_, mtg_, evt_, note_) user_id: string; status: 'queued' | 'processing' | 'indexed' | 'failed'; indexed_at: string | null; } ``` ## Notes API ### notes.list(options?: ListNotesOptions): Promise ```typescript interface ListNotesOptions { ids?: string[]; // filter by note IDs refType?: 'message' | 'meeting' | 'event' | 'entity'; refId?: string; // filter by referenced resource limit?: number; // 1-100, default 50 offset?: number; // default 0 } interface Note { id: string; // note_xxx format user_id: string; integration_id: string; // int_xxx opaque format external_id: string | null; title: string | null; body: string; ref_type: string | null; ref_id: string | null; metadata: Record | null; status: string | null; // queued, processing, indexed, failed indexed_at: string | null; created_at: string; updated_at: string; } ``` ### notes.get(id: string): Promise Retrieve a single note by its opaque ID (note_xxx format). ## Admin Methods (Server-to-Server) ### Attrove.admin(config: AttroveAdminConfig): AdminClient ### admin.users.create(options: CreateUserOptions): Promise ```typescript interface CreateUserOptions { email: string; // Required firstName?: string; lastName?: string; role?: string; } interface CreateUserResponse { id: string; // User UUID apiKey: string; // sk_ prefixed API key } ``` ### admin.users.createConnectToken(userId: string): Promise ```typescript interface CreateTokenResponse { token: string; // pit_ prefixed token (10 min TTL) expires_at: string; // ISO 8601 } ``` ## Streaming API For real-time streaming of query responses via WebSocket: ```typescript const result = await attrove.stream('What happened in the meeting?', { onChunk: (chunk: string) => process.stdout.write(chunk), onState: (state: StreamState) => console.log('State:', state), onEnd: (reason: StreamEndReason) => console.log('Done:', reason), }); console.log('Full answer:', result.answer); ``` ```typescript type StreamState = "selecting_messages" | "streaming" | "completed" | "cancelled" | "error"; type StreamEndReason = "completed" | "cancelled" | "error"; type StreamFrame = | { type: "chunk"; message_id: string; content: string } | { type: "end"; message_id: string; reason: StreamEndReason; used_message_ids?: string[]; used_meeting_ids?: string[]; used_event_ids?: string[] } | { type: "error"; message_id: string; error: string } | { type: "state"; message_id: string; state: StreamState } | { type: "message_ids"; message_id: string; used_message_ids: string[]; used_meeting_ids?: string[]; used_event_ids?: string[] } | { type: "stream_start"; message_id: string }; ``` ## Error Handling — Complete Hierarchy ```typescript import { AttroveError, // Base class for all SDK errors AuthenticationError, // 401 — invalid/expired token NotFoundError, // 404 — resource not found RateLimitError, // 429 — rate limited (check retryAfter) ValidationError, // 400/422 — invalid input NetworkError, // Connection/timeout errors isAttroveError, // Type guard } from '@attrove/sdk'; // Error properties class AttroveError extends Error { code: ErrorCode; statusCode?: number; details?: ErrorDetails; } class RateLimitError extends AttroveError { retryAfter?: number; // Seconds until rate limit resets } // Error codes const ErrorCodes = { AUTH_MISSING_TOKEN: "AUTH_MISSING_TOKEN", AUTH_INVALID_TOKEN: "AUTH_INVALID_TOKEN", AUTH_EXPIRED_TOKEN: "AUTH_EXPIRED_TOKEN", AUTH_USER_MISMATCH: "AUTH_USER_MISMATCH", AUTH_INSUFFICIENT_PERMISSIONS: "AUTH_INSUFFICIENT_PERMISSIONS", RESOURCE_NOT_FOUND: "RESOURCE_NOT_FOUND", RESOURCE_ACCESS_DENIED: "RESOURCE_ACCESS_DENIED", RESOURCE_ALREADY_EXISTS: "RESOURCE_ALREADY_EXISTS", RESOURCE_DELETED: "RESOURCE_DELETED", VALIDATION_INVALID_ID: "VALIDATION_INVALID_ID", VALIDATION_REQUIRED_FIELD: "VALIDATION_REQUIRED_FIELD", VALIDATION_INVALID_FORMAT: "VALIDATION_INVALID_FORMAT", VALIDATION_OUT_OF_RANGE: "VALIDATION_OUT_OF_RANGE", INTEGRATION_OAUTH_FAILED: "INTEGRATION_OAUTH_FAILED", INTEGRATION_EMAIL_EXISTS: "INTEGRATION_EMAIL_EXISTS", INTEGRATION_TOKEN_EXPIRED: "INTEGRATION_TOKEN_EXPIRED", INTEGRATION_SYNC_FAILED: "INTEGRATION_SYNC_FAILED", INTEGRATION_NOT_CONNECTED: "INTEGRATION_NOT_CONNECTED", RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED", INTERNAL_ERROR: "INTERNAL_ERROR", SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE", REQUEST_TIMEOUT: "REQUEST_TIMEOUT", }; ``` ## Response Wrappers ```typescript interface SuccessResponse { success: true; data: T; } interface PaginatedResponse { success: true; data: T[]; pagination: { limit: number; offset: number; has_more: boolean; total_count?: number; }; } ``` ## MCP Server Attrove provides an MCP server for AI assistants (Claude Desktop, Cursor, ChatGPT, Claude Code). **HTTP transport** (Claude Desktop, ChatGPT) — connect to `https://api.attrove.com/mcp`. Auth is automatic via OAuth 2.1. **Stdio transport** (Cursor, Claude Code): ```json { "mcpServers": { "attrove": { "command": "npx", "args": ["-y", "@attrove/mcp@latest"], "env": { "ATTROVE_SECRET_KEY": "sk_...", "ATTROVE_USER_ID": "user-uuid" } } } } ``` ### MCP Tool Schemas #### attrove_query Ask questions and get AI-generated answers with sources. ```json { "name": "attrove_query", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "The question to ask about the user's context" }, "integration_ids": { "type": "array", "items": { "type": "string" }, "description": "Filter to specific integration IDs (int_xxx)" }, "include_sources": { "type": "boolean", "description": "Include source snippets in the response", "default": false }, "instructions": { "type": "string", "description": "Custom instructions for the AI. Controls output format, filtering, and behavior. Max 20,000 chars." }, "context": { "type": "string", "description": "Authoritative reference data for answer generation. Treated as ground truth. Influences query rewriting but not vector search. Max 20,000 chars." } }, "required": ["query"] } } ``` #### attrove_search Semantic search across messages, meetings, and calendar events. ```json { "name": "attrove_search", "inputSchema": { "type": "object", "properties": { "query": { "type": "string", "description": "Semantic search query" }, "after_date": { "type": "string", "description": "Only results after this date (YYYY-MM-DD)" }, "before_date": { "type": "string", "description": "Only results before this date (YYYY-MM-DD)" }, "sender_domains": { "type": "array", "items": { "type": "string" }, "description": "Filter by sender domains" }, "include_body_text": { "type": "boolean", "description": "Include message body text preview for message results", "default": true } }, "required": ["query"] } } ``` #### attrove_integrations List connected services and their status. No input parameters. #### attrove_events Calendar events with attendees. ```json { "name": "attrove_events", "inputSchema": { "type": "object", "properties": { "start_date": { "type": "string", "description": "Start of date range (YYYY-MM-DD)" }, "end_date": { "type": "string", "description": "End of date range (YYYY-MM-DD)" }, "limit": { "type": "number", "description": "Max events (default 25, max 100)" } }, "required": [] } } ``` #### attrove_meetings Meetings with AI summaries and action items. ```json { "name": "attrove_meetings", "inputSchema": { "type": "object", "properties": { "start_date": { "type": "string", "description": "Start of date range (YYYY-MM-DD)" }, "end_date": { "type": "string", "description": "End of date range (YYYY-MM-DD)" }, "provider": { "type": "string", "enum": ["google_meet", "zoom", "teams"], "description": "Filter by provider" }, "limit": { "type": "number", "description": "Max meetings (default 10, max 50)" } }, "required": [] } } ``` #### attrove_notes List notes with optional filtering. ```json { "name": "attrove_notes", "inputSchema": { "type": "object", "properties": { "ref_type": { "type": "string", "enum": ["message", "meeting", "event", "entity"], "description": "Filter by reference type" }, "ref_id": { "type": "string", "description": "Filter by referenced resource ID" }, "limit": { "type": "number", "description": "Max notes (default 20, max 100)" } }, "required": [] } } ``` #### attrove_push_note Save a note to user context. ```json { "name": "attrove_push_note", "inputSchema": { "type": "object", "properties": { "body": { "type": "string", "description": "Note content (1-10,000 chars)" }, "title": { "type": "string", "description": "Optional note title" }, "ref_type": { "type": "string", "enum": ["message", "meeting", "event", "entity"], "description": "Type of referenced resource" }, "ref_id": { "type": "string", "description": "Opaque ID of referenced resource" } }, "required": ["body"] } } ``` ## Supported Integrations Gmail, Google Calendar, Google Meet, Slack, Microsoft Outlook, Microsoft Teams (Chat, Calendar, Meetings). ## Enums Reference ```typescript type IntegrationProvider = "slack" | "gmail" | "outlook" | "google_calendar" | "unknown"; type AuthStatus = "connected" | "disconnected" | "expired" | "error" | "pending" | "unknown"; type SyncStatus = "syncing" | "complete" | "partial" | "error" | "pending" | "paused" | "unknown"; type ConversationType = "channel" | "direct_message" | "group" | "email_thread" | "other" | "unknown"; type MeetingProvider = "google_meet" | "zoom" | "teams" | "manual_meetings" | "unknown"; type ThreadIntegrationType = "slack" | "gmail" | "outlook" | "google_calendar" | "google_meet" | "zoom" | "teams" | "teams_chat" | "teams_meet" | "teams_calendar" | "unknown"; type IntegrationCategory = "email" | "chat" | "calendar" | "meeting" | (string & {}); type EntityType = "person" | "company" | "other" | "bot" | "user"; ``` ## Framework Integration Examples ### OpenAI Agents SDK ```typescript import { Attrove } from '@attrove/sdk'; import OpenAI from 'openai'; const attrove = new Attrove({ apiKey: sk_key, userId }); const openai = new OpenAI(); const tools = [{ type: 'function' as const, function: { name: 'query_communication', description: 'Query user email, Slack, and calendar data', parameters: { type: 'object', properties: { question: { type: 'string' } }, required: ['question'] }, }, }]; const response = await openai.chat.completions.create({ model: 'gpt-4o', messages: [{ role: 'user', content: 'Summarize my meetings this week' }], tools, }); // Handle tool call if (response.choices[0].message.tool_calls) { const question = JSON.parse(response.choices[0].message.tool_calls[0].function.arguments).question; const result = await attrove.query(question); // Feed result.answer back to the model } ``` ### LangChain ```typescript import { Attrove } from '@attrove/sdk'; import { tool } from '@langchain/core/tools'; import { z } from 'zod'; const attrove = new Attrove({ apiKey: sk_key, userId }); const attroveQuery = tool( async ({ question }) => { const result = await attrove.query(question); return result.answer; }, { name: 'attrove_query', description: 'Query user email, Slack, calendar, and meeting data with natural language', schema: z.object({ question: z.string() }), }, ); ``` ### Claude Desktop / Cursor (MCP — zero code) ```json { "mcpServers": { "attrove": { "command": "npx", "args": ["-y", "@attrove/mcp@latest"], "env": { "ATTROVE_SECRET_KEY": "sk_...", "ATTROVE_USER_ID": "user-uuid" } } } } ``` ### OpenClaw (Skill-Based) OpenClaw agents discover Attrove via SKILL.md. Configure the agent with environment variables: ```yaml # OpenClaw agent config skills: - name: attrove source: npm:@attrove/mcp@latest env: ATTROVE_SECRET_KEY: sk_... ATTROVE_USER_ID: user-uuid ``` The agent reads the SKILL.md trigger phrases and automatically invokes Attrove tools when the user asks about email, Slack, calendar, or meeting data. ## A2A Agent Card Attrove publishes an A2A Agent Card for agent discovery and direct agent-to-agent invocation: - Canonical card: `https://attrove.com/.well-known/agent-card.json` - Legacy alias: `https://attrove.com/.well-known/agent.json` - HTTP+JSON endpoint: `https://api.attrove.com/a2a/v1` Current A2A support is scoped to the core synchronous path: - `POST /a2a/v1/message:send` - `GET /a2a/v1/tasks` - `GET /a2a/v1/tasks/:id` - `POST /a2a/v1/tasks/:id:cancel` Auth matches Attrove's existing agent-facing surfaces: - OAuth Bearer token for user-self access - `sk_` Bearer token plus `X-Attrove-User-Id` for server-scoped end-user access This first A2A surface is deliberately conservative. It supports synchronous query-style interactions only, and returned tasks are short-lived retrieval records rather than durable workflow runs. A2A streaming, A2A push notifications, and extended authenticated agent cards are not enabled yet. ## Webhooks & Event Subscriptions Attrove delivers real-time event notifications via webhooks. Partners create subscription endpoints, choose which event types to receive, and get signed payloads as data flows through the platform. ### Subscription Management (REST API) All endpoints are authenticated with `Bearer ` + `X-Auth-Type: partner`. - `POST /v1/webhooks` — create a subscription endpoint (URL, event types, optional user ID filter) - `GET /v1/webhooks` — list all subscription endpoints - `GET /v1/webhooks/:id` — get subscription details - `PATCH /v1/webhooks/:id` — update endpoint URL, event types, user scope, or pause/resume via `is_active` - `DELETE /v1/webhooks/:id` — remove a subscription - `POST /v1/webhooks/:id/test` — send a synthetic `webhook.test` event to validate connectivity - `POST /v1/webhooks/:id/rotate-secret` — rotate the HMAC signing secret - `GET /v1/webhooks/:id/deliveries` — inspect delivery history with status filtering ### Subscribable Event Types - `messages.new` — newly synced messages are indexed and query-ready - `sync.completed` — integration sync cycle finished with summary metrics - `integration.status_changed` — connected integration status transition (e.g. active → error) - `webhook.test` — synthetic test event for endpoint validation - `meetings.new` — meeting records indexed and queryable - `events.new` — calendar events indexed and queryable - `events.starting_soon` — proactive notification before a calendar event starts (configurable: 5/10/15/30/60 min) - `notes.new` — pushed notes indexed and queryable Reserved (not yet subscribable): `meetings.summary_ready`. ### Delivery Guarantees - HMAC-SHA256 signature on every delivery (`webhook-id`, `webhook-timestamp`, `webhook-signature`) - Automatic retries with exponential backoff (up to 18 attempts over 24 hours) - Dead letter queue for failed deliveries with replay/dismiss - Circuit breaker: auto-pauses endpoint after 50 consecutive failures - Each delivery includes an `idempotency_key` for server-side deduplication - Payloads conform to CloudEvents 1.0 specification ### Write Operations Summary Beyond webhooks, Attrove exposes these write endpoints: - `POST /partner/users` — provision a new user - `POST /v1/users/:id/tokens` — generate short-lived integration token - `PATCH /v1/users/settings` — update sync preferences - `POST /api/v1/users/:id/push/*` — push messages, meetings, events, notes - `PATCH /api/v1/calendars/:id` — update calendar sync preferences ## Machine-Readable API Spec OpenAPI 3.1 specification: https://api.attrove.com/openapi.json ## Links - SDK: https://www.npmjs.com/package/@attrove/sdk - MCP: https://www.npmjs.com/package/@attrove/mcp - Examples: https://github.com/attrove/examples - Documentation: https://attrove.com/docs - API reference: https://attrove.com/docs/api-reference - Dashboard: https://connect.attrove.com ## Optional - Quickstart: https://github.com/attrove/examples/tree/main/quickstart (B2B2B provisioning flow) - Meeting prep agent: https://github.com/attrove/examples/tree/main/meeting-prep-agent (~160 lines) - Daily rundown: https://github.com/attrove/examples/tree/main/daily-rundown (scheduled digest, ~230 lines) - Search agent: https://github.com/attrove/examples/tree/main/search-agent (ad-hoc Q&A, ~65 lines) - MCP demo: https://github.com/attrove/examples/tree/main/mcp-demo (zero-code Claude/Cursor/ChatGPT setup) - Support: support@attrove.com