What is MCP

MCP là gì

The Model Context Protocol (MCP) is a standard for exposing tools and data to AI models. TWDAgentsHub implements MCP to let AI clients (Claude, GPT, etc.) query ERP data and perform operations through a standardized JSON-RPC interface.

Model Context Protocol (MCP) là tiêu chuẩn để expose tools và data cho AI models. TWDAgentsHub triển khai MCP cho phép AI clients (Claude, GPT, etc.) truy vấn dữ liệu ERP và thực hiện các thao tác qua giao diện JSON-RPC chuẩn hóa.

Key Features

Tính năng chính

  • Stateless HTTP Transport - Fresh server per request, no session management
  • JWT Pass-through Auth - Caller's ERP JWT forwarded to backend
  • Role-based Scoping - Reps see only their data; managers see all
  • Domain Adapters - Pluggable ERP backends (Twendee, etc.)
  • Stateless HTTP Transport - Server mới cho mỗi request, không cần quản lý session
  • JWT Pass-through Auth - JWT của ERP được forward đến backend
  • Role-based Scoping - Rep chỉ thấy data của mình; manager thấy tất cả
  • Domain Adapters - Backend ERP có thể thay đổi (Twendee, etc.)

Architecture

Kiến trúc

Component Overview

Tổng quan các thành phần

flowchart TB
    subgraph Callers["Callers"]
        AI["Claude / GPT\n(MCP Client)"]
        DA["DynamicAgent\nToolExecutor"]
        Test["curl / tests"]
    end

    subgraph Hub["TWDAgentsHub API"]
        MCP["McpController\n/api/mcp/stream"]
        Registry["McpToolRegistry"]
        Tools["crmToolDefinitions[]"]
    end

    subgraph Adapter["ERP Adapter Layer"]
        TW["TwendeeERPAdapter"]
    end

    ERP[("Twendee ERP\nAPI")]

    AI -->|"POST JSON-RPC\n+ Bearer JWT"| MCP
    DA -->|"internal call"| MCP
    Test -->|"POST JSON-RPC"| MCP

    MCP --> Registry
    Registry --> Tools
    Tools -->|"handler(input, adapter, ctx)"| TW
    TW -->|"HTTP + JWT"| ERP
    ERP -->|"JSON response"| TW
          

Request Flow

Luồng Request

sequenceDiagram
    participant Client as AI Client / Agent
    participant Ctrl as McpController
    participant Ctx as extractMcpContext
    participant Prov as McpAdapterProvider
    participant Reg as McpToolRegistry
    participant Tool as crmToolDefinition
    participant Adapter as TwendeeERPAdapter
    participant ERP as Twendee ERP

    Client->>Ctrl: POST /api/mcp/stream
Authorization: Bearer jwt Ctrl->>Ctx: extractMcpContext(req) Ctx-->>Ctrl: { userId, roles, requestId } Ctrl->>Prov: forErpJwt(jwt) Prov-->>Ctrl: adapter instance Ctrl->>Reg: registerTools(server, adapter, ctx) Reg->>Tool: server.registerTool(name, schema, handler) Note over Ctrl: McpServer.connect(transport) Client->>Ctrl: tools/call: crm_search_deals Ctrl->>Tool: handler(input, adapter, ctx) Tool->>Adapter: adapter.deals.findAll(query, ctx) Adapter->>ERP: GET /crm/deals + JWT ERP-->>Adapter: { data: [...] } Adapter-->>Tool: canonical result Tool-->>Ctrl: McpToolResult Ctrl-->>Client: JSON-RPC response

Data Flow

Luồng dữ liệu

  1. Client sends JSON-RPC + JWT header
  2. Controller extracts context from JWT claims
  3. AdapterProvider binds JWT to adapter
  4. Registry registers tools on McpServer
  5. Tool handler calls adapter methods
  6. Adapter forwards JWT to ERP API
  1. Client gửi JSON-RPC + JWT header
  2. Controller trích xuất context từ JWT claims
  3. AdapterProvider bind JWT vào adapter
  4. Registry đăng ký tools trên McpServer
  5. Tool handler gọi adapter methods
  6. Adapter forward JWT đến ERP API

Key Files

