CodamAIDocs
Topicdone

Schema change through the MCP server

How an AI client changes a model through the hub's MCP server: read, validate the blueprint, plan the change set, approve, apply exactly once.

Variants
read onlyvalidate blueprintplan change setapproveapprove a destructive changeapplysecond apply at the same timeundo (roll back)Operation Context: one target CDMSsomeone else's operationIdblueprint with an environmentbase has changedconnecting Claude Codeanother MCP client

What this is about

You can maintain models in the hub by hand. But you can also let an AI client change them, for example Claude. For this, the hub has an MCP server. MCP (Model Context Protocol) is a standard that lets an AI client call the tools of a server.

The AI client does not simply start writing. Every change goes through the same gate: describe, check, plan, get your approval, apply once.

Three terms come up again and again on this page:

The three building blocks of a change
Operation Context
the bracket · operationId
  • binds all following calls to one CDMS
  • belongs to the user who creates it
  • is valid for 30 minutes
Blueprint
the wish · JSON
  • describes what should exist
  • symbolic refs instead of IDs
  • only adds; change and delete only through migrations
Change Set
the order · changeSetId
  • the frozen plan
  • is approved and then applied

The tools

The MCP server offers 15 tools. Most of them only read.

Tooldoeschanges anything?
cdms_list_targetslists the CDMS systems you are allowed to seeno
cdms_get_targetmetadata of a CDMSno
cdms_get_capabilitiesallowed field, relation, rule, model and endpoint types, naming rules, system fieldsno
cdms_get_structurefolders with IDs and pathsno
cdms_get_schemathe complete current schemano
cdms_get_elementa single element with its blueprint keyno
cdms_create_operation_contextopens a change to a CDMSno, only opens the bracket
cdms_get_operation_contextstatus and target of a changeno
cdms_validate_blueprintchecks a blueprintno
cdms_plan_changeschecks and calculates what would changeno
cdms_create_change_setfreezes the plan as a change setno
cdms_approve_change_setapproves the change setonly the status
cdms_apply_change_setwrites the change to the hubyes
cdms_get_change_set_statusstatus and resultsno
cdms_rollback_change_setundoes an applied change setyes

In the hub, a CDMS is a module. Its ID is the cdmsId that all tools work with. The same information is also available as readable MCP resources under cdms://targets/{cdmsId}/….

The path of a change

Six stations up to the change in the hub
  1. AI client
    Read
    Which CDMS exist, what is allowed, what does the schema look like today?
  2. MCP server
    Open the bracket
    Is the user allowed to see this CDMS?
    ↳ no CDMS_TARGET_NOT_FOUND
  3. MCP server
    Validate
    Is the blueprint valid, do target and bracket match?
    ↳ no List of all errors and warnings, fix the blueprint
  4. MCP server
    Plan
    What is created, what stays, what is destructive?
    ↳ no BLUEPRINT_INVALID
  5. User
    Approve
    Have you seen the plan and explicitly agreed? For destructive changes: have you confirmed them separately, and do you hold the role for them?
    ↳ no Change set stays in PENDING_APPROVAL, for destructive changes with DESTRUCTIVE_CHANGE_BLOCKED
  6. MCP server
    Apply
    Is it approved, unchanged, the only one under way on this CDMS right now, and is the schema still the same as when it was planned?
    ↳ no CHANGE_SET_NOT_APPROVED, BLUEPRINT_INVALID, DESTRUCTIVE_CHANGE_BLOCKED or SCHEMA_VERSION_CONFLICT, nothing written
  7. The model in the hub is changed

This is what it looks like as a conversation between the parties:

