# EZDML MCP Agent Operations and Data Flow Guide

> This document is intended for AI agents that use the EZDML MCP service. It describes the capabilities and operating rules of the service. Tool names, parameter names, and enum values remain in English so they can be used directly in MCP calls.

## 1. Service Overview

- Service version: `0.7.0`
- Default port: `8589`
- Health check: `GET http://127.0.0.1:8589/health`
- MCP endpoint: `POST http://127.0.0.1:8589/mcp`
- Compatibility endpoints: `GET /sse` and `POST /message?sessionId=...`
- Access scope: local requests only
- Current tool count: `53`

Streamable HTTP requests must contain:

```http
Content-Type: application/json
Accept: application/json, text/event-stream
```

Standard MCP call structure:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "ezdml_context_get",
    "arguments": {}
  }
}
```

## 2. Domain Rules Agents Must Follow

### 2.1 Names Are Unique Identifiers

- A model is uniquely identified by `modelName`. Duplicate model names are not allowed in one file.
- A logical table is uniquely identified by `tableName`.
- Same-name tables in different models are instances of one logical table, not independent objects.
- A field is identified by `tableName + fieldName`. Duplicate field names are not allowed in one logical table.
- A diagram instance is identified by `modelName + tableName` and is used only to read or modify layout.
- Do not use the internal `CtGuid` as an MCP parameter or as an identity stored by the agent.
- When renaming an object, locate it with its old name and place the new name in `patch.name`.

### 2.2 Same-Name Table Synchronization

- Modifying the business properties of any table instance synchronizes all same-name tables in every model.
- Field creation, update, deletion, and reordering are also synchronized across all same-name tables.
- A field change is treated as a table change and does not create a separate field-level `TCtMetaChange`.
- `GraphDesc` and layout belong to a model-specific diagram instance and are not synchronized across same-name tables.
- Use `ezdml_table_update` to modify a logical table.
- Use `ezdml_graph_layout_update` to modify layout in one specific model.
- Use `ezdml_table_delete` to delete an entire logical table.
- Use `ezdml_table_detach` to remove only one model instance.

### 2.3 Revision and Saving

- `revision` is the document-level optimistic lock. It is returned as a string, for example, `"7"`.
- Every write tool must submit the most recently read `baseRevision`.
- If `baseRevision` differs from the current revision, the service returns `CONFLICT` and performs no changes.
- One successful write transaction increments the revision exactly once.
- A changeset containing multiple operations also increments the revision only once.
- A no-op describe or changeset creates no history and does not increment the revision.
- Write operations modify only the current in-memory model and do not automatically save the formal file.
- Call `ezdml_file_save` or `ezdml_file_save_as` only when the user explicitly asks to save.

## 3. Recommended Data Flow

```mermaid
flowchart TD
    A["Read context"] --> B["Read describe text or structured objects"]
    B --> C["Generate a design proposal"]
    C --> D["Call describe or changeset preview"]
    D --> E{"Validation passed?"}
    E -- "No" --> C
    E -- "Yes" --> F{"Confirmation required?"}
    F -- "Yes" --> G["Obtain explicit user confirmation"]
    F -- "No" --> H["Apply with the same token and revision"]
    G --> H
    H --> I["Validate again"]
    I --> J{"Did the user request saving?"}
    J -- "Yes" --> K["file_save or file_save_as"]
    J -- "No" --> L["Keep the in-memory changes"]