File chính

  • mcp.controller.ts
  • mcp-adapter.provider.ts
  • mcp-tool-registry.service.ts
  • crm-tool-definitions.ts
  • extract-mcp-context.ts

Key Components

Thành phần chính

McpContext

interface McpContext {
  userId: string;      // From JWT sub/userId claim
  roles: string[];     // From JWT role/roles claim
  requestId: string;   // Auto-generated or x-mcp-request-id header
  traceId?: string;    // x-mcp-trace-id header (observability)
  tenantId?: string;   // x-mcp-tenant-id header (multi-tenant)
}

Authentication

Xác thực

Auth Model: Caller sends ERP JWT directly. Hub parses claims for metadata but does NOT verify signature — ERP validates authenticity on each adapter call.

Mô hình Auth: Caller gửi JWT của ERP trực tiếp. Hub parse claims để lấy metadata nhưng KHÔNG verify signature — ERP xác thực tính hợp lệ trong mỗi adapter call.

// extract-mcp-context.ts
export function extractMcpContext(req: Request): McpContext {
  const authHeader = req.header('authorization') ?? '';
  const bearer = authHeader.slice(7).trim();

  // Parse JWT claims WITHOUT verification
  const claims = parseJwtClaimsUnsafe(bearer);

  return {
    userId: claims?.userId ?? claims?.sub ?? 'anonymous',
    roles: normalizeRoles(claims),
    requestId: req.header('x-mcp-request-id') ?? randomUUID(),
  };
}

ERP Adapter

ERP Adapter

interface IERPAdapter {
  readonly name: string;
  readonly crm?: ICRMAdapter;  // CRM domain adapter

  authenticate(): Promise<void>;
  healthCheck(ctx?: McpContext): Promise<HealthStatus>;
  getSupportedDomains(): DomainName[];
}

interface ICRMAdapter {
  companies: { findAll, findById };
  leads: { findAll, findById };
  deals: { findAll, findById, create, update };
  followUps: { findAll };
  dashboard: { getMetrics };
}

Project Setup

Cài đặt dự án

Get started building your own MCP server.

Bắt đầu xây dựng MCP server của bạn.

1. Install Dependencies

1. Cài đặt Dependencies

pnpm add @modelcontextprotocol/sdk zod

2. Environment Variables

2. Biến môi trường

# .env
TWENDEE_ERP_BASE_URL=https://staging-erp.twendeesoft.com/api
TWENDEE_ERP_TIMEOUT_MS=15000

3. Register MCP Module

3. Đăng ký MCP Module

// app.module.ts
import { McpModule } from './mcp/mcp.module';

@Module({
  imports: [
    ConfigModule.forRoot(),
    McpModule,
  ],
})
export class AppModule {}

Define a Tool

Định nghĩa Tool

Tools are defined with a Zod schema for input validation and metadata for the AI client.

Tools được định nghĩa với Zod schema để validate input và metadata cho AI client.

import { z } from 'zod';

// Simple tool definition
const helloWorldTool = {
  name: 'hello_world',
  description: 'Returns a greeting for the given name',

  inputSchema: {
    name: z.string().describe('Name to greet'),
  },

  // Metadata for tool registry
  tier: 'essential',     // 'essential' or 'advanced'
  destructive: false,    // true if modifies data
  roleScoped: false,     // true if needs ownership filter
};

Tool Metadata

Metadata của Tool

  • name - Unique identifier (snake_case)
  • description - What the AI sees when selecting tools
  • inputSchema - Zod schema for parameters
  • tier - Loading priority (essential = always loaded)
  • name - Định danh duy nhất (snake_case)
  • description - Mô tả AI thấy khi chọn tools
  • inputSchema - Zod schema cho parameters
  • tier - Độ ưu tiên load (essential = luôn được load)

Implement Handler

Viết Handler

The handler receives validated input and returns an McpToolResult.

Handler nhận input đã được validate và trả về McpToolResult.

// Helper to format JSON results
function jsonResult(value: unknown): McpToolResult {
  return {
    content: [{ type: 'text', text: JSON.stringify(value, null, 2) }]
  };
}

// Tool handler
const helloWorldTool = {
  // ... schema from above

  handler: async (input, adapter, ctx) => {
    // input is already validated by Zod
    const greeting = `Hello, ${input.name}!`;

    // Return as MCP result
    return jsonResult({ greeting, userId: ctx.userId });
  },
};

