[{"data":1,"prerenderedAt":1309},["ShallowReactive",2],{"blog-graphpg-end-to-end-type-safety-claude-code":3,"blog-graphpg-end-to-end-type-safety-claude-code-favor-of":1308},{"id":4,"title":5,"body":6,"cover_image":1292,"date":1293,"description":1294,"extension":1295,"meta":1296,"navigation":152,"path":1298,"seo":1299,"stem":1300,"tags":1301,"type":1306,"__hash__":1307},"blog\u002Fblog\u002Fgraphpg-end-to-end-type-safety-claude-code.md","End-to-End Type Safety for Claude Code: PostgreSQL Schema to GraphQL SDL with GraphPG",{"type":7,"value":8,"toc":1281},"minimark",[9,13,20,23,268,271,279,282,285,288,291,296,299,321,323,327,330,338,341,370,372,376,381,384,524,530,692,696,699,786,789,939,943,946,1191,1193,1197,1200,1221,1224,1244,1250,1256,1259,1261,1265,1277],[10,11,5],"h1",{"id":12},"end-to-end-type-safety-for-claude-code-postgresql-schema-to-graphql-sdl-with-graphpg",[14,15,16],"p",{},[17,18,19],"em",{},"Why do coding agents hallucinate database tool arguments, and how does generating TypeScript types from PostgreSQL schemas through GraphQL SDL stop contract drift?",[14,21,22],{},"When developers build tools for Claude Code or Model Context Protocol (MCP) servers, they typically reach for manual schema validators like Zod to define input arguments:",[24,25,30],"pre",{"className":26,"code":27,"language":28,"meta":29,"style":29},"language-typescript shiki shiki-themes github-dark-default","\u002F\u002F Fragile: A hand-written Zod contract decoupled from the database catalog\nexport const UpdateRecordSchema = z.object({\n  recordId: z.string().uuid(),\n  status: z.enum([\"active\", \"archived\", \"pending\"]),\n  metadata: z.record(z.unknown()).optional(),\n});\n\nserver.tool(\n  \"update_record\",\n  \"Updates database record status\",\n  UpdateRecordSchema.shape,\n  async (args) => {\n    \u002F\u002F Manual SQL execution with unverified column mappings\n    return db.query(\"UPDATE records SET status = $1 WHERE id = $2\", [\n      args.status,\n      args.recordId,\n    ]);\n  },\n);\n","typescript","",[31,32,33,42,70,88,118,141,147,154,166,175,183,189,211,217,238,244,250,256,262],"code",{"__ignoreMap":29},[34,35,38],"span",{"class":36,"line":37},"line",1,[34,39,41],{"class":40},"sH3jZ","\u002F\u002F Fragile: A hand-written Zod contract decoupled from the database catalog\n",[34,43,45,49,52,56,59,63,67],{"class":36,"line":44},2,[34,46,48],{"class":47},"suJrU","export",[34,50,51],{"class":47}," const",[34,53,55],{"class":54},"sFSAA"," UpdateRecordSchema",[34,57,58],{"class":47}," =",[34,60,62],{"class":61},"sZEs4"," z.",[34,64,66],{"class":65},"sc3cj","object",[34,68,69],{"class":61},"({\n",[34,71,73,76,79,82,85],{"class":36,"line":72},3,[34,74,75],{"class":61},"  recordId: z.",[34,77,78],{"class":65},"string",[34,80,81],{"class":61},"().",[34,83,84],{"class":65},"uuid",[34,86,87],{"class":61},"(),\n",[34,89,91,94,97,100,104,107,110,112,115],{"class":36,"line":90},4,[34,92,93],{"class":61},"  status: z.",[34,95,96],{"class":65},"enum",[34,98,99],{"class":61},"([",[34,101,103],{"class":102},"s9uIt","\"active\"",[34,105,106],{"class":61},", ",[34,108,109],{"class":102},"\"archived\"",[34,111,106],{"class":61},[34,113,114],{"class":102},"\"pending\"",[34,116,117],{"class":61},"]),\n",[34,119,121,124,127,130,133,136,139],{"class":36,"line":120},5,[34,122,123],{"class":61},"  metadata: z.",[34,125,126],{"class":65},"record",[34,128,129],{"class":61},"(z.",[34,131,132],{"class":65},"unknown",[34,134,135],{"class":61},"()).",[34,137,138],{"class":65},"optional",[34,140,87],{"class":61},[34,142,144],{"class":36,"line":143},6,[34,145,146],{"class":61},"});\n",[34,148,150],{"class":36,"line":149},7,[34,151,153],{"emptyLinePlaceholder":152},true,"\n",[34,155,157,160,163],{"class":36,"line":156},8,[34,158,159],{"class":61},"server.",[34,161,162],{"class":65},"tool",[34,164,165],{"class":61},"(\n",[34,167,169,172],{"class":36,"line":168},9,[34,170,171],{"class":102},"  \"update_record\"",[34,173,174],{"class":61},",\n",[34,176,178,181],{"class":36,"line":177},10,[34,179,180],{"class":102},"  \"Updates database record status\"",[34,182,174],{"class":61},[34,184,186],{"class":36,"line":185},11,[34,187,188],{"class":61},"  UpdateRecordSchema.shape,\n",[34,190,192,195,198,202,205,208],{"class":36,"line":191},12,[34,193,194],{"class":47},"  async",[34,196,197],{"class":61}," (",[34,199,201],{"class":200},"sQhOw","args",[34,203,204],{"class":61},") ",[34,206,207],{"class":47},"=>",[34,209,210],{"class":61}," {\n",[34,212,214],{"class":36,"line":213},13,[34,215,216],{"class":40},"    \u002F\u002F Manual SQL execution with unverified column mappings\n",[34,218,220,223,226,229,232,235],{"class":36,"line":219},14,[34,221,222],{"class":47},"    return",[34,224,225],{"class":61}," db.",[34,227,228],{"class":65},"query",[34,230,231],{"class":61},"(",[34,233,234],{"class":102},"\"UPDATE records SET status = $1 WHERE id = $2\"",[34,236,237],{"class":61},", [\n",[34,239,241],{"class":36,"line":240},15,[34,242,243],{"class":61},"      args.status,\n",[34,245,247],{"class":36,"line":246},16,[34,248,249],{"class":61},"      args.recordId,\n",[34,251,253],{"class":36,"line":252},17,[34,254,255],{"class":61},"    ]);\n",[34,257,259],{"class":36,"line":258},18,[34,260,261],{"class":61},"  },\n",[34,263,265],{"class":36,"line":264},19,[34,266,267],{"class":61},");\n",[14,269,270],{},"At first glance, this approach appears safe. Zod validates incoming payloads at runtime, rejecting malformed tool calls from the model.",[14,272,273,274,278],{},"The hidden failure lies in ",[275,276,277],"strong",{},"contract drift",".",[14,280,281],{},"A handwritten Zod schema lives strictly in application code. It knows nothing about the underlying PostgreSQL database. When a database migration renames a column, alters a nullability constraint, or updates an enum type in the catalog, your Zod definitions remain frozen.",[14,283,284],{},"The application compiles cleanly. The JSON Schema exposed to Claude Code appears valid. But when the agent calls the tool during a multi-file refactor, the database rejects the query with runtime syntax or constraint violations. The agent enters an expensive retry loop, burning tokens trying to guess the correct schema shape.",[14,286,287],{},"To make agentic tool calling reliable, you must cut manual contracts. Your database schema must serve as the single source of truth, generating types all the way to the agent tool boundary.",[289,290],"hr",{},[292,293,295],"h2",{"id":294},"the-fragility-of-application-layer-tool-contracts","The Fragility of Application-Layer Tool Contracts",[14,297,298],{},"When an AI agent modifies code or calls operational tools, manual schemas introduce three compounding vulnerabilities:",[300,301,302,306,309],"ol",{},[303,304,305],"li",{},"Dual schema maintenance forces developers to write the data model twice—first in SQL migrations or ORM schemas, then in Zod validators for MCP tools. Over dozens of tables, engineers inevitably miss updating tool signatures when altering relational columns.",[303,307,308],{},"Invisible schema drift occurs when PostgreSQL alters a column default or drops a table key. The TypeScript compiler has no visibility into the mismatch because Zod types run in isolation from database DDL.",[303,310,311,312,106,315,106,318,320],{},"Agent hallucination loops trigger when an unhandled database error bubbles up to Claude Code through an MCP tool. The model attempts to self-correct by inventing arbitrary argument names (",[31,313,314],{},"record_id",[31,316,317],{},"id",[31,319,84],{},"), compounding the failure.",[289,322],{},[292,324,326],{"id":325},"the-solution-the-database-driven-type-chain","The Solution: The Database-Driven Type Chain",[14,328,329],{},"Instead of manually duplicating schemas in Zod, we construct an automated, end-to-end type generation pipeline:",[24,331,336],{"className":332,"code":334,"language":335,"meta":29},[333],"language-text","┌──────────────────────────────────────────────┐\n│  Single Source of Truth: PostgreSQL Catalog  │\n│      (Tables, Columns, Checks, Enums)        │\n└──────────────────────┬───────────────────────┘\n                       │\n                       ▼\n┌──────────────────────────────────────────────┐\n│        GraphPG High-Performance Engine       │\n│      (Compiles Catalog to GraphQL SDL)       │\n└──────────────────────┬───────────────────────┘\n                       │\n                       ▼\n┌──────────────────────────────────────────────┐\n│           GraphQL Code Generator             │\n│   (Emits Typed Document Nodes & TS Types)    │\n└──────────────────────┬───────────────────────┘\n                       │\n                       ▼\n┌──────────────────────────────────────────────┐\n│     Claude Code \u002F MCP Tool Implementation    │\n│    (Deterministic Compile Errors on Drift)   │\n└──────────────────────────────────────────────┘\n","text",[31,337,334],{"__ignoreMap":29},[14,339,340],{},"This pipeline relies on four tightly coupled stages:",[300,342,343,346,357,363],{},[303,344,345],{},"Define the data model once in standard SQL migrations, establishing PostgreSQL as the single source of truth.",[303,347,348,349,352,353,356],{},"GraphPG monitors the PostgreSQL catalog. Whenever a migration runs, GraphPG introspects the schema and rewrites ",[31,350,351],{},"schema.graphql"," (the GraphQL SDL) and ",[31,354,355],{},"openapi.json"," in milliseconds.",[303,358,359,360,362],{},"GraphQL Code Generator consumes ",[31,361,351],{}," and emits static TypeScript types for every query, mutation, and filter.",[303,364,365,366,369],{},"MCP tools consume the generated TypeScript operation types. If a column changes, ",[31,367,368],{},"tsc"," immediately fails across every tool implementation before runtime execution.",[289,371],{},[292,373,375],{"id":374},"wiring-graphpg-to-claude-code-tools","Wiring GraphPG to Claude Code Tools",[377,378,380],"h3",{"id":379},"_1-generating-the-graphql-sdl","1. Generating the GraphQL SDL",[14,382,383],{},"Consider a healthcare schema defining clinical records. In PostgreSQL:",[24,385,389],{"className":386,"code":387,"language":388,"meta":29,"style":29},"language-sql shiki shiki-themes github-dark-default","CREATE TYPE record_status AS ENUM ('active', 'archived', 'pending');\n\nCREATE TABLE clinical_records (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    organization_id UUID NOT NULL,\n    patient_name TEXT NOT NULL,\n    status record_status NOT NULL DEFAULT 'active',\n    diagnosis TEXT NOT NULL,\n    created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()\n);\n","sql",[31,390,391,423,427,440,454,464,477,494,505,520],{"__ignoreMap":29},[34,392,393,396,399,402,405,408,411,413,416,418,421],{"class":36,"line":37},[34,394,395],{"class":47},"CREATE",[34,397,398],{"class":47}," TYPE",[34,400,401],{"class":65}," record_status",[34,403,404],{"class":47}," AS",[34,406,407],{"class":61}," ENUM (",[34,409,410],{"class":102},"'active'",[34,412,106],{"class":61},[34,414,415],{"class":102},"'archived'",[34,417,106],{"class":61},[34,419,420],{"class":102},"'pending'",[34,422,267],{"class":61},[34,424,425],{"class":36,"line":44},[34,426,153],{"emptyLinePlaceholder":152},[34,428,429,431,434,437],{"class":36,"line":72},[34,430,395],{"class":47},[34,432,433],{"class":47}," TABLE",[34,435,436],{"class":65}," clinical_records",[34,438,439],{"class":61}," (\n",[34,441,442,445,448,451],{"class":36,"line":90},[34,443,444],{"class":61},"    id UUID ",[34,446,447],{"class":47},"PRIMARY KEY",[34,449,450],{"class":47}," DEFAULT",[34,452,453],{"class":61}," gen_random_uuid(),\n",[34,455,456,459,462],{"class":36,"line":120},[34,457,458],{"class":61},"    organization_id UUID ",[34,460,461],{"class":47},"NOT NULL",[34,463,174],{"class":61},[34,465,466,469,472,475],{"class":36,"line":143},[34,467,468],{"class":61},"    patient_name ",[34,470,471],{"class":47},"TEXT",[34,473,474],{"class":47}," NOT NULL",[34,476,174],{"class":61},[34,478,479,482,485,487,489,492],{"class":36,"line":149},[34,480,481],{"class":47},"    status",[34,483,484],{"class":61}," record_status ",[34,486,461],{"class":47},[34,488,450],{"class":47},[34,490,491],{"class":102}," 'active'",[34,493,174],{"class":61},[34,495,496,499,501,503],{"class":36,"line":156},[34,497,498],{"class":61},"    diagnosis ",[34,500,471],{"class":47},[34,502,474],{"class":47},[34,504,174],{"class":61},[34,506,507,510,513,515,517],{"class":36,"line":168},[34,508,509],{"class":61},"    created_at ",[34,511,512],{"class":47},"TIMESTAMPTZ",[34,514,474],{"class":47},[34,516,450],{"class":47},[34,518,519],{"class":61}," clock_timestamp()\n",[34,521,522],{"class":36,"line":177},[34,523,267],{"class":61},[14,525,526,527,529],{},"When this migration executes, GraphPG reflects the table directly into ",[31,528,351],{},":",[24,531,535],{"className":532,"code":533,"language":534,"meta":29,"style":29},"language-graphql shiki shiki-themes github-dark-default","enum RecordStatus {\n  active\n  archived\n  pending\n}\n\ntype ClinicalRecord {\n  id: UUID!\n  organizationId: UUID!\n  patientName: String!\n  status: RecordStatus!\n  diagnosis: String!\n  createdAt: DateTime!\n}\n\ninput UpdateClinicalRecordInput {\n  status: RecordStatus\n  diagnosis: String\n}\n","graphql",[31,536,537,546,551,556,561,566,570,580,594,605,617,629,640,652,656,660,670,679,688],{"__ignoreMap":29},[34,538,539,541,544],{"class":36,"line":37},[34,540,96],{"class":47},[34,542,543],{"class":54}," RecordStatus",[34,545,210],{"class":61},[34,547,548],{"class":36,"line":44},[34,549,550],{"class":47},"  active\n",[34,552,553],{"class":36,"line":72},[34,554,555],{"class":47},"  archived\n",[34,557,558],{"class":36,"line":90},[34,559,560],{"class":47},"  pending\n",[34,562,563],{"class":36,"line":120},[34,564,565],{"class":61},"}\n",[34,567,568],{"class":36,"line":143},[34,569,153],{"emptyLinePlaceholder":152},[34,571,572,575,578],{"class":36,"line":149},[34,573,574],{"class":47},"type",[34,576,577],{"class":54}," ClinicalRecord",[34,579,210],{"class":61},[34,581,582,585,588,591],{"class":36,"line":156},[34,583,584],{"class":200},"  id",[34,586,587],{"class":61},": ",[34,589,590],{"class":54},"UUID",[34,592,593],{"class":47},"!\n",[34,595,596,599,601,603],{"class":36,"line":168},[34,597,598],{"class":200},"  organizationId",[34,600,587],{"class":61},[34,602,590],{"class":54},[34,604,593],{"class":47},[34,606,607,610,612,615],{"class":36,"line":177},[34,608,609],{"class":200},"  patientName",[34,611,587],{"class":61},[34,613,614],{"class":54},"String",[34,616,593],{"class":47},[34,618,619,622,624,627],{"class":36,"line":185},[34,620,621],{"class":200},"  status",[34,623,587],{"class":61},[34,625,626],{"class":54},"RecordStatus",[34,628,593],{"class":47},[34,630,631,634,636,638],{"class":36,"line":191},[34,632,633],{"class":200},"  diagnosis",[34,635,587],{"class":61},[34,637,614],{"class":54},[34,639,593],{"class":47},[34,641,642,645,647,650],{"class":36,"line":213},[34,643,644],{"class":200},"  createdAt",[34,646,587],{"class":61},[34,648,649],{"class":54},"DateTime",[34,651,593],{"class":47},[34,653,654],{"class":36,"line":219},[34,655,565],{"class":61},[34,657,658],{"class":36,"line":240},[34,659,153],{"emptyLinePlaceholder":152},[34,661,662,665,668],{"class":36,"line":246},[34,663,664],{"class":47},"input",[34,666,667],{"class":54}," UpdateClinicalRecordInput",[34,669,210],{"class":61},[34,671,672,674,676],{"class":36,"line":252},[34,673,621],{"class":200},[34,675,587],{"class":61},[34,677,678],{"class":54},"RecordStatus\n",[34,680,681,683,685],{"class":36,"line":258},[34,682,633],{"class":200},[34,684,587],{"class":61},[34,686,687],{"class":54},"String\n",[34,689,690],{"class":36,"line":264},[34,691,565],{"class":61},[377,693,695],{"id":694},"_2-defining-typed-operations","2. Defining Typed Operations",[14,697,698],{},"Rather than writing hand-crafted schemas, we define a typed GraphQL document:",[24,700,702],{"className":532,"code":701,"language":534,"meta":29,"style":29},"mutation UpdateClinicalRecord($id: UUID!, $input: UpdateClinicalRecordInput!) {\n  updateClinicalRecord(id: $id, input: $input) {\n    id\n    status\n    diagnosis\n  }\n}\n",[31,703,704,739,762,767,772,777,782],{"__ignoreMap":29},[34,705,706,709,712,714,717,719,721,724,726,729,731,734,736],{"class":36,"line":37},[34,707,708],{"class":47},"mutation",[34,710,711],{"class":65}," UpdateClinicalRecord",[34,713,231],{"class":61},[34,715,716],{"class":200},"$id",[34,718,587],{"class":61},[34,720,590],{"class":54},[34,722,723],{"class":47},"!",[34,725,106],{"class":61},[34,727,728],{"class":200},"$input",[34,730,587],{"class":61},[34,732,733],{"class":54},"UpdateClinicalRecordInput",[34,735,723],{"class":47},[34,737,738],{"class":61},") {\n",[34,740,741,744,746,748,750,752,754,756,758,760],{"class":36,"line":44},[34,742,743],{"class":200},"  updateClinicalRecord",[34,745,231],{"class":61},[34,747,317],{"class":200},[34,749,587],{"class":61},[34,751,716],{"class":200},[34,753,106],{"class":61},[34,755,664],{"class":200},[34,757,587],{"class":61},[34,759,728],{"class":200},[34,761,738],{"class":61},[34,763,764],{"class":36,"line":72},[34,765,766],{"class":200},"    id\n",[34,768,769],{"class":36,"line":90},[34,770,771],{"class":200},"    status\n",[34,773,774],{"class":36,"line":120},[34,775,776],{"class":200},"    diagnosis\n",[34,778,779],{"class":36,"line":143},[34,780,781],{"class":61},"  }\n",[34,783,784],{"class":36,"line":149},[34,785,565],{"class":61},[14,787,788],{},"Running GraphQL Code Generator outputs exact TypeScript types:",[24,790,792],{"className":26,"code":791,"language":28,"meta":29,"style":29},"\u002F\u002F Generated automatically from schema.graphql\nexport type UpdateClinicalRecordMutationVariables = {\n  id: string;\n  input: {\n    status?: RecordStatus;\n    diagnosis?: string;\n  };\n};\n\nexport type UpdateClinicalRecordMutation = {\n  updateClinicalRecord?: {\n    id: string;\n    status: RecordStatus;\n    diagnosis: string;\n  } | null;\n};\n",[31,793,794,799,813,825,834,845,856,861,866,870,883,891,902,912,922,935],{"__ignoreMap":29},[34,795,796],{"class":36,"line":37},[34,797,798],{"class":40},"\u002F\u002F Generated automatically from schema.graphql\n",[34,800,801,803,806,809,811],{"class":36,"line":44},[34,802,48],{"class":47},[34,804,805],{"class":47}," type",[34,807,808],{"class":200}," UpdateClinicalRecordMutationVariables",[34,810,58],{"class":47},[34,812,210],{"class":61},[34,814,815,817,819,822],{"class":36,"line":72},[34,816,584],{"class":200},[34,818,529],{"class":47},[34,820,821],{"class":54}," string",[34,823,824],{"class":61},";\n",[34,826,827,830,832],{"class":36,"line":90},[34,828,829],{"class":200},"  input",[34,831,529],{"class":47},[34,833,210],{"class":61},[34,835,836,838,841,843],{"class":36,"line":120},[34,837,481],{"class":200},[34,839,840],{"class":47},"?:",[34,842,543],{"class":200},[34,844,824],{"class":61},[34,846,847,850,852,854],{"class":36,"line":143},[34,848,849],{"class":200},"    diagnosis",[34,851,840],{"class":47},[34,853,821],{"class":54},[34,855,824],{"class":61},[34,857,858],{"class":36,"line":149},[34,859,860],{"class":61},"  };\n",[34,862,863],{"class":36,"line":156},[34,864,865],{"class":61},"};\n",[34,867,868],{"class":36,"line":168},[34,869,153],{"emptyLinePlaceholder":152},[34,871,872,874,876,879,881],{"class":36,"line":177},[34,873,48],{"class":47},[34,875,805],{"class":47},[34,877,878],{"class":200}," UpdateClinicalRecordMutation",[34,880,58],{"class":47},[34,882,210],{"class":61},[34,884,885,887,889],{"class":36,"line":185},[34,886,743],{"class":200},[34,888,840],{"class":47},[34,890,210],{"class":61},[34,892,893,896,898,900],{"class":36,"line":191},[34,894,895],{"class":200},"    id",[34,897,529],{"class":47},[34,899,821],{"class":54},[34,901,824],{"class":61},[34,903,904,906,908,910],{"class":36,"line":213},[34,905,481],{"class":200},[34,907,529],{"class":47},[34,909,543],{"class":200},[34,911,824],{"class":61},[34,913,914,916,918,920],{"class":36,"line":219},[34,915,849],{"class":200},[34,917,529],{"class":47},[34,919,821],{"class":54},[34,921,824],{"class":61},[34,923,924,927,930,933],{"class":36,"line":240},[34,925,926],{"class":61},"  } ",[34,928,929],{"class":47},"|",[34,931,932],{"class":54}," null",[34,934,824],{"class":61},[34,936,937],{"class":36,"line":246},[34,938,865],{"class":61},[377,940,942],{"id":941},"_3-registering-the-tool-with-generated-types","3. Registering the Tool with Generated Types",[14,944,945],{},"Now the MCP tool handler directly references the generated types:",[24,947,949],{"className":26,"code":948,"language":28,"meta":29,"style":29},"import { McpServer } from \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js\";\nimport { executeGraphQL } from \".\u002Fgraphql-client.js\";\nimport {\n  UpdateClinicalRecordDocument,\n  UpdateClinicalRecordMutationVariables,\n} from \".\u002Fgenerated\u002Fgraphql.js\";\n\nconst server = new McpServer({\n  name: \"clinical-tools\",\n  version: \"1.0.0\",\n});\n\n\u002F\u002F The input contract directly binds to the database-generated GraphQL variables\nserver.tool(\n  \"update_clinical_record\",\n  \"Updates a patient record status and diagnosis\",\n  async (args: UpdateClinicalRecordMutationVariables) => {\n    const result = await executeGraphQL(UpdateClinicalRecordDocument, {\n      id: args.id,\n      input: args.input,\n    });\n\n    return {\n      content: [{ type: \"text\", text: JSON.stringify(result.data) }],\n    };\n  },\n);\n",[31,950,951,967,981,987,992,997,1009,1013,1031,1041,1051,1055,1059,1064,1072,1079,1086,1104,1123,1128,1134,1140,1145,1152,1175,1181,1186],{"__ignoreMap":29},[34,952,953,956,959,962,965],{"class":36,"line":37},[34,954,955],{"class":47},"import",[34,957,958],{"class":61}," { McpServer } ",[34,960,961],{"class":47},"from",[34,963,964],{"class":102}," \"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fmcp.js\"",[34,966,824],{"class":61},[34,968,969,971,974,976,979],{"class":36,"line":44},[34,970,955],{"class":47},[34,972,973],{"class":61}," { executeGraphQL } ",[34,975,961],{"class":47},[34,977,978],{"class":102}," \".\u002Fgraphql-client.js\"",[34,980,824],{"class":61},[34,982,983,985],{"class":36,"line":72},[34,984,955],{"class":47},[34,986,210],{"class":61},[34,988,989],{"class":36,"line":90},[34,990,991],{"class":61},"  UpdateClinicalRecordDocument,\n",[34,993,994],{"class":36,"line":120},[34,995,996],{"class":61},"  UpdateClinicalRecordMutationVariables,\n",[34,998,999,1002,1004,1007],{"class":36,"line":143},[34,1000,1001],{"class":61},"} ",[34,1003,961],{"class":47},[34,1005,1006],{"class":102}," \".\u002Fgenerated\u002Fgraphql.js\"",[34,1008,824],{"class":61},[34,1010,1011],{"class":36,"line":149},[34,1012,153],{"emptyLinePlaceholder":152},[34,1014,1015,1018,1021,1023,1026,1029],{"class":36,"line":156},[34,1016,1017],{"class":47},"const",[34,1019,1020],{"class":54}," server",[34,1022,58],{"class":47},[34,1024,1025],{"class":47}," new",[34,1027,1028],{"class":65}," McpServer",[34,1030,69],{"class":61},[34,1032,1033,1036,1039],{"class":36,"line":168},[34,1034,1035],{"class":61},"  name: ",[34,1037,1038],{"class":102},"\"clinical-tools\"",[34,1040,174],{"class":61},[34,1042,1043,1046,1049],{"class":36,"line":177},[34,1044,1045],{"class":61},"  version: ",[34,1047,1048],{"class":102},"\"1.0.0\"",[34,1050,174],{"class":61},[34,1052,1053],{"class":36,"line":185},[34,1054,146],{"class":61},[34,1056,1057],{"class":36,"line":191},[34,1058,153],{"emptyLinePlaceholder":152},[34,1060,1061],{"class":36,"line":213},[34,1062,1063],{"class":40},"\u002F\u002F The input contract directly binds to the database-generated GraphQL variables\n",[34,1065,1066,1068,1070],{"class":36,"line":219},[34,1067,159],{"class":61},[34,1069,162],{"class":65},[34,1071,165],{"class":61},[34,1073,1074,1077],{"class":36,"line":240},[34,1075,1076],{"class":102},"  \"update_clinical_record\"",[34,1078,174],{"class":61},[34,1080,1081,1084],{"class":36,"line":246},[34,1082,1083],{"class":102},"  \"Updates a patient record status and diagnosis\"",[34,1085,174],{"class":61},[34,1087,1088,1090,1092,1094,1096,1098,1100,1102],{"class":36,"line":252},[34,1089,194],{"class":47},[34,1091,197],{"class":61},[34,1093,201],{"class":200},[34,1095,529],{"class":47},[34,1097,808],{"class":200},[34,1099,204],{"class":61},[34,1101,207],{"class":47},[34,1103,210],{"class":61},[34,1105,1106,1109,1112,1114,1117,1120],{"class":36,"line":258},[34,1107,1108],{"class":47},"    const",[34,1110,1111],{"class":54}," result",[34,1113,58],{"class":47},[34,1115,1116],{"class":47}," await",[34,1118,1119],{"class":65}," executeGraphQL",[34,1121,1122],{"class":61},"(UpdateClinicalRecordDocument, {\n",[34,1124,1125],{"class":36,"line":264},[34,1126,1127],{"class":61},"      id: args.id,\n",[34,1129,1131],{"class":36,"line":1130},20,[34,1132,1133],{"class":61},"      input: args.input,\n",[34,1135,1137],{"class":36,"line":1136},21,[34,1138,1139],{"class":61},"    });\n",[34,1141,1143],{"class":36,"line":1142},22,[34,1144,153],{"emptyLinePlaceholder":152},[34,1146,1148,1150],{"class":36,"line":1147},23,[34,1149,222],{"class":47},[34,1151,210],{"class":61},[34,1153,1155,1158,1161,1164,1167,1169,1172],{"class":36,"line":1154},24,[34,1156,1157],{"class":61},"      content: [{ type: ",[34,1159,1160],{"class":102},"\"text\"",[34,1162,1163],{"class":61},", text: ",[34,1165,1166],{"class":54},"JSON",[34,1168,278],{"class":61},[34,1170,1171],{"class":65},"stringify",[34,1173,1174],{"class":61},"(result.data) }],\n",[34,1176,1178],{"class":36,"line":1177},25,[34,1179,1180],{"class":61},"    };\n",[34,1182,1184],{"class":36,"line":1183},26,[34,1185,261],{"class":61},[34,1187,1189],{"class":36,"line":1188},27,[34,1190,267],{"class":61},[289,1192],{},[292,1194,1196],{"id":1195},"what-happens-when-the-database-changes","What Happens When the Database Changes",[14,1198,1199],{},"Imagine an engineer alters the database schema:",[24,1201,1203],{"className":386,"code":1202,"language":388,"meta":29,"style":29},"ALTER TABLE clinical_records RENAME COLUMN diagnosis TO clinical_summary;\n",[31,1204,1205],{"__ignoreMap":29},[34,1206,1207,1210,1212,1215,1218],{"class":36,"line":37},[34,1208,1209],{"class":47},"ALTER",[34,1211,433],{"class":47},[34,1213,1214],{"class":61}," clinical_records RENAME COLUMN diagnosis ",[34,1216,1217],{"class":47},"TO",[34,1219,1220],{"class":61}," clinical_summary;\n",[14,1222,1223],{},"Here is what happens in the developer environment:",[300,1225,1226,1232,1238],{},[303,1227,1228,1229,1231],{},"GraphPG detects the catalog update and regenerates ",[31,1230,351],{}," within 90 milliseconds.",[303,1233,1234,1235,278],{},"The code generator runs and updates ",[31,1236,1237],{},"generated\u002Fgraphql.js",[303,1239,1240,1241,529],{},"The TypeScript compiler immediately flags ",[31,1242,1243],{},"UpdateClinicalRecordDocument",[24,1245,1248],{"className":1246,"code":1247,"language":335,"meta":29},[333],"src\u002Ftools\u002Fclinical.ts:24:9 - error TS2322: Type '{ diagnosis: string; }' is not assignable to type 'UpdateClinicalRecordInput'.\n  Property 'diagnosis' does not exist on type 'UpdateClinicalRecordInput'. Did you mean 'clinical_summary'?\n",[31,1249,1247],{"__ignoreMap":29},[14,1251,1252,1253,1255],{},"When Claude Code works on the repository, it does not encounter an ambiguous database runtime error. It sees a precise ",[31,1254,368],{}," error pointing directly to the affected line.",[14,1257,1258],{},"Instead of guessing argument formats or hallucinating field names, the agent reads the compiler error and updates the tool query cleanly in a single pass.",[289,1260],{},[292,1262,1264],{"id":1263},"key-takeaways","Key Takeaways",[1266,1267,1268,1271,1274],"ul",{},[303,1269,1270],{},"Stop maintaining parallel Zod schemas for AI agent tools when your database catalog already defines your data model.",[303,1272,1273],{},"Use GraphPG to compile PostgreSQL catalog structures into GraphQL SDL instantly upon schema changes.",[303,1275,1276],{},"Let GraphQL Code Generator produce strict TypeScript types so that database migrations turn agent hallucinations into deterministic compiler errors before execution.",[1278,1279,1280],"style",{},"html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":29,"searchDepth":44,"depth":44,"links":1282},[1283,1284,1285,1290,1291],{"id":294,"depth":44,"text":295},{"id":325,"depth":44,"text":326},{"id":374,"depth":44,"text":375,"children":1286},[1287,1288,1289],{"id":379,"depth":72,"text":380},{"id":694,"depth":72,"text":695},{"id":941,"depth":72,"text":942},{"id":1195,"depth":44,"text":1196},{"id":1263,"depth":44,"text":1264},"\u002Fimages\u002Fcovers\u002Fgraphpg-end-to-end-type-safety-claude-code.png","2026-11-10","Why manual Zod tool contracts drift from your database, and how GraphPG compiles PostgreSQL schemas into GraphQL SDL to generate end-to-end TypeScript types that stop AI tool hallucinations at compile time.","md",{"canonical_url":1297},"https:\u002F\u002Fmihai.ltd\u002Fblog\u002Fgraphpg-end-to-end-type-safety-claude-code\u002F","\u002Fblog\u002Fgraphpg-end-to-end-type-safety-claude-code",{"title":5,"description":1294},"blog\u002Fgraphpg-end-to-end-type-safety-claude-code",[1302,1303,534,28,1304,1305],"postgresql","graphpg","claude-code","agentic-ai","blog_post","N8KwUuqH3UraY9J038dFM-8wtYnpHDf7dkxVZIkb0NM",null,1794289299597]