Skip to main content

MCP Server

Cync exposes an MCP (Model Context Protocol) server that enables Claude.ai and other LLM clients to interact with Cync data.

Overview

MCP provides a standardized way for AI assistants to:

  • Access application data
  • Execute actions
  • Maintain user context

Rollout Phases

Phase 1: AIC Wrike Bot (Single User)

Endpoint: /api/wrike-bot Auth: None (hardcoded credentials) Tools: 5 Wrike-specific tools

Purpose: Learn MCP patterns with minimal complexity.

Phase 2: Cync Bot (Multi-User)

Endpoint: /api/cync Auth: Bearer Token (Supabase session) Tools: Same 5 Wrike tools + user auth

Purpose: Proper multi-user support with RLS.

Phase 3: Expansion

Endpoint: /api/cync Auth: Bearer Token Tools: 10+ tools (Wrike, Stickies, News)

Purpose: Full data access.

Tools

Wrike Tools

ToolDescription
search_wrike_tasksSearch tasks by keyword
create_wrike_taskCreate new task
add_comment_to_wrike_taskAdd comment
edit_wrike_taskUpdate task
get_wrike_task_detailsGet task details

Data Tools (Phase 3)

ToolDescription
get_stickiesRetrieve notes
create_stickyCreate note
edit_stickyUpdate note
search_newsSearch articles
get_weekly_news_digestNews summary

Authentication

Bearer Token Flow

  1. User logs into Cync (Supabase Auth)
  2. User gets access token from session
  3. User configures Claude.ai with token
  4. MCP server validates token per request

Getting Your Token

// In browser console on cync.aicholdings.com
const { data: { session } } = await supabase.auth.getSession();
console.log(session.access_token);
// Copy this token

Claude.ai Setup

Settings → Custom Connectors

Name: Cync Bot
URL: https://cync.aicholdings.com/api/cync
Auth Type: Bearer Token
Token: <paste your token>

Implementation

Route Handler

// app/api/cync/[transport]/route.ts
import { createMcpHandler } from 'mcp-handler';
import { createAuthenticatedClient } from '@/lib/mcp/auth';

const handler = createMcpHandler(
async (server, request) => {
// Extract and validate token
const token = request.headers
.get('Authorization')
?.replace('Bearer ', '');

const { supabase, user } = await createAuthenticatedClient(token);

// Register tools with authenticated context
server.tool('get_stickies', {}, async () => {
const { data } = await supabase
.from('stickies')
.select('*')
.eq('user_id', user.id);
return { content: [{ type: 'text', text: JSON.stringify(data) }] };
});

// ... more tools
}
);

export { handler as GET, handler as POST };

Auth Helper

// lib/mcp/auth.ts
export async function createAuthenticatedClient(bearerToken: string) {
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
global: {
headers: { Authorization: `Bearer ${bearerToken}` }
}
}
);

const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) throw new Error('Invalid token');

return { supabase, user };
}

Security

RLS Enforcement

Using authenticated Supabase client ensures:

  • Users only see their own data
  • No service role bypass
  • Policies enforced automatically

Token Expiration

Supabase access tokens expire (default: 1 hour):

  • User must refresh token periodically
  • Future: Long-lived API keys or refresh flow

Testing

MCP Inspector

# Test locally
curl -X POST http://localhost:3000/api/cync \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Verifying RLS

  1. Get tokens for two users
  2. Query stickies with each token
  3. Confirm each user only sees their data