In Part 1, we examined six MCP security incidents—from remote code execution to cross-tenant data exposure—and found a common thread: boundaries that everyone assumed existed, but no one enforced. JSON Schema validates structure, not semantics. Tool descriptions suggest constraints to LLMs, but suggestions aren't guarantees. The question we left with: what would formal MCP contracts actually look like?

The Case for Contracts as Code

Consider what we're asking JSON Schema to do:

{
  "name": "execute_query",
  "description": "Run a SQL query. Only SELECT statements allowed.",
  "parameters": {
    "query": { "type": "string" }
  }
}

The constraint lives in a description field—plain English that an LLM might respect, might misunderstand, or might ignore entirely when prompted creatively. The schema validates that query is a string. It cannot validate that the string contains only SELECT statements.

Now consider the alternative:

tool execute_query {
  parameter query: SqlQuery {
    constraint: SelectOnly
    tables: ["users", "orders", "products"]
    forbidden: ["DELETE", "DROP", "TRUNCATE", "UPDATE", "INSERT"]
  }
}

This isn't documentation. It's a specification—one that can be:

  • Parsed into a typed AST at development time
  • Validated against a formal grammar before deployment
  • Compiled into runtime validators that reject violations
  • Displayed to users showing exactly what the tool can and cannot do

The description didn't disappear. It became structured—and structure is enforceable.

This is the premise of the Contract DSL approach: move security-critical constraints out of natural language and into a formal language designed for the job.

What MCP Really Needs: Contract-Level Type Safety

The security community has responded to these incidents with familiar prescriptions: input sanitization, rate limiting, SIEM integration, human-in-the-loop approvals. These controls are necessary but insufficient. They treat symptoms without addressing the root cause.

MCP's fundamental problem is that it lacks a formal contract language for expressing what tools should and shouldn't do. JSON Schema validates shapes. Descriptions suggest behaviors. Neither constitutes a machine-verifiable contract.

What would contract-level type safety actually look like?

Resource Capabilities as Types

Instead of string paths with documentation, a contract language could express:

 resource FileAccess {
   workspace_root: Path
   
   constraint readable_file(p: Path) {
     p.starts_with(workspace_root) and p.is_file()
   }
   
   constraint writable_file(p: Path) {
     readable_file(p) and not p.extension in [".exe", ".sh", ".bat"]
   }
 }
Tools would then declare which capabilities they require:
 tool edit_document {
   requires FileAccess with writable_file
   param document: writable_file
   param content: string
 }

A runtime monitor could verify that all file operations respect these constraints—not as a description, but as enforced behavior.

Authorization Scopes as First Class Constructs

The GitHub MCP breach exploited overly-broad PAT scopes. A contract language could make scope boundaries explicit and verifiable:

 scope github_public {
   allows read_issue(repo where repo.visibility = "public")
   allows read_comment(repo where repo.visibility = "public")
 }
 
 scope github_private {
   extends github_public
   allows read_issue(repo where user.has_access(repo))
   allows read_repository(repo where user.has_access(repo))
 }
 
 tool get_issue {
   requires github_public or github_private
   param repo: Repository
   param issue_number: int
 }

Tool invocations would then be checked against granted scopes, catching privilege escalation attempts before execution.

Data Flow Constraints

The WhatsApp exfiltration worked because nothing prevented data from flowing across trust boundaries. Contract-level type safety could express:

 sensitivity WhatsApp = HIGH
 sensitivity PublicAPI = LOW
 
 constraint no_exfiltration {
   data.sensitivity(source) <= data.sensitivity(destination)
 }
 
 tool send_to_webhook {
   param data: any
   param url: URL
   enforces no_exfiltration between (data, url)
 }

This would make data flow policies machine-checkable rather than implicit in natural language descriptions.

Enter Langium: DSLs for the Contract Layer

Building a contract language sounds like a multi-year research project. It's not—if you have the right foundation.

Langium is an open-source language engineering toolkit that generates complete TypeScript-based language servers from grammar definitions. It produces typed abstract syntax trees, provides LSP integration for IDE support, and runs anywhere JavaScript runs: VS Code extensions, CLI tools, web applications, CI/CD pipelines.

Here's why Langium is uniquely suited for MCP contract definition:

Type-Safe AST Generation