sequenceDiagram
    actor B as User
    participant K as AI client
    participant M as MCP server (hub)
    participant DB as Hub database
    K->>M: cdms_list_targets, cdms_get_capabilities, cdms_get_schema
    M-->>K: targets, rules, current schema
    K->>M: cdms_create_operation_context(cdmsId)
    M-->>K: operationId (valid for 30 min)
    K->>M: cdms_validate_blueprint(operationId, blueprint)
    M-->>K: valid, errors, warnings
    K->>M: cdms_plan_changes(operationId, blueprint)
    M-->>K: planId, summary, changes, destructiveChanges
    K->>M: cdms_create_change_set(operationId, planId)
    M-->>K: changeSetId, PENDING_APPROVAL
    K->>B: shows the plan
    B-->>K: "Yes, apply"
    K->>M: cdms_approve_change_set(operationId, changeSetId, confirmDestructive)
    M-->>K: APPROVED
    K->>M: cdms_apply_change_set(operationId, changeSetId, idempotencyKey)
    M->>DB: read the current schema again and compare
    M->>DB: all changes in one transaction
    M-->>K: APPLIED + result per element

The blueprint

The blueprint is a JSON document. It describes folders, enums, models, fields, relations and access filters the way they should exist after the change.

New enum and new field on an existing model
cdms_validate_blueprint
{
  "operationId": "4c2b…",
  "blueprint": {
    "blueprintVersion": "1",
    "target": { "cdmsId": "a7f5…", "expectedCdmsName": "CVC Backend" },
    "folders": [
      { "ref": "folder.crm", "name": "CRM",
        "existingFolderId": "2a7f…" }            ← existing folder
    ],
    "enums": [
      { "ref": "enum.status", "folderRef": "folder.crm",
        "name": "CUSTOMER_STATUS",
        "values": [ { "key": "ACTIVE" }, { "key": "BLOCKED" } ] }
    ],
    "models": [
      { "ref": "model.customer", "folderRef": "folder.crm",
        "name": "Customer",                      ← already exists
        "fields": [
          { "ref": "field.customer.status", "name": "status",
            "type": "ENUM", "enumRef": "enum.status",
            "required": true }
        ] }
    ]
  }
}
BlueprintValidationResult
{
  "valid": true,
  "errors": [],
  "warnings": [
    { "code": "BLUEPRINT_INVALID", "element": "model.customer",
      "message": "Das Modell '/crm/Customer' existiert bereits … wird wiederverwendet …" }
  ],
  "models": [ { "ref": "model.customer", "existingId": "9b1…" } ], …
}

You should know four rules:

  1. Symbolic names instead of IDs. Elements refer to each other through their ref, for example enumRef: "enum.status". The hub assigns the IDs.
  2. Add, never delete silently. The hub recognizes what already exists by its path (/crm/Customer) and reuses it. What is missing from the blueprint stays unchanged. Changing or deleting existing things only works explicitly through the migrations section.
  3. What you use, you declare too. If you want to reference an existing model or enum, it must be in the blueprint, otherwise you get BLUEPRINT_REFERENCE_NOT_FOUND. For existing folders you set existingFolderId.
  4. A relation is a pair of fields. Both sides are in the blueprint as a RELATION field and point to each other through inverseFieldRef, see Both sides of a relation.
  5. Do not name an environment. A CDMS has no environment: its model is the same in development, test and production, and the environments differ only in their data. target.expectedEnvironment is therefore an error.

The hub checks strictly: an unknown JSON key is already an error. But it collects all content findings, so the AI client can fix everything in one pass.

ResultExamples
Error (valid: false)name breaks the naming rule or is reserved (id, _createdOn …), duplicate ref, field already exists in the base model, other side of a relation is missing, unknown field or rule type, cycle in the inheritance, target.expectedEnvironment set
Warning (valid: true)element already exists and is reused, a different value on an existing element is not taken over, details such as displayName are ignored, empty blueprint

Explicit changes: migrations

Everything that changes or removes existing things is its own entry under migrations:

KindExamples
rebuildRENAME, MOVE, SET_OPTIONS, SET_FIELD_TYPE, SET_RULE
removeREMOVE_RULE, REMOVE_RECURSION, REMOVE_ENDPOINT, REMOVE_ROLE, REMOVE_ENUM_VALUE
deleteDELETE_FIELD, DELETE_MODEL, DELETE_ENUM, DELETE_FOLDER

Every migration counts as destructive in the plan. A cascading delete through a relation, a removed access filter and a model that is audited are destructive as well. You approve a change set with destructive changes only with a dedicated role and only if you confirm those changes separately, see “Approving destructive changes” further down.