```

Recommended priority:

1. Prefer `ezdml_describe_get` for simple reads to reduce context usage.
2. Prefer describe preview/apply when modifying the basic representation of tables, fields, and models.
3. Prefer changeset preview/apply for multi-object designs.
4. Use structured CRUD only for advanced properties and layouts that describe text cannot express.
5. Do not simulate a batch transaction by calling many single-object write tools in sequence.

## 4. Response Structure

Tool results contain both compatibility text and structured data:

```json
{
  "content": [
    { "type": "text", "text": "{...JSON string...}" }
  ],
  "isError": false,
  "structuredContent": {
    "ok": true,
    "operation": "table_get",
    "revision": "7"
  }
}
```

Agents should read `structuredContent` first. Do not parse `content[0].text` again unless the client does not support structured results.

Business errors are still returned as MCP tool results, but with `isError=true`:

```json
{
  "ok": false,
  "tool": "ezdml_model_get",
  "error": {
    "code": "NOT_FOUND",
    "message": "Model not found: demo",
    "retryable": false
  }
}
```

### 4.1 Common Error Codes

| Error code | Meaning | Agent action |
|---|---|---|
| `CONFLICT` | The revision changed | Re-read context and affected objects, then create a new preview |
| `NOT_FOUND` | The specified name does not exist | Check the name or call the corresponding list tool again |
| `ALREADY_EXISTS` | A name conflicts with an existing object | Choose another name; use attach if the table already exists |
| `REFERENCE_CONFLICT` | The object to delete is still referenced | Stop by default; use `cascade=true` only after user confirmation |
| `CONFIRMATION_REQUIRED` | A destructive changeset was not confirmed | Obtain confirmation, then apply with `confirm=true` |
| `DB_NOT_CONNECTED` | `CtCurMetaDbConn` is not connected | Stop database operations and ask the user to connect from EZDML |
| `INVALID_ARGUMENT` | A parameter or enum value is invalid | Correct the arguments; do not retry unchanged |
| `READ_ONLY` | The current file mode does not support writes | Stop writing and explain the limitation |
| `APP_BUSY` | The GUI is performing a modal or file operation | Retry later |
| `HISTORY_NOT_FOUND` | The history ID is invalid or belongs to another file | Call the history list again |
| `ROLLBACK_FAILED` | Automatic rollback failed | Stop immediately, preserve the checkpoint, and request manual recovery |
| `INTERNAL_ERROR` | Unclassified internal error | Preserve the error details and stop consecutive writes |

## 5. File Tools

| Tool | Main arguments | Description |
|---|---|---|
| `ezdml_file_new` | `on_dirty`, `save_path` | Create an empty document |
| `ezdml_file_open` | `path`, `on_dirty`, `save_path`, `disable_tmp_recovery` | Open a local model file |
| `ezdml_online_examples_list` | `query?`, `cursor?`, `limit?` | List official online example models and return the `sid` used for loading |
| `ezdml_online_example_load` | `sid`, `on_dirty?`, `save_path?` | Download and load an official example by a listed SID |
| `ezdml_file_save` | `path?`, `overwrite?` | Save the current file |
| `ezdml_file_save_as` | `path`, `overwrite`, `utf8_encode_strings` | Save as another file |

Allowed `on_dirty` values:

- `error`: reject the operation if unsaved changes exist.
- `discard`: discard current unsaved changes.
- `save`: save first; `save_path` is required if the current document has no path.
- `backup`: create a quick-recovery temporary backup first.

Notes:

- `file_save` and `file_save_as` do not increment the revision.
- Saving persists the current revision and model state.
- `disable_tmp_recovery=true` opens the specified file directly without loading a quick-recovery file.
- Obtain the SID from `ezdml_online_examples_list` before loading; the load tool does not accept arbitrary URLs.
- Online examples are downloaded to temporary `.~dmj` files. Use `ezdml_file_save_as` to keep one permanently.
- `ezdml_online_example_load` replaces the current document and follows the same `on_dirty` policy. Do not use `on_dirty=discard` without explicit user approval.

## 6. Query Tools

| Tool | Arguments | Returned data |
|---|---|---|
| `ezdml_context_get` | none | Current path, revision, active model, object counts, and modified state |
| `ezdml_model_list` | `cursor`, `limit`, `includeDeleted` | Paginated model summaries |
| `ezdml_model_get` | `modelName`, `includeTables` | Model properties, table instances, and layouts |
| `ezdml_model_json_get` | `modelName`, `includeJsonStr?` | Complete model JSON from the object's `JsonStr` property |
| `ezdml_table_list` | `modelName?`, `query?`, `cursor`, `limit` | Deduplicated logical tables or instances in one model |
| `ezdml_table_get` | `tableName`, `modelName?`, `includeFields` | Table properties, owning models, fields, and optional instance layout |
| `ezdml_table_json_get` | `tableName`, `modelName?`, `includeJsonStr?` | Complete table JSON from the object's `JsonStr` property |
| `ezdml_field_list` | `tableName` | Ordered field list |
| `ezdml_field_json_get` | `tableName`, `fieldName`, `modelName?`, `includeJsonStr?` | Complete field JSON from the object's `JsonStr` property |
| `ezdml_validate` | `scope`, `modelName?`, `tableName?` | Name, field uniqueness, type, and foreign-key validation |

### 6.1 Pagination Rules

- `cursor` is a zero-based result offset.
- `limit` defaults to 50 and has a maximum of 200.
- Use the returned `nextCursor` to fetch the next page.
- An empty `nextCursor` means there are no more results.

### 6.2 Table Query Rules

- Without `modelName`, `ezdml_table_list` returns a same-name table only once across all models.
- With `modelName`, it returns the instances and independent layouts in that model.
- `query` performs a case-insensitive containment search against table name, display name, and physical name.
- Queries exclude objects with `DataLevel=ctdlDeleted` by default.

### 6.3 Validation Scope

Supported values of `ezdml_validate.scope`:

- `document`: validate the entire current document.
- `model`: validate the specified `modelName`.
- `table`: validate the specified `tableName`.

Current validation checks include:

- Duplicate model names.
- Empty table or field names.
- Duplicate field names in one table.
- Missing related tables.
- Missing related fields.
- A relation uses a malformed `{Link:xxx}` marker.
- Unknown field data types.

### 6.4 Full JsonStr Reads

Use `ezdml_model_json_get`, `ezdml_table_json_get`, and `ezdml_field_json_get` when full-fidelity object properties are needed. They read the same `JsonStr` property exposed in EZDML scripts and return a structured `json` object by default. Pass `includeJsonStr=true` only when the raw serialized text is needed.

JsonStr, by default, only outputs content with values, omitting default value attributes with empty strings "", numeric values of 0, enumeration index values of 0, dates of 0 (1899-12-30 00:00:00), and boolean values of false to reduce size. For attributes with a default value of true, a prefix Not_ is added to the attribute name, and then its negated value is saved (for example, Not_Nullable: true, the loading result is Nullable: false).

- enumeration type
 Store as an integer index value in JsonStr, for example, "DataLevel" is 2, indicating TctDataLevel.ctdlNew.
 The following are commonly used enumeration types:


| Type | Common properties | Available values |
| :--- | :--- | :--- |
| `TctDataLevel` | `DataLevel` | `ctdlUnknown`, `ctdlNormal`, `ctdlNew`, `ctdlModify`, `ctdlDeleted`, `ctdlDraft`, `ctdlDebug` |
| `TCtFieldDataType` | `DataType` | `cfdtUnknow`, `cfdtString`, `cfdtInteger`, `cfdtFloat`, `cfdtDate`, `cfdtBool`, `cfdtEnum`, `cfdtBlob`, `cfdtObject`, `cfdtCalculate`, `cfdtList`, `cfdtFunction`, `cfdtEvent`, `cfdtOther` |
| `TCtKeyFieldType` | `KeyFieldType` | `cfktNormal`, `cfktId`, `cfktPid`, `cfktRid`, `cfktName`, `cfktCaption`, `cfktComment`, `cfktTypeName`, `cfktOrgId`, `cfktDeptId`, `cfktCreatorId`, `cfktCreatorName`, `cfktCreateDate`, `cfktModifierId`, `cfktModifierName`, `cfktModifyDate`, `cfktVersionNo`, `cfktHistoryId`, `cfktLockStamp`, `cfktInstNo`, `cfktProcID`, `cfktURL`, `cfktStatus`, `cfktOrderNo`, `cfktOthers` |
| `TCtFieldIndexType` | `IndexType` | `cfitNone`, `cfitUnique`, `cfitNormal` |
| `TCtFieldDropDownMode` | `DropDownMode` | `cfddNone`, `cfddFixed`, `cfddEditable`, `cfddAppendable`, `cfddAutoComplete`, `cfddAutoCompleteFixed` |
| `TCtFieldFixColType` | `FixColType` | `cffcNone`, `cffcLeft`, `cffcRight` |
| `TCtTextAlignment` | `TextAlign` | `cftaAuto`, `cftaLeft`, `cftaRight`, `cftaCenter` |
| `TCtModulePublishType` | `PublishType` | `cmptAuto`, `cmptNone`, `cmptFunction`, `cmptModule`, `cmptMenu` |


- Complete JSON structure of a model:
```json
{
    "ID": 1,
    "CtGuid": "",
    "PID": 0,
    "RID": 0,
    "Name": "",
    "Caption": "",
    "TypeName": "",
    "Memo": "",
    "Creator": 0,
    "CreateDate": "1899-12-30",
    "CreatorName": "",
    "Modifier": 0,
    "ModifyDate": "1899-12-30",
    "ModifierName": "",
    "VersionNo": 0,
    "HistoryID": 0,
    "LockStamp": "",
    "DataLevel": 0,
    "OrderNo": 1,
    "Params": "",
    "CustomAttr1": "",
    "CustomAttr2": "",
    "CustomAttr3": "",
    "CustomAttr4": "",
    "CustomAttr5": "",
    "GraphWidth": 0,
    "GraphHeight": 0,
    "DefDbEngine": "",
    "DbConnectStr": "",
    "ConfigStr": "",
    "ModelPath": "",
    "PurviewRoles": "",
    "PublishType": 0,
    "ExtraProps": "",
    "Tables": {
        "Count": 1,
        "items": [ ... ]
    }
}
```

- JSON structure of tables:
```json
{
    "ID": 1,
    "CtGuid": "",
    "PID": 0,
    "RID": 0,
    "Name": "",
    "Caption": "",
    "TypeName": "",
    "Memo": "",
    "Creator": 0,
    "CreateDate": "1899-12-30",
    "CreatorName": "",
    "Modifier": 0,
    "ModifyDate": "1899-12-30",
    "ModifierName": "",
    "VersionNo": 0,
    "HistoryID": 0,
    "LockStamp": "",
    "DataLevel": 0,
    "OrderNo": 1,
    "Params": "",
    "CustomAttr1": "",
    "CustomAttr2": "",
    "CustomAttr3": "",
    "CustomAttr4": "",
    "CustomAttr5": "",
    "CustomModId": 0,
    "GraphDesc": "",
    "ScriptRules": "",
    "CustomConfigs": "",
    "UIDisplayText": "",
    "Not_GenDatabase": false,
    "Not_GenCode": false,
    "ExtraSQL": "",
    "UILogic": "",
    "BusinessLogic": "",
    "ExtraProps": "",
    "PhysicalName": "",
    "ViewSQL": "",
    "ListSQL": "",
    "BgColor": 0,
    "DesignNotes": "",
    "GroupName": "",
    "OwnerCategory": "",
    "PartitionInfo": "",
    "SQLAlias": "",
    "SQLOrderByClause": "",
    "SQLWhereClause": "",
    "PublishType": 0,
    "PurviewRoles": "",
    "DataPurview": "",
    "IsReadOnly": false,
    "IdentifyCode": "",
    "MetaFields": {
        "Count": 11,
        "items": [ ... ]
    }
}
```

- Complete JSON structure of fields:
```json
{
    "ID": 1,
    "CtGuid": "",
    "PID": 0,
    "RID": 0,
    "Name": "",
    "Caption": "",
    "TypeName": "",
    "Memo": "",
    "Creator": 0,
    "CreateDate": "1899-12-30",
    "CreatorName": "",
    "Modifier": 0,
    "ModifyDate": "1899-12-30",
    "ModifierName": "",
    "VersionNo": 0,
    "HistoryID": 0,
    "LockStamp": "",
    "DataLevel": 0,
    "OrderNo": 1,
    "Params": "",
    "CustomAttr1": "",
    "CustomAttr2": "",
    "CustomAttr3": "",
    "CustomAttr4": "",
    "CustomAttr5": "",
    "DisplayName": "",
    "DataType": 2,
    "DataTypeName": "",
    "KeyFieldType": 1,
    "RelateTable": "",
    "RelateField": "",
    "IndexType": 0,
    "IndexFields": "",
    "DBCheck": "",
    "SQLExpression": "",
    "Hint": "",
    "DefaultValue": "",
    "Not_Nullable": false,
    "DataLength": 0,
    "DataScale": 0,
    "Url": "",
    "ResType": "",
    "ResSQL": "",
    "ResSQLWhere": "",
    "Formula": "",
    "FormulaCondition": "",
    "AggregateFun": "",
    "MeasureUnit": "",
    "ValidateRule": "",
    "EditorType": "",
    "LabelText": "",
    "ExplainText": "",
    "EditorReadOnly": false,
    "Not_EditorEnabled": false,
    "IsHidden": false,
    "DisplayFormat": "",
    "EditFormat": "",
    "FontName": "",
    "FontSize": 0,
    "FontStyle": 0,
    "ForeColor": 0,
    "BackColor": 0,
    "DropDownItems": "",
    "DropDownMode": 0,
    "GraphDesc": "",
    "Visibility": 0,
    "TextAlign": 0,
    "ColWidth": 0,
    "MaxLength": 0,
    "Searchable": false,
    "Queryable": false,
    "InitValue": "",
    "ValueMin": "",
    "ValueMax": "",
    "ValueFormat": "",
    "ExtraProps": "",
    "CustomConfigs": "",
    "TextClipSize": 0,
    "DropDownSQL": "",
    "ItemColCount": 0,
    "FixColType": 0,
    "HideOnList": false,
    "HideOnEdit": false,
    "HideOnView": false,
    "AutoMerge": false,
    "ColGroup": "",
    "SheetGroup": "",
    "ColSortable": false,
    "ShowFilterBox": false,
    "AutoTrim": false,
    "Required": false,
    "EditorProps": "",
    "TestDataType": "",
    "TestDataRules": "",
    "UILogic": "",
    "BusinessLogic": "",
    "Exportable": false,
    "Desensitised": false,
    "DesignNotes": "",
    "IsFactMeasure": false,
    "TestDataNullPercent": 0,
    "FieldWeight": 0
}
```


## 7. Describe Tools

| Tool | Arguments | Description |
|---|---|---|
| `ezdml_describe_get` | `scope`, `modelName?`, `tableName?` | Get canonical describe text |
| `ezdml_describe_preview` | `scope`, `modelName?`, `tableName?`, `mode`, `text`, `baseRevision` | Parse in a copy without changing the current document |
| `ezdml_describe_apply` | `previewToken`, `baseRevision` | Atomically apply the same preview result |

### 7.1 Describe Format

Describe is a distinctive EZDML feature. It uses a Markdown-like, human- and machine-readable summary to represent a table without the database dependency and readability problems of DDL SQL. Its general format is:

```ezdml
tableName:realTableName(logicalTableName)
//table comment
-------------------------------------
physicalFieldName1(logicalFieldName1)  field1_DataType  //<<field constraints>>field comment
physicalFieldName2(logicalFieldName2)  field2_DataType  //<<field constraints>>field comment
physicalFieldName3(logicalFieldName3)  field3_DataType  //<<field constraints>>field comment
...
```

Note: `:realTableName` is optional. If `realTableName` is not empty, it indicates that this is a virtual view, which is built on another real physical table.

Example:

```ezdml
TableA(Data Table A)
-------------------------------------
Id                        PKInteger
Pid(Parent ID)            FKInteger      //<<Relation:TableA.Id>>
Rid(Related ID)           FKInteger      //<<NotNull,Relation:TableB.Id>>ID from Table B
Name(Obj Name)            String(255)
Memo                      String
TypeName(Class Name)      String
CreatorId(Creator ID)     Integer        //<<NormalIndex,NotNull,Relation:SysUser.Id>>Current user ID
CreatorName(Creator Name) String
CreateDate(Created At)    Date           //<<Default:sysdate>>Defaults to current time
DataLevel(Data Status)    Enum           //Options: 0=normal 1=review -1=deleted -2=draft
OrderNo(Order Number)     Float(6,2)
```

Format requirements:

- The describe block for one table must be continuous and contain no blank lines. Separate multiple table blocks with one or more blank lines.
- The table-header separator must contain at least five `-` characters.
- A table must contain at least one field.
- Table name, header separator, field name, and field type are required and must not be empty.
- Real table name, logical table name, table comment, logical field name, field constraints, and field comment are optional.

Field type rules:

- Field types are generic logical types and are not tied to a specific database.
- Basic values are:
  - `Unknow`: unknown
  - `String`: string
  - `Integer`: integer
  - `Float`: floating-point number
  - `Date`: date and time
  - `Bool`: boolean
  - `Enum`: enum
  - `Blob`: binary large object
- Extended types for special models are:
  - `Object`: object such as JSON or XML
  - `Calculate`: calculated field
  - `List`: list
  - `Function`: function or method
  - `Event`: event
  - `Other`: another type
- Do not specify a physical type by default. When necessary, set it in a constraint, for example, `//<<TypeName:NVARCHAR>>`.
- Prefix the type with `PK` for a primary key, for example, `PKString` or `PKInteger`.
- Prefix the type with `FK` for a foreign key, for example, `FKString` or `FKInteger`, and specify the referenced table field in the constraints.

