<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Christoph Hermanns</title>
    <description>The latest articles on DEV Community by Christoph Hermanns (@cherware).</description>
    <link>https://dev.to/cherware</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4112480%2F18528ec6-565c-4147-93a1-9b0dc60bcf63.png</url>
      <title>DEV Community: Christoph Hermanns</title>
      <link>https://dev.to/cherware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cherware"/>
    <language>en</language>
    <item>
      <title>Making API Performance Tests More Realistic: From Endpoint Metrics to Role-Based Journeys</title>
      <dc:creator>Christoph Hermanns</dc:creator>
      <pubDate>Wed, 23 Sep 2026 20:05:59 +0000</pubDate>
      <link>https://dev.to/cherware/making-api-performance-tests-more-realistic-from-endpoint-metrics-to-role-based-journeys-348i</link>
      <guid>https://dev.to/cherware/making-api-performance-tests-more-realistic-from-endpoint-metrics-to-role-based-journeys-348i</guid>
      <description>&lt;p&gt;Daniel Balcarek's article &lt;a href="https://dev.to/gramli/api-performance-testing-how-to-design-realistic-tests-59gn"&gt;API Performance Testing: How to Design Realistic Tests&lt;/a&gt; makes a straightforward point: load should reflect how people use the system, and the target load should come from an explicit requirement or telemetry. It made me ask a more useful question about my existing suite: &lt;strong&gt;What does its load actually represent?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My suite already measured percentiles, throughput, error rates, warm-up phases, and stepped load. It could detect technical regressions and compare individual endpoints under repeatable conditions. What it could not yet explain was which real usage pattern those requests represented.&lt;/p&gt;

&lt;p&gt;I extended it with roles, sessions, think time, read and write journeys, and explicit cleanup rules. Before I obtained performance numbers worth discussing, the new journeys exposed authentication, API-contract, cleanup, and rate-limit problems. That is the central result: a journey must be valid before its latency can mean anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the existing suite already did well
&lt;/h2&gt;

&lt;p&gt;The starting point was useful and technically sound. It already measured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;p50, p95, and p99 latency instead of relying on averages alone,&lt;/li&gt;
&lt;li&gt;throughput and error rates,&lt;/li&gt;
&lt;li&gt;warm-up phases,&lt;/li&gt;
&lt;li&gt;stepped load,&lt;/li&gt;
&lt;li&gt;and repeatable reports for comparing changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;P50 is the value below which 50 percent of measured response times fall. P95 and p99 provide the corresponding boundaries for 95 and 99 percent. A warm-up phase lets the system handle a small amount of traffic before the measured phase starts.&lt;/p&gt;

&lt;p&gt;These tests answered a clearly scoped question: &lt;strong&gt;How do defined endpoints behave under controlled, repeatable load?&lt;/strong&gt; They helped me detect regressions, compare changes, and apply pressure to individual endpoints.&lt;/p&gt;

&lt;p&gt;They did not answer how people actually use the system. A person rarely calls the same endpoint continuously for a minute. They sign in, load a list, open a detail view, read, perhaps comment, and move between contexts. Different roles see different data and can perform different actions.&lt;/p&gt;

&lt;p&gt;That difference between endpoint traffic and user behavior became the starting point for the next version of the suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I decided to add
&lt;/h2&gt;

&lt;p&gt;My inventory resulted in five extension areas:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Already covered&lt;/th&gt;
&lt;th&gt;Extension&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Percentiles, throughput, and error rate&lt;/td&gt;
&lt;td&gt;Role-based user journeys&lt;/td&gt;
&lt;td&gt;Implemented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm-up and load stages&lt;/td&gt;
&lt;td&gt;Think time and staggered session starts&lt;/td&gt;
&lt;td&gt;Ramp-up only; no warm-up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeatable endpoint load&lt;/td&gt;
&lt;td&gt;A defined mix of read and write paths&lt;/td&gt;
&lt;td&gt;Implemented separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical thresholds&lt;/td&gt;
&lt;td&gt;Targets derived from business requirements or production telemetry&lt;/td&gt;
&lt;td&gt;Open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result reports&lt;/td&gt;
&lt;td&gt;Continuous correlation with system and resource signals&lt;/td&gt;
&lt;td&gt;Open; snapshots only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think time means deliberately waiting between actions instead of letting a virtual user send requests as quickly as possible.&lt;/p&gt;

&lt;p&gt;The implemented slice is deliberately narrow. It adds a reviewable journey model, but it has no telemetry-derived workload target and no continuous resource correlation across every relevant system layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The role-based journeys
&lt;/h2&gt;

&lt;p&gt;I created three read-oriented journeys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A regular user verifies the session, loads an issue list, and opens an issue.&lt;/li&gt;
&lt;li&gt;An agent additionally reads comments and activity.&lt;/li&gt;
&lt;li&gt;An administrator additionally requests administrative overviews and health information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final low-load comparison used a &lt;code&gt;5:3:1&lt;/code&gt; mix of regular-user, agent, and administrator sessions. This was the reduced configuration after a sixth regular-user login received &lt;code&gt;429&lt;/code&gt;. Each session signed in separately, verified the role returned by the server, waited between actions, and started during a ramp-up period rather than at the same instant.&lt;/p&gt;

&lt;p&gt;The runner extends an existing PowerShell performance suite; it was not selected as a claim that PowerShell measures better than k6 or another load-testing tool. Each virtual session owns one .NET &lt;code&gt;HttpClient&lt;/code&gt; and cookie handler, reuses that client during its journey, and records request duration after the full response body is read. Percentiles use the nearest-rank calculation. This keeps the browser-style cookie flow close to the existing automation, but it also means client and network time are part of the measured latency.&lt;/p&gt;

&lt;p&gt;The mix describes sessions, not people. The runner takes one credential pair per role, so the five regular-user sessions reuse one regular-user account, the three agent sessions reuse one agent account, and the administrator has one account. That is a useful low-load session comparison, but not a simulation of nine independent identities.&lt;/p&gt;

&lt;p&gt;I also separated read and write tests. The write journey creates an issue, updates it, adds a comment, verifies the result, and removes the generated data again. Because it changes state, it needs stronger safety controls than the read-only journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functional preflight before performance measurement
&lt;/h2&gt;

&lt;p&gt;I used a staged process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prove that a journey is functionally and technically valid.&lt;/li&gt;
&lt;li&gt;Check that it remains stable when sessions run in parallel.&lt;/li&gt;
&lt;li&gt;Only then interpret latency, throughput, and error rate.&lt;/li&gt;
&lt;li&gt;Use system and resource metrics when investigating causes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This distinction matters. Preparing a representative performance journey can reveal functional and technical contract problems, but that does not turn every &lt;code&gt;401&lt;/code&gt; or &lt;code&gt;422&lt;/code&gt; into a performance finding.&lt;/p&gt;

&lt;h3&gt;
  
  
  A successful login was not yet a usable session
&lt;/h3&gt;

&lt;p&gt;The login request succeeded, but the following request for the current session returned &lt;code&gt;401 Unauthorized&lt;/code&gt; for every role. The credentials were correct. The test program did not yet reproduce the complete request context of a stateful browser session.&lt;/p&gt;

&lt;p&gt;The application uses Laravel 13 and Sanctum for cookie-based authentication. Its &lt;a href="https://laravel.com/framework/docs/13.x/sanctum" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; requires stateful SPA requests to use the same top-level domain and to send &lt;code&gt;Accept: application/json&lt;/code&gt; plus &lt;code&gt;Referer&lt;/code&gt; or &lt;code&gt;Origin&lt;/code&gt;; the stateful-domain configuration decides which requests may use session cookies. The runner already sent &lt;code&gt;Accept: application/json&lt;/code&gt; but was missing suitable &lt;code&gt;Origin&lt;/code&gt; and &lt;code&gt;Referer&lt;/code&gt; values. Once I added them, role verification succeeded.&lt;/p&gt;

&lt;p&gt;A successful login status therefore did not prove that the resulting session was usable. If a journey represents a browser session, authentication and request context are part of its contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safe writes and cleanup on failure paths
&lt;/h3&gt;

&lt;p&gt;The write journey revealed more contract details. A token read before login produced &lt;code&gt;419&lt;/code&gt; on the first later mutation. The runner now reads the XSRF cookie again before every non-GET request. The login handler regenerates the session ID, but I did not establish whether that rotation or another cookie change caused the earlier token to fail; I treat the re-read as an observed compatibility requirement, not an explanation of Sanctum internals.&lt;/p&gt;

&lt;p&gt;The next attempt reached application validation and returned &lt;code&gt;422 Unprocessable Entity&lt;/code&gt;: the creation payload did not yet fully match the current API contract. The update operation also required more fields than I had initially assumed.&lt;/p&gt;

&lt;p&gt;Again, these were not performance bottlenecks. But a simplified journey that bypasses real authentication or API contracts measures a path that the application does not actually execute.&lt;/p&gt;

&lt;p&gt;The most useful discovery happened on a failure path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The test created an object successfully.&lt;/li&gt;
&lt;li&gt;A later update failed.&lt;/li&gt;
&lt;li&gt;The normal control flow never reached cleanup.&lt;/li&gt;
&lt;li&gt;The generated test artifact remained in the system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That made cleanup an explicitly tested property of the journey. Two conditions are equally important:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The test must remove its own data even after a failed run.&lt;/li&gt;
&lt;li&gt;It must never delete data that it cannot prove belongs to that run.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The safe control flow needs to retain two independent outcomes: the journey failure and a possible cleanup failure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;primary_error = none
cleanup_error = none

try:
    artifact = create(payload_with_unique_run_marker)
    remember_in_run_manifest(artifact.id)
    update_and_verify(artifact)
catch error:
    primary_error = error
finally:
    try:
        if artifact exists and
           manifest_contains(artifact.id) and
           artifact.project == dedicated_test_project and
           artifact.run_marker == current_run_marker:
            delete_and_purge(artifact)
    catch error:
        cleanup_error = error

if primary_error exists:
    raise primary_error, with cleanup_error attached
if cleanup_error exists:
    raise cleanup_error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the actual runner, writes are restricted to a dedicated test project. Every generated object receives a unique run marker. A manifest stores only the identifiers and states created by that run. Immediately before deletion, the runner verifies the identifier, project, and marker again. Remote writes also require a separate acknowledgement.&lt;/p&gt;

&lt;p&gt;The current runner records the original journey error and cleans up the owned artifact when that cleanup succeeds. It also provides a manifest-bound &lt;code&gt;Cleanup&lt;/code&gt; action for recovery. A hard process abort, a lost create response, or a cleanup failure still requires an explicit recovery sweep; the runner does not yet report both a primary and cleanup error as separate terminal results. A regression test explicitly covers this sequence:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create succeeds → update fails → soft delete → permanent removal&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A green run that silently leaves artifacts behind is not truly green. A failed run must not respond by deleting data it cannot prove it owns.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;429&lt;/code&gt; was a finding about identity, not just a blocker
&lt;/h3&gt;

&lt;p&gt;During the first &lt;code&gt;6:3:1&lt;/code&gt; parallel comparison attempt, the sixth regular-user login returned &lt;code&gt;429 Too Many Requests&lt;/code&gt;. Journeys that were already authenticated continued without errors.&lt;/p&gt;