The plan

cdms_plan_changes validates once more and then compares the blueprint with the current schema. The result is a plan:

Result of the planning
cdms_plan_changes
{ "operationId": "4c2b…", "blueprint": { … as above … } }
CdmsChangePlan
{
  "planId": "af72…",
  "summary": { "createCount": 2, "updateCount": 0, "deleteCount": 0,
               "unchangedCount": 2, "destructiveChangeCount": 0 },
  "changes": [
    { "type": "UNCHANGED", "elementType": "folder", "path": "/crm" },
    { "type": "CREATE",    "elementType": "enum",   "path": "/crm/CUSTOMER_STATUS" },
    { "type": "UNCHANGED", "elementType": "model",  "path": "/crm/Customer" },
    { "type": "CREATE",    "elementType": "field",  "path": "/crm/Customer#status" }
  ],
  "destructiveChanges": [],
  "blueprintHash": "269a…"
}

Every row in changes has a type: CREATE, UPDATE, DELETE or UNCHANGED. destructiveChanges lists in plain text all changes that can destroy something. This exact plan is what the AI client shows you before you approve. A plan lives for 30 minutes.

The change set and its states

cdms_create_change_set takes only a planId, no new blueprint. So what gets approved is exactly what was planned.

stateDiagram-v2
    [*] --> PENDING_APPROVAL: cdms_create_change_set
    PENDING_APPROVAL --> APPROVED: cdms_approve_change_set
    APPROVED --> APPLIED: cdms_apply_change_set, success
    APPROVED --> FAILED: cdms_apply_change_set, error
    APPLIED --> ROLLED_BACK: cdms_rollback_change_set
    FAILED --> [*]
    ROLLED_BACK --> [*]
    APPLIED --> [*]
Statemeanswhat next
PENDING_APPROVALplanned, waiting for youapprove, or just leave it
APPROVEDapprovedapply
APPLIEDwritten to the hubdone, or undo
FAILEDapplying failed, nothing was writtenplan again
ROLLED_BACKundoneplan again

FAILED and ROLLED_BACK are final states. A change set is never tried a second time. There is no tool to discard one: a change set that you do not approve stays in PENDING_APPROVAL and is never applied.

Approving destructive changes

If the plan lists entries under destructiveChanges, a plain “yes” is not enough:

Is a change set with destructive changes approved?
Role for destructive changesconfirmDestructiveWhat happens
yestrueAPPROVED
yesmissing or falseDESTRUCTIVE_CHANGE_BLOCKED (destructive-change-not-confirmed), stays PENDING_APPROVAL
noeitherDESTRUCTIVE_CHANGE_BLOCKED (destructive-change-requires-role), stays PENDING_APPROVAL

The AI client shows you the destructive changes one by one and asks about them separately. Only if you confirm exactly that part does it send confirmDestructive: true. Your administrator grants the role; which role it is, the operator of the hub decides. On apply the server checks the role once more. If it has been taken from you in the meantime, nothing is written.

A change set without destructive changes needs neither the role nor the confirmation.

Apply exactly once

  1. 1
    AI client→MCP server
    calls cdms_apply_change_set with operationId, changeSetId and an idempotencyKey
  2. 2
    MCP server
    Is the change set APPROVED?
  3. 3
    MCP server
    already APPLIED or ROLLED_BACK → CHANGE_SET_ALREADY_APPLIED; not yet approved → CHANGE_SET_NOT_APPROVED
  4. 4
    MCP server
    Was the same idempotencyKey already used for this change set?
    Then the earlier result comes back. Nothing is written twice. This only works for you: whoever repeats someone else's key gets OPERATION_CONTEXT_NOT_FOUND.
  5. 5
    MCP server
    Is another apply or undo running on this CDMS right now?
  6. 6
    MCP server
    yes → SCHEMA_VERSION_CONFLICT (cdms-change-in-progress), nothing written. Try again later.
  7. 7
    MCP server
    Does the frozen blueprint still match its blueprintHash? If the change set has destructive changes, do you still hold the role for them?
  8. 8
    MCP server
    no → BLUEPRINT_INVALID or DESTRUCTIVE_CHANGE_BLOCKED, nothing written
  9. 9
    MCP server→Database
    reads the current schema again and compares it with the state at planning time
  10. 10
    MCP server
    base has changed → SCHEMA_VERSION_CONFLICT, nothing written
  11. 11
    MCP server→Database
    writes all changes in the order of the plan, in one transaction
    Writing goes through the normal system layers of the hub, so with permission checks and hooks like any other write access.
  12. 12
    MCP server
    checks the result: every element exists exactly once, every ref is resolved
  13. 13
    MCP server
    any error → everything is discarded, status FAILED
  14. 14
    MCP server→AI client
    commit, status APPLIED, result per element (CREATED, REUSED, …)