McpToolResult Shape

Cấu trúc McpToolResult

interface McpToolResult {
  content: Array<{ type: 'text'; text: string }>;
  isError?: boolean;  // Set true to indicate failure
}

Register Tools

Đăng ký Tools

Tools are registered with the MCP server via the tool registry service.

Tools được đăng ký với MCP server thông qua tool registry service.

// mcp-tool-registry.service.ts
@Injectable()
export class McpToolRegistry {
  registerTools(server: McpServer, adapter: IERPAdapter, ctx: McpContext) {
    for (const tool of crmToolDefinitions) {
      server.tool(
        tool.name,
        tool.description,
        zodToJsonSchema(tool.inputSchema),
        async (args) => tool.handler(args, adapter.crm!, ctx)
      );
    }
  }
}

Test with curl

Test với curl

Test your MCP server using curl before connecting an AI client.

Test MCP server của bạn bằng curl trước khi kết nối AI client.

1. Discovery

curl -s http://localhost:3000/.well-known/mcp.json | jq

2. Initialize Handshake

2. Khởi tạo Handshake

curl -X POST http://localhost:3000/api/mcp/stream \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": { "name": "curl", "version": "0" }
    }
  }'

3. List Tools

3. Liệt kê Tools

curl -X POST http://localhost:3000/api/mcp/stream \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

4. Call a Tool

4. Gọi một Tool

curl -X POST http://localhost:3000/api/mcp/stream \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "hello_world",
      "arguments": { "name": "Developer" }
    }
  }'

Connect to LLM

Kết nối LLM

Configure Claude Desktop or other MCP clients to use your server.

Cấu hình Claude Desktop hoặc các MCP client khác để sử dụng server của bạn.

Claude Desktop Config

Cấu hình Claude Desktop

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "twd-agents-hub": {
      "url": "http://localhost:3000/api/mcp/stream",
      "transport": "streamable-http",
      "headers": {
        "Authorization": "Bearer YOUR_ERP_JWT"
      }
    }
  }
}

You're Ready!

Sẵn sàng!

Restart Claude Desktop, and your tools will appear in the toolbox. Ask Claude to "search for companies" or "list my deals" to test.

Khởi động lại Claude Desktop, và tools của bạn sẽ xuất hiện trong toolbox. Hỏi Claude "tìm công ty" hoặc "liệt kê deals của tôi" để test.

Tool Structure

Cấu trúc Tool

Complete tool definition interface with all available options.

Interface định nghĩa tool đầy đủ với tất cả các tùy chọn.

interface CrmToolDefinition<TInput extends z.ZodRawShape> {
  // Identity
  name: string;         // e.g., "crm_search_deals"
  title: string;        // Human-readable name
  description: string;  // What the tool does (shown to AI)

  // Input validation
  inputSchema: TInput;  // Zod schema for parameters

  // Metadata
  tier: 'essential' | 'advanced';  // Loading priority
  destructive: boolean;            // Requires confirmation?
  roleScoped: boolean;             // Needs ownership filter?

  // Implementation
  handler: (
    input: z.infer<z.ZodObject<TInput>>,
    adapter: ICRMAdapter,
    ctx: McpContext,
  ) => Promise<McpToolResult>;
}

Example: Search Deals Tool

Ví dụ: Search Deals Tool

export const crmSearchDealsTool: CrmToolDefinition = {
  name: 'crm_search_deals',
  title: 'Search CRM deals',
  description: `Search CRM deals (sales opportunities). Returns paginated list
    with name, status, type, value, currency. Rep users only see deals
    they created OR follow; manager-tier users see all.`,

  inputSchema: {
    page: z.number().int().positive().optional(),
    limit: z.number().int().positive().max(100).optional(),
    search: z.string().optional(),
    status: z.string().optional(),
    companyId: z.string().optional(),
  },

  tier: 'essential',
  destructive: false,
  roleScoped: true,

  handler: async (input, adapter, ctx) => {
    const query = scopeDealQuery(input, ctx);
    const result = await adapter.deals.findAll(query, ctx);
    return jsonResult(result);
  },
};