Length and precision:

- String:
  - Specify a length as `String(255)`.
  - If omitted, EZDML chooses the length; most databases default to 4000.
  - A value beyond the database's `varchar` limit, such as `String(99999)`, is converted to a large-text type such as CLOB or text.
- Integer:
  - Specify a length as `Integer(15)`.
  - If omitted, EZDML chooses the length.
  - For databases that use integer subtypes rather than lengths, EZDML maps lengths as follows:
    - Length 4 or less: `tinyint`
    - Length 5 or 6: `smallint`
    - Length 7 or 8: `mediumint` on MySQL, `integer` on PostgreSQL, and `int` elsewhere
    - Length 9, 10, or 11: `integer` on PostgreSQL and `int` elsewhere
    - Length 12 or greater: `bigint`
- Float:
  - Specify a length as `Float(6)`.
  - Specify length and scale as `Float(6,2)`.

Constraint rules:

- Put constraints at the beginning of the field comment: `//<<{constraints}>>`.
- Replace `>` inside a constraint value with `#62#`.
- Separate multiple constraints with ASCII commas.
- Replace a comma inside a constraint value with `#44#`.
- Constraint names may be expressed in English or Chinese depending on the user's environment:
  - `NotNull`: the value is required
  - `UniqueIndex`: unique index
  - `NormalIndex`: normal index
  - `Default`: followed by a colon and default expression, for example, `Default:sysdate-1`
  - `AutoInc`: auto-increment
  - `Relation`: used with a foreign key and followed by a referenced field, for example, `Relation:TableB.Id`
  - `TypeName`: followed by a physical type, for example, `TypeName:NVARCHAR`
  - `SQLExpr`: a SELECT expression, for example, `SQLExpr:trunc(price*quantity)`