When the base has changed in the meantime

Between planning and applying, someone else may have changed the model in the hub. That is why the server reads the schema again before writing:

Does the plan still fit the schema?
What has changed since planning?What happens
nothingis applied
a planned new element now existsSCHEMA_VERSION_CONFLICT, plan again
a reused element has disappearedSCHEMA_VERSION_CONFLICT, plan again
an old value no longer matches (length, default, auditing …)SCHEMA_VERSION_CONFLICT, plan again
the target of a migration has changed or is goneSCHEMA_VERSION_CONFLICT, plan again

Undo

cdms_rollback_change_set only works from APPLIED. The rollback undoes, in reverse order, what the change set created or changed. Elements that were only reused (REUSED) stay untouched. The rollback also runs in one transaction, and as with apply, only one runs on a CDMS at a time. It does not need the role for destructive changes: it only takes back what the same change set did.

A change set that deleted a model (DELETE_MODEL) or an enum (DELETE_ENUM) cannot be undone. In that case the server refuses the rollback right away.

The Operation Context: one target, half an hour

What the Operation Context checks on every call
  1. MCP server
    Context known
    Does the operationId exist, is it younger than 30 minutes, and did you create it?
    ↳ no OPERATION_CONTEXT_NOT_FOUND
  2. MCP server
    Same target
    Does the call, including target.cdmsId in the blueprint, point to the same CDMS?
    ↳ no CDMS_TARGET_MISMATCH
  3. MCP server
    Same bracket
    Do plan and change set belong to this operationId?
    ↳ no Error, nothing happens
  4. The call affects exactly the CDMS that was chosen when it was opened

The operationId is not a key you can pass on. If someone else knows it, for example from a shared chat history, they cannot approve, apply or undo anything with it: for them, this context does not exist.

The context is valid for 30 minutes from when it is created. Approve, apply and undo need it. So the whole chain from opening to applying must run within this time. When the time is up, you open a new bracket and plan again.

When opening, the server accepts only the UUID of the CDMS. The reading tools also accept the exact module name. Several matches result in CDMS_TARGET_AMBIGUOUS. There is no fuzzy search.

Connecting the AI client

Your access

You get the access data from us when you register or sign the contract:

ItemMeaning
Base URLwhere the AI client connects, e.g. https://<host>/api/mcp
Client IDthe identifier of your access. It is public and may go into the repository
Callback portthe local port on which the AI client receives the answer after login

There is no client secret. Your access is a public OAuth client secured with PKCE: for every login the AI client creates a one-time key that only it knows. A tool on your machine could not keep a secret secret anyway.

What you may see and change is decided by your user account, not by the access. A CDMS you have no rights on does not show up in cdms_list_targets.

Claude Code

claude mcp add --transport http --client-id <your-client-id> --callback-port <port> \
  codamai-cdms https://<host>/api/mcp
  • --transport http goes before the name and the URL. Without it, Claude Code creates a local stdio server.
  • --client-id and --callback-port are required. Without them Claude Code tries to register itself as a client, and the hub rejects that.
  • No --client-secret, see above.
  • With --scope project the entry goes into the project’s .mcp.json and applies to the whole team; with --scope user it applies to all your projects.

