AI agents for the backend you already run. Every tool proven, or refused.
Tibyaan reads your Spring application's source and exposes only the operations it can prove: which real method backs each tool, on which entity, returning what. Everything it cannot prove, it refuses, with the reason and the file. Zero wrong bindings on every application it has read.
0 wrong bindings across three real, unmodified applications, on every run.
The software that runs the business predates the agent era.
Rewrite it
Rebuild the system so it speaks a modern protocol. Expensive, slow, risky, and most of the time unnecessary.
Hand-build every connection
Wire up each thing an agent might need, by hand. The work never ends, and it falls out of date the moment the system changes.
Let the agent improvise
Point an agent at the system directly. The dangerous one. The agent can call the wrong thing on a wrong assumption, and you have no way to know it did.
So the value stays locked away from the agents that could use it. Tibyaan is a fourth path.
It never guesses.
Your backend already knows how to look up a customer, an order, an invoice. That knowledge works, and it sits locked inside a system built before AI agents existed.
Tibyaan reads that system's source code, the same files your team maintains, and works out which read operations it can prove safe to hand to an AI agent. Proof is literal. It exposes an operation only when the code's structure shows which real method backs it and what that method returns. Where the structure leaves any doubt, it refuses, out loud, with the reason and the file.
What survives becomes a set of tools that AI agents already know how to call, served from inside your own application, hitting your real logic and your real data. Your code is never modified. The set is smaller than your system, and every entry in it is right.
Discovered (25):
by-id (16):
+ Contact: getContact -> ContactServiceImpl#findById; returns the entity Contact directly (section 4.1) (partial: 3 fields skipped)
+ ContactCategory: getContactCategory -> ContactCategoryServiceImpl#findById; returns the entity ContactCategory directly (section 4.1) (partial: 1 fields skipped)
+ ContactGroup: getContactGroup -> ContactGroupServiceImpl#findById; returns the entity ContactGroup directly (section 4.1) (partial: 1 fields skipped)
... (+13 more discovered by id; every one shown in full in the linked ledger)
list (9):
+ listAccount -> AccountServiceImpl#findAll
+ listContact -> ContactServiceImpl#findAll (paged)
... (+7 more discovered list reads)
Failed (9):
- EmailTemplate [NO_IDENTIFIER]: entity 'EmailTemplate' has @Id 'type' of type 'EmailTemplateType', which does not map to a bindable primitive, so no read-by-id capability is modeled (Volume 5 section 4.1). (partial: 1 fields skipped)
- OutboundEmail [NO_BACKING_METHOD]: no service method backs read capability 'getOutboundEmail' for entity 'OutboundEmail'.
- OutboundNotification [NO_BACKING_METHOD]: no service method backs read capability 'getOutboundNotification' for entity 'OutboundNotification'.
- Quote [AMBIGUOUS_BINDING]: read capability 'getQuote' for entity 'Quote' is backed by more than one candidate: [QuoteServiceImpl#findById (entity-returning), QuoteServiceImpl#getInvoice (view-returning byte[])]. (partial: 2 fields skipped)
- SMSMessage [NO_BACKING_METHOD]: no service method backs read capability 'getSMSMessage' for entity 'SMSMessage'. (partial: 1 fields skipped)
- Delivery [NO_BACKING_METHOD]: no service method backs read capability 'getDelivery' for entity 'Delivery'. (partial: 1 fields skipped)
- DeletedUser [NO_BACKING_METHOD]: no service method backs read capability 'getDeletedUser' for entity 'DeletedUser'.
- User [AMBIGUOUS_BINDING]: read capability 'getUser' for entity 'User' is backed by more than one candidate: [UserService#findById (entity-returning), UserService#findByUsername (entity-returning)].
- InventoryTransaction [NO_BACKING_METHOD]: no service method backs read capability 'getInventoryTransaction' for entity 'InventoryTransaction'.
List-refused (1):
- ProductCategory [LIST_AMBIGUOUS]: list capability 'listProductCategory' has more than one list-all candidate: [ProductCategoryServiceImpl#findAll, ProductCategoryServiceImpl#findAll].
Totals: discovered 25 (by-id 16, list 9), failed 9 (by cause: NO_IDENTIFIER 1, NO_BACKING_METHOD 6, AMBIGUOUS_BINDING 2), list-refused 1, skipped-by-parse 0, partial 13 (22 fields skipped), mis-bound 0Unedited output of the ERP benchmark survey (2026-07-17), generated by tibyan CLI 0.2.0-SNAPSHOT (current build). Elisions are marked in the text and only shorten the discovered lists; every refusal is shown in full. The full per-entity ledger is public (opens in a new tab).
Refusals are the product working, not failing.
A large application does not turn into a hundred tools. It turns into a precise, verified subset plus a named ledger of everything Tibyaan declined to expose. Here it found 25 capabilities and named all 9 refusals, with no wrong binding.
Look at User: two methods both fit the shape of a read-by-id, so Tibyaan refused rather than pick one. That refusal is the whole point. The number to watch is mis-bound, and it stays 0.
The honesty is legible in the output itself.
One verified model produces an MCP server and an OpenAPI document. The tool writes its own limits into what it generates: a list read says the page envelope is not modeled, a partial entity's schema names the fields it did not represent. Nothing here is retyped; both snippets are copied from files the current build really generated.
{ "name": "getProduct", "description": "Read Product by id.", "binding": "ProductServiceImpl#findById", "output": "ENTITY_REF(Product)", "inputs": [ { "name": "id", "type": "UUID" } ] }, { "name": "listContact", "description": "List all Contact. Returns a page of results; the total count and page envelope are not modeled.", "binding": "ContactServiceImpl#findAll", "output": "ENTITY_REF(Contact)", "outputMultiple": true, "paged": true, "inputs": [] }
From the real MCP manifest. tibyan CLI 0.2.0-SNAPSHOT (current build), 2026-07-17. The second description is the tool disclosing what it does not model.
"Product": { "type": "object", "description": "Entity Product as modeled by Tibyaan. Partial model: 4 field(s) of the source entity are not represented: suppliers, images, documents, pricings.", "properties": { "id": { "type": "string", "format": "uuid" }, "barcodeType": { "$ref": "#/components/schemas/BarcodeType" }, "unit": { "$ref": "#/components/schemas/Unit" } ... (22 more properties) } ... ("required" array of the 25 modeled property names, omitted for length) }
From the real OpenAPI 3.0 document. tibyan CLI 0.2.0-SNAPSHOT (current build), 2026-07-17. The description states, in the artifact, exactly which source fields are not represented.
Each of these is a boundary Tibyaan draws on purpose.
Filtered reads
findByCategory, search, and similar. A parameter's meaning is a claim the tool cannot verify from structure, and a tool description an agent acts on must not carry an unverified claim.
Writes and actions
Refused at the discovery level until an explicit policy gate exists. A write is never generated into the manifest.
List envelopes
Custom paged result-set wrapper types are not yet seen through. The unfiltered list is modeled; the wrapped variants are not.
Non-Spring stacks
The model is framework-neutral by design, but only the Spring source provider exists today.
The path beyond it is already designed: human-attested capabilities, labeled as such, never guesses. Tibyaan builds it when a real adopter hits the boundary, not before.
31 verified tools. 0 wrong bindings.
| Application | Entities | Capabilities | Wrong bindings |
|---|---|---|---|
ERP backend Services return entities; discovers well. | 25 | 25 (16 by-id, 9 list) | 0 |
PMS backend Services return view objects; most reads refused by name. | 43 | 2 (2 by-id, 0 list) | 0 |
A real multi-module enterprise Spring application Reuses entity names across bounded contexts; emits valid output where simple-name keying could not. | 169 modules / 111 | 4 | 0 |
Read these numbers honestly. Coverage is idiom-bound: two of the three apps share an author, the apps discover through different rules, and your app's number depends on your idioms. It could land anywhere in that range. The one guarantee that transfers is zero wrong bindings. The third row is a real multi-module enterprise application, kept private and shown by counts and shapes only. Preview your own app to see your real number before you decide.
Read, prove, refuse or emit, serve.
Read
Parses your source and maps the entities and read operations it finds. Files it cannot parse are skipped and named, never silently dropped.
Prove
Each candidate needs structural evidence: the real method that backs it, on the real repository of the real entity. Names carry no weight.
Refuse or emit
Undecidable cases are refused with a located reason. Decided cases become verified tools. On a large app, expect most to be refused.
Serve
Your own build generates the manifest from your source, and a small runtime inside your app serves the tools to agents over MCP. The same model also emits an OpenAPI document.
v1 does not fit every aging Spring backend. Check before you spend a build change.
Preview your app. It reads your source and changes nothing.
The preview is read-only: it writes nothing, touches no build file, and cannot break anything. It shows you exactly what Tibyaan would expose and what it would refuse, so you decide with the evidence in front of you. Ran it already? Send us your numbers: every real survey shapes what gets built next.