- `PK` and `FK` are expressed directly in the field type and are not repeated in the constraint list.
- Escape backslashes and line breaks in comments: `\\` for a backslash, `\r` for carriage return, and `\n` for newline.
- Describe output pads field columns with spaces for readability in a monospaced font.

### 7.2 Scope Semantics

- `scope=file`: read-only; returns deduplicated logical-table describe text for the whole file.
- `scope=model`: returns multi-table describe text for the specified model.
- `scope=table`: returns the complete describe text of one logical table.

### 7.3 Write Semantics

- `scope=table` always uses replace semantics.
- A single-table describe is the complete field set; fields omitted from it are deleted.
- Model `mode=merge` preserves existing tables that are absent from the text.
- Each table block present in a merge is still the complete field set for that table.
- Model `mode=replace` soft-deletes normal tables absent from the text.
- Diagram objects that describe text cannot express, such as GROUP and TEXT, are preserved during model replace.
- `scope=file` is currently read-only and cannot be applied.

Describe example:

```text
Orders(Orders)
//Order header table
-------------------------------------
Id                        PKInteger
CustomerId(Customer ID)   FKInteger    //<<NotNull,Relation:Customers.Id>>Referenced customer ID
CreatedAt(Created At)     Date         //<<Default:sysdate>>
ItemCount(Item Count)     Integer      //Denormalized detail count
```

Safe call sequence:

1. Call `ezdml_describe_get` to read the current canonical text.
2. Generate the complete modified text.
3. Call `ezdml_describe_preview`.
4. Inspect `changed`, `beforeText`, `afterText`, and affected tables.
5. Call `ezdml_describe_apply` with the returned `previewToken`.
6. Never construct a token manually or reuse an expired token.

Preview argument example:

```json
{
  "scope": "table",
  "tableName": "Orders",
  "text": "Orders(Orders)\n...",
  "baseRevision": "7"
}
```

## 8. Model CRUD

| Tool | Arguments |
|---|---|
| `ezdml_model_create` | `value`, `baseRevision` |
| `ezdml_model_update` | `modelName`, `patch`, `baseRevision` |
| `ezdml_model_json_update` | `modelName`, `json/jsonStr`, `baseRevision` |
| `ezdml_model_delete` | `modelName`, `baseRevision` |

Model property whitelist:

```json
{
  "name": "Sales",
  "caption": "Sales Model",
  "graphWidth": 2000,
  "graphHeight": 1200,
  "defDbEngine": "MYSQL",
  "dbConnectStr": "",
  "modelPath": "",
  "publishType": "none",
  "purviewRoles": "",
  "extraProps": ""
}
```

Allowed `publishType` values:

- `auto`
- `none`
- `function`
- `module`
- `menu`

Model rename example:

```json
{
  "modelName": "OldSales",
  "patch": { "name": "Sales" },
  "baseRevision": "7"
}
```

`ezdml_model_delete` uses soft deletion and does not immediately release the domain object.

Use `ezdml_model_json_update` after `ezdml_model_json_get` when a full `JsonStr` JSON round-trip is needed. Pass either a structured `json` object or raw `jsonStr`.

## 9. Table CRUD and Instance Management

| Tool | Arguments | Purpose |
|---|---|---|
| `ezdml_table_create` | `modelName`, `value`, `baseRevision` | Create a logical table and its first model instance, optionally with fields and layout |
| `ezdml_table_attach` | `modelName`, `tableName`, `layout?`, `baseRevision` | Add an existing logical table to another model |
| `ezdml_table_detach` | `modelName`, `tableName`, `baseRevision` | Remove only one instance |
| `ezdml_table_update` | `tableName`, `patch`, `baseRevision` | Update or rename a logical table and synchronize all instances |
| `ezdml_table_json_update` | `tableName`, `modelName?`, `json/jsonStr`, `baseRevision` | Replace a table from complete `JsonStr` JSON |
| `ezdml_table_delete` | `tableName`, `cascade?`, `baseRevision` | Soft-delete a logical table from every model |

### 9.1 Table Property Whitelist

For table properties outside this whitelist, use `ezdml_table_json_get/update` for full `JsonStr` JSON round-trips, or `ezdml_script_run` when custom procedural logic is needed.

```json
{
  "name": "Orders",
  "caption": "Orders",
  "physicalName": "orders",
  "typeName": "",
  "memo": "Order header table",
  "bgColor": 16777215,
  "genDatabase": true,
  "genCode": true,
  "ownerCategory": "Sales",
  "groupName": "Core",
  "designNotes": ""
}
```

### 9.2 Create Table Example

```json
{
  "modelName": "Sales",
  "value": {
    "name": "Orders",
    "caption": "Orders",
    "physicalName": "orders",
    "fields": [
      {
        "name": "Id",
        "displayName": "ID",
        "dataType": "integer",
        "keyFieldType": "primary",
        "nullable": false
      }
    ],
    "layout": { "left": 120, "top": 80 }
  },
  "baseRevision": "7"
}
```

### 9.3 Attach and Detach

- If `tableName` already exists in another model, do not create it again.
- Use `ezdml_table_attach` to add the existing logical table to the target model.
- Attach accepts only the target model and independent layout, not another business-field definition.
- `ezdml_table_detach` does not delete the logical table.
- Detaching the final table instance is not allowed; use `ezdml_table_delete` instead.

### 9.4 Deletion and References

- `ezdml_table_delete` checks foreign-key references by default.
- It returns `REFERENCE_CONFLICT` while references exist.
- Use `cascade=true` only when the user explicitly asks to clear references.
- Cascade mode clears related table and field names before soft-deleting the target table.

## 10. Field CRUD

| Tool | Arguments |
|---|---|
| `ezdml_field_create` | `tableName`, `value`, `baseRevision` |
| `ezdml_field_update` | `tableName`, `fieldName`, `patch`, `baseRevision` |
| `ezdml_field_json_update` | `tableName`, `fieldName`, `modelName?`, `json/jsonStr`, `baseRevision` |
| `ezdml_field_delete` | `tableName`, `fieldName`, `cascade?`, `baseRevision` |
| `ezdml_field_reorder` | `tableName`, `fieldNames`, `baseRevision` |

### 10.1 Field Property Whitelist

For field properties outside this whitelist, use `ezdml_field_json_get/update` for full `JsonStr` JSON round-trips, or `ezdml_script_run` when custom procedural logic is needed.

```json
{
  "name": "CustomerId",
  "displayName": "Customer ID",
  "dataType": "integer",
  "dataTypeName": "",
  "dataLength": 0,
  "dataScale": 0,
  "nullable": false,
  "defaultValue": "",
  "keyFieldType": "normal",
  "indexType": "normal",
  "indexFields": "",
  "relateTableName": "Customers",
  "relateFieldName": "Id",
  "memo": "",
  "designNotes": ""
}
```

### 10.2 Enum Values

Allowed `dataType` values:

`unknown`, `string`, `integer`, `float`, `date`, `bool`, `enum`, `blob`, `object`, `calculate`, `list`, `function`, `event`, `other`.

Allowed `indexType` values:

- `none`
- `unique`
- `normal`

`keyFieldType` supports stable English EZDML key-field types. Use `primary` for a normal primary key; the service maps it to the ID type.

### 10.3 Related Field Rules

- A valid foreign key must be active, physical, use ID/RID `KeyFieldType`, and have `relateTableName` and `relateFieldName` resolve to an existing table and field.
- When a relation is not a foreign key, `relateFieldName` may be empty when `relateTableName` is set. This represents table-level relatedness only and deliberately prevents EZDML from treating the relation as a foreign key.
- When a relation is not a foreign key, `relateFieldName` may use `{Link:xxx}`, for example `{Link:Direct}`, to represent a named connection or reference. This marker also prevents foreign-key classification and must start with `{Link:`, end with `}`, and contain a nonempty name.
- When `relateFieldName` is an ordinary field name, that field must exist in the related table.

### 10.4 Field Rename and Reorder