Then you log in inside a Claude Code session:

  1. 1
    Developer→AI client
    types /mcp and picks Authenticate for codamai-cdms
  2. 2
    AI client→Browser
    opens the login page
  3. 3
    User→Keycloak
    logs in with their account
  4. 4
    Keycloak→AI client
    sends the code to http://localhost:<port>/callback
  5. 5
    AI client→Keycloak
    exchanges the code for a token and stores it locally
  6. 6
    AI client→MCP server
    server is connected, the tools are ready

In the same /mcp menu you see the server’s tools and can log out again, for example when you switch users or your permissions have changed. You can also manage the entry from the command line:

claude mcp list                  # all servers with connection status
claude mcp get codamai-cdms      # details for one server
claude mcp remove codamai-cdms   # remove a server

Instead of claude mcp add you can also write the server directly into the project’s .mcp.json:

{
  "mcpServers": {
    "codamai-cdms": {
      "type": "http",
      "url": "https://<host>/api/mcp",
      "oauth": { "clientId": "<your-client-id>", "callbackPort": <port> }
    }
  }
}

Do not add an Authorization header. The client gets the token itself through the login, and a fixed header would be a committed access key.

Fewer prompts, but not for writing

Claude Code asks before every tool call. You can allow the reading tools permanently in .claude/settings.json. The names follow the scheme mcp__<servername>__<toolname>:

{
  "permissions": {
    "allow": [
      "mcp__codamai-cdms__cdms_list_targets",
      "mcp__codamai-cdms__cdms_get_target",
      "mcp__codamai-cdms__cdms_get_capabilities",
      "mcp__codamai-cdms__cdms_get_structure",
      "mcp__codamai-cdms__cdms_get_schema"
    ]
  }
}

cdms_approve_change_set, cdms_apply_change_set and cdms_rollback_change_set never go into this list. The prompt before these calls is your approval.

Other AI clients

Connecting depending on the client

When: recommended

claude mcp add as above, with client ID and callback port.

Result: Login via /mcp

When: the client supports Streamable HTTP and OAuth

Transport Streamable HTTP on https://<host>/api/mcp. The client finds the login by itself: to the first call without a token the server answers 401 and names the discovery document (/.well-known/oauth-protected-resource/api/mcp) in the WWW-Authenticate header. Enter your client ID as client_id and http://localhost:<port>/callback with your callback port as the redirect URI.

When: the client can only register itself

Some clients, such as custom connectors in Claude Desktop, only log in through dynamic client registration. The hub does not offer that. In such cases use Claude Code or a client that lets you pass the client ID and callback port.

Permissions

Everything the MCP server reads and writes goes through the same permission checks as the REST API. A CDMS you may not see does not exist for you: the answer is CDMS_TARGET_NOT_FOUND, see Invisible means 404.

And then?

Applying only changes the models in the hub. Your application only notices it at the next build:

  1. 1
    MCP server→Hub
    Change set APPLIED: model in the hub changed
  2. 2
    Build→Hub
    fetches the metadata again at the next build
  3. 3
    Generator
    creates the code from the new model
  4. 4
    CDMS→Database
    adjusts the database schema at startup, depending on the migration mode
    Result: The application knows the new field

How the build fetches the metadata is described in Code generation in the build.

Pitfalls

Sources in the code and the knowledge base
  • hub-backend – mcp/server/CdmsMcpTools, CdmsMcpResources, McpServerConfiguration
  • hub-backend – mcp/blueprint/CdmsBlueprint, CdmsBlueprintValidator, CdmsModelBlueprintValidator, CdmsMigrationValidator
  • hub-backend – mcp/plan/CdmsChangePlanner, CdmsChangePlan
  • hub-backend – mcp/changeset/CdmsChangeSet, CdmsChangeSetService, CdmsChangeSetExecutor, CdmsChangeSetStore, McpDestructiveRoles
  • hub-backend – docs/adr/ADR-005-mcp-approval-bound-to-user.md
  • hub-backend – mcp/context/CdmsOperationContextService, mcp/target/CdmsTargetService, mcp/auth/McpAuthorizationFilter
  • hub-backend – README.md section 9.3 (Claude Code), mcp/server/McpServerConfiguration (MCP_ENDPOINT)
  • documentation/90-hub/02-mcp-server.md
Search