<?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: Bracketly</title>
    <description>The latest articles on DEV Community by Bracketly (@ethan_5b3022150e2c07a4030).</description>
    <link>https://dev.to/ethan_5b3022150e2c07a4030</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%2F4033478%2F8e37796d-86e3-4623-91c8-7d484dca518a.png</url>
      <title>DEV Community: Bracketly</title>
      <link>https://dev.to/ethan_5b3022150e2c07a4030</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ethan_5b3022150e2c07a4030"/>
    <language>en</language>
    <item>
      <title>A validator for Claude's memory tool — built around a real CVE, not a hypothetical one</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Mon, 31 Aug 2026 07:27:34 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/a-validator-for-claudes-memory-tool-built-around-a-real-cve-not-a-hypothetical-one-11kn</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/a-validator-for-claudes-memory-tool-built-around-a-real-cve-not-a-hypothetical-one-11kn</guid>
      <description>&lt;h1&gt;
  
  
  A validator for Claude's memory tool — built around a real CVE, not a hypothetical one
&lt;/h1&gt;

&lt;p&gt;Anthropic's memory tool (&lt;code&gt;memory_20250818&lt;/code&gt;) lets Claude keep files across conversations — six commands (&lt;code&gt;view&lt;/code&gt;, &lt;code&gt;create&lt;/code&gt;, &lt;code&gt;str_replace&lt;/code&gt;, &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;rename&lt;/code&gt;), all executed by &lt;em&gt;your&lt;/em&gt; application, not Anthropic's. Claude only ever asks for the operation; your handler decides whether it's safe. That split is the whole point of the design, and it's also exactly where things go wrong.&lt;/p&gt;

&lt;p&gt;Anthropic's own docs carry a blunt warning about it: a path like &lt;code&gt;/memories/../../secrets.env&lt;/code&gt; has to be rejected, or it walks straight out of the sandbox. That's not a theoretical footnote. GitHub Security Advisory GHSA-5474-4w2j-mq4c (CVE-2026-34451, CVSS 6.3) documents this exact bug class shipping in &lt;code&gt;@anthropic-ai/sdk&lt;/code&gt; 0.79.0–0.80.x: a path check that tested &lt;code&gt;path.startsWith('/memories')&lt;/code&gt; with no boundary guard, so &lt;code&gt;/memories-backup/secrets.env&lt;/code&gt; — a sibling directory that merely &lt;em&gt;shares a string prefix&lt;/em&gt; with the real memory root — sailed straight through. Fixed in 0.81.0.&lt;/p&gt;

&lt;p&gt;That's a specific, patchable mistake, but it's an easy one to reintroduce in any hand-rolled handler, because the vulnerable check and the correct one look almost identical at a glance. So the new tool at &lt;code&gt;/tools/claude-memory-tool-validator/&lt;/code&gt; runs every path you give it through both, side by side: a "naive" check that deliberately reproduces the CVE's own vulnerable logic, and a "hardened" check that decodes, normalizes &lt;code&gt;.&lt;/code&gt;/&lt;code&gt;..&lt;/code&gt; segments, and verifies real containment. Paste &lt;code&gt;/memories/../../etc/passwd&lt;/code&gt; or a URL-encoded traversal attempt and you'll see the naive check say PASS while the hardened one says FAIL — which is the entire bug, made visible instead of theoretical.&lt;/p&gt;

&lt;p&gt;It also validates the structural shape of each of the six memory commands against Anthropic's documented schema (catching things like a &lt;code&gt;rename&lt;/code&gt; targeting the memory root itself, which the tool description explicitly says Claude can't do), plus a second panel for the newer &lt;code&gt;context_management&lt;/code&gt; config (the &lt;code&gt;clear_tool_uses_20250919&lt;/code&gt; / &lt;code&gt;clear_thinking_20251015&lt;/code&gt; strategies that trim old tool results or thinking blocks from long conversations) — including the easy-to-miss rule that when you combine both strategies, &lt;code&gt;clear_thinking_20251015&lt;/code&gt; has to come first in the array.&lt;/p&gt;

&lt;p&gt;Everything runs client-side, as always here — no API key, no network calls, nothing you paste leaves your browser.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>HTTP 402 Is a Real Payment Protocol Now — Here's a Way to Check the Signature, Not Just the JSON</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Sun, 30 Aug 2026 07:28:02 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/http-402-is-a-real-payment-protocol-now-heres-a-way-to-check-the-signature-not-just-the-json-5286</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/http-402-is-a-real-payment-protocol-now-heres-a-way-to-check-the-signature-not-just-the-json-5286</guid>
      <description>&lt;h1&gt;
  
  
  HTTP 402 Is a Real Payment Protocol Now — Here's a Way to Check the Signature, Not Just the JSON
&lt;/h1&gt;

&lt;p&gt;HTTP 402 "Payment Required" sat unused in the spec for thirty years as the one status code nobody implemented. Since mid-2025 it's been revived for real: x402, originated by Coinbase and now stewarded by the independent x402 Foundation under the Linux Foundation, lets a server respond 402 with a payment requirement and a client retry with a signed authorization proving it paid — built for AI agents and APIs that need to transact without a human at a checkout page. It's early enough that the spec itself has two live shapes (v1 and v2 restructured the payload envelope) and tooling is still thin.&lt;/p&gt;

