From b7fc91a3128f6e19126eb1137aeaf5ee2b45bfda Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 1 Jan 2026 11:46:12 +0800 Subject: [PATCH] Update Agent Documentation with MCP Directory Structure and Model Integration - Added a new section in the MCP documentation detailing the directory structure for assistants, enhancing clarity on how to define namespaced MCP servers. - Included examples of using assistant models within MCP tools, providing guidance on referencing models in tool configurations. - Updated the README to include a link to the new Models documentation, improving navigation and resource accessibility for users. --- agent/README.md | 1 + agent/docs/mcp.md | 97 ++++++++++-- agent/docs/models.md | 342 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 425 insertions(+), 15 deletions(-) create mode 100644 agent/docs/models.md diff --git a/agent/README.md b/agent/README.md index e2794641..9f4abf38 100644 --- a/agent/README.md +++ b/agent/README.md @@ -181,6 +181,7 @@ ctx.Send({ - [Hooks](docs/hooks.md) - Create/Next hooks and agent lifecycle - [Context API](docs/context-api.md) - Messaging, memory, trace, MCP - [MCP Integration](docs/mcp.md) - Tool servers and resources +- [Models](docs/models.md) - Assistant-scoped data models - [Search](docs/search.md) - Web, knowledge base, and database search - [Pages](docs/pages.md) - Web UI for agents (SUI framework) - [Internationalization](docs/i18n.md) - Multi-language support diff --git a/agent/docs/mcp.md b/agent/docs/mcp.md index 5ed0914e..9e0a0966 100644 --- a/agent/docs/mcp.md +++ b/agent/docs/mcp.md @@ -2,6 +2,26 @@ Model Context Protocol (MCP) enables tool integration with external services. +## Directory Structure + +Assistants can define their own namespaced MCP servers in the `mcps/` directory: + +``` +assistants/ +└── my-assistant/ + ├── package.yao + └── mcps/ + ├── tools.mcp.yao # → agents.my-assistant.tools + ├── calculator.mcp.yao # → agents.my-assistant.calculator + └── mapping/ + └── tools/ + └── schemes/ + ├── search.in.yao + └── search.out.yao +``` + +MCP servers are automatically loaded with `agents..` prefix. + ## Defining MCP Servers Create `mcps/tools.mcp.yao` in the assistant directory: @@ -75,9 +95,7 @@ Map Yao Processes directly to MCP tools: ```json { "mcp": { - "servers": [ - { "server_id": "tools", "tools": ["search", "calculate"] } - ] + "servers": [{ "server_id": "tools", "tools": ["search", "calculate"] }] } } ``` @@ -106,8 +124,8 @@ function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create { messages, mcp_servers: [ { server_id: "tools", tools: ["search"] }, - { server_id: "data", resources: ["data://reports"] } - ] + { server_id: "data", resources: ["data://reports"] }, + ], }; } ``` @@ -126,7 +144,7 @@ const tools = ctx.mcp.ListTools("server-id"); ```typescript const result = ctx.mcp.CallTool("server-id", "search", { query: "example", - limit: 10 + limit: 10, }); // { content: [{ type: "text", text: "..." }] } ``` @@ -137,13 +155,13 @@ const result = ctx.mcp.CallTool("server-id", "search", { // Sequential const results = ctx.mcp.CallTools("server-id", [ { name: "step1", arguments: { input: "a" } }, - { name: "step2", arguments: { input: "b" } } + { name: "step2", arguments: { input: "b" } }, ]); // Parallel const results = ctx.mcp.CallToolsParallel("server-id", [ { name: "api1", arguments: {} }, - { name: "api2", arguments: {} } + { name: "api2", arguments: {} }, ]); ``` @@ -189,9 +207,60 @@ mcps/ ``` The `x-process-args` maps MCP arguments to Yao Process parameters: + - `":arguments"` - Pass entire arguments object - `"$args.field"` - Extract specific field +### Schema with Nested Objects + +```json +{ + "type": "object", + "description": "Extract structured data from input", + "properties": { + "intent": { + "type": "string", + "enum": ["query", "create", "update"], + "description": "Operation intent" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "value": { "type": "number" } + }, + "required": ["name", "value"] + } + } + }, + "required": ["intent"], + "x-process-args": [":arguments"] +} +``` + +## Using Assistant Models in MCP + +MCP tools can reference assistant's own models: + +**mcps/data.mcp.yao** + +```json +{ + "label": "Data Tools", + "transport": "process", + "tools": { + "list_orders": "models.agents.my-assistant.order.Paginate", + "get_order": "models.agents.my-assistant.order.Find", + "create_order": "models.agents.my-assistant.order.Create", + "custom_query": "agents.my-assistant.orders.Query" + } +} +``` + +See [Models](models.md) for defining assistant models. + ## Error Handling ```typescript @@ -237,9 +306,7 @@ function Next(ctx: agent.Context, payload: agent.Payload): agent.Next { "name": "Math Assistant", "connector": "gpt-4o", "mcp": { - "servers": [ - { "server_id": "calculator", "tools": ["add", "multiply"] } - ] + "servers": [{ "server_id": "calculator", "tools": ["add", "multiply"] }] } } ``` @@ -254,7 +321,7 @@ function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create { // Enable calculator return { messages, - mcp_servers: [{ server_id: "calculator" }] + mcp_servers: [{ server_id: "calculator" }], }; } return { messages }; @@ -264,13 +331,13 @@ function Next(ctx: agent.Context, payload: agent.Payload): agent.Next { const { tools } = payload; if (tools?.length > 0) { - const calcResult = tools.find(t => t.server === "calculator"); + const calcResult = tools.find((t) => t.server === "calculator"); if (calcResult?.result) { return { data: { answer: calcResult.result, - expression: calcResult.arguments - } + expression: calcResult.arguments, + }, }; } } diff --git a/agent/docs/models.md b/agent/docs/models.md new file mode 100644 index 00000000..e180591f --- /dev/null +++ b/agent/docs/models.md @@ -0,0 +1,342 @@ +# Assistant Models + +Assistants can define their own namespaced data models in the `models/` directory. These models are automatically loaded with the `agents..` prefix and use isolated database tables. + +## Directory Structure + +``` +assistants/ +└── my-assistant/ + ├── package.yao + └── models/ + ├── order.mod.yao # → agents.my-assistant.order + ├── item.mod.yao # → agents.my-assistant.item + └── nested/ + └── log.mod.yao # → agents.my-assistant.nested.log +``` + +## Model Definition + +Standard Yao model definition with automatic table prefixing. + +**models/order.mod.yao** + +```json +{ + "name": "Order", + "label": "Order Record", + "description": "Customer orders", + "table": { + "name": "order", + "comment": "Order records" + }, + "columns": [ + { + "name": "id", + "type": "ID", + "label": "ID", + "primary": true + }, + { + "name": "order_no", + "type": "string", + "label": "Order Number", + "length": 100, + "nullable": false, + "unique": true, + "index": true + }, + { + "name": "customer_id", + "type": "string", + "label": "Customer ID", + "length": 255, + "nullable": false, + "index": true + }, + { + "name": "total_amount", + "type": "decimal", + "label": "Total Amount", + "precision": 15, + "scale": 2, + "nullable": false + }, + { + "name": "status", + "type": "enum", + "label": "Status", + "option": ["pending", "confirmed", "shipped", "completed", "cancelled"], + "default": "pending", + "nullable": false, + "index": true + }, + { + "name": "metadata", + "type": "json", + "label": "Metadata", + "nullable": true + } + ], + "relations": { + "items": { + "type": "hasMany", + "model": "item", + "key": "order_id", + "foreign": "id" + } + }, + "indexes": [ + { + "name": "idx_customer_status", + "columns": ["customer_id", "status"], + "type": "index" + } + ], + "option": { + "timestamps": true, + "soft_deletes": true + } +} +``` + +## Table Naming + +Tables are automatically prefixed with `agents__`: + +| Assistant ID | Model File | Model ID | Table Name | +| ------------ | ---------------------- | ------------------------ | ------------------------ | +| `expense` | `models/order.mod.yao` | `agents.expense.order` | `agents_expense_order` | +| `tests.demo` | `models/user.mod.yao` | `agents.tests.demo.user` | `agents_tests_demo_user` | + +## Using Models + +### In Hooks + +```typescript +import { Process } from "@yao/runtime"; + +function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create { + // Query assistant's own model + const orders = Process("models.agents.my-assistant.order.Paginate", { + wheres: [{ column: "status", value: "pending" }], + limit: 10, + }); + + return { messages }; +} +``` + +### In MCP Tools + +```json +{ + "transport": "process", + "tools": { + "list_orders": "models.agents.my-assistant.order.Paginate", + "get_order": "models.agents.my-assistant.order.Find", + "create_order": "models.agents.my-assistant.order.Create", + "update_order": "models.agents.my-assistant.order.Update" + } +} +``` + +### In Scripts + +**src/orders.ts** + +```typescript +import { Process } from "@yao/runtime"; + +export function ListPending(): any[] { + return Process("models.agents.my-assistant.order.Get", { + wheres: [{ column: "status", value: "pending" }], + orders: [{ column: "created_at", option: "desc" }], + }); +} + +export function CreateOrder(data: any): any { + return Process("models.agents.my-assistant.order.Create", data); +} + +export function UpdateStatus(id: number, status: string): any { + return Process("models.agents.my-assistant.order.Update", id, { status }); +} +``` + +## Column Types + +| Type | Description | Options | +| ------------ | -------------------------- | ----------------------- | +| `ID` | Auto-increment primary key | `primary: true` | +| `string` | VARCHAR | `length` (default: 255) | +| `text` | TEXT | - | +| `integer` | INT | - | +| `bigInteger` | BIGINT | - | +| `float` | FLOAT | `precision`, `scale` | +| `decimal` | DECIMAL | `precision`, `scale` | +| `boolean` | BOOLEAN | - | +| `date` | DATE | - | +| `datetime` | DATETIME | - | +| `timestamp` | TIMESTAMP | - | +| `json` | JSON/JSONB | - | +| `enum` | ENUM | `option: [...]` | + +## Column Options + +| Option | Type | Description | +| ----------- | --------- | ----------------- | +| `nullable` | `boolean` | Allow NULL values | +| `default` | `any` | Default value | +| `unique` | `boolean` | Unique constraint | +| `index` | `boolean` | Create index | +| `primary` | `boolean` | Primary key | +| `length` | `integer` | String length | +| `precision` | `integer` | Decimal precision | +| `scale` | `integer` | Decimal scale | +| `comment` | `string` | Column comment | + +## Relations + +```json +{ + "relations": { + "items": { + "type": "hasMany", + "model": "item", + "key": "order_id", + "foreign": "id" + }, + "customer": { + "type": "hasOne", + "model": "customer", + "key": "id", + "foreign": "customer_id" + } + } +} +``` + +| Type | Description | +| ---------------- | ----------------------------- | +| `hasOne` | One-to-one relationship | +| `hasMany` | One-to-many relationship | +| `hasOneThrough` | Has one through intermediate | +| `hasManyThrough` | Has many through intermediate | + +## Model Options + +```json +{ + "option": { + "timestamps": true, + "soft_deletes": true, + "permission": true + } +} +``` + +| Option | Description | +| -------------- | -------------------------------------- | +| `timestamps` | Add `created_at`, `updated_at` columns | +| `soft_deletes` | Add `deleted_at` for soft delete | +| `permission` | Enable permission checks | + +## Process Reference + +Common model processes: + +| Process | Description | Arguments | +| ------------- | ---------------- | --------------------------- | +| `Find` | Get by ID | `id`, `query?` | +| `Get` | Get records | `query` | +| `Paginate` | Paginated list | `query`, `page`, `pagesize` | +| `Create` | Create record | `data` | +| `Update` | Update record | `id`, `data` | +| `Save` | Create or update | `data` | +| `Delete` | Delete record | `id` | +| `Destroy` | Hard delete | `id` | +| `Insert` | Batch insert | `columns`, `rows` | +| `UpdateWhere` | Batch update | `query`, `data` | +| `DeleteWhere` | Batch delete | `query` | + +## Migration + +Models are automatically migrated when Yao starts. The migration: + +1. Creates tables if not exist +2. Adds new columns +3. Creates indexes +4. Does NOT drop columns (safe migration) + +To force schema sync: + +```bash +yao migrate --reset # Warning: drops and recreates tables +``` + +## Example: Complete Assistant with Models + +**assistants/inventory/package.yao** + +```json +{ + "name": "Inventory Assistant", + "connector": "gpt-4o", + "mcp": { + "servers": [{ "server_id": "inventory" }] + } +} +``` + +**assistants/inventory/models/product.mod.yao** + +```json +{ + "name": "Product", + "table": { "name": "product" }, + "columns": [ + { "name": "id", "type": "ID", "primary": true }, + { "name": "sku", "type": "string", "length": 50, "unique": true }, + { "name": "name", "type": "string", "length": 200 }, + { "name": "quantity", "type": "integer", "default": 0 }, + { "name": "price", "type": "decimal", "precision": 10, "scale": 2 } + ], + "option": { "timestamps": true } +} +``` + +**assistants/inventory/mcps/inventory.mcp.yao** + +```json +{ + "label": "Inventory", + "transport": "process", + "tools": { + "list_products": "models.agents.inventory.product.Paginate", + "get_product": "models.agents.inventory.product.Find", + "update_stock": "agents.inventory.stock.Update" + } +} +``` + +**assistants/inventory/src/stock.ts** + +```typescript +import { Process } from "@yao/runtime"; + +export function Update(args: { sku: string; quantity: number }): any { + const product = Process("models.agents.inventory.product.Get", { + wheres: [{ column: "sku", value: args.sku }], + limit: 1, + }); + + if (!product || product.length === 0) { + throw new Error(`Product not found: ${args.sku}`); + } + + return Process("models.agents.inventory.product.Update", product[0].id, { + quantity: args.quantity, + }); +} +```