&lt;p&gt;The first mix exceeded a configured login limiter. The final &lt;code&gt;5:3:1&lt;/code&gt; configuration made this comparison executable, but it is a calibration choice rather than evidence for a user-volume or identity model.&lt;/p&gt;

&lt;p&gt;I excluded that run from the comparison and reduced the number of sessions for the affected role. That was a reasonable calibration step, but it was not the main lesson and it is not the final model for a larger test.&lt;/p&gt;

&lt;p&gt;The more important conclusion is this: &lt;strong&gt;virtual sessions and independent user identities are not interchangeable.&lt;/strong&gt; Several sessions using one account may primarily test repeated logins for one principal, including the protections attached to that identity. That is not automatically representative of several people signing in.&lt;/p&gt;

&lt;p&gt;For future tests, the number and reuse of identities must therefore be part of the workload definition. Login traffic and already authenticated activity may also need separate scenarios. Reducing sessions made this comparison executable; it did not make the underlying modeling question disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  What workload model did I actually test?
&lt;/h2&gt;

&lt;p&gt;The runner uses a closed workload model. Each virtual session waits for a response and its think time before starting the next action. If the system responds more slowly, the session completes fewer iterations and the offered request rate can fall.&lt;/p&gt;

&lt;p&gt;This is appropriate for comparing complete user journeys. It does not maintain a fixed arrival rate when the system slows down, so it cannot answer a saturation question. The &lt;a href="https://grafana.com/docs/k6/latest/using-k6/scenarios/concepts/open-vs-closed/" rel="noopener noreferrer"&gt;k6 documentation on open and closed workload models&lt;/a&gt; makes this throughput dependency explicit.&lt;/p&gt;

&lt;p&gt;An open model starts new iterations according to an arrival schedule independent of how long previous iterations take. That is the appropriate next experiment for questions such as: What happens when the system must sustain a specified arrival rate as latency increases? When a test claims to model such a schedule but only sends the next request after the previous response, its percentile view can also suffer from &lt;em&gt;coordinated omission&lt;/em&gt;; Gil Tene's &lt;a href="https://github.com/giltene/wrk2/blob/master/README.md" rel="noopener noreferrer"&gt;wrk2 notes&lt;/a&gt; explain that distinction. This closed-loop comparison makes no fixed-arrival claim and does not quantify that effect.&lt;/p&gt;

&lt;p&gt;Neither model is universally “more realistic.” They answer different questions. For this initial journey comparison I used the closed model; I did not use it to infer saturation behavior or production capacity.&lt;/p&gt;

&lt;h2&gt;
  
  
  A low-load engineering comparison—not a capacity result
&lt;/h2&gt;

&lt;p&gt;After functional preflight and parallel validation, I ran three identically configured read-only measurements.&lt;/p&gt;

&lt;p&gt;Each virtual session executed its journey for 60 seconds with one second of think time between actions. Sessions started gradually during a 15-second ramp-up. Because every session had its own 60-second window, the complete runner took about 78 seconds from the first start to final completion.&lt;/p&gt;

&lt;p&gt;The one-second think time is fixed and identical for every action. That is a deterministic pacing control, not a claim about natural human timing. Together with the fixed journeys and duration, it explains why each retained run produced 513 calls. The runner also has no separate journey warm-up phase; ramp-up staggers session starts but does not replace warm-up.&lt;/p&gt;

&lt;p&gt;The reported throughput divides measured API calls by that complete runtime, including ramp-up and final completion. It is therefore a whole-run comparison value, not a steady-state offered load. CSRF initialization and login requests were not counted as performance calls, while the following role verification was counted.&lt;/p&gt;

&lt;p&gt;The following table is an aggregate across all three &lt;strong&gt;read-only roles and paths&lt;/strong&gt;. The separate write journey is not included.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Measured calls&lt;/th&gt;
&lt;th&gt;Requests/s&lt;/th&gt;
&lt;th&gt;P50&lt;/th&gt;
&lt;th&gt;P95&lt;/th&gt;
&lt;th&gt;P99&lt;/th&gt;
&lt;th&gt;Errors&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;513&lt;/td&gt;
&lt;td&gt;6.55&lt;/td&gt;
&lt;td&gt;83.4 ms&lt;/td&gt;
&lt;td&gt;137.7 ms&lt;/td&gt;
&lt;td&gt;200.6 ms&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;513&lt;/td&gt;
&lt;td&gt;6.57&lt;/td&gt;
&lt;td&gt;82.6 ms&lt;/td&gt;
&lt;td&gt;136.8 ms&lt;/td&gt;
&lt;td&gt;182.1 ms&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;513&lt;/td&gt;
&lt;td&gt;6.55&lt;/td&gt;
&lt;td&gt;83.5 ms&lt;/td&gt;
&lt;td&gt;137.5 ms&lt;/td&gt;
&lt;td&gt;206.2 ms&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across the three runs, the median run-level p95 was 137.5 milliseconds. The run-level p95 values ranged from 136.8 to 137.7 milliseconds, and median throughput was 6.55 requests per second. None of the runs produced HTTP or worker errors.&lt;/p&gt;

&lt;p&gt;The p99 figures are included for continuity with the existing suite, not for a stable tail estimate: with 513 observations, the 99th percentile is determined by only a handful of values. The visible spread from 182.1 to 206.2 milliseconds is descriptive. The retained runs contained only successful HTTP events. In a failed run, the runner records the non-success response before ending that worker, so the status mix and percentiles must be read together rather than treated as a comparable baseline. The internal reports retain a status mix and p95 for every role and path; I have left those low-volume diagnostic values out of this article.&lt;/p&gt;

&lt;p&gt;I used 500 milliseconds p95 as a provisional technical comparison threshold. It already existed in the previous suite for authenticated reads, but it was not derived from production telemetry, a user study, or an approved capacity requirement. Staying below it means only that these runs met this technical starting value under the described configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducible for whom?
&lt;/h2&gt;

&lt;p&gt;The three runs were repeatable inside the defined test setup: same runner, three role accounts reused across the &lt;code&gt;5:3:1&lt;/code&gt; sessions, fixed timing, target environment, dataset, and journeys. That is internal repeatability, not independent reproducibility for a reader.&lt;/p&gt;

&lt;p&gt;I have named the runner approach, role mix, timing, request accounting, and journey structure so that the experiment can be assessed. I have not published the source code, fixtures, exact dataset volume, infrastructure topology, or the generator's network position. These omissions make the article an implementation report to adapt, not an independently reproducible benchmark; the cleanup pseudocode is transferable, while the latency values are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What snapshots can and cannot show
&lt;/h2&gt;

&lt;p&gt;I captured load-generator CPU load and free memory, plus application-health signals, before and after the comparison. At those two moments, the generator did not show unusual CPU or memory utilization. The report did not continuously capture worker-pool saturation, database connection-pool saturation, or provider and network telemetry, so it cannot rule out short-lived peaks during the runs.&lt;/p&gt;

&lt;p&gt;Snapshots provide context, but they cannot establish causality. Good response times do not prove that the overall system is healthy. Likewise, a health signal observed at the same time would not prove that it caused a latency value. Before/after values show temporal coincidence and change, not cause and effect.&lt;/p&gt;

&lt;p&gt;Without continuous resource data across the relevant layers, I can detect a regression more readily than I can explain its cause. Database connections, queues, worker capacity, and other system signals remain part of the next stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;Seven lessons stand out for me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before extending a suite, state clearly which question it already answers well.&lt;/li&gt;
&lt;li&gt;A journey must be functionally valid before its performance metrics are meaningful.&lt;/li&gt;
&lt;li&gt;Roles, sessions, permissions, think time, and read/write paths shape the workload model.&lt;/li&gt;
&lt;li&gt;Virtual sessions, login attempts, and independent identities are different dimensions.&lt;/li&gt;
&lt;li&gt;Write tests need explicit ownership, cleanup, and a manifest-bound recovery path.&lt;/li&gt;
&lt;li&gt;Fixed think time makes a useful calibration control, not a faithful model of human timing.&lt;/li&gt;
&lt;li&gt;Closed and open workload models answer different questions; a short closed-loop comparison is not a capacity result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The original suite was not wrong. Its metrics, warm-up phases, and load stages were the foundation for this work. The important change was adding a clearer, testable description of the behavior behind the requests.&lt;/p&gt;

&lt;p&gt;For me, a more representative performance test is not a particular tool or the largest possible script. It is a reviewable model of who does what, under which conditions, for a defined slice of usage. That model can include roles, sessions, permissions, think time, read and write paths, failure paths, rate limits, ownership of test data, cleanup rules, measurement boundaries, and missing telemetry.&lt;/p&gt;

&lt;p&gt;Including those elements does not make a test perfectly realistic. It makes it more representative of the usage slice it claims to model—and makes its blind spots easier to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI assistance disclosure
&lt;/h2&gt;

&lt;p&gt;This article is based on my own test implementation, measurements, and conclusions. I used AI assistance for editorial restructuring and the English adaptation, reviewed the technical claims against the underlying reports and scripts, and remain responsible for the final text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Daniel Balcarek (2026): &lt;a href="https://dev.to/gramli/api-performance-testing-how-to-design-realistic-tests-59gn"&gt;API Performance Testing: How to Design Realistic Tests&lt;/a&gt;, accessed September 21, 2026.&lt;/li&gt;
&lt;li&gt;Laravel (2026): &lt;a href="https://laravel.com/framework/docs/13.x/sanctum" rel="noopener noreferrer"&gt;Laravel Sanctum&lt;/a&gt;, accessed September 23, 2026.&lt;/li&gt;
&lt;li&gt;Grafana Labs (2026): &lt;a href="https://grafana.com/docs/k6/latest/using-k6/scenarios/concepts/open-vs-closed/" rel="noopener noreferrer"&gt;Open and closed models&lt;/a&gt;, accessed September 21, 2026.&lt;/li&gt;
&lt;li&gt;Gil Tene: &lt;a href="https://github.com/giltene/wrk2/blob/master/README.md" rel="noopener noreferrer"&gt;wrk2 README&lt;/a&gt;, accessed September 23, 2026.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>performance</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Can We Govern AI as Fast as We Build It?</title>
      <dc:creator>Christoph Hermanns</dc:creator>
      <pubDate>Tue, 15 Sep 2026 04:27:57 +0000</pubDate>
      <link>https://dev.to/cherware/can-we-govern-ai-as-fast-as-we-build-it-2ojh</link>
      <guid>https://dev.to/cherware/can-we-govern-ai-as-fast-as-we-build-it-2ojh</guid>
      <description>&lt;p&gt;What if the biggest problem with AI is not just how capable it may become, but how quickly those capabilities are advancing?&lt;/p&gt;

&lt;p&gt;In his essay &lt;a href="https://darioamodei.com/post/we-must-pace-the-frontier" rel="noopener noreferrer"&gt;&lt;em&gt;We Must Pace the Frontier&lt;/em&gt;&lt;/a&gt;, Anthropic CEO Dario Amodei argues that frontier AI development should be deliberately paced. Not stopped, but slowed enough for safety research, independent evaluation, and public decision-making to keep up.&lt;/p&gt;

