# Building from Source

> **Complete guide to building an IETF vCon-compliant MCP Server from scratch**

## 📋 Table of Contents

1. [Prerequisites](#prerequisites)
2. [Project Overview](#project-overview)
3. [Phase 1: Environment Setup](#phase-1-environment-setup)
4. [Phase 2: Database Setup](#phase-2-database-setup)
5. [Phase 3: Project Structure](#phase-3-project-structure)
6. [Phase 4: Core Implementation](#phase-4-core-implementation)
7. [Phase 5: MCP Server](#phase-5-mcp-server)
8. [Phase 6: Testing & Validation](#phase-6-testing--validation)
9. [Phase 7: Deployment](#phase-7-deployment)
10. [Troubleshooting](#troubleshooting)
11. [Next Steps](#next-steps)

***

## Prerequisites

### Required Knowledge

* ✅ TypeScript/JavaScript programming
* ✅ Node.js and npm/yarn
* ✅ PostgreSQL basics
* ✅ REST APIs and JSON
* ✅ Git version control

### Required Software

* ✅ **Node.js** 18.x or higher ([Download](https://nodejs.org/))
* ✅ **npm** or **yarn** package manager
* ✅ **Git** ([Download](https://git-scm.com/))
* ✅ **Code editor** (VS Code recommended)
* ✅ **Supabase account** ([Sign up](https://supabase.com/))

### Time Estimate

* **Total:** 4-6 hours for first-time implementation
* **Experienced developers:** 2-3 hours

***

## Project Overview

### What We're Building

An **MCP (Model Context Protocol) Server** that:

* Stores and manages IETF vCon (Virtual Conversation) data
* Provides tools for AI assistants to interact with vCons
* Ensures full compliance with `draft-ietf-vcon-vcon-core-02`
* Uses Supabase (PostgreSQL) as the database backend

### Key Features

* ✅ Create, read, update, delete vCons
* ✅ Add dialog, analysis, and attachments
* ✅ Search and query vCon data
* ✅ Privacy and consent management
* ✅ Spec-compliant data validation
* ✅ MCP tools for AI integration

### Architecture

```
┌─────────────────┐
│   AI Assistant  │ (Claude, etc.)
└────────┬────────┘
         │ MCP Protocol
┌────────▼────────┐
│   MCP Server    │ (This project)
└────────┬────────┘
         │ Supabase Client
┌────────▼────────┐
│    Supabase     │ (PostgreSQL + REST API)
└─────────────────┘
```

***

## Phase 1: Environment Setup

### Step 1.1: Create Project Directory

```bash
# Create project directory
mkdir vcon-mcp-server
cd vcon-mcp-server

# Initialize git repository
git init
```

### Step 1.2: Initialize Node.js Project

```bash
# Create package.json
npm init -y
```

### Step 1.3: Install Dependencies

```bash
# Core dependencies
npm install @modelcontextprotocol/sdk @supabase/supabase-js zod

# Development dependencies
npm install -D typescript @types/node tsx vitest eslint \
  @typescript-eslint/eslint-plugin @typescript-eslint/parser
```

**What each package does:**

* `@modelcontextprotocol/sdk` - MCP server framework
* `@supabase/supabase-js` - Supabase client library
* `zod` - Schema validation library
* `typescript` - TypeScript compiler
* `tsx` - TypeScript execution for development
* `vitest` - Testing framework

### Step 1.4: Configure TypeScript

Create `tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
}
```

### Step 1.5: Update package.json Scripts

Add these scripts to your `package.json`:

```json
{
  "type": "module",
  "scripts": {
    "build": "tsc",
    "dev": "tsx watch src/index.ts",
    "test": "vitest",
    "test:compliance": "vitest run tests/vcon-compliance.test.ts",
    "lint": "eslint src/**/*.ts"
  }
}
```

### Step 1.6: Create .env.example

Create `.env.example`:

```bash
# Supabase Configuration
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key-here

# Optional: For service role operations
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
```

### Step 1.7: Create .gitignore

Create `.gitignore`:

```
# Node
node_modules/
npm-debug.log*
package-lock.json

# TypeScript
dist/
*.tsbuildinfo

# Environment variables
.env
.env.local

# IDE
.vscode/
.idea/
.DS_Store

# Testing
coverage/

# Logs
*.log
```

### ✅ Phase 1 Checkpoint

Verify your setup:

```bash
# Check Node.js version
node --version  # Should be 18.x or higher

# Check TypeScript
npx tsc --version

# Verify package.json
cat package.json

# Check dependencies
npm list
```

***

## Phase 2: Database Setup

### Step 2.1: Create Supabase Project

1. Go to [supabase.com](https://supabase.com/)
2. Click "New Project"
3. Fill in:
   * **Name:** vcon-mcp-server
   * **Database Password:** (Generate strong password)
   * **Region:** (Choose closest to you)
4. Click "Create new project"
5. Wait 2-3 minutes for provisioning

### Step 2.2: Get Supabase Credentials

1. Go to Project Settings → API
2. Copy:
   * **Project URL** → SUPABASE\_URL
   * **anon/public key** → SUPABASE\_ANON\_KEY

Create `.env` file:

```bash
SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

### Step 2.3: Run Database Schema

1. Go to SQL Editor in Supabase dashboard
2. Copy the entire schema from `CORRECTED_SCHEMA.md`
3. Paste into SQL Editor
4. Click "Run" to execute

**Key tables created:**

* `vcons` - Main vCon records
* `parties` - Conversation participants
* `dialog` - Conversation content (recordings, text, etc.)
* `analysis` - AI/ML analysis results
* `attachments` - File attachments
* `party_history` - Party event timeline

### Step 2.4: Verify Database Schema

Run these verification queries in SQL Editor:

```sql
-- Check tables exist
SELECT tablename FROM pg_tables 
WHERE schemaname = 'public'
ORDER BY tablename;

-- Check analysis table has 'schema' field (not 'schema_version')
SELECT column_name, data_type, is_nullable 
FROM information_schema.columns
WHERE table_name = 'analysis'
ORDER BY ordinal_position;

-- Verify vendor is NOT NULL
SELECT column_name, is_nullable
FROM information_schema.columns
WHERE table_name = 'analysis' 
AND column_name = 'vendor';
-- Should show 'NO' for is_nullable

-- Verify dialog type constraint exists
SELECT constraint_name, check_clause
FROM information_schema.check_constraints
WHERE constraint_name = 'dialog_type_check';

-- Check that encoding fields have no default values
SELECT column_name, column_default
FROM information_schema.columns
WHERE table_name IN ('dialog', 'analysis', 'attachments')
AND column_name = 'encoding';
-- All should show NULL for column_default
```

**Expected results:**

* ✅ 8+ tables created
* ✅ `analysis.schema` exists (NOT `schema_version`)
* ✅ `analysis.vendor` is NOT NULL
* ✅ `analysis.body` is TEXT type
* ✅ No DEFAULT values on encoding fields
* ✅ Dialog type constraint exists

### Step 2.5: Set Up Row Level Security (Optional)

For multi-tenant applications, enable RLS:

```sql
-- Enable RLS on all tables
ALTER TABLE vcons ENABLE ROW LEVEL SECURITY;
ALTER TABLE parties ENABLE ROW LEVEL SECURITY;
ALTER TABLE dialog ENABLE ROW LEVEL SECURITY;
ALTER TABLE analysis ENABLE ROW LEVEL SECURITY;
ALTER TABLE attachments ENABLE ROW LEVEL SECURITY;

-- Example policy: Users can only access their own vCons
CREATE POLICY "Users can view own vcons"
  ON vcons FOR SELECT
  USING (auth.uid() = user_id);

CREATE POLICY "Users can create own vcons"
  ON vcons FOR INSERT
  WITH CHECK (auth.uid() = user_id);
```

### ✅ Phase 2 Checkpoint

Verify database setup:

```bash
# Test connection with a simple query
node -e "
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
  process.env.SUPABASE_URL,
  process.env.SUPABASE_ANON_KEY
);
const { data, error } = await supabase.from('vcons').select('count');
console.log('Connection test:', error ? 'FAILED' : 'SUCCESS');
"
```

***

## Phase 3: Project Structure

### Step 3.1: Create Directory Structure

```bash
# Create all directories
mkdir -p src/{types,tools,resources,prompts,db,utils}
mkdir -p tests
mkdir -p scripts
```

### Step 3.2: Verify Structure

Your project should now look like this:

```
vcon-mcp-server/
├── src/
│   ├── types/          # TypeScript type definitions
│   ├── tools/          # MCP tool implementations
│   ├── resources/      # MCP resource handlers
│   ├── prompts/        # MCP prompt templates
│   ├── db/            # Database client and queries
│   └── utils/         # Utility functions
├── tests/             # Test files
├── scripts/           # Build and maintenance scripts
├── package.json
├── tsconfig.json
├── .env
├── .env.example
└── .gitignore
```

### ✅ Phase 3 Checkpoint

```bash
# Verify directory structure
ls -R src/
```

***

## Phase 4: Core Implementation

### Step 4.1: Create vCon Types

Create `src/types/vcon.ts`:

```typescript
/**
 * IETF vCon Core Types - Compliant with draft-ietf-vcon-vcon-core-02
 * CRITICAL: Uses corrected field names per specification
 */

export type VConVersion = '0.4.0';
export type Encoding = 'base64url' | 'json' | 'none';
export type DialogType = 'recording' | 'text' | 'transfer' | 'incomplete';

// Section 4.2 - Party Object
export interface Party {
  tel?: string;
  sip?: string;
  stir?: string;
  mailto?: string;
  name?: string;
  did?: string;
  validation?: string;
  jcard?: object;
  gmlpos?: string;
  civicaddress?: object;
  timezone?: string;
  uuid?: string;  // Section 4.2.12 - REQUIRED FIELD
}

// Section 4.3 - Dialog Object
export interface Dialog {
  type: DialogType;
  start?: string;
  duration?: number;
  parties?: number | number[] | (number | number[])[];
  originator?: number;
  mediatype?: string;
  filename?: string;
  body?: string;
  encoding?: Encoding;
  url?: string;
  content_hash?: string | string[];
  disposition?: 'no-answer' | 'congestion' | 'failed' | 'busy' | 'hung-up' | 'voicemail-no-message';
  session_id?: string;      // Section 4.3.10
  application?: string;     // Section 4.3.13
  message_id?: string;      // Section 4.3.14
}

// Section 4.5 - Analysis Object
// ⚠️ CRITICAL: This is the CORRECTED version
export interface Analysis {
  type: string;
  dialog?: number | number[];
  mediatype?: string;
  filename?: string;
  vendor: string;        // ✅ REQUIRED per spec Section 4.5.5
  product?: string;
  schema?: string;       // ✅ CORRECT: Not 'schema_version'
  body?: string;         // ✅ CORRECT: String, not object
  encoding?: Encoding;
  url?: string;
  content_hash?: string | string[];
}

// Section 4.4 - Attachment Object
export interface Attachment {
  type?: string;
  start?: string;
  party?: number;
  dialog?: number;
  mediatype?: string;
  filename?: string;
  body?: string;
  encoding?: Encoding;
  url?: string;
  content_hash?: string | string[];
}

// Section 4.1 - Main vCon Object
export interface VCon {
  vcon: VConVersion;
  uuid: string;
  extensions?: string[];      // Section 4.1.3
  critical?: string[];        // Section 4.1.4 (renamed from "must_support" in v0.4.0)
  created_at: string;
  updated_at?: string;
  subject?: string;
  redacted?: {
    uuid?: string;
    type?: string;
    url?: string;
    content_hash?: string | string[];
  };
  amended?: {                 // Renamed from "appended" in v0.4.0
    uuid?: string;
    url?: string;
    content_hash?: string | string[];
  };
  group?: Array<{
    uuid?: string;
    body?: string;
    encoding?: 'json';
    url?: string;
    content_hash?: string | string[];
  }>;
  parties: Party[];
  dialog?: Dialog[];
  analysis?: Analysis[];
  attachments?: Attachment[];
}

// Validation helper functions
export function isValidDialogType(type: string): type is DialogType {
  return ['recording', 'text', 'transfer', 'incomplete'].includes(type);
}

export function isValidEncoding(encoding: string): encoding is Encoding {
  return ['base64url', 'json', 'none'].includes(encoding);
}
```

**💡 Key Points:**

* ✅ Uses `schema` not `schema_version` in Analysis
* ✅ `vendor` is required (no `?`) in Analysis
* ✅ `body` is `string` type (not `object`)
* ✅ Includes `uuid` field in Party
* ✅ Includes new fields: `session_id`, `application`, `message_id`

### Step 4.2: Create Database Client

Create `src/db/client.ts`:

```typescript
import { createClient, SupabaseClient } from '@supabase/supabase-js';

let supabase: SupabaseClient | null = null;

export function getSupabaseClient(): SupabaseClient {
  if (!supabase) {
    const url = process.env.SUPABASE_URL;
    const key = process.env.SUPABASE_ANON_KEY;

    if (!url || !key) {
      throw new Error(
        'Missing Supabase credentials. Set SUPABASE_URL and SUPABASE_ANON_KEY environment variables.'
      );
    }

    supabase = createClient(url, key);
  }

  return supabase;
}

export function closeSupabaseClient(): void {
  supabase = null;
}
```

### Step 4.3: Create Database Queries

Create `src/db/queries.ts`:

```typescript
import { SupabaseClient } from '@supabase/supabase-js';
import { VCon, Analysis, Dialog, Party } from '../types/vcon';

export class VConQueries {
  constructor(private supabase: SupabaseClient) {}

  async createVCon(vcon: VCon): Promise<{ uuid: string }> {
    // Insert main vcon
    const { data: vconData, error: vconError } = await this.supabase
      .from('vcons')
      .insert({
        uuid: vcon.uuid,
        vcon_version: vcon.vcon,
        subject: vcon.subject,
        created_at: vcon.created_at,
        updated_at: vcon.updated_at,
        extensions: vcon.extensions,
        critical: vcon.critical,
      })
      .select('id, uuid')
      .single();

    if (vconError) throw vconError;

    // Insert parties
    if (vcon.parties.length > 0) {
      const partiesData = vcon.parties.map((party, index) => ({
        vcon_id: vconData.id,
        party_index: index,
        tel: party.tel,
        sip: party.sip,
        mailto: party.mailto,
        name: party.name,
        did: party.did,
        uuid: party.uuid,  // ✅ Corrected field
        validation: party.validation,
      }));

      const { error: partiesError } = await this.supabase
        .from('parties')
        .insert(partiesData);

      if (partiesError) throw partiesError;
    }

    return { uuid: vconData.uuid };
  }

  async addAnalysis(vconUuid: string, analysis: Analysis): Promise<void> {
    const { data: vcon, error: vconError } = await this.supabase
      .from('vcons')
      .select('id')
      .eq('uuid', vconUuid)
      .single();

    if (vconError) throw vconError;

    // Get next analysis index
    const { data: existingAnalysis } = await this.supabase
      .from('analysis')
      .select('analysis_index')
      .eq('vcon_id', vcon.id)
      .order('analysis_index', { ascending: false })
      .limit(1);

    const nextIndex = existingAnalysis?.length 
      ? existingAnalysis[0].analysis_index + 1 
      : 0;

    // ✅ CORRECTED: Use 'schema' not 'schema_version'
    const { error: analysisError } = await this.supabase
      .from('analysis')
      .insert({
        vcon_id: vcon.id,
        analysis_index: nextIndex,
        type: analysis.type,
        dialog_indices: Array.isArray(analysis.dialog) 
          ? analysis.dialog 
          : (analysis.dialog ? [analysis.dialog] : null),
        mediatype: analysis.mediatype,
        filename: analysis.filename,
        vendor: analysis.vendor,    // ✅ REQUIRED field
        product: analysis.product,
        schema: analysis.schema,    // ✅ Correct field name
        body: analysis.body,        // ✅ TEXT type
        encoding: analysis.encoding,
        url: analysis.url,
        content_hash: analysis.content_hash,
      });

    if (analysisError) throw analysisError;
  }

  async getVCon(uuid: string): Promise<VCon> {
    // Get main vcon
    const { data: vconData, error: vconError } = await this.supabase
      .from('vcons')
      .select('*')
      .eq('uuid', uuid)
      .single();

    if (vconError) throw vconError;

    // Get parties
    const { data: parties } = await this.supabase
      .from('parties')
      .select('*')
      .eq('vcon_id', vconData.id)
      .order('party_index');

    // Get dialog
    const { data: dialogs } = await this.supabase
      .from('dialog')
      .select('*')
      .eq('vcon_id', vconData.id)
      .order('dialog_index');

    // Get analysis - ✅ Queries 'schema' not 'schema_version'
    const { data: analysis } = await this.supabase
      .from('analysis')
      .select('*')
      .eq('vcon_id', vconData.id)
      .order('analysis_index');

    // Reconstruct vCon
    return {
      vcon: vconData.vcon_version as '0.4.0',
      uuid: vconData.uuid,
      extensions: vconData.extensions,
      critical: vconData.critical,
      created_at: vconData.created_at,
      updated_at: vconData.updated_at,
      subject: vconData.subject,
      parties: parties?.map(p => ({
        tel: p.tel,
        sip: p.sip,
        mailto: p.mailto,
        name: p.name,
        did: p.did,
        uuid: p.uuid,
        validation: p.validation,
      })) || [],
      dialog: dialogs?.map(d => ({
        type: d.type,
        start: d.start_time,
        duration: d.duration_seconds,
        parties: d.parties,
        originator: d.originator,
        mediatype: d.mediatype,
        body: d.body,
        encoding: d.encoding,
        session_id: d.session_id,
        application: d.application,
        message_id: d.message_id,
      })),
      analysis: analysis?.map(a => ({
        type: a.type,
        dialog: a.dialog_indices,
        vendor: a.vendor,
        product: a.product,
        schema: a.schema,  // ✅ Correct field name
        body: a.body,
        encoding: a.encoding,
      })),
    };
  }

  async searchVCons(filters: {
    subject?: string;
    partyName?: string;
    startDate?: string;
    endDate?: string;
  }): Promise<VCon[]> {
    let query = this.supabase
      .from('vcons')
      .select('*');

    if (filters.subject) {
      query = query.ilike('subject', `%${filters.subject}%`);
    }

    if (filters.startDate) {
      query = query.gte('created_at', filters.startDate);
    }

    if (filters.endDate) {
      query = query.lte('created_at', filters.endDate);
    }

    const { data, error } = await query;
    if (error) throw error;

    // Fetch full vCons for results
    return Promise.all(
      data.map(v => this.getVCon(v.uuid))
    );
  }
}
```

### Step 4.4: Create Validation Utilities

Create `src/utils/validation.ts`:

```typescript
import { VCon, Analysis, Dialog } from '../types/vcon';

export class VConValidator {
  private errors: string[] = [];

  validate(vcon: VCon): { valid: boolean; errors: string[] } {
    this.errors = [];

    this.validateVersion(vcon);
    this.validateUUID(vcon.uuid);
    this.validateParties(vcon.parties);
    if (vcon.dialog) this.validateDialogs(vcon.dialog);
    if (vcon.analysis) this.validateAnalysis(vcon.analysis);

    return {
      valid: this.errors.length === 0,
      errors: this.errors
    };
  }

  private validateVersion(vcon: VCon): void {
    if (vcon.vcon !== '0.4.0') {
      this.errors.push(`Invalid vcon version: ${vcon.vcon}`);
    }
  }

  private validateUUID(uuid: string): void {
    const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
    if (!uuidRegex.test(uuid)) {
      this.errors.push(`Invalid UUID format: ${uuid}`);
    }
  }

  private validateParties(parties: any[]): void {
    if (parties.length === 0) {
      this.errors.push('vCon must have at least one party');
    }
  }

  private validateDialogs(dialogs: Dialog[]): void {
    dialogs.forEach((dialog, i) => {
      const validTypes = ['recording', 'text', 'transfer', 'incomplete'];
      if (!validTypes.includes(dialog.type)) {
        this.errors.push(`Dialog ${i} has invalid type: ${dialog.type}`);
      }
    });
  }

  private validateAnalysis(analyses: Analysis[]): void {
    analyses.forEach((analysis, i) => {
      // ✅ CRITICAL: vendor is required
      if (!analysis.vendor) {
        this.errors.push(`Analysis ${i} missing required field: vendor`);
      }
    });
  }
}

export function validateVCon(vcon: VCon) {
  return new VConValidator().validate(vcon);
}
```

### ✅ Phase 4 Checkpoint

Verify your implementation:

```bash
# Compile TypeScript
npm run build

# Should compile without errors
# Check dist/ directory was created
ls dist/
```

***

## Phase 5: MCP Server

### Step 5.1: Create MCP Tool Definitions

Create `src/tools/vcon-crud.ts`:

```typescript
import { z } from 'zod';

// ✅ CORRECTED: Analysis schema with proper field names
export const AnalysisSchema = z.object({
  type: z.string(),
  dialog: z.union([z.number(), z.array(z.number())]).optional(),
  vendor: z.string(),              // ✅ REQUIRED
  product: z.string().optional(),
  schema: z.string().optional(),   // ✅ Correct field name
  body: z.string().optional(),     // ✅ String type
  encoding: z.enum(['base64url', 'json', 'none']).optional(),
});

export const PartySchema = z.object({
  tel: z.string().optional(),
  mailto: z.string().optional(),
  name: z.string().optional(),
  uuid: z.string().uuid().optional(),  // ✅ Added
});

export const DialogSchema = z.object({
  type: z.enum(['recording', 'text', 'transfer', 'incomplete']),
  start: z.string().optional(),
  body: z.string().optional(),
  encoding: z.enum(['base64url', 'json', 'none']).optional(),
  session_id: z.string().optional(),
  application: z.string().optional(),
  message_id: z.string().optional(),
});

// MCP Tool: Create vCon
export const createVConTool = {
  name: 'create_vcon',
  description: 'Create a new vCon compliant with IETF spec',
  inputSchema: {
    type: 'object',
    properties: {
      subject: { type: 'string' },
      parties: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            name: { type: 'string' },
            tel: { type: 'string' },
            mailto: { type: 'string' },
            uuid: { type: 'string' },
          }
        },
        minItems: 1
      }
    },
    required: ['parties']
  }
};

// MCP Tool: Add Analysis
export const addAnalysisTool = {
  name: 'add_analysis',
  description: 'Add analysis to a vCon',
  inputSchema: {
    type: 'object',
    properties: {
      vcon_uuid: { type: 'string', format: 'uuid' },
      analysis: {
        type: 'object',
        properties: {
          type: { type: 'string' },
          vendor: { type: 'string' },    // ✅ REQUIRED
          product: { type: 'string' },
          schema: { type: 'string' },    // ✅ Correct name
          body: { type: 'string' },      // ✅ String type
          encoding: {
            type: 'string',
            enum: ['base64url', 'json', 'none']
          }
        },
        required: ['type', 'vendor']     // ✅ vendor required
      }
    },
    required: ['vcon_uuid', 'analysis']
  }
};

// MCP Tool: Get vCon
export const getVConTool = {
  name: 'get_vcon',
  description: 'Retrieve a vCon by UUID',
  inputSchema: {
    type: 'object',
    properties: {
      uuid: { type: 'string', format: 'uuid' }
    },
    required: ['uuid']
  }
};

// MCP Tool: Search vCons
export const searchVConsTool = {
  name: 'search_vcons',
  description: 'Search vCons by criteria',
  inputSchema: {
    type: 'object',
    properties: {
      subject: { type: 'string' },
      party_name: { type: 'string' },
      start_date: { type: 'string', format: 'date-time' },
      end_date: { type: 'string', format: 'date-time' }
    }
  }
};
```

### Step 5.2: Create MCP Server

Create `src/index.ts`:

```typescript
#!/usr/bin/env node

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { getSupabaseClient } from './db/client.js';
import { VConQueries } from './db/queries.js';
import { validateVCon } from './utils/validation.js';
import {
  createVConTool,
  addAnalysisTool,
  getVConTool,
  searchVConsTool,
} from './tools/vcon-crud.js';
import { VCon, Analysis } from './types/vcon.js';

// Initialize MCP server
const server = new Server(
  {
    name: 'vcon-mcp-server',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// Initialize database
const supabase = getSupabaseClient();
const queries = new VConQueries(supabase);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      createVConTool,
      addAnalysisTool,
      getVConTool,
      searchVConsTool,
    ],
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    switch (name) {
      case 'create_vcon': {
        const vcon: VCon = {
          vcon: '0.4.0',
          uuid: crypto.randomUUID(),
          created_at: new Date().toISOString(),
          subject: args.subject,
          parties: args.parties,
        };

        // Validate before saving
        const validation = validateVCon(vcon);
        if (!validation.valid) {
          return {
            content: [{
              type: 'text',
              text: `Validation failed: ${validation.errors.join(', ')}`,
            }],
            isError: true,
          };
        }

        const result = await queries.createVCon(vcon);
        return {
          content: [{
            type: 'text',
            text: `Created vCon with UUID: ${result.uuid}`,
          }],
        };
      }

      case 'add_analysis': {
        const analysis: Analysis = args.analysis;
        
        // ✅ Ensure vendor is provided
        if (!analysis.vendor) {
          return {
            content: [{
              type: 'text',
              text: 'Error: vendor is required in analysis',
            }],
            isError: true,
          };
        }

        await queries.addAnalysis(args.vcon_uuid, analysis);
        return {
          content: [{
            type: 'text',
            text: `Added analysis to vCon ${args.vcon_uuid}`,
          }],
        };
      }

      case 'get_vcon': {
        const vcon = await queries.getVCon(args.uuid);
        return {
          content: [{
            type: 'text',
            text: JSON.stringify(vcon, null, 2),
          }],
        };
      }

      case 'search_vcons': {
        const results = await queries.searchVCons({
          subject: args.subject,
          partyName: args.party_name,
          startDate: args.start_date,
          endDate: args.end_date,
        });
        return {
          content: [{
            type: 'text',
            text: JSON.stringify(results, null, 2),
          }],
        };
      }

      default:
        throw new Error(`Unknown tool: ${name}`);
    }
  } catch (error) {
    return {
      content: [{
        type: 'text',
        text: `Error: ${error.message}`,
      }],
      isError: true,
    };
  }
});

// Start server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('vCon MCP Server running on stdio');
}

main().catch((error) => {
  console.error('Fatal error:', error);
  process.exit(1);
});
```

### Step 5.3: Test the Server

```bash
# Run in development mode
npm run dev

# In another terminal, test with MCP Inspector
npm run test:console
```

### ✅ Phase 5 Checkpoint

The server should:

* ✅ Start without errors
* ✅ List 4 tools (create, add\_analysis, get, search)
* ✅ Accept tool calls
* ✅ Return proper responses

***

## Phase 6: Testing & Validation

### Step 6.1: Create Compliance Tests

Create `tests/vcon-compliance.test.ts`:

```typescript
import { describe, it, expect } from 'vitest';
import { VCon, Analysis } from '../src/types/vcon';
import { validateVCon } from '../src/utils/validation';

describe('IETF vCon Spec Compliance', () => {
  it('should use "schema" not "schema_version"', () => {
    const analysis: Analysis = {
      type: 'test',
      vendor: 'TestVendor',
      schema: 'v1.0',  // ✅ Correct
      body: 'test',
      encoding: 'none'
    };

    expect(analysis.schema).toBe('v1.0');
    expect((analysis as any).schema_version).toBeUndefined();
  });

  it('should require vendor in analysis', () => {
    const vcon: VCon = {
      vcon: '0.4.0',
      uuid: crypto.randomUUID(),
      created_at: new Date().toISOString(),
      parties: [{ name: 'Test' }],
      analysis: [{
        type: 'test',
        body: 'test',
        encoding: 'none'
      } as any]
    };

    const result = validateVCon(vcon);
    expect(result.valid).toBe(false);
    expect(result.errors.some(e => e.includes('vendor'))).toBe(true);
  });

  it('should accept string body in analysis', () => {
    const analysis: Analysis = {
      type: 'transcript',
      vendor: 'TestVendor',
      body: 'Plain text content',  // ✅ String
      encoding: 'none'
    };

    expect(typeof analysis.body).toBe('string');
  });

  it('should support party uuid field', () => {
    const vcon: VCon = {
      vcon: '0.4.0',
      uuid: crypto.randomUUID(),
      created_at: new Date().toISOString(),
      parties: [{
        name: 'Test',
        uuid: crypto.randomUUID()  // ✅ uuid field
      }]
    };

    expect(vcon.parties[0].uuid).toBeDefined();
    expect(validateVCon(vcon).valid).toBe(true);
  });
});
```

### Step 6.2: Run Tests

```bash
# Run all tests
npm test

# Run compliance tests only
npm run test:compliance
```

### Step 6.3: Verify No Incorrect Field Names

```bash
# Search for incorrect field names in code
grep -r "schema_version" src/
# Should return NO results

grep -r "vendor?" src/types/
# Should return NO results (vendor should not be optional)
```

### ✅ Phase 6 Checkpoint

All tests should pass:

* ✅ No `schema_version` in codebase
* ✅ `vendor` is required in Analysis type
* ✅ `body` accepts string values
* ✅ All validation tests pass

***

## Phase 7: Deployment

### Step 7.1: Build for Production

```bash
# Clean and build
rm -rf dist/
npm run build

# Verify build
ls dist/
```

### Step 7.2: Configure MCP Client

Add to your MCP client configuration (e.g., Claude Desktop):

**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "vcon": {
      "command": "node",
      "args": ["/absolute/path/to/vcon-mcp-server/dist/index.js"],
      "env": {
        "SUPABASE_URL": "https://xxxxx.supabase.co",
        "SUPABASE_ANON_KEY": "eyJhbGciOiJIUzI1NiI..."
      }
    }
  }
}
```

### Step 7.3: Test with AI Assistant

Restart your AI assistant and try:

```
Create a new vCon with parties Alice and Bob
```

### ✅ Phase 7 Checkpoint

The AI should:

* ✅ See the vCon tools
* ✅ Successfully create vCons
* ✅ Add analysis with correct field names
* ✅ Retrieve and search vCons

***

## Troubleshooting

### Issue: TypeScript Compilation Errors

**Symptom:** Errors about missing types or incorrect field names

**Solution:**

```bash
# Reinstall dependencies
rm -rf node_modules package-lock.json
npm install

# Check TypeScript version
npx tsc --version  # Should be 5.x

# Verify tsconfig.json is correct
cat tsconfig.json
```

### Issue: Database Connection Fails

**Symptom:** "Missing Supabase credentials" error

**Solution:**

```bash
# Verify .env file exists and has correct values
cat .env

# Test connection
node -e "
require('dotenv').config();
console.log('URL:', process.env.SUPABASE_URL ? '✓' : '✗');
console.log('KEY:', process.env.SUPABASE_ANON_KEY ? '✓' : '✗');
"
```

### Issue: "schema\_version does not exist" Error

**Symptom:** Database error about unknown column

**Solution:**

```sql
-- Verify database schema is correct
SELECT column_name FROM information_schema.columns
WHERE table_name = 'analysis';

-- Should show 'schema' NOT 'schema_version'
```

If incorrect, re-run the corrected schema from `CORRECTED_SCHEMA.md`.

### Issue: Vendor Validation Fails

**Symptom:** "vendor is required" error

**Solution:**

* ✅ Always include `vendor` in analysis objects
* ✅ Check that Analysis type doesn't have `vendor?` (with question mark)
* ✅ Verify tool schema marks vendor as required

### Issue: MCP Server Doesn't Start

**Symptom:** Server crashes on startup

**Solution:**

```bash
# Check for syntax errors
npm run build

# Run with detailed logging
NODE_ENV=development npm run dev

# Verify MCP SDK version
npm list @modelcontextprotocol/sdk
```

***

## Next Steps

### Enhancements to Consider

1. **Add More Tools**
   * Update vCon
   * Delete vCon
   * Add dialog
   * Add attachments
   * Export vCon to JWS/JWE
2. **Add Resources**
   * `vcon://v1/vcons` URI scheme
   * List recent vCons
   * vCon templates
3. **Add Prompts**
   * Summarize conversation
   * Extract action items
   * Compliance check
4. **Advanced Features**
   * Privacy redaction
   * Consent management
   * Group vCons
   * Digital signatures (JWS)
   * Encryption (JWE)
5. **Performance**
   * Caching layer
   * Batch operations
   * Pagination
   * Indexes optimization
6. **Security**
   * Row Level Security
   * API rate limiting
   * Input sanitization
   * Audit logging

### Learning Resources

* **IETF vCon Spec:** `background_docs/draft-ietf-vcon-vcon-core-02.txt`
* **MCP Documentation:** <https://modelcontextprotocol.io/>
* **Supabase Docs:** <https://supabase.com/docs>
* **Implementation Guide:** `CLAUDE.md`
* **Quick Reference:** `QUICK_REFERENCE.md`

### Community & Support

* **vCon Working Group:** <https://datatracker.ietf.org/wg/vcon/>
* **MCP Discord:** <https://discord.gg/modelcontextprotocol>
* **Issues:** File bugs/questions in your repo issues

***

## Appendix: Command Reference

### Development Commands

```bash
# Install dependencies
npm install

# Build project
npm run build

# Run in development
npm run dev

# Run tests
npm test
npm run test:compliance

# Lint code
npm run lint

# Type check
npx tsc --noEmit
```

### Git Commands

```bash
# Initialize repo
git init

# Add all files
git add -A

# Commit
git commit -m "Initial commit"

# Create remote and push
git remote add origin <url>
git push -u origin main
```

### Database Commands

```bash
# Connect to Supabase
psql "postgresql://postgres:password@db.xxx.supabase.co:5432/postgres"

# List tables
\dt

# Describe table
\d analysis

# Run SQL file
\i schema.sql
```

***

## Success Checklist

Your implementation is complete when:

* [ ] All dependencies installed
* [ ] TypeScript compiles without errors
* [ ] Database schema matches spec
* [ ] No `schema_version` in codebase
* [ ] Analysis vendor is required
* [ ] Analysis body is string type
* [ ] Party has uuid field
* [ ] All compliance tests pass
* [ ] MCP server starts successfully
* [ ] Tools are callable from AI assistant
* [ ] Can create, read, search vCons
* [ ] vCons are spec-compliant
* [ ] Database operations work correctly

***

**🎉 Congratulations!** You've built a fully spec-compliant IETF vCon MCP Server!

***

*Last Updated: October 7, 2025*\
\&#xNAN;*Spec Version: draft-ietf-vcon-vcon-core-02*\
\&#xNAN;*vCon Schema: 0.4.0*


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mcp.conserver.io/development/building.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