- Locate a field update with the old `fieldName`; place the new name in `patch.name`.
- Renaming a field updates same-name table instances and related-field references.
- `field_reorder.fieldNames` must contain every active field exactly once.
- Omissions and duplicates are not allowed.
- Field reordering is finally merged into one table change.

## 11. Layout Tools

Example arguments for `ezdml_graph_layout_update`:

```json
{
  "modelName": "Sales",
  "tableName": "Orders",
  "layout": {
    "left": 120,
    "top": 80,
    "width": 260,
    "height": 180,
    "autoSize": false,
    "backgroundLeft": 0,
    "backgroundTop": 0
  },
  "baseRevision": "7"
}
```

Rules:

- Both `modelName` and `tableName` are required.
- Only the table instance in the specified model is modified.
- `GraphDesc` is not synchronized to other models.
- Do not place `left`, `top`, or other layout properties in `ezdml_table_update`.
- Layout properties omitted from the request retain their previous values.

`ezdml_graph_layout_auto` supports three algorithms:

| `layoutMode` | Algorithm | Description |
|---|---|---|
| `grid` | MCP grid layout | Deterministic rows and columns with fixed spacing |
| `flow` | `TDMLObjList.ReArrange` | EZDML fast flow layout, suitable for large models |
| `network` | `TDMLObjList.ReArrangeV2` | Default; relation-weighted network expansion with intelligent link routing |

Common arguments are `modelName`, `baseRevision`, `layoutMode?`, `layoutSeed?`, and `colorizeRelated?`. If `layoutMode` is omitted, `network` is used. If omitted, `layoutSeed` is derived stably from the model name and mode so changeset preview and apply produce identical results.

With `colorizeRelated=true`, the service calls `GenRandBgColors`. It colors only tables that still use the default color and meet the relation-count threshold. Colors are logical-table properties and synchronize to same-name tables in all models; layout and foreign-key routes remain model-instance specific.

Grid mode also accepts `columns` (1-50), `startLeft`, `startTop`, `horizontalGap`, `verticalGap`, `width`, and `height`. The operation can be called directly or included in a changeset as `op=graph.layout.auto`. A changeset is preferred when arranging tables immediately after batch creation.

```json
{
  "op": "graph.layout.auto",
  "modelName": "Sales",
  "layoutMode": "network",
  "layoutSeed": 12345,
  "colorizeRelated": true
}
```

## 12. History Tools

| Tool | Arguments | Description |
|---|---|---|
| `ezdml_history_list` | `cursor`, `limit` | Return opaque `historyId` values in descending time order |
| `ezdml_history_preview` | `historyId`, `detail` | Compare a history summary with the current canonical describe text |
| `ezdml_history_restore` | `historyId`, `baseRevision`, `confirm` | Restore history as a new current revision |

Allowed `detail` values:

- `auto`
- `summary`
- `full`

A summary history preview primarily uses logical-table describe text and returns:

- `historyText`
- `currentText`
- `changed`
- `summaryOnly=true`
- `notCovered`

Summary mode does not cover model membership, layout, canvas, or advanced properties. Therefore, `changed=false` in summary mode does not prove that the complete files are identical. Use `detail=full` when a full comparison is required.

### 12.1 historyId Rules

- A history ID is an opaque identifier, not a local path.
- Agents must use only IDs returned by `ezdml_history_list`.
- A history ID is scoped to the current document.
- The service rejects directory paths, history belonging to another file, and missing history.

### 12.2 Restore Rules

- Restore is destructive and requires explicit `confirm=true`.
- The service backs up the complete current file before restoring.
- If restore fails, the pre-restore checkpoint is used for rollback.
- Restore never moves revision backwards.
- After restore, revision becomes the pre-restore revision plus one.
- The result returns a new `changeSetId` and `checkpointHistoryId`.

## 13. Changeset Tools

| Tool | Arguments |
|---|---|
| `ezdml_changeset_preview` | `title?`, `reason?`, `operations`, `baseRevision` |
| `ezdml_changeset_apply` | `previewToken`, `baseRevision`, `confirm?` |
| `ezdml_changeset_undo` | `changeSetId?`, `baseRevision`, `confirm` |

### 13.1 Supported Operations

| `op` | Equivalent single-object tool |
|---|---|
| `model.create` | `ezdml_model_create` |
| `model.update` | `ezdml_model_update` |
| `model.json.update` | `ezdml_model_json_update` |
| `model.delete` | `ezdml_model_delete` |
| `table.create` | `ezdml_table_create` |
| `table.attach` | `ezdml_table_attach` |
| `table.detach` | `ezdml_table_detach` |
| `table.update` | `ezdml_table_update` |
| `table.json.update` | `ezdml_table_json_update` |
| `table.delete` | `ezdml_table_delete` |
| `field.create` | `ezdml_field_create` |
| `field.update` | `ezdml_field_update` |
| `field.json.update` | `ezdml_field_json_update` |
| `field.delete` | `ezdml_field_delete` |
| `field.reorder` | `ezdml_field_reorder` |
| `graph.layout.update` | `ezdml_graph_layout_update` |
| `graph.layout.auto` | `ezdml_graph_layout_auto` |

### 13.2 Complete Example

```json
{
  "title": "Create order model",
  "reason": "Add the core order-management structure",
  "baseRevision": "7",
  "operations": [
    {
      "op": "model.create",
      "value": {
        "name": "Sales",
        "caption": "Sales"
      }
    },
    {
      "op": "table.create",
      "modelName": "Sales",
      "value": {
        "name": "Orders",
        "caption": "Orders",
        "fields": [
          {
            "name": "Id",
            "displayName": "ID",
            "dataType": "integer",
            "keyFieldType": "primary",
            "nullable": false
          },
          {
            "name": "CreatedAt",
            "displayName": "Created At",
            "dataType": "date",
            "defaultValue": "sysdate"
          }
        ],
        "layout": {
          "left": 120,
          "top": 80
        }
      }
    }
  ]
}
```

### 13.3 Preview and Apply

- Preview executes every operation in order on a complete in-memory copy.
- Preview does not modify the current document and creates no disk history.
- Preview returns `previewToken`, `changes`, `unifiedDiff`, before/after describe text, validation, `approvalRequired`, and `approvalReasons`.
- Apply executes only the same server-cached operations.
- If operations change, create a new preview.
- Apply must use the same `baseRevision` as preview.
- Apply creates only one checkpoint.
- After every operation succeeds, revision is incremented only once.
- If any operation fails, the entire checkpoint is restored and no partial result remains.