&lt;p&gt;It is a remarkable proposal, especially from someone whose company is itself competing at the frontier. But it raises difficult questions.&lt;/p&gt;

&lt;p&gt;Who decides what pace is still responsible? Who determines when a model is safe enough? How independent can evaluators really be if they depend on deep access to private companies? And can voluntary restraint work when commercial and geopolitical competition rewards those who move fastest?&lt;/p&gt;

&lt;p&gt;These questions are not limited to AI labs and governments. They also concern those of us who use and build with AI.&lt;/p&gt;

&lt;p&gt;Are we still using AI as a tool, or are we beginning to delegate decisions we can no longer properly verify?&lt;/p&gt;

&lt;p&gt;Which skills and judgements do we deliberately want to retain?&lt;/p&gt;

&lt;p&gt;And would we accept the same use of AI if we were not the ones controlling it—if an employer, public authority, insurance company, or platform used it to evaluate us?&lt;/p&gt;

&lt;p&gt;Perhaps the central question is therefore not simply whether technological progress should be slowed down. It is whether our institutions, organisations, and individual judgement can develop quickly enough to govern it responsibly.&lt;/p&gt;

&lt;p&gt;And if they cannot, what should we accelerate: the technology, our ability to govern it—or our willingness to set limits?&lt;/p&gt;

&lt;p&gt;I do not think there is an easy answer. But waiting until the answer becomes obvious may mean waiting until meaningful choices have already disappeared.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image created with ChatGPT using AI image generation.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>aigovernance</category>
      <category>ethics</category>
    </item>
    <item>
      <title>Implementing a Secure MCP Server</title>
      <dc:creator>Christoph Hermanns</dc:creator>
      <pubDate>Sun, 13 Sep 2026 23:01:00 +0000</pubDate>
      <link>https://dev.to/cherware/implementing-a-secure-mcp-server-27a0</link>
      <guid>https://dev.to/cherware/implementing-a-secure-mcp-server-27a0</guid>
      <description>&lt;p&gt;An MCP server often starts with a seemingly simple requirement: an AI host should find and summarise tickets about a topic. Once current domain data enters a model context, the endpoint is no longer the central question. What matters is which data becomes visible, under whose permissions it is searched, and which actions the server is allowed to expose at all.&lt;/p&gt;

&lt;p&gt;This question arose while implementing MCP access for &lt;a href="https://dev.to/cherware/how-a-ticket-system-became-my-agentic-ai-lab-hen"&gt;Lutions&lt;/a&gt;, a private development project centred on a web application for project and ticket work. There was no security incident behind the decision that followed. Before the first production tool existed, the architectural position was clear: the MCP server must not become a second open product API or an autonomous agent layer. The first article in this series explains the path between host, model, MCP server, and domain system (Hermanns, 2026e). This retrospective shows how that position was put into practice in access design, domain permissions, and operations.&lt;/p&gt;

&lt;p&gt;For orientation, the host is the AI application that passes a user question and permitted MCP capabilities to a model. The MCP server then supplies bounded context or performs a permitted domain operation; it does not answer the user’s question itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cut the access surface before adding capabilities
&lt;/h2&gt;

&lt;p&gt;MCP separates resources, prompts, and tools. Tools are functions a model may request through a host; resources and prompts serve different roles in constructing context. The current specification treats these server capabilities separately and identifies tools as a security-relevant, model-controlled interface (Model Context Protocol, 2026a; Model Context Protocol, 2026b).&lt;/p&gt;

&lt;p&gt;In Lutions, this led to a simple but consequential choice: the production core starts with six read-only tools. They search visible tickets, load individual or multiple tickets, and return bounded comments, links, or a compact context bundle. The server describes itself accordingly as read-only, and the status view lists the same six tools as read-only.&lt;/p&gt;

&lt;p&gt;This boundary reduces several uncertainties at once. A read tool can still touch sensitive information, so it needs the same visibility and project boundaries as the application itself. But it does not change a ticket’s workflow, ownership, priority, or external effect. The first security question therefore remains manageable: may this token see this information—and may this exact information enter the host context?&lt;/p&gt;

&lt;p&gt;The narrow tool surface also helps the model. It is not a security control in itself; tool descriptions are not authorisation decisions. But it reduces misuse and makes it possible to align tool lists more closely with the actual permission context (Model Context Protocol, 2026b).&lt;/p&gt;

&lt;h3&gt;
  
  
  One tool in practice: search, not free-form access
&lt;/h3&gt;

&lt;p&gt;The gain is not only in the number of tools, but in the contract of each one. For example, &lt;code&gt;search_zockets&lt;/code&gt; accepts no universal command. It accepts bounded search text, a result limit from 1 to 50, and optional domain filters. This is a simplified, readable version of the server-side schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search_zockets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"readOnly"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string, maximum 200 characters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer, 1 to 50, default 10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"optional"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"optional"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"optional"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"projectKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"optional"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema does not replace authorisation. It does, however, prevent search from becoming a hidden “perform arbitrary access” function. Server-side validation and the user context still determine which results can be returned at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a request becomes
&lt;/h3&gt;

&lt;p&gt;A user might ask for open risks and decisions on a topic. The host first uses &lt;code&gt;search_zockets&lt;/code&gt; to find visible candidates. Only then does it use &lt;code&gt;get_zocket_context_bundle&lt;/code&gt; to load a bounded selection of tickets, optionally visible comments, and links. The model’s answer is created only from that limited material.&lt;/p&gt;

&lt;p&gt;In Lutions, research follows a clear sequence: search first, retrieve targeted context second, answer last. This does not prevent a model from misunderstanding content. It does prevent a request from beginning with unbounded access to all tickets or a blanket full-text export. That is the first practical benefit of a read core: current ticket information is available for analysis without abandoning the research boundary or domain permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions remain in the domain system
&lt;/h2&gt;

&lt;p&gt;An MCP connection does not create its own trust zone. It must not reveal tickets or information to an AI host that the associated user could not see in Lutions directly.&lt;/p&gt;

&lt;p&gt;The Lutions endpoint therefore uses the existing API-token authentication. For read access, &lt;code&gt;issues:read&lt;/code&gt; is the domain basis. &lt;code&gt;mcp:connect&lt;/code&gt; additionally marks MCP access and can be enforced after the documented migration path. Project-bound tokens remain limited to their permitted projects over MCP as well, and the endpoint has its own rate limit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6eyndg8f3yyiitqwdvuz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6eyndg8f3yyiitqwdvuz.png" alt="Selection of a dedicated Lutions API token with MCP and read scopes" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: Test configuration for a read-only MCP connection with &lt;code&gt;mcp:connect&lt;/code&gt; and &lt;code&gt;issues:read&lt;/code&gt;. Token secrets are not displayed in either the UI or this article.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The MCP server does not invent its own permission model. It translates a request into a domain operation and must respect the same boundaries that apply outside MCP. This follows a core MCP security principle: data access and actions require effective access controls, and the protocol does not enforce that architecture by itself (Model Context Protocol, 2026a).&lt;/p&gt;

&lt;p&gt;In retrospect, this is one of the most durable decisions. MCP is an additional integration surface, not a bypass around the UI, API, and policies. Without that boundary, scopes easily become labels and tool descriptions become false security promises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit should explain, not duplicate
&lt;/h2&gt;

&lt;p&gt;Tool calls quickly create a temptation to log everything. For a ticket system, that would be the wrong reflex. Complete arguments, ticket descriptions, comment text, and result payloads would turn audit data into a second, difficult-to-control copy of domain content.&lt;/p&gt;

&lt;p&gt;Lutions therefore records a tool call as &lt;code&gt;mcp.tool_called&lt;/code&gt;. The audit holds technical metadata: tool name, method, status, duration, actor and token context, project context, result count, and, where applicable, an error code. Full arguments and domain payloads are explicitly excluded. The MCP status view uses those same metadata for operational views and aggregations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqyuzz864mo6h9l71x6p3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqyuzz864mo6h9l71x6p3.png" alt="Data-minimising audit view for an MCP tool call" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: Audit view of an MCP tool call with method, tool name, status, and duration. Identifiers and IP information are masked for publication.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This form of observability does not answer every security question. It does show whether and how a tool was used without replicating the content of a ticket analysis. In my assessment, it is a useful balance: enough evidence for troubleshooting, rate-limit signals, and abuse detection; too little content for the audit trail to become a shadow case file.&lt;/p&gt;

&lt;p&gt;The point generalises beyond Lutions. With MCP, it is not enough to decide what a server may do. It also matters which traces a tool call leaves and who can later read them. Security-relevant observability starts with a data-minimisation decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Lutions currently remains read-only
&lt;/h2&gt;

&lt;p&gt;The active Lutions MCP contract is read-only. Write tools are not part of the production tool inventory. Earlier considerations and pilot contracts for write actions deliberately remain concept and follow-up work, not an implicit approval in ongoing operations.&lt;/p&gt;

&lt;p&gt;A read tool supplies context; a write tool creates an effect. A comment, link, or status change is therefore not simply the next type of tool. The concrete approval boundary remains the responsibility of the domain system and host; MCP does not prescribe a particular interaction model (Model Context Protocol, 2026b).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;MCP access status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read-only&lt;/td&gt;
&lt;td&gt;Search, read ticket, read comments&lt;/td&gt;
&lt;td&gt;Active, with read scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write domain actions&lt;/td&gt;
&lt;td&gt;Internal comment, link, status change&lt;/td&gt;
&lt;td&gt;No active MCP tool; conceivable only with a separate, server-side secured contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-risk actions&lt;/td&gt;
&lt;td&gt;Administrative, visibility-changing, or destructive actions&lt;/td&gt;
&lt;td&gt;Blocked for the MCP/agent process, or subject to a separate high-risk process&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For possible future write cases, the implementation design uses a server-side sequence of preview, human approval, and execute: short validity, unchanged payload, idempotency, and audit correlation. It is an architectural boundary for later decisions, not a hidden approval for current operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the current state can—and cannot—show
&lt;/h2&gt;

&lt;p&gt;The existing core makes current, visible ticket information available through a bounded tool chain. It can provide search results, ticket details, limited comments, and links for analysis; the status view and audit make technical tool calls traceable.&lt;/p&gt;

&lt;p&gt;It does not prove that an AI host decomposes every request correctly, selects the best tool sequence, or reliably evaluates ticket data. Nor does a functioning read integration imply that write agents are ready for production. The general runtime boundaries for preview, approval, and execute are still absent for more consequential action classes.&lt;/p&gt;

&lt;p&gt;This is not a flaw alongside the architecture. It defines the next evaluation step: which hosts actually work well with bounded results? What amount of data helps a ticket analysis without overloading context? And when does an additional capability raise value more than it raises cost, attack surface, and explanatory burden?&lt;/p&gt;

&lt;h3&gt;
  
  
  A concrete model observation
&lt;/h3&gt;