&lt;p&gt;What's actually inside that payment payload is an EIP-3009 &lt;code&gt;transferWithAuthorization&lt;/code&gt; — an EIP-712 typed-data structure, signed with a real ECDSA key over a real token contract's domain. That means a broken client integration doesn't fail with a stack trace; it fails as a payload that &lt;em&gt;looks&lt;/em&gt; like valid JSON but was never actually signed by who it claims, or was signed for a different value than the one now being presented.&lt;/p&gt;

&lt;p&gt;So this tool doesn't just pretty-print the fields. Paste a payload (raw JSON or the base64 string form) and, when you also have the matching payment requirements from the 402 response, it reconstructs the actual EIP-712 digest and recovers the signing address from the signature using real secp256k1 math — the same class of check a payment facilitator itself runs — then checks it against the claimed sender.&lt;/p&gt;

&lt;p&gt;Building the recovery step was a good reminder that "my test passed" isn't the same as "my code is correct": the first version I wrote self-verified cleanly — sign with a key, recover, confirm a match — and was still wrong, because it was internally consistent with a byte-ordering assumption it invented for itself rather than the one real Ethereum signatures actually use (the recovery byte's position relative to r and s). It only broke visibly once I tested it against a signature built the standard way instead of one built by my own signer. Worth remembering any time "round-trips correctly" is the only evidence a piece of crypto code has been checked.&lt;/p&gt;

&lt;p&gt;Free, runs entirely in your browser, no wallet connection or network call involved: &lt;a href="https://bracketly.pages.dev/tools/x402-payment-validator/" rel="noopener noreferrer"&gt;bracketly.pages.dev/tools/x402-payment-validator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your LLM Traces Have a Spec Now — Here's a Way to Check Them Against It</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Sat, 29 Aug 2026 07:25:25 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/your-llm-traces-have-a-spec-now-heres-a-way-to-check-them-against-it-197g</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/your-llm-traces-have-a-spec-now-heres-a-way-to-check-them-against-it-197g</guid>
      <description>&lt;h1&gt;
  
  
  Your LLM Traces Have a Spec Now — Here's a Way to Check Them Against It
&lt;/h1&gt;

&lt;p&gt;If you've instrumented an LLM call with OpenTelemetry recently, you've probably typed &lt;code&gt;gen_ai.request.model&lt;/code&gt; or &lt;code&gt;gen_ai.usage.input_tokens&lt;/code&gt; into a span attribute and moved on, trusting you got the name right. There's an actual spec behind those attributes now — the GenAI semantic conventions, recently split out into their own dedicated repository — and it's still explicitly marked Development stability, meaning attribute names, types, and requirement levels are genuinely still moving. That's a different kind of instability than, say, the HTTP or database conventions, which have been settled for years.&lt;/p&gt;

&lt;p&gt;A few things stood out once I read the actual registry instead of a summary blog post. &lt;code&gt;gen_ai.provider.name&lt;/code&gt; is deliberately open-ended — new providers get added regularly, so an unrecognized value there isn't wrong, just unlisted. But &lt;code&gt;gen_ai.response.status&lt;/code&gt; is a small, closed, fixed set (&lt;code&gt;queued&lt;/code&gt;, &lt;code&gt;in_progress&lt;/code&gt;, &lt;code&gt;completed&lt;/code&gt;, &lt;code&gt;incomplete&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, &lt;code&gt;cancelled&lt;/code&gt;) — a &lt;code&gt;"done"&lt;/code&gt; or &lt;code&gt;"success"&lt;/code&gt; there is a real mismatch, not a stylistic choice. Each span type has its own expected name template too: an inference span should be named &lt;code&gt;{operation} {model}&lt;/code&gt; (e.g. &lt;code&gt;chat gpt-4o&lt;/code&gt;), an &lt;code&gt;execute_tool&lt;/code&gt; span should be &lt;code&gt;execute_tool {tool_name}&lt;/code&gt; — easy to get subtly wrong if you're hand-rolling the instrumentation rather than using an SDK that already does it for you. And &lt;code&gt;invoke_agent&lt;/code&gt; spans have two different required-attribute sets depending on whether the call crossed a network boundary or stayed in-process, which the spec ties to the span's own &lt;code&gt;kind&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;The only real conformance checker that exists is Weaver, a Rust CLI OTel itself uses in CI against a live registry checkout — genuinely useful if you're building a collector or an instrumentation library, overkill if you just want to know whether the span your app just emitted is shaped correctly.&lt;/p&gt;

&lt;p&gt;So: paste a span — a real OTLP JSON export with an &lt;code&gt;attributes&lt;/code&gt; array, a full &lt;code&gt;resourceSpans&lt;/code&gt; trace dump, or just a flattened object of &lt;code&gt;gen_ai.*&lt;/code&gt; key/value pairs if you're testing by hand — and get every attribute checked for type and enum correctness, the operation-specific required fields verified, and the span name checked against its documented template.&lt;/p&gt;

&lt;p&gt;Free, runs entirely in your browser, nothing you paste leaves your machine: &lt;a href="https://bracketly.pages.dev/tools/otel-genai-span-validator/" rel="noopener noreferrer"&gt;bracketly.pages.dev/tools/otel-genai-span-validator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Before You Run mcp-publisher, Check Your server.json Actually Matches the Schema</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Mon, 24 Aug 2026 07:21:07 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/before-you-run-mcp-publisher-check-your-serverjson-actually-matches-the-schema-3oe2</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/before-you-run-mcp-publisher-check-your-serverjson-actually-matches-the-schema-3oe2</guid>
      <description>&lt;h1&gt;
  
  
  Before You Run mcp-publisher, Check Your server.json Actually Matches the Schema