### 13.4 Destructive Operation Confirmation

Preview returns `approvalRequired=true` when the changeset contains:

- Any `.delete` operation.
- `table.detach`.

After obtaining explicit user confirmation, call:

```json
{
  "previewToken": "token returned by the service",
  "baseRevision": "7",
  "confirm": true
}
```

The agent must not assume that the user has confirmed the operation.

`ezdml_changeset_undo` can undo only the latest MCP changeset in the current document. If `changeSetId` is omitted, it uses `ModelFileConfig.LastChangeSetId`. The service locates the pre-apply checkpoint through `CheckpointForChangeSetId`. Undo requires `confirm=true` and creates both a new revision and a pre-restore checkpoint.

## 14. Table SQL, Relations, Database, Script, and Code Generation Tools

### 14.1 Table Object Tools

| Tool | Main arguments | Description |
|---|---|---|
| `ezdml_table_sql_generate` | `tableName`, `sqlKind`, `dbType?` | Calls `GenSqlEx`, `GenDropSql`, `GenDqlDmlSql`, `GenTestDataInsertSql`, or `GenListSQL`; it generates SQL but never executes it |
| `ezdml_table_relations_get` | `tableName`, `foreignKeysOnly?` | Returns `outgoing`, `incoming`, `manyToMany`, and `weak` relations |

`sqlKind` accepts `ddl`, `drop`, `dql_dml`, `test_insert`, and `list`. For `dql_dml`, `sqlType` accepts `all/select/insert/insert_value/update/delete`. Test data is limited to 1,000 rows. With `useConnectedDatabase=true`, generation uses the current database type and schema; a missing connection returns `DB_NOT_CONNECTED`.

Relation discovery follows the table property relation-map rules. A valid foreign key must be active, physical, use ID/RID `KeyFieldType`, and resolve both its related table and field. The `weak` array reports table-level relatedness expressed by an empty `relateFieldName` on a non-foreign-key field, connection/reference relations expressed by `{Link:xxx}`, relations that do not meet the physical foreign-key rules, and table-level links established through real table names. A real-table-name item has `relationType="physicalName"` and a `direction` of `incoming`, `outgoing`, or `bidirectional`. These weak relations are omitted when `foreignKeysOnly=true`.

### 14.2 Database Tools

| Tool | Main arguments | Description |
|---|---|---|
| `ezdml_db_connection_get` | None | Returns status, engine, database, user, and schema without exposing the password |
| `ezdml_db_names_get` | None | Returns database names or connection examples from the adapter |
| `ezdml_db_users_get` | None | Returns users or schemas |
| `ezdml_db_objects_get` | `schema?` | Lists database objects |
| `ezdml_db_object_exists` | `objectName`, `schema?` | Tests object existence |
| `ezdml_db_objects_reverse` | `objectNames`, `schema?`, `options?` | Batch calls `GetObjInfos`; returns EZDML object JSON and Describe text for tables |
| `ezdml_db_sync_sql_generate` | `tableNames`, `schema?`, `sqlPart?` | Reverse-engineers existing objects and batch calls `GenObjSql` to generate incremental DDL without executing it |
| `ezdml_db_exec_sql` | `sql`, `confirm` | Calls `ExecSql` against the external database and requires `confirm=true` |
| `ezdml_db_query` | `sql`, `maxRows?`, `maxCellChars?` | Calls `OpenTable` and returns `rows` as `ARRAY<JSON_DICT>` |

Recommended flow:

1. Call `ezdml_db_connection_get` first and continue only when `connected=true`.
2. Use users, objects, and exists tools to identify the exact schema and object names.
3. Reverse-engineer at most 100 objects per call. Results are DTOs and are not imported into the current model automatically.
4. For forward synchronization, inspect `tables[].sql` and `combinedSql`, then show the proposed DDL to the user.
5. Call `ezdml_db_exec_sql(confirm=true)` only when the user explicitly requests real execution.
6. Verify the real database state afterward with exists, reverse, or query tools.

`ezdml_db_query` accepts one read-only `SELECT/WITH/SHOW/DESC/DESCRIBE` statement. It returns 100 rows by default and at most 1,000; long string cells are truncated and binary values are not inlined. Reverse engineering, SQL generation, and queries do not change the EZDML document revision. `ezdml_db_exec_sql` changes an external database, so EZDML history and `ezdml_changeset_undo` cannot undo it.

### 14.3 Script and Code Generation Tools

| Tool | Main arguments | Description |
|---|---|---|
| `ezdml_script_run` | `scriptType`, `content`, `baseRevision`, `confirm` | Compiles and runs inline EZDML script content on the GUI main thread and returns `output` |
| `ezdml_code_templates_list` | none | Lists available code generation templates from the `Templates` folder, returning single-file and multi-file system templates |
| `ezdml_code_generate` | `templatePath`, `tableNames`, `outputPath`, `confirm`, `runAutoOpenOnFinished?` | Batch calls `TCtGenCodeUtil.DoGenCode` on the GUI main thread and returns the generation `log` |

`ezdml_script_run` is a high-impact tool because EZDML scripts can modify the model, write files, or operate on databases. It requires `confirm=true` and the current `baseRevision`. The service creates a complete EZDML history checkpoint before execution. If the script changes the document, the revision is incremented once and the result includes `changeSetId` and `checkpointHistoryId`; if the script only prints output, the revision is unchanged. `validateAfter` defaults to `true`; validation failure rolls the model back to the checkpoint.

Script execution uses `scriptType=pas/js` plus `content`; MCP exposes only inline script content for this tool. Before writing or running nontrivial scripts, read the language-specific reference resource: use `ezdml://guide/script/js` for `scriptType=js/javascript`, and `ezdml://guide/script/pascal` for `scriptType=pas/ps/pascal`. `tableName` is optional and supplies the current table context; otherwise EZDML uses the currently selected table. Script output is returned in `output`; very large output is truncated by an internal service limit rather than a caller-provided parameter.