&lt;p&gt;The screenshots document a deliberately small but complete test case. The question names a known ticket reference: the model should summarise its content briefly. The ticket is written in German; the response should be summarized in English. This tests neither open-ended research nor a complex agent task. It tests whether a local host can retrieve a concrete domain fact through MCP in a controlled way and express it clearly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test profile.&lt;/strong&gt; Host: LM Studio, which can integrate MCP servers as a host (LM Studio, 2026). Model identifier: &lt;code&gt;google/gemma-4-e4b&lt;/code&gt; from the Gemma 4 family (Gemma Team, 2026). Task: Summarise a German-language ticket in English. Permitted capabilities: only the six read-only tools of the Lutions MCP server. Expected observation: for a known reference, the model selects the single-item retrieval tool and keeps its answer grounded in the ticket. The reference, prompt, and model identifier are recorded; this is not yet a publicly reproducible run with host version, timestamp, and standardised question set.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpoa3jycm3vztig5oc8eb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpoa3jycm3vztig5oc8eb.png" alt="A local Gemma 4 model selects the read-only get_zocket tool for a known ticket reference" width="800" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: Gemma 4 receives the six read-only tools of the Lutions MCP server and selects &lt;code&gt;get_zocket&lt;/code&gt; with the &lt;code&gt;issueRef&lt;/code&gt; parameter for a known ticket reference.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first screenshot shows the visible flow of the local &lt;code&gt;google/gemma-4-e4b&lt;/code&gt; model in LM Studio. The host exposes the six read-only Lutions MCP tools. For the known reference, the model chooses &lt;code&gt;get_zocket&lt;/code&gt;, not search, batch retrieval, comments, links, or the context bundle. The visible call contains only the required ticket reference as &lt;code&gt;issueRef&lt;/code&gt;. The returned ticket content follows in the MCP context and answer view. The technical observation is narrow but real: the model recognises the fitting, bounded domain operation and invokes it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1z84wor9xfbz6cs308y8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1z84wor9xfbz6cs308y8.png" alt="English summary of a Lutions ticket written in German, retrieved via ‘get_zocket’ from a local Gemma-4 model" width="800" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4: The model response condenses the retrieved ticket and captures the essential statements of the test ticket.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second screenshot shows the resulting answer. It correctly identifies the test ticket as transferring an LLM-wiki idea to Lutions. It captures its core point—a maintained, verifiable knowledge layer rather than an isolated chatbot—and names its three central aspects: knowledge layer rather than chatbot, source code as primary truth, and a process of knowledge-impact check, ingest, test, and knowledge status. The comparison with the ticket confirms that interpretation. For this case, the chain “question → appropriate tool → ticket context → factual answer” is complete.&lt;/p&gt;

&lt;p&gt;The observation remains deliberately narrow. It shows that the local model path can retrieve a known reference correctly through a read-only tool and produce a useful English answer, even though the ticket is in German. It says nothing about how reliably Gemma 4 finds unknown tickets, researches across multiple steps, compares sources across results, or handles ambiguous questions. That is where a further evaluation of search planning, context selection, and answer quality begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The current Lutions state shows that local models can use MCP access meaningfully when the server does not become a second, uncontrolled entry point into the domain system. This retrospective has shown how that framework emerges from small tools, domain permissions, and data-minimising audit.&lt;/p&gt;

&lt;p&gt;The LM Studio case demonstrates single-item retrieval: Gemma 4 selects &lt;code&gt;get_zocket&lt;/code&gt;, receives bounded ticket context, and produces a suitable answer. Separately, internal retrieval tests show the multi-ticket path: known references can be loaded through &lt;code&gt;get_zocket_context_bundle&lt;/code&gt; and summarised with source binding. Neither is a quality judgement about every model or research task. Together, they are practical evidence that the controlled read path is useful outside a single cloud host.&lt;/p&gt;

&lt;p&gt;That is the appeal of this kind of development environment. An MCP integration can be designed and then tested in practice with real domain permissions, bounded context, and observable audit events. The connection can first be observed at a coarse level—and then refined, retested, and developed with intent.&lt;/p&gt;

&lt;p&gt;The remaining question is no longer whether the basic connection works, but how well it works in use: how reliably do different models support research and analysis? Which context selections and prompt forms hold up, and where do their limits remain? Once sufficiently robust observations exist, that practical perspective can support an article of its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and scope
&lt;/h2&gt;

&lt;p&gt;This is an English adaptation of the German original, &lt;a href="https://www.cherware.de/implementierung-eines-mcp-servers/" rel="noopener noreferrer"&gt;&lt;em&gt;MCP-Server sicher entwerfen: Was sich bei Lutions bewährt hat&lt;/em&gt;&lt;/a&gt;, published on my blog. It is a project-specific technical retrospective, not a general production-security certification. I prepared the English adaptation and the accompanying graphics with the assistance of AI tools and reviewed both before publication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary external sources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026a).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28" rel="noopener noreferrer"&gt;&lt;em&gt;Specification, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026b).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/server/tools" rel="noopener noreferrer"&gt;&lt;em&gt;Tools, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio (2026).&lt;/strong&gt; &lt;a href="https://lmstudio.ai/docs/app/mcp" rel="noopener noreferrer"&gt;&lt;em&gt;Use MCP Servers.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemma Team (2026).&lt;/strong&gt; &lt;a href="https://huggingface.co/google/gemma-4-E4B" rel="noopener noreferrer"&gt;&lt;em&gt;google/gemma-4-E4B: Model Card.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related article
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hermanns, Christoph (2026e).&lt;/strong&gt; &lt;a href="https://dev.to/cherware/understanding-mcp-servers-how-ai-hosts-reliably-connect-to-domain-systems-g49"&gt;&lt;em&gt;Understanding MCP Servers: How AI Hosts Reliably Connect to Domain Systems.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>architecture</category>
      <category>gemma</category>
    </item>
    <item>
      <title>Understanding MCP Servers: How AI Hosts Reliably Connect to Domain Systems</title>
      <dc:creator>Christoph Hermanns</dc:creator>
      <pubDate>Thu, 10 Sep 2026 20:59:09 +0000</pubDate>
      <link>https://dev.to/cherware/understanding-mcp-servers-how-ai-hosts-reliably-connect-to-domain-systems-g49</link>
      <guid>https://dev.to/cherware/understanding-mcp-servers-how-ai-hosts-reliably-connect-to-domain-systems-g49</guid>
      <description>&lt;p&gt;How can an LLM gain controlled access to current data and permitted actions? Anyone who wants to integrate AI into their own system—or make information held there available to an LLM—quickly encounters that question. MCP provides an open standard for a shared approach across hosts (Model Context Protocol, 2026a).&lt;/p&gt;

&lt;p&gt;That is why I have examined the Model Context Protocol more closely in &lt;a href="https://dev.to/cherware/how-a-ticket-system-became-my-agentic-ai-lab-hen"&gt;my own project&lt;/a&gt;. Work on an MCP server is already well advanced; this article documents the foundations that need to be understood first. My starting point was not how to implement a server as quickly as possible, but what MCP actually is, how it works, and whether it fits a domain system.&lt;/p&gt;

&lt;p&gt;The practical problem is straightforward. A model does not automatically know current tickets, permissions, domain terminology, or internal tools. Without controlled access, it is left with guesses, copied context, or another one-off integration. Before MCP, every AI application needed its own bridge: a chat client to a ticket system, an IDE to a file system, an agent script to a REST API. Each could work locally. Together, they formed a difficult-to-maintain tangle of duplicate tool descriptions, different authentication paths, and security decisions that could barely be reused. When MCP was introduced in November 2024, Anthropic described precisely this problem of fragmented point-to-point integrations between AI assistants and data sources (Anthropic, 2024).&lt;/p&gt;

&lt;p&gt;MCP addresses that point. The &lt;code&gt;2026-07-28&lt;/code&gt; specification defines an open protocol for connecting LLM applications to external data sources and tools: through a host-client-server architecture, JSON-RPC 2.0, a stateless protocol core, and components such as resources, prompts, and tools (JSON-RPC Working Group, 2013; Model Context Protocol, 2026a; Model Context Protocol, 2026b).&lt;/p&gt;

&lt;p&gt;This first article documents the foundations I needed to clarify for my project. It answers the questions that arise at the beginning: What is MCP? Which problem does it solve? And how does a user question reach the data of a domain system? Part 2 applies these foundations to my project's MCP server and examines how a responsible interface can be designed and secured from them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why MCP?
&lt;/h2&gt;

&lt;p&gt;MCP becomes tangible when we start with a practical need. An AI application should not merely talk about a domain system; where appropriate, it should work with the system's current data and permitted functions. It therefore needs to know which information it may request, which actions are allowed, and under what limits they take place. An AI host—the application that coordinates the model, context, and MCP connections—organises that path. A shared language is needed so that the path does not have to be rebuilt for every AI application.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 The concrete problem
&lt;/h3&gt;

&lt;p&gt;The example in this article is not fictional. I use the ticket system being developed as part of my project. I built an MCP server for it, which provides the practical frame for this article series. The server can connect an AI host—such as a chat client, IDE, or agent tool—to the ticket system, whose data and actions should be available only within the applicable permissions. A user might ask the AI host: “What risks, open decisions, and next steps emerge from all tickets related to customer onboarding?” The expected answer should be grounded in current, authorised data from the ticket system.&lt;/p&gt;

&lt;p&gt;Without a common standard, every host needs a separate route: perhaps a plugin for the chat client, a direct REST integration for the IDE, and a dedicated API client for an agent script. To users, the result may initially look similar: all three can search tickets. To the team behind them, however, it means three sets of tool descriptions, three input-validation paths, and often three different ways to handle authentication, permissions, and logging. If the ticket model or a security rule later changes, every integration must determine what changed. Each variant may work in isolation; collectively they create a hard-to-maintain network in which domain objects are described repeatedly, security decisions are difficult to compare, and little can be reused between hosts.&lt;/p&gt;

&lt;p&gt;This is where MCP starts. The server describes its capabilities once in a shared form that compatible hosts can read. A host can first discover what the server offers and then decide, in a controlled way, what to use for a particular request. In the ticket example, three kinds of offer are easy to distinguish: &lt;strong&gt;resources&lt;/strong&gt; are readable context objects, such as an individual ticket; &lt;strong&gt;prompts&lt;/strong&gt; are reusable work templates, for example for a risk analysis; and &lt;strong&gt;tools&lt;/strong&gt; perform clearly named domain operations, such as &lt;code&gt;search_tickets&lt;/code&gt;, &lt;code&gt;get_ticket&lt;/code&gt;, or &lt;code&gt;list_ticket_comments&lt;/code&gt; (Model Context Protocol, 2026b; Model Context Protocol, 2026c; Model Context Protocol, 2026d; Model Context Protocol, 2026e).&lt;/p&gt;

&lt;p&gt;This shared description does not replace the three special integrations by magic. It does avoid making every host reinvent the same domain capability. The control boundary remains essential: the host determines which offers it exposes to the model, which results enter the model context, and which calls it permits. The visibility of a call and the point at which consent is needed are decisions of the particular host implementation. Section 3 examines the three concepts more closely.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 The article's thesis
&lt;/h3&gt;

&lt;p&gt;In the ticket example, MCP does not replace the existing ticket API. Instead, it exposes selected capabilities of the system in a form that an AI host can understand and use in a controlled manner. An MCP server is therefore not an additional product API in the usual sense; it is an AI-oriented integration surface.&lt;/p&gt;

