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
| Tool | Description |
|---|---|
search_wrike_tasks | Search tasks by keyword |
create_wrike_task | Create new task |
add_comment_to_wrike_task | Add comment |
edit_wrike_task | Update task |
get_wrike_task_details | Get task details |
Data Tools (Phase 3)
| Tool | Description |
|---|---|
get_stickies | Retrieve notes |
create_sticky | Create note |
edit_sticky | Update note |
search_news | Search articles |
get_weekly_news_digest | News summary |
Authentication
Bearer Token Flow
- User logs into Cync (Supabase Auth)
- User gets access token from session
- User configures Claude.ai with token
- 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
- Get tokens for two users
- Query stickies with each token
- Confirm each user only sees their data