Role Scoping

Phân quyền theo Role

Tools marked with roleScoped: true filter data based on user role.

Tools có roleScoped: true sẽ lọc data dựa trên role của user.

const MANAGER_ROLES = ['ADMIN', 'MANAGER', 'SALES_MANAGER', 'CRM_ADMIN'];

export function isManagerRole(ctx: McpContext): boolean {
  return ctx.roles.some(r => MANAGER_ROLES.includes(r.toUpperCase()));
}

export function scopeDealQuery(query: DealQuery, ctx: McpContext): DealQuery {
  if (isManagerRole(ctx)) return query;
  // Rep users: inject followerId filter
  return { ...query, followerId: ctx.userId };
}

Manager Role

  • ✓ See all deals
  • ✓ See all follow-ups
  • ✓ Team-wide dashboard
  • ✓ Xem tất cả deals
  • ✓ Xem tất cả follow-ups
  • ✓ Dashboard toàn team

Rep Role

  • ✓ Own deals only
  • ✓ Own follow-ups only
  • ✓ Personal dashboard
  • ✓ Chỉ deals của mình
  • ✓ Chỉ follow-ups của mình
  • ✓ Dashboard cá nhân

Write Tools

Write Tools

Tools that modify data require additional safety wrappers and return undo tokens.

Tools sửa đổi data cần thêm safety wrappers và trả về undo tokens.

// Write tool response shape
interface WriteToolResponse<T> {
  result: T;                // Created/updated record
  undoToken: string;        // Base64-encoded undo payload
  undoExpiresAt: number;    // TTL (5 minutes)
  auditId: string;          // For logging
}

// Usage
const response = await crm_create_deal({ name: "Big Deal", ... });
// Returns: { result: {...}, undoToken: "eyJ...", undoExpiresAt: 1775893391058 }

// To undo within 5 minutes:
await crm_undo({ undoToken: response.undoToken });
// Returns: { undone: true, toolName: "crm_create_deal", ... }

Stage Transition Guards

Bảo vệ chuyển đổi Stage

// Legal transitions (illegal moves rejected unless overrideReason provided)
DISCOVERY          → PREPARING_PROPOSAL, ON_HOLD, DROPPED, LOST
PREPARING_PROPOSAL → PROPOSAL_SENT, DISCOVERY, ON_HOLD, DROPPED, LOST
PROPOSAL_SENT      → WON, LOST, ON_HOLD, DROPPED
WON                → PROJECT_STARTED
PROJECT_STARTED    → PROJECT_COMPLETED, ON_HOLD
PROJECT_COMPLETED  → (terminal)
LOST               → (terminal)
DROPPED            → (terminal)
ON_HOLD            → DISCOVERY

Destructive Operations

Thao tác phá hủy

Tools with destructive: true get audit logging, validation guards, and 5-minute undo capability.

Tools có destructive: true sẽ có audit logging, validation guards, và khả năng undo trong 5 phút.

Error Handling

Xử lý lỗi

Tools return errors via the isError flag with structured prefixes.

Tools trả về lỗi qua flag isError với các prefix có cấu trúc.

// Success response
{
  "content": [{ "type": "text", "text": "{...JSON data...}" }]
}

// Error response
{
  "content": [{ "type": "text", "text": "Error: [PREFIX] message" }],
  "isError": true
}

Error Prefixes

Prefix lỗi

Prefix Meaning
[ROLE_SCOPING_DENIED] User lacks permission to access resource
[ILLEGAL_STAGE_TRANSITION] Deal status change not allowed
[UNDO_ALREADY_CONSUMED] Undo token already used or expired
[UNDO_OWNERSHIP_MISMATCH] User didn't create the original action
[UNDO_TOKEN_INVALID] Token tampered or malformed
Prefix Ý nghĩa
[ROLE_SCOPING_DENIED] User không có quyền truy cập resource
[ILLEGAL_STAGE_TRANSITION] Không được phép chuyển trạng thái deal
[UNDO_ALREADY_CONSUMED] Undo token đã sử dụng hoặc hết hạn
[UNDO_OWNERSHIP_MISMATCH] User không phải người tạo action gốc
[UNDO_TOKEN_INVALID] Token bị thay đổi hoặc sai định dạng