&lt;p&gt;Its purpose is not to expose as many functions as possible. It should shape context, prompt templates, and tools so that a host can use them in a comprehensible and bounded way. The exact design of approvals, logging, and audit remains the responsibility of the specific host and server implementations.&lt;/p&gt;

&lt;p&gt;This leads to a simple architectural rule: design an MCP server from the domain system outward, not from the language model inward. For a ticket system, that means first clarifying which tickets a user may see, which actions have an effect, which inputs are valid, and which results may enter the model context at all. A useful selection of tools and resources emerges only from those boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 What MCP is not
&lt;/h3&gt;

&lt;p&gt;The distinction may sound dry, but it matters: MCP adds a shared integration contract to existing layers. It does not replace them.&lt;/p&gt;

&lt;p&gt;MCP is &lt;strong&gt;not a product API&lt;/strong&gt;. REST or GraphQL often remain a system's primary interfaces. MCP offers an AI host only selected capabilities in a suitable form—“search tickets,” for example, rather than the ticket system's complete public API.&lt;/p&gt;

&lt;p&gt;MCP is &lt;strong&gt;not an agent framework&lt;/strong&gt; and &lt;strong&gt;not a workflow engine&lt;/strong&gt;. It does not plan goals, choose multi-step strategies, or execute business processes as its own workflow machine. Whether several tools are combined, follow-up questions are asked, or work steps are planned is decided by the host, the model, or an agent system above it.&lt;/p&gt;

&lt;p&gt;MCP is &lt;strong&gt;not a database&lt;/strong&gt;. Resources can make data visible as context, but they do not replace storage, search, transaction logic, the data model, or the target system's permission system. Tickets in the example remain tickets of the domain system, even if the host can read individual ones.&lt;/p&gt;

&lt;p&gt;MCP is &lt;strong&gt;not a complete plugin system&lt;/strong&gt;. Installation, distribution, user interface, permission dialogues, and product integration sit above the protocol. A product may use MCP for an integration and build a plugin experience around it. OpenAI, for example, distinguishes its own connectors from arbitrary remote MCP servers; that is an OpenAI-specific product category, not part of the MCP specification (OpenAI, 2026a).&lt;/p&gt;

&lt;p&gt;And MCP is &lt;strong&gt;not AI&lt;/strong&gt;. An MCP server does not need to contain a language model. It provides context and capabilities; interpreting the user's question and generating an answer happen in the host's LLM context.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; MCP standardises the exchange between host and server, not the security of a domain system. A responsible host implementation must design approvals, data sharing, and the admission of content into context. A production server must itself implement authentication, domain permissions, data minimisation, and audit. The protocol provides boundaries and recommendations, but not a ready-made security architecture (Model Context Protocol, 2026a; Model Context Protocol, 2026e; Model Context Protocol, 2026f).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. The architecture map
&lt;/h2&gt;

&lt;p&gt;The following diagram is the most useful point of orientation for the rest of the article. It deliberately shows the higher level: the host contains the LLM and MCP client, and it remains the control boundary between the user request, model, and domain system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ab7jmriqxrl65vr8pu7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9ab7jmriqxrl65vr8pu7.png" alt="Simplified information flow between an AI host, MCP server, and domain system" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: Simplified MCP architecture. Inside the host, the LLM proposes an action; the MCP client transmits only the call permitted by the host. Results first return to the host and, from there, to its model context.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 The roles
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;host&lt;/strong&gt; is the AI application: a chat client, IDE, or agent tool, for example. It coordinates the user request, model context, tool selection, approvals, and answer output. The specification describes the host as a container and coordinator that manages client instances, enforces security and consent requirements, and aggregates context (Model Context Protocol, 2026b).&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;MCP client&lt;/strong&gt; lives inside the host. It mediates protocol messages between the host and exactly one MCP server. A host can have several clients: one for tickets, one for documentation, and one for Git, for example.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;MCP server&lt;/strong&gt; provides specialised capabilities. It makes resources, prompts, and tools available and translates calls into domain operations against the target system. A production server must validate incoming requests within its own authentication, authorisation, and domain boundaries. The &lt;strong&gt;target system&lt;/strong&gt; is the actual application: a ticket system, documentation platform, database, monitoring system, or internal operational system.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;LLM&lt;/strong&gt; formulates answers and can propose tool calls. It is not itself the MCP server and does not access the target system directly. That separation is the core of the architecture.&lt;/p&gt;

&lt;p&gt;The Language Server Protocol (LSP) is a helpful analogy. LSP was an explicit inspiration for MCP—not as a technical predecessor or direct extension, but because of its integration principle. LSP did not make all editors and programming languages the same. It created a shared integration contract: a defined language for the requests an editor can make and the answers a language server can provide. Editors and language servers no longer need bespoke knowledge of every counterpart. MCP applies that idea to context and tools for AI applications (Model Context Protocol, 2026a; Microsoft, 2026).&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 The flow of an MCP request
&lt;/h3&gt;

&lt;p&gt;Let us walk through Figure 1 using the ticket example. A product manager asks the AI host: “What risks, open decisions, and next steps emerge from all tickets related to customer onboarding?” For that person, it is an ordinary question. Technically, however, it moves through a controlled chain—not a direct line from the model to the ticket system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: the host builds context.&lt;/strong&gt; It assembles the information the model should work from: the user's question, persistent application rules such as security and formatting requirements, and the selected MCP capabilities. It may optionally retrieve a prompt template provided by the server and add it to the model context. No ticket has been read at this point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: the LLM proposes what is needed next.&lt;/strong&gt; If the existing context is enough, it can answer directly or formulate a follow-up question. If it needs current or more detailed information, it can propose a suitable tool call—for example, &lt;code&gt;search_tickets&lt;/code&gt; with &lt;code&gt;{ "topic": "customer onboarding", "limit": 20 }&lt;/code&gt;. If a returned ticket refers to two other tickets, the LLM can then propose &lt;code&gt;get_ticket&lt;/code&gt; for them after the first result. Such a proposal is still not an executed domain operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third: the host permits the call—or does not.&lt;/strong&gt; Its concrete policy determines which tools are visible, which arguments are accepted, and whether the user must approve the action. If the call is permitted, the MCP client sends it to the MCP server as a JSON-RPC message. The target system behind it may still use REST, database access, or another internal interface (Model Context Protocol, 2026b; Model Context Protocol, 2026h).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fourth: the server performs the domain operation within its own boundaries.&lt;/strong&gt; A production ticket server must itself enforce authentication, domain permissions, project boundaries, and limits; MCP does not remove that responsibility. It calls the target system and prepares a bounded, structured result for the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fifth: the host decides whether to return the result to the model context.&lt;/strong&gt; Only then can the LLM evaluate the permitted ticket data, propose another call where necessary, or formulate the answer.&lt;/p&gt;

&lt;p&gt;The central information flow is therefore clear: MCP is not a direct tunnel from the model into the ticket system. The host controls which model proposals become MCP calls; the server enforces the domain and security boundaries toward the target system. Both sides must implement their responsibilities concretely.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 An important versioning nuance
&lt;/h3&gt;

&lt;p&gt;This article refers to the MCP specification &lt;code&gt;2026-07-28&lt;/code&gt;. That revision describes MCP as stateless: there is no protocol state bound to a connection, and each request carries its own protocol information. Older clients and servers may still use the previous session-based protocol. This does not change the architecture in Figure 1; the details for production remote servers belong in Part 2 (Model Context Protocol, 2026b; Model Context Protocol, 2026g; Model Context Protocol, 2026h).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What flows through an MCP server
&lt;/h2&gt;

&lt;p&gt;An MCP server does not simply deliver “data to AI.” It provides several clearly separate things to the host.&lt;/p&gt;

&lt;p&gt;For this introductory article, five guiding questions are enough. How does the host discover what a server can do? Which data objects can it read as context? Which work instructions can it adopt as prompt templates? Which functions can it have executed as tools? And which transport do client and server use to communicate?&lt;/p&gt;

&lt;p&gt;In MCP, these building blocks are called discovery, resources, prompts, tools, and transport. At first, the terms may sound similar. They mark different points in the information flow shown in Figure 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Discovery and capability negotiation
&lt;/h3&gt;

&lt;p&gt;Before a host can use a capability meaningfully, it needs to know what a server offers. In the current specification, that capability negotiation is not a connection-bound opening dialogue; it happens per request. Clients send their protocol version, client information, and capabilities as metadata with each request. They can also query supported protocol versions, capabilities, and server information in advance with &lt;code&gt;server/discover&lt;/code&gt; (Model Context Protocol, 2026b).&lt;/p&gt;

&lt;p&gt;In the ticket example, the server can therefore make visible that it supports context objects, prompt templates, and executable tools. The specific set of tools may depend on the authorisation context: a product manager might see &lt;code&gt;search_tickets&lt;/code&gt; and &lt;code&gt;list_ticket_comments&lt;/code&gt;, but not an administrative export tool. The specification explicitly permits the returned tool or resource list to depend on the request's credentials, provided it does not depend covertly on the state of a connection (Model Context Protocol, 2026c; Model Context Protocol, 2026e).&lt;/p&gt;

&lt;p&gt;Discovery does not replace authorisation. It merely prevents false expectations. The server must still check every later call on the server side.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Resources: context objects
&lt;/h3&gt;

&lt;p&gt;Resources are context objects. The specification describes them as a standardised way to provide data to clients, including files, database schemas, or application-specific information. Each resource is identified by a URI. Clients can list and read resources and, where the server supports it, receive notifications when resources change (Model Context Protocol, 2026c).&lt;/p&gt;

&lt;p&gt;In the ticket example, an individual ticket might appear as &lt;code&gt;ticket://ONB-123&lt;/code&gt;. That resource is not automatically a fact in the model's awareness. The host decides whether to offer it to the user for selection, load it as the result of a search, or insert it into the model conversation as context. The separation matters: a resource makes context discoverable and referenceable, but it does not force the host to pour everything into the model without filtering.&lt;/p&gt;

&lt;p&gt;Good resources are narrow and understandable. A ticket object containing a title, status, assignee, short description, and link is usually more helpful than an undifferentiated database dump. The better a resource is scoped, the easier it is for the host to support data minimisation, source display, and user control.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Prompts: work instructions, not execution
&lt;/h3&gt;

&lt;p&gt;Prompts are reusable, parameterised work instructions. In substance, such a template can be an ordinary prompt a person could also write. The specification describes them as prompt templates that servers provide to clients; hosts may, for example, offer them as selectable actions in their interface (Model Context Protocol, 2026d).&lt;/p&gt;

&lt;p&gt;In the ticket example, the MCP server could offer a prompt named &lt;code&gt;analyze_onboarding_tickets&lt;/code&gt;: “Analyse tickets on &lt;code&gt;{topic}&lt;/code&gt;. Group the findings into risks, open decisions, and next steps. Name sources for every claim.” As an MCP prompt, this instruction is more than copyable text: it has a stable name, describes expected parameters such as &lt;code&gt;topic&lt;/code&gt; or &lt;code&gt;output_style&lt;/code&gt;, and can be discovered by the host. A chat client, IDE, and agent tool can then use the same domain-maintained template instead of each carrying its own variant.&lt;/p&gt;