&lt;/h1&gt;

&lt;p&gt;The MCP Registry gives every Model Context Protocol server a home — a central place clients can search to discover and install one. To get listed, you write a &lt;code&gt;server.json&lt;/code&gt; manifest and hand it to the official &lt;code&gt;mcp-publisher&lt;/code&gt; CLI. The manifest looks simple: a name, a description, a version, and either a package a client can install or a remote endpoint it can connect to directly. In practice it's easy to get subtly wrong in ways that only surface when publishing fails or, worse, when a client can't actually launch the server it just installed.&lt;/p&gt;

&lt;p&gt;A few gotchas stood out once I read the schema closely. The &lt;code&gt;name&lt;/code&gt; field isn't just any string — it has to be reverse-DNS namespace form with exactly one slash, like &lt;code&gt;io.github.user/weather&lt;/code&gt;, and it's capped at 200 characters. &lt;code&gt;description&lt;/code&gt; is capped at 100 — tighter than you'd guess, easy to blow past if you're used to writing a normal sentence. Every package's &lt;code&gt;version&lt;/code&gt; explicitly rejects the literal string &lt;code&gt;"latest"&lt;/code&gt; — the schema forbids it outright, not just as a style suggestion — and version ranges like &lt;code&gt;^1.2.3&lt;/code&gt; or &lt;code&gt;1.x&lt;/code&gt; are rejected too, since the registry wants one specific, resolvable version. And the two ways of describing how to reach a server — &lt;code&gt;packages&lt;/code&gt; for something a client installs and runs locally, &lt;code&gt;remotes&lt;/code&gt; for something already running somewhere reachable over HTTP — aren't interchangeable: a &lt;code&gt;remotes&lt;/code&gt; entry can't use a &lt;code&gt;stdio&lt;/code&gt; transport, because there's nothing local to spawn.&lt;/p&gt;

&lt;p&gt;None of that is exotic once you know it, but the tooling to catch it before you publish has been entirely CLI-based — &lt;code&gt;mcp-publisher --dry-run&lt;/code&gt;, or a handful of npm packages that assume you're comfortable running a script. There wasn't a place to just paste your manifest and see what's wrong.&lt;/p&gt;

&lt;p&gt;So that's what this is: paste a &lt;code&gt;server.json&lt;/code&gt; and get every field checked against the registry's actual JSON Schema — name format, description/version limits, package and remote transports, &lt;code&gt;fileSha256&lt;/code&gt; (required specifically for MCPB packages, optional otherwise), icons, and repository metadata — with the schema-mandated rejections (like &lt;code&gt;"latest"&lt;/code&gt;) flagged as hard errors and softer conventions flagged as warnings.&lt;/p&gt;

&lt;p&gt;Free, runs entirely in your browser, nothing you paste leaves your machine: &lt;a href="https://bracketly.pages.dev/tools/mcp-server-json-validator/" rel="noopener noreferrer"&gt;bracketly.pages.dev/tools/mcp-server-json-validator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>MCP's Tasks Extension Lets a Tool Call Run in the Background — If You Get the Object Shape Right</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Sat, 22 Aug 2026 07:24:57 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/mcps-tasks-extension-lets-a-tool-call-run-in-the-background-if-you-get-the-object-shape-right-jj0</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/mcps-tasks-extension-lets-a-tool-call-run-in-the-background-if-you-get-the-object-shape-right-jj0</guid>
      <description>&lt;h1&gt;
  
  
  MCP's Tasks Extension Lets a Tool Call Run in the Background — If You Get the Object Shape Right
&lt;/h1&gt;

&lt;p&gt;Model Context Protocol has quietly grown an answer to a problem every MCP server hits eventually: what do you do when a tool call takes thirty seconds, or thirty minutes? A CI pipeline, a batch export, a step that's waiting on a human to click approve — none of that fits the request-response model MCP started with, where the client just sits on the connection until a result comes back.&lt;/p&gt;

&lt;p&gt;The Tasks extension is the fix. Instead of blocking, a server can hand back a task handle immediately: a &lt;code&gt;taskId&lt;/code&gt;, a &lt;code&gt;status&lt;/code&gt;, and enough metadata for the client to poll intelligently. The client checks in with &lt;code&gt;tasks/get&lt;/code&gt; (or gets pushed a &lt;code&gt;notifications/tasks/status&lt;/code&gt; update), waits for the status to land on &lt;code&gt;completed&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, or &lt;code&gt;cancelled&lt;/code&gt;, and then — this is the part that trips people up — fetches the actual output with a &lt;em&gt;separate&lt;/em&gt; &lt;code&gt;tasks/result&lt;/code&gt; request. The task object itself never carries the result. It's pure status metadata: &lt;code&gt;taskId&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, a required-but-nullable &lt;code&gt;ttl&lt;/code&gt;, timestamps, and a couple of optional fields. If your mental model says "the completed task has the result attached," you'll write code that works in your own test and breaks against a spec-following client.&lt;/p&gt;