Adapter Pattern

Adapter Pattern

Adapters abstract ERP backends, allowing tools to work with any data source.

Adapters trừu tượng hóa ERP backends, cho phép tools làm việc với bất kỳ nguồn dữ liệu nào.

// McpAdapterProvider - creates adapter per request
@Injectable()
export class McpAdapterProvider {
  constructor(private config: ConfigService) {}

  forErpJwt(erpJwt: string): IERPAdapter {
    return new TwendeeERPAdapter({
      baseUrl: this.config.get('TWENDEE_ERP_BASE_URL'),
      jwtProvider: () => erpJwt,  // Injected per-request
      timeoutMs: 15000,
    });
  }
}

Adding a New Adapter

Thêm Adapter mới

  1. Implement IERPAdapter interface
  2. Add domain adapters (CRM, HR, etc.) as needed
  3. Register in McpAdapterProvider
  4. Tools automatically work via interface contract
  1. Implement interface IERPAdapter
  2. Thêm domain adapters (CRM, HR, etc.) khi cần
  3. Đăng ký trong McpAdapterProvider
  4. Tools tự động hoạt động qua interface contract

CRM Tools Overview

Tổng quan CRM Tools

The Sale Agent uses MCP tools to interact with CRM data in Twendee ERP. This is a real-world example of an MCP tool catalog.

Sale Agent sử dụng MCP tools để tương tác với dữ liệu CRM trong Twendee ERP. Đây là ví dụ thực tế về danh mục MCP tool.

21

Total Tools

Tổng Tools

10

Read-only

Chỉ đọc

11

Destructive

Phá hủy

11

Role-scoped

Theo role

Auth Model

Mô hình Auth

POST /api/mcp/stream HTTP/1.1
Authorization: Bearer <twendee_erp_jwt>
Content-Type: application/json
Accept: application/json, text/event-stream

Role Tiers

Các cấp Role

Tier Roles Visibility
Manager ADMIN, MANAGEMENT, BOD, SALES_MANAGER All deals/leads/companies/follow-ups
Rep SALES, EMPLOYEE, etc. Only records they created or follow
Cấp Roles Phạm vi
Manager ADMIN, MANAGEMENT, BOD, SALES_MANAGER Tất cả deals/leads/companies/follow-ups
Rep SALES, EMPLOYEE, etc. Chỉ records họ tạo hoặc follow

Tool Catalog

Danh mục Tool

All 21 CRM tools available to the Sale Agent.

Tất cả 21 CRM tools có sẵn cho Sale Agent.

Read Tools

Read Tools

crm_search_companies

read

Search CRM companies with pagination and filters

crm_get_company

read

Get single company by ID

crm_search_leads

read

Search CRM leads (prospective contacts)

crm_get_lead

read

Get single lead by ID

crm_search_deals

scoped read

Search deals (role-scoped: reps see own deals only)

crm_get_deal

scoped read

Get single deal by ID (ownership enforced)

crm_list_follow_ups

scoped read

List scheduled follow-ups (calls, meetings)

crm_get_dashboard_metrics

scoped read

Pipeline KPIs: total deals, value, won/lost counts

crm_list_deal_comments

scoped read

List comments on a deal

crm_undo

read

Undo a recent write operation (5-min TTL)

Write Tools

Write Tools

crm_create_company

write

Create a new company record

crm_update_company

write

Update an existing company

crm_create_lead

write

Create a new lead (prospective contact)

crm_update_lead

write

Update an existing lead

crm_create_deal

write

Create a new sales deal (requires leadId)

crm_update_deal

scoped write

Update an existing deal (ownership enforced)

crm_change_deal_status

scoped write

Move deal to new status (with transition guards)

crm_create_follow_up

scoped write

Schedule a follow-up (call, meeting)

crm_update_follow_up

scoped write

Update a follow-up (reschedule, change title)

crm_complete_follow_up

scoped write

Mark follow-up as done

crm_create_deal_comment

scoped write

Post a comment on a deal

Usage Examples

Ví dụ sử dụng

Real curl commands against the CRM MCP server.

Các lệnh curl thực tế cho CRM MCP server.

Search Deals

Tìm kiếm Deals