&lt;p&gt;When a host uses the template, it retrieves it from the server, fills in its parameters, and can add the resulting messages to the model context. The template may be static or vary with its parameters and the authorisation context. It does not, however, start an analysis: at that stage, the MCP server has not read tickets, drawn conclusions, or formulated an answer.&lt;/p&gt;

&lt;p&gt;That is more than an implementation detail. A prompt parameter configures a work instruction; arguments to a tool call such as &lt;code&gt;search_tickets&lt;/code&gt;, by contrast, determine a server-side domain operation. Keeping the two separate prevents prompts and tools from being confused.&lt;/p&gt;

&lt;p&gt;A prompt template also does not technically assign tools. The host exposes the tools permitted by its policy; based on the instruction, the LLM decides which ones to propose. The host then checks every concrete tool proposal.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Tools: executable domain capabilities
&lt;/h3&gt;

&lt;p&gt;Tools are executable functions. They can query databases, call APIs, perform calculations, or prepare domain actions. The specification describes tools as model-controlled: a model can discover available tools and call them based on context. At the same time, it emphasises that applications should make tool calls visible and allow people to reject or confirm relevant operations (Model Context Protocol, 2026e).&lt;/p&gt;

&lt;p&gt;For the host, tools initially are a list of described capabilities. Their names, purposes, and expected parameters tell both the host and LLM what a tool can do. The host exposes only the tools that its policy permits. The LLM compares the user's question, the existing context, and those descriptions, then proposes a suitable tool with concrete arguments. For a ticket analysis, it might first choose &lt;code&gt;search_tickets&lt;/code&gt;, then retrieve details with &lt;code&gt;get_ticket&lt;/code&gt;, and propose &lt;code&gt;list_ticket_comments&lt;/code&gt; if required. The selection is therefore a model decision based on clear tool descriptions; execution nevertheless remains with the host and server.&lt;/p&gt;

&lt;p&gt;In the ticket example, &lt;code&gt;search_tickets&lt;/code&gt;, &lt;code&gt;get_ticket&lt;/code&gt;, and &lt;code&gt;list_ticket_comments&lt;/code&gt; are good initial tools. They are named in domain terms, read-only, bounded, and easy to explain. A generic &lt;code&gt;query_database&lt;/code&gt; would be much riskier: it moves too much semantics into a free-form query string and makes the permission model, input validation, and audit more difficult.&lt;/p&gt;

&lt;p&gt;For an initial production setup, the ticket server should therefore begin with narrowly described read tools. This is not a mandatory MCP sequence; it is a derived security decision. The MCP specification stresses visibility and human rejection or confirmation for tool calls. For plugin and API integration contexts, OpenAI additionally sets expectations for clear tool definitions, correct annotations, data-minimising inputs where possible, and server-side authorisation for every request (Model Context Protocol, 2026e; OpenAI, 2026b; OpenAI, 2026c).&lt;/p&gt;

&lt;p&gt;In practical terms, a later &lt;code&gt;add_internal_comment&lt;/code&gt; tool can be justified if it has a distinct write scope, project binding, maximum length, internal visibility, structured errors, correct read-only/destructive annotations, and audit without full text. A &lt;code&gt;change_ticket_status&lt;/code&gt; tool is riskier because it changes a workflow. At a minimum, it needs a concrete preview, explicit approval, retry safety or recognisable non-idempotency, and audit correlation (OpenAI, 2026b; OpenAI, 2026c).&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 Transport: how host and server exchange messages
&lt;/h3&gt;

&lt;p&gt;Resources, prompts, and tools describe &lt;strong&gt;what&lt;/strong&gt; an MCP server offers. Transport answers the other, simpler question: &lt;strong&gt;how&lt;/strong&gt; do protocol messages move between the host and server? It changes neither the roles in Figure 1 nor the domain responsibility; it is only the technical path of transmission.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;2026-07-28&lt;/code&gt; specification names two standard paths. With &lt;code&gt;stdio&lt;/code&gt;, the host starts the MCP server as a local process and exchanges messages through its standard input and output. This suits local developer tools, such as those for files, Git, or builds. With Streamable HTTP, the MCP server runs as a reachable service and host and server communicate over a web connection. That better suits SaaS services, enterprise applications, and other remote systems (Model Context Protocol, 2026h).&lt;/p&gt;

&lt;p&gt;For a basic understanding, that distinction is enough: &lt;code&gt;stdio&lt;/code&gt; is typically local and process-adjacent; Streamable HTTP is typically remote and network-based. The exact transport technology and the operational requirements of a production remote server—such as HTTPS, authentication, rate limits, and monitoring—belong in Part 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Brief conclusion and outlook
&lt;/h2&gt;

&lt;p&gt;So far, the focus has been on understanding the architecture: who talks to whom, when data enters the model context, and why MCP is not a direct tunnel from the model to the target system. The central insight is this: the value of an MCP server does not arise from tools alone, but from the controlled path between the AI host and the domain system.&lt;/p&gt;

&lt;p&gt;Part 2 will therefore ask more than how to program tools. It will show how an existing domain API can become a responsible MCP surface: which capability should become a tool, which information a resource, and how to prevent a helpful assistant from gaining uncontrolled access to far-reaching actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and scope
&lt;/h2&gt;

&lt;p&gt;This is an English adaptation of the German original, &lt;a href="https://www.cherware.de/mcp-server-verstehen/" rel="noopener noreferrer"&gt;&lt;em&gt;MCP-Server verstehen: Wie KI-Hosts kontrolliert mit Fachsystemen sprechen&lt;/em&gt;&lt;/a&gt;, published on my blog. It is an introductory technical article, not a complete design or security guide for production MCP servers. I prepared the English adaptation and the accompanying graphics with the assistance of AI tools and reviewed both before publication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary external sources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic (2024).&lt;/strong&gt; &lt;a href="https://www.anthropic.com/news/model-context-protocol" rel="noopener noreferrer"&gt;&lt;em&gt;Introducing the Model Context Protocol.&lt;/em&gt;&lt;/a&gt; Anthropic News, 25 November 2024.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026a).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28" rel="noopener noreferrer"&gt;&lt;em&gt;Specification, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026b).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/architecture" rel="noopener noreferrer"&gt;&lt;em&gt;Architecture, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026c).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/server/resources" rel="noopener noreferrer"&gt;&lt;em&gt;Resources, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026d).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/server/prompts" rel="noopener noreferrer"&gt;&lt;em&gt;Prompts, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026e).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/server/tools" rel="noopener noreferrer"&gt;&lt;em&gt;Tools, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026f).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices" rel="noopener noreferrer"&gt;&lt;em&gt;Security Best Practices, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026g).&lt;/strong&gt; &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" rel="noopener noreferrer"&gt;&lt;em&gt;The 2026-07-28 Specification.&lt;/em&gt;&lt;/a&gt; Model Context Protocol Blog, 28 July 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Context Protocol (2026h).&lt;/strong&gt; &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports" rel="noopener noreferrer"&gt;&lt;em&gt;Transports, Revision 2026-07-28.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI (2026a).&lt;/strong&gt; &lt;a href="https://developers.openai.com/api/docs/guides/tools-connectors-mcp" rel="noopener noreferrer"&gt;&lt;em&gt;MCP and Connectors.&lt;/em&gt;&lt;/a&gt; OpenAI API Documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI (2026b).&lt;/strong&gt; &lt;a href="https://developers.openai.com/plugins/build/mcp-server" rel="noopener noreferrer"&gt;&lt;em&gt;Build an MCP server – Plugins.&lt;/em&gt;&lt;/a&gt; OpenAI Developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI (2026c).&lt;/strong&gt; &lt;a href="https://developers.openai.com/plugins/app-guidelines" rel="noopener noreferrer"&gt;&lt;em&gt;Plugin guidelines.&lt;/em&gt;&lt;/a&gt; OpenAI Developers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Background and context
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JSON-RPC Working Group (2013).&lt;/strong&gt; &lt;a href="https://www.jsonrpc.org/specification" rel="noopener noreferrer"&gt;&lt;em&gt;JSON-RPC 2.0 Specification.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft (2026).&lt;/strong&gt; &lt;a href="https://microsoft.github.io/language-server-protocol/" rel="noopener noreferrer"&gt;&lt;em&gt;Language Server Protocol.&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
    </item>
    <item>
      <title>The Grilling Pattern: Clarify Requirements Before They Become Code</title>
      <dc:creator>Christoph Hermanns</dc:creator>
      <pubDate>Wed, 09 Sep 2026 18:03:17 +0000</pubDate>
      <link>https://dev.to/cherware/the-grilling-pattern-clarify-requirements-before-they-become-code-3e4e</link>
      <guid>https://dev.to/cherware/the-grilling-pattern-clarify-requirements-before-they-become-code-3e4e</guid>
      <description>&lt;p&gt;A requirement such as “improve the approval overview” may look clear at first glance. Yet important implementation questions remain: Who should see which entries? Which states matter? Is an existing area being extended, or is a new workflow being created? If these questions are answered only while implementing, a small request can turn into a longer cycle of assumptions, follow-up questions, and rework.&lt;/p&gt;

&lt;p&gt;In this article, the &lt;em&gt;Grilling Pattern&lt;/em&gt; means a bounded clarification step before an implementation plan becomes binding. “Pattern” does not mean a software design pattern here. It is a recurring way of working. It should not turn every request into an interview. Instead, it exposes only those unanswered questions whose different answers would materially change scope, architecture, verification—the planned evidence through tests, reviews, or other checks—or visible behaviour. The working hypothesis is that this targeted clarification can provide more orientation in agentic development than another generic process step, because it makes assumptions visible while they are still easy to correct.&lt;/p&gt;

&lt;p&gt;The practical context is Lutions, a web application for structured project and ticket work. Its ongoing development follows a documented, partly AI-agent-assisted process: people make product decisions, while specialised agents can analyse context, prepare and implement changes, and verify results. I describe the surrounding development environment in &lt;a href="https://dev.to/cherware/how-a-ticket-system-became-my-agentic-ai-lab-hen"&gt;&lt;em&gt;How a Ticket System Became My Agentic AI Lab&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The article first places the Grilling Pattern in the context of requirements clarification and related approaches in agentic development. It then explains the project-specific working structure through a practical case and describes how it fits into a development workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. When agents derive solutions from open requirements
&lt;/h2&gt;

&lt;p&gt;Even without agents, unclear or differently interpreted requirements often cause errors and rework only later. Eliciting, refining, documenting, and validating requirements is therefore a central concern of requirements engineering. ISO/IEC/IEEE 29148 addresses requirements processes and work products across the system and software life cycle. The IIBA standard includes Elicitation and Collaboration, Requirements Life Cycle Management, and Requirements Analysis and Design Definition as distinct knowledge areas (IEEE, 2018; IIBA, 2025).&lt;/p&gt;

&lt;p&gt;In agentic development, that ambiguity can have an effect more quickly. An implementing agent can derive concrete plans, changes, and verification from a short request in a very short time. Where a product decision is missing, the agent does not merely state an assumption—it can immediately translate it into a technically plausible but productively wrong solution. That speed is useful when the target state is correct. It does not replace a decision about what should be built and for whom.&lt;/p&gt;