Langium grammars produce TypeScript interfaces for the abstract syntax tree. When you define:

 Tool:
   'tool' name=ID '{'
     ('requires' requirements+=Capability (',' requirements+=Capability)*)?
     ('param' params+=Parameter)*
     ('enforces' constraints+=Constraint)*
   '}';
 
 Capability:
   name=ID ('with' bound=ConstraintRef)?;

Langium generates interfaces like:

 interface Tool {
   name: string;
   requirements: Capability[];
   params: Parameter[];
   constraints: Constraint[];
 }

Your contract validation code operates on strongly-typed structures, not string-parsed JSON. Typos become compile errors. Structural inconsistencies get caught at build time.

Cross-Reference Resolution

MCP tools reference other tools, scopes reference other scopes, constraints reference capabilities. Langium handles cross-reference resolution automatically—including across multiple files in a workspace. When an MCP server declares it requires FileAccess, Langium's linking infrastructure verifies that FileAccess exists and has the right structure.

This enables compositional contract definitions where organizations build reusable capability libraries that tool authors reference.

Validation Infrastructure

Langium provides hooks for semantic validation that go beyond syntax checking:

 export function registerValidationChecks(checks: ValidationChecks) {
   checks.register('Tool', (tool, accept) => {
     for (const param of tool.params) {
       if (param.type.name === 'Path' && !tool.requirements.some(r => r.name === 'FileAccess')) {
         accept('error', 'Tool uses Path parameter but does not require FileAccess', {
           node: param
         });
       }
     }
   });
 }

These validations appear as IDE errors in VS Code, CLI errors in build pipelines, and runtime errors in contract enforcement. The same logic protects developers writing contracts and operators deploying MCP servers.

IDE Integration by Default

Because Langium implements the Language Server Protocol, your contract DSL automatically gets:

  • Syntax highlighting
  • Error squiggles
  • Auto-completion
  • Go-to-definition
  • Find references
  • Rename refactoring

Security teams defining organizational policies get the same editing experience as developers writing application code. This matters because security policies that are hard to write correctly don't get written correctly.

A Practical Architecture: MCP + Contract DSL

Here's how a Langium-based contract layer integrates with existing MCP infrastructure:

Compile-Time Validation

Tool authors write contract definitions alongside their MCP server implementations:

 // tools/database.contracts
 
 capability DatabaseAccess {
   connection_string: secret
   
   constraint read_only_query(sql: string) {
     sql.lowercase.starts_with("select") and
     not sql.lowercase.contains("drop") and
     not sql.lowercase.contains("delete") and
     not sql.lowercase.contains("update") and
     not sql.lowercase.contains("insert")
   }
 }
 
 tool query_database {
   requires DatabaseAccess with read_only_query
   param query: string @ read_only_query
   returns json
 }

The @ annotation binds the parameter to a constraint. The Langium-generated validator ensures the constraint is applicable to the parameter type.

During build, the contract compiler:

  1. Validates all contract files syntactically and semantically
  2. Generates TypeScript validators from constraints
  3. Produces JSON manifests describing tool capabilities
  4. Flags inconsistencies between contracts and MCP tool definitions

Runtime Enforcement

The generated validators become middleware in the MCP server:

 import { createContractValidator } from './generated/database.contracts';
 
 server.on('tools/call', async (request) => {
   const validator = createContractValidator(request.tool);
   
   const violations = validator.check(request.arguments);
   if (violations.length > 0) {
     return {
       error: {
         code: 'CONTRACT_VIOLATION',
         message: violations[0].message,
         data: { violations }
       }
     };
   }
   
   // Proceed with tool execution
 });

Constraint violations are caught before tool logic executes—not as an LLM suggestion, but as a programmatic enforcement.

Client-Side Transparency

MCP clients can fetch and display contract manifests, giving users visibility into what tools are actually permitted to do:

 {
   "tool": "query_database",
   "capabilities": {
     "DatabaseAccess": {
       "constraints": ["read_only_query"],
       "description": "Allows SELECT queries only"
     }
   },
   "verified": "2025-02-01T10:30:00Z",
   "signature": "0x..."
 }

Security teams can now audit tool permissions mechanically rather than reading descriptions and hoping they're accurate.

Organizational Policy Enforcement

Enterprises deploying MCP servers can define organizational policy contracts:

 // policies/data-classification.contracts
 
 sensitivity_level PII > INTERNAL > PUBLIC
 
 constraint pii_handling {
   tool_output.sensitivity <= granted_scope.max_sensitivity
 }
 
 policy enterprise_ai {
   all tools enforce pii_handling
   all tools with sensitivity(PII) require human_approval
 }