curl -s -X POST http://127.0.0.1:3999/api/mcp/stream \
  -H "authorization: Bearer $TWENDEE_TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "crm_search_deals",
      "arguments": {
        "status": "DISCOVERY",
        "limit": 5
      }
    }
  }'

Create Company + Undo

Tạo Company + Undo

# Create company
curl -s -X POST http://127.0.0.1:3999/api/mcp/stream \
  -H "authorization: Bearer $TWENDEE_TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 10,
    "method": "tools/call",
    "params": {
      "name": "crm_create_company",
      "arguments": {
        "name": "[TEST] Acme Corp",
        "countryCode": "VN"
      }
    }
  }'

# Response includes undoToken - grab it!
# { "result": {...}, "undoToken": "eyJ...", "undoExpiresAt": ... }

# Undo within 5 minutes
UNDO_TOKEN='eyJ...'
curl -s -X POST http://127.0.0.1:3999/api/mcp/stream \
  -H "authorization: Bearer $TWENDEE_TOKEN" \
  -H "content-type: application/json" \
  -d "{\"jsonrpc\":\"2.0\",\"id\":11,\"method\":\"tools/call\",\"params\":{\"name\":\"crm_undo\",\"arguments\":{\"undoToken\":\"$UNDO_TOKEN\"}}}"

Get Dashboard Metrics

Lấy Dashboard Metrics

curl -s -X POST http://127.0.0.1:3999/api/mcp/stream \
  -H "authorization: Bearer $TWENDEE_TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 20,
    "method": "tools/call",
    "params": {
      "name": "crm_get_dashboard_metrics",
      "arguments": {
        "period": "THIS_MONTH"
      }
    }
  }'

Sale Agent in Action

Sale Agent hoạt động

The Sale Agent (via chat-widget) calls these tools to help sales reps manage their pipeline. Example prompts: "Show me my deals in discovery", "Create a follow-up for Acme tomorrow", "What's my pipeline value this month?"

Sale Agent (qua chat-widget) gọi các tools này để giúp sales reps quản lý pipeline. Ví dụ: "Hiển thị deals đang discovery của tôi", "Tạo follow-up cho Acme ngày mai", "Pipeline value tháng này là bao nhiêu?"

Manual Testing

Test thủ công

Test MCP endpoints with curl against a running dev server.

Test các MCP endpoints với curl trên dev server đang chạy.

1. Start Dev Server

1. Khởi động Dev Server

cd /path/to/TWDAgentsHub

TWENDEE_ERP_BASE_URL='https://staging-erp.twendeesoft.com/api' \
  pnpm --filter @twd/api exec tsx src/mcp/scripts/start-mcp-dev.ts

# Listens on http://127.0.0.1:3999

2. Export Your JWT

2. Export JWT của bạn

export TWENDEE_TOKEN='<your twendee-erp JWT>'

3. Test Commands

3. Các lệnh Test

# Discovery
curl -s http://127.0.0.1:3999/.well-known/mcp.json | jq

# Initialize
curl -s -X POST http://127.0.0.1:3999/api/mcp/stream \
  -H "authorization: Bearer $TWENDEE_TOKEN" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'

# List tools
curl -s -X POST http://127.0.0.1:3999/api/mcp/stream \
  -H "authorization: Bearer $TWENDEE_TOKEN" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# Search deals
curl -s -X POST http://127.0.0.1:3999/api/mcp/stream \
  -H "authorization: Bearer $TWENDEE_TOKEN" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"crm_search_deals","arguments":{"limit":5}}}'

Response Format

Định dạng Response

The SDK streams JSON-RPC as Server-Sent Events. To parse:

SDK stream JSON-RPC dưới dạng Server-Sent Events. Để parse:

curl ... | grep '^data:' | sed 's/^data: //' | jq

MCP Inspector

MCP Inspector

Use the official MCP Inspector GUI for interactive testing.

Sử dụng giao diện MCP Inspector chính thức để kiểm tra tương tác.

Terminal A — Start API

Terminal A — Khởi động API

cd /path/to/TWDAgentsHub/apps/api
PORT=3999 pnpm start

Terminal B — Start Inspector

Terminal B — Khởi động Inspector

npx @modelcontextprotocol/inspector