&lt;p&gt;This is where the Grilling Pattern starts. It is neither complete requirements engineering nor an arbitrary questionnaire. It is a risk filter for the short phase before implementation: which uncertainties must be resolved for responsible planning to be possible, and which can remain open without material risk?&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What the Grilling Pattern is—and is not
&lt;/h2&gt;

&lt;p&gt;The name is deliberately informal. Before implementation is planned in a binding way, decision-relevant ambiguities are examined deliberately.&lt;/p&gt;

&lt;p&gt;The sources used here do not define one uniform operational procedure for clarifying individual software requirements. ISO/IEC/IEEE 29148 frames requirements engineering as processes and work products distributed across the life cycle. The IIBA standard covers, among other things, eliciting and confirming information, the life cycle of requirements, and their analysis and design. Matt Pocock’s grilling approaches, by contrast, describe a conversational way to clarify an initially vague idea; &lt;code&gt;grill-with-docs&lt;/code&gt; additionally brings an existing codebase into the conversation and records its context (IEEE, 2018; IIBA, 2025; Pocock, 2026).&lt;/p&gt;

&lt;p&gt;For the application context considered here, these sources suggest a set of shared principles: use existing information first, confirm understanding, make assumptions explicit, record decisions in a traceable way, and clarify only the uncertainty relevant to the next decision. This is an interpretation of the sources, not a quotation or a standard model.&lt;/p&gt;

&lt;p&gt;In my work on Lutions, I turn those principles into a compact working structure. The following five elements are a project-specific operationalisation. They combine the dialogic clarification in Pocock’s approaches with my development workflow. They were not adopted unchanged from Pocock’s work, nor derived directly from requirements-engineering standards; they are not a generally recognised standard.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Restate the problem in your own words, making misunderstandings about the starting point and goal visible.&lt;/li&gt;
&lt;li&gt;Before asking a question, inspect available project sources: the ticket, documentation, earlier decisions, and recognisable code and UI patterns.&lt;/li&gt;
&lt;li&gt;Record answered decisions separately from remaining assumptions, including whether a follow-up question was necessary.&lt;/li&gt;
&lt;li&gt;If a decisive question cannot be answered safely from the sources, ask at most one such question at a time and offer a reasoned default assumption.&lt;/li&gt;
&lt;li&gt;Stop grilling when no open question remains whose answer would materially change scope, architecture, verification, or visible behaviour.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Grilling must be distinguished from acceptance criteria. Acceptance criteria describe the verifiable conditions under which a work result is considered fulfilled. Grilling comes before them: it checks whether the problem, roles, target state, non-goals, and verification path are clear enough for meaningful acceptance criteria to be formulated.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A documented practical case: simple tables in a rich-text editor
&lt;/h2&gt;

&lt;p&gt;One request was to let users create tables in the rich-text editor used in Lutions. That initially sounds simple. But “tables” could mean a basic presentation of structured content—or a broad table editor with office-style features.&lt;/p&gt;

&lt;p&gt;This example comes from my work on Lutions and has been condensed for this article. It summarises the starting point, decisions, and verification; it is not a verbatim transcript, and the questions below are not historical quotations.&lt;/p&gt;

&lt;p&gt;The decisive clarification can be simplified as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should users be able to create simple tables with rows and columns, or should the editor also support complex table capabilities such as merged cells, freely adjustable widths, specialised cell formatting, or calculations?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The documented decision limited the first scope to simple, readable tables. Users should be able to insert a table and add or remove rows and columns. Merged cells, freely adjustable column widths, spreadsheet functions, and advanced cell formatting are explicitly out of scope.&lt;/p&gt;

&lt;p&gt;The resulting grilling record can be represented in condensed form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Grilling result&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Problem: Structured information is difficult to present in the rich-text editor.
&lt;span class="p"&gt;-&lt;/span&gt; Target state: Users can create simple tables and edit their rows and columns.
&lt;span class="p"&gt;-&lt;/span&gt; Important decision: Support intentionally remains limited to simple tables, not the feature breadth of a spreadsheet.
&lt;span class="p"&gt;-&lt;/span&gt; Follow-up decision: The functional boundary cannot be inferred from the request alone, so the first scope is limited to simple tables.
&lt;span class="p"&gt;-&lt;/span&gt; Non-goals: No merged cells, configurable widths, calculations, or advanced cell formatting.
&lt;span class="p"&gt;-&lt;/span&gt; Verification: Create, edit, save, and reopen a table; verify that content and structure persist.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verification revealed another distinction. In an initial manual test, a table could be created in the editor but was gone after saving. The cause was not a new product requirement. It was a technical gap between the editor and server-side handling of rich-text content. Only after the table structure was accepted and preserved there could the agreed target state be verified.&lt;/p&gt;

&lt;p&gt;The check also raised a further product question: should tables within tables be possible? It was not part of the original clarification, but it affected usability, complexity, and the permitted content structure. The decision was to disallow nested tables for the time being. That boundary was applied both in the UI and when processing saved content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzpoehiuxjnmeeccz3noe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzpoehiuxjnmeeccz3noe.png" alt="The result of the implementation: creating and editing tables in the editor" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The result of the implementation: creating and editing tables in the editor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The case demonstrates two aspects of the pattern. First, a vague request becomes traceable decisions, non-goals, and a verification path. Second, clarification does not replace verification: tests can reveal further technical or product questions that must then be decided and documented deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Putting it into practice in Lutions
&lt;/h2&gt;

&lt;p&gt;The preceding example shows what a grilling result looks like. This section explains when and how Lutions uses that clarification in its development process.&lt;/p&gt;

&lt;p&gt;In my Lutions workflow, a &lt;em&gt;skill&lt;/em&gt; is a versioned work instruction for an AI agent. It defines rules, steps, and an expected result format for a specific task. The Grilling Pattern belongs to the phase in which a developer agent prepares a requirement for implementation; it is not an independent agent role.&lt;/p&gt;

&lt;p&gt;Operationally, the sequence is simple: restate the problem, inspect available context, separate source-backed decisions from assumptions, decide whether one decisive follow-up is necessary, and record a compact grilling result. Do not finalise an implementation contract while a decision-relevant answer remains unsafe to infer. Stop when the uncertainty that remains can no longer change scope, architecture, verification, or user-facing behaviour.&lt;/p&gt;

&lt;p&gt;The contract mentioned in the example is an implementation contract. It is neither a legal document nor simply a prompt. It is a compact, versioned implementation plan covering scope, non-goals, affected areas, acceptance criteria, and verification. It is created only after decision-relevant questions have been clarified sufficiently.&lt;/p&gt;

&lt;p&gt;Here is how the grilling result from the table case translates into an implementation contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Implementation contract&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Target state: Users can create simple tables in the rich-text editor and edit rows and columns.
&lt;span class="p"&gt;-&lt;/span&gt; Scope: Insert a table and add or remove rows and columns.
&lt;span class="p"&gt;-&lt;/span&gt; Non-goals: No merged cells, configurable widths, calculations, or advanced cell formatting.
&lt;span class="p"&gt;-&lt;/span&gt; Affected areas: The rich-text editor and server-side processing of stored rich-text content.
&lt;span class="p"&gt;-&lt;/span&gt; Acceptance criteria: Created tables persist after saving and can be reopened and edited.
&lt;span class="p"&gt;-&lt;/span&gt; Verification: Create, edit, save, and reopen a table; content and structure persist.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When does grilling happen?
&lt;/h3&gt;

&lt;p&gt;In my workflow, work is classified by scope and risk into tiers. Tier 0 covers purely editorial micro-changes, Tier 1 small corrections, Tier 2 broader product changes, and Tier 3 work with elevated risk. A text correction or clearly bounded UI defect is therefore typically Tier 0 or Tier 1 and needs no grilling. A change to a product workflow involving several roles is typically Tier 2. Work with security, privacy, permission, or operational risk belongs to Tier 3. The pattern is used principally for Tier 2 and Tier 3 work: for example, when roles, user workflows, API or data-model boundaries, visible behaviour, or the target state are still unclear.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does it look like in practice?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;A new requirement is recorded and classified roughly by scope and risk.&lt;/li&gt;
&lt;li&gt;Where meaningful ambiguity exists, the developer agent first inspects the ticket, documentation, earlier decisions, and, where appropriate, existing code or UI patterns.&lt;/li&gt;
&lt;li&gt;The agent records the follow-up decision: either a decision can be derived from reliable sources, or one unresolved, decision-relevant question is posed to the requester with a reasoned default assumption.&lt;/li&gt;
&lt;li&gt;Answers or source evidence, assumptions, non-goals, and the verification path are recorded as the &lt;code&gt;Grilling result&lt;/code&gt;. No final implementation contract is created while an answer cannot safely be derived.&lt;/li&gt;
&lt;li&gt;Only then is the implementation contract written, followed by implementation and verification.&lt;/li&gt;
&lt;li&gt;Security, privacy, permission, and operational risks additionally require an independent review. “Audit” here does not mean external certification; it means a product and technical check performed separately from the implementing agent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rules and result formats are versioned in the development workflow. That keeps the workflow traceable, repeatable, and capable of evolving with the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Limits and risks
&lt;/h2&gt;

&lt;p&gt;The Grilling Pattern cannot replace missing product decisions. If a few decisive questions still leave unclear what should be built, pausing for documented clarification is more honest than speculative implementation. The pattern also depends on the quality of available sources. Asking without product documentation, prior decisions, or system context merely shifts research work to the requester.&lt;/p&gt;

&lt;p&gt;Scope expansion is another risk. The possible question areas are broad, but they should be used only where they genuinely change implementation. That is why non-goals belong in the result. Conversely, a documented result does not create automatic certainty. It can be wrong or incomplete and must be supplemented by testing, product accountability, and—where risk requires it—independent review.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. How to test whether it helps
&lt;/h2&gt;

&lt;p&gt;The pattern should not be considered successful merely because a skill exists or questions were documented. Its effect is a testable hypothesis. Useful questions include: Do decision-relevant assumptions become visible before implementation? Can acceptance criteria and test cases be derived more clearly? Are there fewer product follow-ups or scope corrections after implementation starts? Do documentation and verification effects remain traceable?&lt;/p&gt;

&lt;p&gt;For an initial comparison, similar requirements with and without a documented &lt;code&gt;Grilling result&lt;/code&gt; are enough. The number of questions is not what matters; their quality is. Was the question decisive? Was its answer already available in project sources? Did clarification concretely change implementation or verification? The evidence remains context-dependent. Teams with clear product ownership and well-maintained documentation may need less grilling than new, distributed, or heavily agentic teams. The pattern is therefore not a maturity model. It is a focused risk filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Grilling Pattern does not move an entire requirements-engineering discipline into an agent prompt. It brings a limited but important clarification to the point where an implementation plan is still malleable. The agent does not ask everything. It asks only what would recognisably change the implementation. It inspects existing sources first and records decisions and assumptions in a small artefact.&lt;/p&gt;

&lt;p&gt;I use Lutions to combine that dialogic idea with a versioned development workflow. Its concrete value must prove itself in real cases. The pattern can help clarify decision-relevant uncertainty early—or make visible which product decision is still missing before implementation—without turning every requirement into a heavyweight preliminary process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and scope
&lt;/h2&gt;