CI/CD pipelines validate tool contracts against organizational policies before deployment. Tools that violate policy don't ship.


Color Inside the Lines: The Philosophical Foundation

This approach aligns with a principle I call "coloring inside the lines"—a counterpoint to the prevailing agentic AI narrative.

The industry conversation around AI agents emphasizes autonomy, adaptability, and open-ended capability. Build agents that can figure things out. Let them tool around until they succeed. Trust the foundation model's judgment.

This narrative has produced remarkable demonstrations. It has also produced CVE-2025-6514.

Coloring inside the lines means designing systems where the boundaries are explicit, verifiable, and enforced before execution. Not through descriptions that suggest boundaries. Not through post-hoc monitoring that detects violations. Through contracts that define the possible state space and reject invalid requests programmatically.

Domain-specific languages are the natural expression of this philosophy. A DSL for MCP contracts doesn't make AI less capable. It makes AI capability legible—to developers, to security teams, to operators, and to the agents themselves.

The agent that knows its boundaries can work confidently within them. The agent operating on suggestions and best practices is one prompt injection away from disaster.


Getting Started: A Minimal Contract DSL

For teams interested in exploring this approach, here's a starting point. This Langium grammar defines a minimal contract language for MCP tool capabilities:

grammar McpContracts

entry ContractFile:
  (capabilities+=Capability | tools+=ToolContract)*;

Capability:
  'capability' name=ID '{'
    (constraints+=Constraint)*
  '}';

Constraint:
  'constraint' name=ID '(' params+=Parameter (',' params+=Parameter)* ')' '{'
    body=ConstraintBody
  '}';

Parameter:
  name=ID ':' type=Type;

Type:
  name=('string' | 'int' | 'boolean' | 'path' | 'url' | 'json' | ID);

ConstraintBody:
  expressions+=Expression ('and' expressions+=Expression)*;

Expression:
  left=Operand op=Operator right=Operand;

Operand:
  PropertyRef | StringLiteral | NumberLiteral;

PropertyRef:
  root=ID ('.' path+=ID)*;

Operator:
  '=' | '!=' | 'starts_with' | 'contains' | 'in' | '<' | '<=' | '>' | '>=';

ToolContract:
  'tool' name=ID '{'
    ('requires' requirements+=CapabilityRef (',' requirements+=CapabilityRef)*)?
    ('param' params+=ParamDecl)*
  '}';

CapabilityRef:
  capability=[Capability] ('with' constraint=[Constraint])?;

ParamDecl:
  name=ID ':' type=Type ('@' constraint=[Constraint])?;

hidden terminal WS: /\s+/;
terminal ID: /[a-zA-Z_][a-zA-Z0-9_]*/;
terminal STRING: /"[^"]*"/;
terminal NUMBER: /[0-9]+/;

This is deliberately minimal—enough to express file path constraints, SQL read-only restrictions, and URL domain allowlists. A production implementation would add:

  • Imported capability libraries
  • Parametric constraints
  • Flow sensitivity tracking
  • Cryptographic signatures for manifests
  • Integration with existing IAM systems

But even this minimal grammar, compiled through Langium, produces a working LSP with syntax highlighting, error detection, and auto-completion. It can generate TypeScript validators. It can produce JSON manifests. It's a foundation for real contract enforcement—not a research paper.


The Opportunity

Organizations implementing MCP face a choice. They can continue adopting the protocol with ad-hoc security measures: input sanitization here, rate limiting there, fingers crossed that tool descriptions accurately reflect tool behavior. This path leads to more CVEs, more incidents, more compliance failures.

Or they can treat MCP's security delegation as an opportunity. The protocol's flexibility means organizations can define their own contract layer—one that expresses their specific security requirements, integrates with their existing governance frameworks, and provides verifiable assurances rather than documented intentions.

Langium makes that second path practical. Not theoretical. Not years away. Practical today, with tooling that runs in the same TypeScript ecosystem where MCP servers already live.

The MCP registry has 1,500+ servers. Microsoft, Anthropic, and major cloud providers are betting on the protocol. The question isn't whether MCP will see enterprise adoption. The question is whether that adoption will be secured.

Further Reading

Share this post

Written by

Comments

Generative AI and Information Governance: A Practitioner's Guide
Generative AI: Revolutionizing Information Governance

Generative AI and Information Governance: A Practitioner's Guide

By John F. Holliday 12 min read