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
- Client sends JSON-RPC + JWT header
- Controller extracts context from JWT claims
- AdapterProvider binds JWT to adapter
- Registry registers tools on McpServer
- Tool handler calls adapter methods
- Adapter forwards JWT to ERP API
- Client gửi JSON-RPC + JWT header
- Controller trích xuất context từ JWT claims
- AdapterProvider bind JWT vào adapter
- Registry đăng ký tools trên McpServer
- Tool handler gọi adapter methods
- 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 toolsinputSchema- Zod schema for parameterstier- Loading priority (essential = always loaded)
name- Định danh duy nhất (snake_case)description- Mô tả AI thấy khi chọn toolsinputSchema- Zod schema cho parameterstier- Độ ư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
- Implement
IERPAdapterinterface - Add domain adapters (CRM, HR, etc.) as needed
- Register in
McpAdapterProvider - Tools automatically work via interface contract
- Implement interface
IERPAdapter - Thêm domain adapters (CRM, HR, etc.) khi cần
- Đăng ký trong
McpAdapterProvider - 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
readSearch CRM companies with pagination and filters
crm_get_company
readGet single company by ID
crm_search_leads
readSearch CRM leads (prospective contacts)
crm_get_lead
readGet single lead by ID
crm_search_deals
Search deals (role-scoped: reps see own deals only)
crm_get_deal
Get single deal by ID (ownership enforced)
crm_list_follow_ups
List scheduled follow-ups (calls, meetings)
crm_get_dashboard_metrics
Pipeline KPIs: total deals, value, won/lost counts
crm_list_deal_comments
List comments on a deal
crm_undo
readUndo a recent write operation (5-min TTL)
Write Tools
Write Tools
crm_create_company
writeCreate a new company record
crm_update_company
writeUpdate an existing company
crm_create_lead
writeCreate a new lead (prospective contact)
crm_update_lead
writeUpdate an existing lead
crm_create_deal
writeCreate a new sales deal (requires leadId)
crm_update_deal
Update an existing deal (ownership enforced)
crm_change_deal_status
Move deal to new status (with transition guards)
crm_create_follow_up
Schedule a follow-up (call, meeting)
crm_update_follow_up
Update a follow-up (reschedule, change title)
crm_complete_follow_up
Mark follow-up as done
crm_create_deal_comment
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_dealsreturns deals withcreatedBy: { id, name }— no email (PII stripped) -
✓
crm_search_leadsreturns leads withemailfield populated fromcontacts[]array -
✓
crm_list_follow_upsstripsnote,guestEmails,googleCalendarEventId -
✓
crm_get_dashboard_metricsreturns 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
- Discovery endpoint
- MCP initialize handshake
- tools/list (expects 16+ tools)
- Read tools (search_companies, search_deals, etc.)
- Write + undo cycle (create company → undo → verify deleted)
- Undo one-shot enforcement (2nd call rejected)
- Illegal stage transition guard
- Discovery endpoint
- MCP initialize handshake
- tools/list (mong đợi 16+ tools)
- Read tools (search_companies, search_deals, v.v.)
- Write + undo cycle (tạo company → undo → xác minh đã xóa)
- Undo one-shot enforcement (lần gọi thứ 2 bị reject)
- 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.