Connection Settings

Cài đặt kết nối

  • Transport type: Streamable HTTP
  • URL: http://127.0.0.1:3999/api/mcp/stream
  • Headers: authorization: Bearer <TWENDEE_TOKEN>

Click Connect → Browse all 21 tools → Call via GUI

Nhấn Connect → Duyệt tất cả 21 công cụ → Gọi qua giao diện

What to Verify

Những gì cần kiểm tra

Read Tools

Công cụ đọc

  • crm_search_deals returns deals with createdBy: { id, name }no email (PII stripped)
  • crm_search_leads returns leads with email field populated from contacts[] array
  • crm_list_follow_ups strips note, guestEmails, googleCalendarEventId
  • crm_get_dashboard_metrics returns KPIs + status distribution + monthly trend

Write Tools

Công cụ ghi

  • Every destructive response contains {result, undoToken, undoExpiresAt, auditId}
  • Undo within 5 minutes → {undone: true}
  • Undo 2nd time → [UNDO_ALREADY_CONSUMED]
  • Undo with someone else's token → [UNDO_OWNERSHIP_MISMATCH]
  • Tampered undoToken → [UNDO_TOKEN_INVALID]

Stage Guard

Bảo vệ trạng thái

  • Illegal move without override → [ILLEGAL_STAGE_TRANSITION]
  • Illegal move WITH overrideReason (≥10 chars) → passes through, audit log records reason
  • Same-status no-op → allowed

Role Scoping

Phân quyền theo vai trò

  • Rep searching deals → auto followerId=<userId> injection, sees only own/followed
  • Rep trying to read another rep's deal → [ROLE_SCOPING_DENIED] (403)
  • Manager (ADMIN etc.) → no filter injected, sees all
  • Rep trying to update another rep's deal → pre-hook denial, no mutation

Automated Tests

Test tự động

Run the smoke test script against staging to verify all tools work.

Chạy smoke test script trên staging để xác minh tất cả tools hoạt động.

TWENDEE_TOKEN=<your_jwt> \
  pnpm --filter @twd/api exec tsx src/mcp/scripts/test-mcp-live.ts

What It Tests

Những gì được test

  1. Discovery endpoint
  2. MCP initialize handshake
  3. tools/list (expects 16+ tools)
  4. Read tools (search_companies, search_deals, etc.)
  5. Write + undo cycle (create company → undo → verify deleted)
  6. Undo one-shot enforcement (2nd call rejected)
  7. Illegal stage transition guard
  1. Discovery endpoint
  2. MCP initialize handshake
  3. tools/list (mong đợi 16+ tools)
  4. Read tools (search_companies, search_deals, v.v.)
  5. Write + undo cycle (tạo company → undo → xác minh đã xóa)
  6. Undo one-shot enforcement (lần gọi thứ 2 bị reject)
  7. Illegal stage transition guard

Exits 0 on all pass, 1 on any failure.

Thoát 0 khi tất cả pass, 1 khi có lỗi.

Cleanup if Undo Fails

Dọn dẹp khi Undo thất bại

If any crm_create_* runs successfully but the matching crm_undo fails (server crash, 5-min TTL expired, etc.), the smoke record lives on staging. Find + delete manually:

Nếu bất kỳ crm_create_* nào chạy thành công nhưng crm_undo tương ứng thất bại (server crash, hết hạn 5 phút, v.v.), bản ghi test vẫn còn trên staging. Tìm và xóa thủ công:

# Find all smoke test companies
curl -s -X POST http://127.0.0.1:3999/api/mcp/stream \
  -H "authorization: Bearer $TWENDEE_TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 90,
    "method": "tools/call",
    "params": {
      "name": "crm_search_companies",
      "arguments": {
        "search": "[MCP_SMOKE_TEST]",
        "limit": 50
      }
    }
  }'

Delete each via the ERP admin UI. The MCP server does not yet expose crm_delete_company — that lands in a later phase.

Xóa từng bản ghi qua giao diện admin ERP. MCP server chưa expose crm_delete_company — sẽ có trong phase sau.

Stop Server

Dừng Server

Press Ctrl+C in the terminal running the MCP dev server.

Nhấn Ctrl+C trong terminal đang chạy MCP dev server.