`ezdml_code_templates_list` reads the template catalog from the `Templates` folder under the executable output directory. `singleFileTemplates` comes from the root `_dml_config.INI`, where each section is a template file and `desc` is its description. `systemTemplates` comes from child folders and reads `[dml_settings].desc` from each child `_dml_config.INI`. Before calling `ezdml_code_generate`, prefer this tool and pass the returned `templatePath` directly.

`ezdml_code_generate` writes or publishes generated output and therefore also requires `confirm=true`. It temporarily marks only the requested `tableNames` as checked, calls the existing generator, captures `Proc_LogMsg`, then restores previous table selection state. Code generation does not change the EZDML document revision. Multi-file system templates may configure an `auto_open_on_finished` post-generation command. MCP does not execute that command by default; pass `runAutoOpenOnFinished=true` only after the user explicitly agrees.

## 15. MCP Resources

The service implements `resources/list`, `resources/templates/list`, and `resources/read`. Common static URIs are:

- `ezdml://document/current`
- `ezdml://document/validation`
- `ezdml://models`
- `ezdml://history`
- `ezdml://describe/file`
- `ezdml://database/connection`
- `ezdml://guide/agent`
- `ezdml://guide/script/js`
- `ezdml://guide/script/pascal`

`ezdml://guide/script/js` reads `ezdml_js_reference.md` from the executable output directory, and `ezdml://guide/script/pascal` reads `ezdml_pascal_reference.md`. Aliases are accepted for language names: `ezdml://guide/script/javascript`, `ezdml://guide/script/pas`, and `ezdml://guide/script/ps`.

URI templates support name-based access to models, logical tables, model-specific table instances, fields, history, model/table describe text, and script references through `ezdml://guide/script/{language}`. Names must be URI-encoded. After a write transaction completes, legacy HTTP/SSE sessions receive `notifications/resources/list_changed`.

## 16. Internal Write Transaction Flow

```mermaid
sequenceDiagram
    participant Agent
    participant MCP as MCP HTTP Thread
    participant UI as EZDML Main Thread
    participant Hist as History Storage
    participant Model as Domain Model

    Agent->>MCP: tools/call + baseRevision
    MCP->>UI: QueueAsyncCall
    UI->>UI: compare current revision
    UI->>Hist: SaveDMLToTmpFile checkpoint
    UI->>Model: execute one or more operations
    UI->>Model: synchronize all same-name tables
    UI->>UI: revision = revision + 1
    UI->>UI: refresh tree, properties, and diagram
    UI-->>MCP: structuredContent + checkpointHistoryId
    MCP-->>Agent: tool result
```

Failure flow:

1. Create a complete-file `.~dmh` checkpoint before a formal write.
2. Save the matching `.~dml` describe summary at the same time.
3. If any operation fails, reload the checkpoint immediately.
4. Rollback does not increment revision or create another history entry.
5. A rollback failure returns `ROLLBACK_FAILED`.
6. After `ROLLBACK_FAILED`, the agent must stop all subsequent writes and request manual recovery.

## 17. Common Workflows

### 17.1 Modify One Table

1. Call `ezdml_context_get`.
2. Call `ezdml_describe_get(scope=table)`.
3. Generate the complete new table describe text from the canonical text.
4. Call `ezdml_describe_preview`.
5. Inspect `changed` and `afterText`.
6. Call `ezdml_describe_apply`.
7. Call `ezdml_validate(scope=table)`.

### 17.2 Create a Multi-Table Design

1. Read context and relevant existing tables.
2. Construct one changeset.
3. Create referenced tables and primary keys before referencing tables and foreign-key fields.
4. Call changeset preview.
5. Inspect validation and `approvalRequired`.
6. Call changeset apply.
7. Preserve the returned `checkpointHistoryId`.
8. Run document-level validation again.

### 17.3 Reuse a Logical Table Across Models

1. Call `ezdml_table_get(tableName)` to verify the logical table and its current models.
2. Call `ezdml_table_attach` to add it to the target model.
3. Call `ezdml_graph_layout_update` to adjust the target instance layout.
4. For later field changes, call one `ezdml_field_*` tool; the service synchronizes all same-name tables automatically.

### 17.4 Handle a Revision Conflict

1. Do not immediately retry with stale arguments.
2. Call `ezdml_context_get` again.
3. Re-read affected models, tables, or describe text.
4. Generate a new preview from the latest state.
5. Apply with the new token and `baseRevision`.

### 17.5 Delete a Field or Table

1. Read the target object and its references.
2. Use preview to show the deletion impact.
3. Do not use `cascade=true` by default.
4. If a reference conflict exists, explain the exact referencing objects to the user.
5. Use cascade only after the user explicitly asks to clear references.
6. Run validation immediately after apply.

## 18. Current Limitations

- Built-in database-file mode does not currently support describe writes, object CRUD, changesets, script-run writes, or history-restore writes.
- `detail=summary/auto` history preview uses describe summaries. `detail=full` compares models, tables, fields, model membership, and layout, but some advanced extension properties may still be absent from the diff.
- Authentication tokens are not implemented. The service reduces exposure by accepting localhost requests only.
- Main-thread requests do not yet provide cancellation and timeout protocols. Do not issue concurrent write requests.
- One MCP request is limited to 4 MiB, one changeset to 500 operations, and describe text to 2 MiB.
- Top-level schemas reject additional properties. Nested patches apply only whitelisted properties; other values are not written to domain objects.
- Automatic network layout generates and saves foreign-key link routes. A tool for manually submitting structured route points is not yet exposed.

## 19. Agent Final Checklist

- [ ] Locate objects by name, not by GUID.
- [ ] Read the latest revision before every write.
- [ ] Locate renamed objects with their old names and put new names in the patch.
- [ ] Use one changeset for a multi-object modification.
- [ ] Preview before apply.
- [ ] Do not modify or forge `previewToken`.
- [ ] Confirm references and user intent before deletion or detach.
- [ ] Do not place layout properties in table update.
- [ ] Do not treat same-name tables in different models as independent business objects.
- [ ] Validate again after apply.
- [ ] Save the formal file only when the user explicitly asks.
- [ ] Preserve the `checkpointHistoryId` returned by apply.
- [ ] Use `confirm=true` for script execution, code generation, and SQL execution only after user intent is clear.
- [ ] After `CONFLICT`, re-read state and create a new preview.
- [ ] After `ROLLBACK_FAILED`, stop immediately and request manual recovery.