&lt;p&gt;I went looking for how solid this corner of MCP actually is before building anything, and the honest answer is: still moving. I pulled two versions of the official TypeScript SDK — one from March, one from July — and diffed the actual type definitions. The request-side &lt;code&gt;ttl&lt;/code&gt; field (the one you send when &lt;em&gt;asking&lt;/em&gt; for a task to be created) changed what values it accepts between those releases. A new &lt;code&gt;extensions&lt;/code&gt; field showed up elsewhere in the same schema family. The SDK's own source comments say plainly that these APIs are experimental and may change without notice. That's not a knock on the feature — it's useful and shipping in real servers — but it means a validator has to be built against what's actually in the SDK right now, not against a spec page that might be stating something aspirational.&lt;/p&gt;

&lt;p&gt;So that's what I built: paste a Task/status object, a &lt;code&gt;tasks/get&lt;/code&gt;/&lt;code&gt;tasks/cancel&lt;/code&gt;/&lt;code&gt;tasks/list&lt;/code&gt; request or response, a &lt;code&gt;notifications/tasks/status&lt;/code&gt; push, a request that's asking for task-backed execution, or a capabilities negotiation object, and it checks the shape against the current SDK — including the required-but-nullable &lt;code&gt;ttl&lt;/code&gt; gotcha, the "no result field here" mistake, and the common slip of passing &lt;code&gt;true&lt;/code&gt; instead of an empty object &lt;code&gt;{}&lt;/code&gt; for a capability flag (MCP's own convention for "yes, I support this").&lt;/p&gt;

&lt;p&gt;Free, runs entirely in your browser, nothing you paste leaves your machine: &lt;a href="https://bracketly.pages.dev/tools/mcp-tasks-validator/" rel="noopener noreferrer"&gt;bracketly.pages.dev/tools/mcp-tasks-validator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your SKILL.md Works Fine Locally and Fails the Instant You Try to Share It</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Fri, 21 Aug 2026 07:25:51 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/your-skillmd-works-fine-locally-and-fails-the-instant-you-try-to-share-it-3e53</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/your-skillmd-works-fine-locally-and-fails-the-instant-you-try-to-share-it-3e53</guid>
      <description>&lt;h1&gt;
  
  
  Your SKILL.md Works Fine Locally and Fails the Instant You Try to Share It
&lt;/h1&gt;

&lt;p&gt;Agent Skills — the SKILL.md pattern for packaging reusable instructions for an AI coding agent — has quietly become the thing everyone's writing right now. Claude Code uses it, and it's built on an open, vendor-neutral standard so the same file format works across tools. Write a skill, drop it in a folder, and your agent picks it up. Simple.&lt;/p&gt;

&lt;p&gt;Except the format has two faces, and most people writing skills only ever see one of them.&lt;/p&gt;

&lt;p&gt;Claude Code accepts a generous set of frontmatter fields beyond the open standard — &lt;code&gt;argument-hint&lt;/code&gt;, &lt;code&gt;when_to_use&lt;/code&gt;, &lt;code&gt;disallowed-tools&lt;/code&gt;, &lt;code&gt;effort&lt;/code&gt;, and a handful more, all genuinely useful extensions for that one tool. Write a skill with any of those fields, test it locally, and it works perfectly. Then you try to upload it to claude.ai, publish it through the Skills API, or package it for distribution outside Claude Code, and it fails outright: &lt;code&gt;Unexpected key(s) in SKILL.md frontmatter&lt;/code&gt;. The portable spec recognizes exactly six fields — &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;license&lt;/code&gt;, &lt;code&gt;compatibility&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt;, &lt;code&gt;allowed-tools&lt;/code&gt; — and nothing else. Anything outside that list isn't a warning, it's a hard rejection.&lt;/p&gt;

&lt;p&gt;There's a real, filed bug about this exact confusion: a validator flagging Claude Code's own documented extension fields as errors, because it was checking against the narrow six-field spec instead of the wider set Claude Code itself accepts. Two valid frontmatters, two different rulebooks, and no obvious signal for which one you're being held to until the upload fails.&lt;/p&gt;

&lt;p&gt;The other trap is more mundane but just as common: &lt;code&gt;name&lt;/code&gt; has to be lowercase, hyphens only, capped at 64 characters, no leading or trailing hyphen — easy to violate by accident with an underscore or a capital letter copied from a project name, and &lt;code&gt;description&lt;/code&gt; has its own 1,024-character ceiling that a genuinely thorough one quietly sails past.&lt;/p&gt;

&lt;p&gt;I built a validator that checks a pasted SKILL.md against both layers at once: real spec violations (bad name format, missing required fields, oversized description) flagged as errors, and every field outside the six-field portable set flagged separately as a host extension — fine in Claude Code, but exactly what will break portability elsewhere. It parses the actual frontmatter, not just eyeballs it.&lt;/p&gt;

&lt;p&gt;Free, runs entirely in your browser, nothing you paste leaves your machine: &lt;a href="https://bracketly.pages.dev/tools/skill-md-validator/" rel="noopener noreferrer"&gt;bracketly.pages.dev/tools/skill-md-validator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Editor-Agent Protocol Everyone's Adopting Has a Version Field Bug Waiting to Happen</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Mon, 17 Aug 2026 07:24:52 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/the-editor-agent-protocol-everyones-adopting-has-a-version-field-bug-waiting-to-happen-71o</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/the-editor-agent-protocol-everyones-adopting-has-a-version-field-bug-waiting-to-happen-71o</guid>
      <description>&lt;h1&gt;
  
  
  The Editor-Agent Protocol Everyone's Adopting Has a Version Field Bug Waiting to Happen
&lt;/h1&gt;

&lt;p&gt;The Agent Client Protocol (ACP) is quietly becoming the LSP of AI coding agents — one JSON-RPC wire format so a code editor can talk to any agent, and an agent can talk to any editor, without a bespoke integration on either side. Zed originated it, it hit a stable v1.0, and it's now spoken by 25+ agents including Gemini CLI and JetBrains' tooling. If you're building an agent or an editor integration right now, you're probably hand-implementing at least the handshake yourself.&lt;/p&gt;

&lt;p&gt;Here's the mistake I'd bet money on seeing in the wild: &lt;strong&gt;&lt;code&gt;protocolVersion&lt;/code&gt; is a plain integer, not a version string.&lt;/strong&gt; Every other AI-adjacent protocol I've looked at lately — MCP, Agent Plugins — uses a date-like string for its version field (&lt;code&gt;"2026-07-28"&lt;/code&gt;, that sort of thing). ACP doesn't. It's &lt;code&gt;1&lt;/code&gt;, a bare JSON number, and it's only bumped for breaking changes. Send &lt;code&gt;"1"&lt;/code&gt; as text out of habit from writing three other integrations this month, and you get a rejection with no obvious clue why, because the JSON itself is perfectly valid — just the wrong type.&lt;/p&gt;

&lt;p&gt;The other one that'll bite you: ACP's baseline is exactly four methods every agent &lt;strong&gt;must&lt;/strong&gt; support — &lt;code&gt;session/new&lt;/code&gt;, &lt;code&gt;session/prompt&lt;/code&gt;, &lt;code&gt;session/cancel&lt;/code&gt;, and &lt;code&gt;session/update&lt;/code&gt; — and two of those four are notifications, not requests. &lt;code&gt;session/cancel&lt;/code&gt; in particular is easy to send with an &lt;code&gt;id&lt;/code&gt; attached out of muscle memory (every other action in the protocol is a request/response pair), and if you do, the agent that correctly treats it as fire-and-forget will never send back the response your client is sitting there waiting for.&lt;/p&gt;

&lt;p&gt;I went and pulled the actual published stable schema — the real JSON Schema shipped in &lt;code&gt;@agentclientprotocol/sdk&lt;/code&gt;, not a summary or a blog post — and built a validator against it: paste a JSON-RPC message, get back exactly which required field is missing or which type is wrong, checked against the handshake plus all four baseline methods, with the rest of the ~30 real ACP methods recognized by name even where field-level depth isn't worth building yet. It also tells you the direction — client→agent or agent→client — since half the "why isn't this working" bugs I'd guess are just a message sent the wrong way down the pipe.&lt;/p&gt;

&lt;p&gt;Free, runs entirely in your browser, nothing you paste leaves your machine: &lt;a href="https://bracketly.pages.dev/tools/acp-message-validator/" rel="noopener noreferrer"&gt;bracketly.pages.dev/tools/acp-message-validator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>MCP Just Got a Native App Platform — Its Default CSP Blocks Everything</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Sat, 15 Aug 2026 07:18:47 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/mcp-just-got-a-native-app-platform-its-default-csp-blocks-everything-1f3f</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/mcp-just-got-a-native-app-platform-its-default-csp-blocks-everything-1f3f</guid>
      <description>&lt;h1&gt;
  
  
  MCP Just Got a Native App Platform — Its Default CSP Blocks Everything
&lt;/h1&gt;

&lt;p&gt;MCP Apps is the first official extension folded directly into the Model Context Protocol's stable core spec: a tool can now declare an interactive HTML UI — a &lt;code&gt;ui://&lt;/code&gt; resource — that a host renders in a sandboxed iframe instead of just dumping text back to the model. Think a dashboard, a form, a live chart, embedded right in the conversation. It's a genuinely useful capability, and it's also brand new enough that almost nobody building against it right now has an SDK doing the validation for them.&lt;/p&gt;

&lt;p&gt;I hit three things worth knowing about before you wire this up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mimeType isn't &lt;code&gt;text/html&lt;/code&gt;.&lt;/strong&gt; It's the exact string &lt;code&gt;text/html;profile=mcp-app&lt;/code&gt;. Miss the &lt;code&gt;;profile=mcp-app&lt;/code&gt; suffix and your resource just looks like a regular document to the host — no interactive rendering, no postMessage bridge, and no error telling you why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSP is an allow-list, and the default is nothing.&lt;/strong&gt; The &lt;code&gt;_meta.ui.csp&lt;/code&gt; block has four domain-list fields — &lt;code&gt;connectDomains&lt;/code&gt;, &lt;code&gt;resourceDomains&lt;/code&gt;, &lt;code&gt;frameDomains&lt;/code&gt;, &lt;code&gt;baseUriDomains&lt;/code&gt; — and every single one defaults to fully blocked when omitted, not fully allowed. That's the right security default for a sandboxed iframe a third party's tool controls, but it means the very first time your UI tries to &lt;code&gt;fetch()&lt;/code&gt; a weather API or load a CDN script, it silently fails unless you explicitly listed that domain. No console warning tells you it's the CSP; it just looks like your JavaScript is broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The host↔iframe handshake is hand-rolled JSON-RPC over &lt;code&gt;postMessage&lt;/code&gt;.&lt;/strong&gt; There's no client library abstracting this away yet — you're constructing &lt;code&gt;ui/initialize&lt;/code&gt;, &lt;code&gt;ui/message&lt;/code&gt;, &lt;code&gt;ui/request-display-mode&lt;/code&gt; and friends by hand, and a malformed &lt;code&gt;params&lt;/code&gt; object on any of them just means the message gets silently dropped rather than an error you can debug.&lt;/p&gt;

&lt;p&gt;None of these are exotic bugs. They're exactly the kind of thing that costs an afternoon the first time and is obvious in hindsight the second — which is the whole reason a validator is worth having before the second time.&lt;/p&gt;

&lt;p&gt;So I built one: paste a tool's &lt;code&gt;_meta.ui&lt;/code&gt; block, a &lt;code&gt;resources/read&lt;/code&gt; response, or a single &lt;code&gt;ui/*&lt;/code&gt; postMessage envelope, and it checks each against the published spec — wrong URI scheme, the mimeType typo, malformed CSP domain lists, missing required params per message type. Every check is traced back to the actual spec source, not a guess at what "should" be required. It's free, runs entirely in your browser, and nothing you paste is ever sent anywhere: &lt;a href="https://bracketly.pages.dev/tools/mcp-apps-validator/" rel="noopener noreferrer"&gt;bracketly.pages.dev/tools/mcp-apps-validator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Five AI Companies Just Agreed on a Plugin Format — Here's a Free Validator for It</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Sun, 09 Aug 2026 07:26:43 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/five-ai-companies-just-agreed-on-a-plugin-format-heres-a-free-validator-for-it-91a</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/five-ai-companies-just-agreed-on-a-plugin-format-heres-a-free-validator-for-it-91a</guid>
      <description>&lt;h1&gt;
  
  
  Five AI Companies Just Agreed on a Plugin Format — Here's a Free Validator for It
&lt;/h1&gt;

&lt;p&gt;On August 6th, OpenAI, AWS, Cursor, Microsoft, and Vercel published Agent Plugins 1.0.0 — a shared packaging standard so a single plugin folder can install into ChatGPT, Codex, GitHub Copilot, VS Code, Cursor, and AWS's Kiro without rewriting setup instructions for each one. It's not a new format competing with Agent Skills (&lt;code&gt;SKILL.md&lt;/code&gt;) or MCP — it's the layer that wraps both: a &lt;code&gt;plugin.json&lt;/code&gt; manifest, an optional &lt;code&gt;skills/&lt;/code&gt; folder, and an optional &lt;code&gt;mcp.json&lt;/code&gt; for MCP server configs, all in one portable directory.&lt;/p&gt;

&lt;p&gt;The catch with a spec this fresh: almost nobody has actually validated a manifest against the real schema yet, because almost nobody's had the chance to. The &lt;code&gt;plugin.json&lt;/code&gt; format is deliberately narrow — exactly ten permitted top-level fields, a &lt;code&gt;name&lt;/code&gt; field with a strict character-and-length pattern, a closed &lt;code&gt;author&lt;/code&gt; object — and most people writing their first one right now are working from blog posts and a single canonical example, not the JSON Schema itself. It's easy to add a field that gets silently dropped, or write a plugin name that looks fine and fails validation outright.&lt;/p&gt;

&lt;p&gt;I added a free &lt;a href="https://bracketly.pages.dev/tools/agent-plugin-validator/" rel="noopener noreferrer"&gt;Agent Plugins Manifest Validator&lt;/a&gt; to Bracketly, my free client-side dev tools site. Paste a &lt;code&gt;plugin.json&lt;/code&gt; and it checks it against the published 1.0.0 schema: the exact &lt;code&gt;$schema&lt;/code&gt; URL, the name pattern (lowercase alphanumeric plus hyphens and periods, no doubled &lt;code&gt;--&lt;/code&gt; or &lt;code&gt;..&lt;/code&gt;, must start and end alphanumeric), and correct types on every optional field. It also knows the difference between what the spec treats as fatal versus not — an unrecognized top-level field or a non-object &lt;code&gt;extensions&lt;/code&gt; value is explicitly non-fatal per spec (clients report and ignore it, then keep loading the plugin), so those show as warnings rather than errors, matching how a real client actually behaves instead of over-flagging. Paste an optional &lt;code&gt;mcp.json&lt;/code&gt; too, and it validates the three MCP transport types — including a rule that's easy to miss by hand: a stdio server's &lt;code&gt;command&lt;/code&gt; has to be a single executable token, not a full command line with embedded arguments.&lt;/p&gt;

&lt;p&gt;Every check is built directly from the published JSON Schema and spec text at agent-plugins.org, not a paraphrase of a summary post, and verified against more than three dozen hand-constructed test cases — valid and invalid — before shipping.&lt;/p&gt;

&lt;p&gt;Like everything on the site, it's 100% client-side. Nothing you paste is sent anywhere.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>MCP Just Broke Its Own Handshake — Here's a Free Checker for the 2026-07-28 Spec</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Fri, 07 Aug 2026 07:24:51 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/mcp-just-broke-its-own-handshake-heres-a-free-checker-for-the-2026-07-28-spec-83j</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/mcp-just-broke-its-own-handshake-heres-a-free-checker-for-the-2026-07-28-spec-83j</guid>
      <description>&lt;h1&gt;
  
  
  MCP Just Broke Its Own Handshake — Here's a Free Checker for the 2026-07-28 Spec
&lt;/h1&gt;

&lt;p&gt;On July 28th, the Model Context Protocol spec shipped its biggest breaking change since the protocol launched: the &lt;code&gt;initialize&lt;/code&gt;/&lt;code&gt;initialized&lt;/code&gt; handshake and the &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header — the thing every MCP server and client has been built around since day one — are gone. In their place, every single request now has to carry its own protocol version and capabilities in a &lt;code&gt;_meta&lt;/code&gt; object, on every call, with no more one-time negotiation. There's also new header-based routing (&lt;code&gt;Mcp-Method&lt;/code&gt;, &lt;code&gt;Mcp-Name&lt;/code&gt;, &lt;code&gt;MCP-Protocol-Version&lt;/code&gt;), a &lt;code&gt;resultType&lt;/code&gt; field that's now mandatory on every successful result, and a Multi Round-Trip Requests mechanism that replaces server-initiated streams for mid-call user input.&lt;/p&gt;

&lt;p&gt;All four Tier 1 SDKs — TypeScript, Python, Go, C# — already speak the new spec. Which means if you've got a hand-rolled MCP server, a custom transport layer, or anything that isn't just calling an official SDK's helpers, it can look completely fine in isolation and still fail the moment it talks to an updated client or gateway, with nothing more informative than a routing failure or a silently-dropped request.&lt;/p&gt;

&lt;p&gt;I added a free &lt;a href="https://bracketly.pages.dev/tools/mcp-migration-checker/" rel="noopener noreferrer"&gt;MCP 2026-07-28 Migration Checker&lt;/a&gt; to Bracketly, my free client-side dev tools site, for exactly this. Paste a JSON-RPC message and it flags the deprecated &lt;code&gt;initialize&lt;/code&gt; handshake if it's still there, checks whether &lt;code&gt;params._meta&lt;/code&gt; has the required &lt;code&gt;io.modelcontextprotocol/protocolVersion&lt;/code&gt; and &lt;code&gt;clientCapabilities&lt;/code&gt; fields, and validates that responses declare &lt;code&gt;resultType&lt;/code&gt; and that &lt;code&gt;input_required&lt;/code&gt; results carry the &lt;code&gt;inputRequests&lt;/code&gt;/&lt;code&gt;requestState&lt;/code&gt; fields the new Multi Round-Trip flow needs. If you paste a full raw HTTP block instead of just the JSON body, it also checks the new routing headers — including flagging a lingering &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header as a sign you're still on the old stateful model.&lt;/p&gt;

&lt;p&gt;Building this meant going straight to the published TypeScript schema for the 2026-07-28 revision rather than trusting summaries, since a wire-protocol checker that's subtly wrong about field names is worse than no checker at all — so every check is tuned to how confident I actually am in it: the required-field checks are strict (spec violations), while corners of the spec I'm less certain are fully nailed down, like the exact shape of individual Multi Round-Trip entries, are flagged as gentler notes instead of hard failures.&lt;/p&gt;

&lt;p&gt;Like every tool on the site, it runs entirely in your browser — nothing you paste is sent anywhere. If you're migrating an MCP server or client right now, it's a quick first pass before you go digging through logs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your Custom AI SDK Backend Returns 200 OK and the Chat UI Still Shows Nothing</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Thu, 06 Aug 2026 07:37:10 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/your-custom-ai-sdk-backend-returns-200-ok-and-the-chat-ui-still-shows-nothing-3pfp</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/your-custom-ai-sdk-backend-returns-200-ok-and-the-chat-ui-still-shows-nothing-3pfp</guid>
      <description>&lt;h1&gt;
  
  
  Your Custom AI SDK Backend Returns 200 OK and the Chat UI Still Shows Nothing
&lt;/h1&gt;

&lt;p&gt;Vercel's AI SDK makes streaming a chat response trivial — as long as your backend is also written in JS and calls &lt;code&gt;streamText().toUIMessageStreamResponse()&lt;/code&gt;. The moment you're not in that world — a Go, Python, or Rust backend, a queue that re-serializes the stream, a proxy sitting between your LLM call and the client — you have to hand-produce the exact Server-Sent Events format &lt;code&gt;useChat&lt;/code&gt; expects, and there's no error message when you get it wrong. The request succeeds, the status is 200, and the UI just renders an empty message or silently stalls.&lt;/p&gt;

&lt;p&gt;I hit this building a small side project and ended up staring at raw response bytes in a terminal for longer than I'd like to admit, so I added an &lt;a href="https://bracketly.pages.dev/tools/ai-sdk-stream-debugger/" rel="noopener noreferrer"&gt;AI SDK Stream Protocol Validator &amp;amp; Debugger&lt;/a&gt; to Bracketly.&lt;/p&gt;

&lt;p&gt;The protocol itself is a sequence of &lt;code&gt;data: {...}&lt;/code&gt; JSON lines, each with a &lt;code&gt;type&lt;/code&gt; field — &lt;code&gt;text-start&lt;/code&gt;, &lt;code&gt;text-delta&lt;/code&gt;, &lt;code&gt;text-end&lt;/code&gt; for streamed text; &lt;code&gt;tool-input-start&lt;/code&gt;/&lt;code&gt;tool-input-delta&lt;/code&gt;/&lt;code&gt;tool-input-available&lt;/code&gt; for tool calls; &lt;code&gt;finish&lt;/code&gt; and a closing &lt;code&gt;data: [DONE]&lt;/code&gt; to end the stream. It reads as simple until you're generating it by hand and discover how many ways there are to get it subtly wrong: a &lt;code&gt;text-delta&lt;/code&gt; referencing an &lt;code&gt;id&lt;/code&gt; you never opened with &lt;code&gt;text-start&lt;/code&gt;, a &lt;code&gt;text-start&lt;/code&gt; that never gets a matching &lt;code&gt;text-end&lt;/code&gt;, a missing &lt;code&gt;messageId&lt;/code&gt; on the initial &lt;code&gt;start&lt;/code&gt; event, or just forgetting the trailing &lt;code&gt;[DONE]&lt;/code&gt; so the client waits forever for a stream that already finished.&lt;/p&gt;

&lt;p&gt;The tool parses each event, checks it against the documented part types and their required fields, and tracks state across the whole stream so it can catch the ordering bugs a plain JSON validator would miss — an orphaned delta, an unclosed span, a tool-output event referencing a &lt;code&gt;toolCallId&lt;/code&gt; that was never introduced. It also reconstructs the assembled message text from the deltas, so you can see in one glance what the client would actually have rendered from your stream. Paste the raw response body — straight from &lt;code&gt;curl -N&lt;/code&gt; or your browser's network tab — and it runs entirely in your browser tab; nothing gets sent anywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bracketly.pages.dev/tools/ai-sdk-stream-debugger/" rel="noopener noreferrer"&gt;Try it here&lt;/a&gt; — feedback and edge cases welcome.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Your Truncated Embedding Vector Is Silently the Wrong Length — Here's the One-Line Fix</title>
      <dc:creator>Bracketly</dc:creator>
      <pubDate>Mon, 03 Aug 2026 07:20:00 +0000</pubDate>
      <link>https://dev.to/ethan_5b3022150e2c07a4030/your-truncated-embedding-vector-is-silently-the-wrong-length-heres-the-one-line-fix-3k40</link>
      <guid>https://dev.to/ethan_5b3022150e2c07a4030/your-truncated-embedding-vector-is-silently-the-wrong-length-heres-the-one-line-fix-3k40</guid>
      <description>&lt;h1&gt;
  
  
  Your Truncated Embedding Vector Is Silently the Wrong Length — Here's the One-Line Fix
&lt;/h1&gt;

&lt;p&gt;Most major embedding APIs — OpenAI's text-embedding-3 family, Gemini's gemini-embedding models, Cohere embed-v4, Nomic Embed v1.5 — are now trained with Matryoshka Representation Learning, which means you can safely shorten an embedding after the fact to save storage and search cost, with only a small, predictable accuracy hit. That's a genuinely useful trick. It's also got a one-line gotcha that's easy to miss the first time, so I added an &lt;a href="https://bracketly.pages.dev/tools/embedding-truncator/" rel="noopener noreferrer"&gt;Embedding Vector Truncator &amp;amp; Renormalizer&lt;/a&gt; to Bracketly, my free client-side dev tools site.&lt;/p&gt;

&lt;p&gt;Here's the gotcha: embeddings from these APIs are L2-normalized to unit length by default — that's what makes cosine similarity and dot-product search interchangeable, and it's part of why the math downstream (nearest-neighbor search, reranking, thresholding) behaves predictably. Slice off the last N dimensions of a unit vector and the result is &lt;em&gt;not&lt;/em&gt; unit length anymore — you've changed its magnitude without touching its direction. If you then compare that truncated vector against others with cosine similarity or dot product without renormalizing first, you get numbers that are subtly, silently wrong. Nothing throws an error. Your search results just get quietly worse, and it's the kind of bug that's genuinely hard to notice unless you're specifically looking for it, because the vectors still &lt;em&gt;look&lt;/em&gt; like embeddings — same shape, plausible-looking floats.&lt;/p&gt;

&lt;p&gt;Providers that expose a native truncation parameter (OpenAI's &lt;code&gt;dimensions&lt;/code&gt; argument, for instance) handle this for you automatically. The gap is when you're truncating a vector yourself — a value you already stored at full size and want to shrink for a cheaper index, or a self-hosted Matryoshka-trained model where you're slicing the array by hand. In that case the fix really is one line: take the L2 norm of the truncated slice (square root of the sum of its squared values) and divide every value by it. The tool does exactly that — paste a vector, pick a target dimension, get back the renormalized result plus its norm before and after, so you can confirm it's actually unit length again.&lt;/p&gt;

&lt;p&gt;I also added an optional second-vector field so you can paste a comparison vector and see cosine similarity computed two ways: on the original full-length vectors, and on both truncated-and-renormalized. The two numbers won't be identical — you did genuinely discard information, so a small shift is the expected accuracy/size tradeoff Matryoshka truncation is built around — but seeing the actual delta for your own real vectors is a lot more convincing than trusting a blog post's abstract claim that "it should still work reasonably well." Everything runs as plain array arithmetic in your browser tab; nothing you paste is sent anywhere.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