&lt;p&gt;This article is an English adaptation of the German original, &lt;a href="https://www.cherware.de/das-grilling-pattern/" rel="noopener noreferrer"&gt;&lt;em&gt;Das Grilling Pattern&lt;/em&gt;&lt;/a&gt;, published on my blog. It describes a project-specific practice from my work on Lutions. It is neither official product documentation nor a claim that the Grilling Pattern is a general standard or proven best practice. I prepared the English adaptation and the accompanying graphics with the assistance of AI tools and reviewed both before publication.&lt;/p&gt;

&lt;p&gt;The external references provide context for requirements engineering and adjacent approaches. The Lutions-specific examples are personal practice reports, not independently reproducible evidence.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IEEE (2018). &lt;a href="https://standards.ieee.org/standard/29148-2018.html" rel="noopener noreferrer"&gt;&lt;em&gt;ISO/IEC/IEEE 29148-2018: Systems and software engineering—Life cycle processes—Requirements engineering&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;IIBA (2025). &lt;a href="https://www.iiba.org/globalassets/business-analysis-resources/the-business-analysis-standard/files/the-business-analysis-standard.pdf" rel="noopener noreferrer"&gt;&lt;em&gt;The Business Analysis Standard&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Pocock, Matt (2026). &lt;a href="https://www.aihero.dev/skills-grill-me" rel="noopener noreferrer"&gt;&lt;em&gt;The /grill-me Skill&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Hermanns, Christoph (2026). &lt;a href="https://dev.to/cherware/how-a-ticket-system-became-my-agentic-ai-lab-hen"&gt;&lt;em&gt;How a Ticket System Became My Agentic AI Lab&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>How a Ticket System Became My Agentic AI Lab</title>
      <dc:creator>Christoph Hermanns</dc:creator>
      <pubDate>Mon, 07 Sep 2026 06:03:49 +0000</pubDate>
      <link>https://dev.to/cherware/how-a-ticket-system-became-my-agentic-ai-lab-hen</link>
      <guid>https://dev.to/cherware/how-a-ticket-system-became-my-agentic-ai-lab-hen</guid>
      <description>&lt;p&gt;What does an AI coding agent need in order to be useful in a real software project?&lt;/p&gt;

&lt;p&gt;Not a toy repository. Not a greenfield demo with a single happy path. A project with history, conventions, old decisions, security boundaries, tests, release procedures, and requirements that are never quite as complete as one would like.&lt;/p&gt;

&lt;p&gt;That question is why a project I call Lutions—a self-hosted web application for projects and ticket workflows—grew into more than just the ticket system I originally set out to build.&lt;/p&gt;

&lt;p&gt;This is an English adaptation of my original German article, &lt;a href="https://www.cherware.de/lutions-project-einstieg/" rel="noopener noreferrer"&gt;&lt;em&gt;Agentic AI: Wie ich zu meiner idealen Entwicklungsumgebung kam&lt;/em&gt;&lt;/a&gt;. It is not a product announcement or a claim that everyone should build their own ticketing system. It is a field note about the kind of working context in which agentic software development becomes concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project began with a much simpler problem
&lt;/h2&gt;

&lt;p&gt;Rising licence costs, increasing pressure towards cloud-only tools, and a growing sense that I was accepting conditions that did not fit my own idea of digital sovereignty were the initial triggers. Rather than only complain about them, I wanted to find out how difficult it would be to build a modern ticket system myself.&lt;/p&gt;

&lt;p&gt;I knew from the beginning that this would not be a weekend project. A ticket system still felt like the right-sized starting point: complex enough to expose both technical and organisational questions, but bounded enough to start with a defined minimum viable product and grow it step by step.&lt;/p&gt;

&lt;p&gt;The name &lt;em&gt;Lutions&lt;/em&gt; had no grand origin story. It was a pragmatic working title for a project that first had to prove itself.&lt;/p&gt;

&lt;p&gt;Over time, the centre of gravity shifted. Lutions is now a web application for projects and ticket workflows, with permissions, UI conventions, API integrations, release routines, audits, and a growing body of documentation. That may sound like ordinary administrative software. For this experiment, that ordinariness is exactly the point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23cgynpa2rk8llr0lo7t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23cgynpa2rk8llr0lo7t.png" alt="Diagram of the Lutions development landscape: localhost development moves through a production-like QAS test corridor to the Lutions production system of record." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Lutions landscape combines local-first development with production-like QA and a deliberate release path to the production system of record.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why an ordinary application is a useful testbed
&lt;/h2&gt;

&lt;p&gt;An agent working in a mature codebase has to do more than change a file successfully. It needs to understand why a pattern exists, which rules apply, what evidence is expected, and where a seemingly small modification may have consequences elsewhere.&lt;/p&gt;

&lt;p&gt;That is close to the challenge described by SWE-bench. The benchmark uses real GitHub issues and corresponding pull requests, and many tasks require changes across several files and functions rather than isolated code generation (Jimenez et al., 2023). SWE-agent makes a related point: the interface and feedback loop available to an agent affect whether it can navigate repositories, edit code, and run tests effectively (Yang et al., 2024).&lt;/p&gt;

&lt;p&gt;Lutions is not a benchmark, and it is not a scientific study. It is a personal development laboratory. But it raises the same practical question: what happens when an agent works in a system with conventions, history, risks, test data, and social coordination?&lt;/p&gt;

&lt;p&gt;The answer is not simply “better prompts”. A useful agent needs an environment that makes the relevant context and constraints available.&lt;/p&gt;

&lt;h2&gt;
  
  
  The application became part of the development process
&lt;/h2&gt;

&lt;p&gt;The interesting change was gradual. Occasional coding assistance turned into a question about how a development process must work when an AI agent contributes regularly to an established system.&lt;/p&gt;

&lt;p&gt;Three concerns moved to the foreground:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requirements:&lt;/strong&gt; How do we state expectations precisely enough that an agent works on the actual problem rather than merely producing plausible code?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge:&lt;/strong&gt; How do we keep architectural knowledge, security conventions, and product boundaries from disappearing into chat history?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Governance and review:&lt;/strong&gt; Where does human review add necessary judgement, where can specialised agent roles help, and what must still be tested independently?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Lutions, these questions have practical homes. Requirements can be captured as tickets. Work steps and decisions can be documented. Reviews, test evidence, and process changes can be made traceable. The agent therefore works not only &lt;em&gt;on&lt;/em&gt; Lutions, but also &lt;em&gt;with and in&lt;/em&gt; Lutions.&lt;/p&gt;

&lt;p&gt;That distinction matters. An agent that receives a task without the project context will often optimise for the locally visible result. An agent working inside a process can be asked to inspect the relevant sources, record assumptions, run proportionate checks, and leave evidence that another person can evaluate later.&lt;/p&gt;

&lt;p&gt;In practice, even a small change can make the difference visible. A request to add an action to a ticket page is not only a UI task: the agent may need to check the relevant permission, follow the established interaction pattern, and run the checks that make the change safe to review. The code change is only one part of the work.&lt;/p&gt;

&lt;p&gt;This is not bureaucracy for its own sake. It is an attempt to make a fast-moving workflow inspectable enough to trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context is the real engineering problem
&lt;/h2&gt;

&lt;p&gt;Public discussions of agentic AI often focus on autonomy, tool use, or the latest model capability. Those matter, but they are only part of the picture. Software engineering also includes requirements, architecture, testing, maintenance, and the difficult work of clarifying intent.&lt;/p&gt;

&lt;p&gt;Roychoudhury argues that this clarification of developer intent is central to trustworthy agentic software workflows, alongside verification and validation as automation increases (Roychoudhury, 2025). My experience with Lutions points in the same direction. The difficult part is rarely getting an agent to suggest a change. The difficult part is establishing whether that change fits the system, the requirement, and the risk involved.&lt;/p&gt;

&lt;p&gt;That is why a context layer, explicit checks, and review roles are more than process decoration. They are part of the working interface between people, agents, and a codebase.&lt;/p&gt;

&lt;p&gt;For me, a useful mental model is simple: an agentic development workflow is not a model plus a prompt. It is a model operating within a system of context, tools, constraints, and feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Lutions is not
&lt;/h2&gt;

&lt;p&gt;Lutions is neither an open-source product nor a universal blueprint for other organisations. Building and maintaining a ticket system carries real long-term costs: security, operation, maintenance, and the responsibility to keep it useful. That decision should be made soberly.&lt;/p&gt;

&lt;p&gt;The transferable part is not the ticket system itself. It is the working context. Agents become more useful when they meet a codebase with explicit rules and verifiable consequences, rather than an empty canvas and an optimistic prompt.&lt;/p&gt;

&lt;p&gt;Small demo projects are often too smooth to reveal these problems. Large production systems can be too slow, too risky, or too expensive to use as an experimental space. Lutions sits between those extremes: complex enough for real friction to appear, but close enough to my day-to-day work that I can learn from it without a large coordination apparatus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I am writing about it
&lt;/h2&gt;

&lt;p&gt;The conversation around agentic AI can become very loud very quickly. Terms such as agents, MCP, autonomy, and governance invite large claims. I am more interested in the ordinary working day: incomplete requirements, existing rules, a result that still has to be tested, and a person who remains accountable for the outcome.&lt;/p&gt;

&lt;p&gt;Lutions gives these observations a place. The project lets me examine, case by case, what helps: clearer requirements, more accessible knowledge, better-chosen checks, appropriate review roles, and less friction between human judgement and machine assistance.&lt;/p&gt;

&lt;p&gt;None of this produces a final recipe. It does produce a useful discipline: do not treat an AI agent as an isolated code generator. Treat it as a participant in a development process that takes context, tests, reviews, and limits seriously.&lt;/p&gt;

&lt;p&gt;That is the experiment Lutions makes possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and scope
&lt;/h2&gt;

&lt;p&gt;This article is a personal practice report based on the ongoing development and documentation of Lutions. It is not official product documentation, a roadmap, or a general recommendation to build custom project-management software. I prepared the English adaptation and the accompanying graphics with the assistance of AI tools and reviewed both before publication.&lt;/p&gt;

&lt;p&gt;The research references provide context for the broader engineering questions; they do not validate the personal observations made here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jimenez, Carlos E., et al. (2023). &lt;a href="https://arxiv.org/abs/2310.06770" rel="noopener noreferrer"&gt;&lt;em&gt;SWE-bench: Can Language Models Resolve Real-World GitHub Issues?&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Yang, John, et al. (2024). &lt;a href="https://arxiv.org/abs/2405.15793" rel="noopener noreferrer"&gt;&lt;em&gt;SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Roychoudhury, Abhik (2025). &lt;a href="https://arxiv.org/abs/2508.17343" rel="noopener noreferrer"&gt;&lt;em&gt;Agentic AI for Software: thoughts from Software Engineering community&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Hermanns, Christoph (2026). &lt;a href="https://www.cherware.de/lutions-project-einstieg/" rel="noopener noreferrer"&gt;&lt;em&gt;Agentic AI: Wie ich zu meiner idealen Entwicklungsumgebung kam&lt;/em&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>softwareengineering</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
