<?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: CMeIn</title>
    <description>The latest articles on DEV Community by CMeIn (@barak066).</description>
    <link>https://dev.to/barak066</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%2F4223709%2F0f9c866d-a7fe-42e8-a6ad-8b9c9eb41ece.png</url>
      <title>DEV Community: CMeIn</title>
      <link>https://dev.to/barak066</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/barak066"/>
    <language>en</language>
    <item>
      <title>We built an MCP server for a consumer product and left the main feature out</title>
      <dc:creator>CMeIn</dc:creator>
      <pubDate>Tue, 03 Nov 2026 07:35:28 +0000</pubDate>
      <link>https://dev.to/barak066/we-built-an-mcp-server-for-a-consumer-product-and-left-the-main-feature-out-m8j</link>
      <guid>https://dev.to/barak066/we-built-an-mcp-server-for-a-consumer-product-and-left-the-main-feature-out-m8j</guid>
      <description>&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%2Fq3gzz1eciiokhoflrluo.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%2Fq3gzz1eciiokhoflrluo.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most MCP servers are developer tools. Databases, file systems, issue trackers, CI. The pattern is familiar: you give an agent a capability it did not have, and the capability is usually "do a thing to a system."&lt;/p&gt;

&lt;p&gt;We ship a consumer product. CMeIn turns a handful of ordinary phone photos of a person into realistic photos of that person, for dating profiles and social media. Last week we published an MCP server for it at &lt;code&gt;https://mcp.cmein.com/mcp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The obvious tool to expose would have been image generation. That is the product. That is what people pay for. We did not expose it, and the reasoning turned out to be the most useful part of the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the server actually does
&lt;/h2&gt;

&lt;p&gt;Six tools need no account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;photo_advice&lt;/code&gt; answers questions about photos of yourself from articles we have published, and returns the source link with the answer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmein_pricing&lt;/code&gt; and &lt;code&gt;cmein_info&lt;/code&gt; read the live pages, so the numbers are current rather than remembered&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;see_examples&lt;/code&gt; and &lt;code&gt;find_video&lt;/code&gt; point at real output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contact_us&lt;/code&gt; sends a message to a person&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four more work on a signed-in account: &lt;code&gt;my_credits&lt;/code&gt;, &lt;code&gt;my_generations&lt;/code&gt;, &lt;code&gt;find_scene&lt;/code&gt; and &lt;code&gt;request_credits_back&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Notice what that list is. It is reading, searching and advising. The one thing the product exists to do is absent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why generation stayed out
&lt;/h2&gt;

&lt;p&gt;Three reasons, in increasing order of how long they took us to articulate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It costs money per call.&lt;/strong&gt; An agent that can spend a user's credits is an agent that can spend them wrongly, in a loop, without the user seeing the prompt that caused it. The failure mode is not a bad response, it is a bill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It needs photos the agent does not have.&lt;/strong&gt; Generation starts from reference photos of a specific person. That upload is a consent moment. Routing it through a chat transcript makes it casual, and it should not be casual.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The output needs to be looked at.&lt;/strong&gt; The product is photographs of a person's face. The review step, where you decide this one looks like you and that one does not, is the product as much as the generation is. An agent returning twelve images into a conversation is not a shortcut, it is a worse version of the thing.&lt;/p&gt;

&lt;p&gt;So the boundary we landed on: the agent can answer questions, read live state and help you decide. The act that costs money, consumes a person's likeness and needs human judgement stays in the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that was genuinely hard
&lt;/h2&gt;

&lt;p&gt;Account-scoped tools over a remote server. &lt;code&gt;my_credits&lt;/code&gt; has to know whose credits.&lt;/p&gt;

&lt;p&gt;The server is a resource server and nothing more. It mints no tokens, holds no secret, and knows nothing about passwords. It verifies an RS256 JWT against the key set published at &lt;code&gt;cmein.com/.well-known/jwks.json&lt;/code&gt;, then checks three things that matter more than the signature does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issuer.&lt;/strong&gt; Must be cmein.com. A valid token from somewhere else is somebody else's token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audience.&lt;/strong&gt; Must be this server. This is RFC 8707, and it is the check that is easy to leave out. A token obtained for one resource used to be replayable against any other resource that trusted the same issuer. Refusing a token whose audience is not you is the entire mitigation, and it is one line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope.&lt;/strong&gt; Per tool. A grant of "see my credits" is not a grant of "spend them", and the person chose between those on the consent screen.&lt;/p&gt;

&lt;p&gt;Two things that cost us time and are worth stealing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;createRemoteJWKSet&lt;/code&gt; from &lt;code&gt;jose&lt;/code&gt; caches the key set and refetches when it sees an unknown &lt;code&gt;kid&lt;/code&gt;, with its own cooldown. That is what lets you rotate keys without redeploying the MCP server, and what stops a stream of bad tokens turning into a stream of requests to your own auth server.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;WWW-Authenticate&lt;/code&gt; header on the 401 is not decoration. A client does not go looking for an authorization server on its own, it does it because a 401 pointed it at the RFC 9728 metadata document. Get that header wrong and the client reports "unauthorized" forever and never offers to connect. It looks like a broken server and it is a missing header.&lt;/p&gt;

&lt;p&gt;One more detail, which loops back to the top. The server defines and enforces a &lt;code&gt;photos:write&lt;/code&gt; scope, and no tool uses it. It is deliberately left out of the advertised scopes, because a consent screen that asks for a permission nothing exercises is asking for more than it needs.&lt;/p&gt;

&lt;p&gt;You can read the whole surface without an account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://mcp.cmein.com/.well-known/oauth-protected-resource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why a consumer product wants this at all
&lt;/h2&gt;

&lt;p&gt;Your assistant already has opinions about profile photos. It assembled them from listicles and from the marketing of companies selling headshots, and it will state them with the same confidence either way.&lt;/p&gt;

&lt;p&gt;We generate these photos for a living and write up what works. The connector hands the assistant those passages with a link, so the claim can be checked instead of trusted.&lt;/p&gt;

&lt;p&gt;This also sits inside a wider position we hold. Our &lt;code&gt;robots.txt&lt;/code&gt; blocks the crawlers that collect training corpora, and explicitly allows the ones that crawl to answer a question and cite a source. We publish an &lt;a href="https://cmein.com/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt;. The MCP server is the same idea taken one step further: do not train on us, do use us, and link back when you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http cmein https://mcp.cmein.com/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source is MIT at &lt;a href="https://github.com/BarakBurger/cmein-mcp" rel="noopener noreferrer"&gt;github.com/BarakBurger/cmein-mcp&lt;/a&gt;, and the setup page for other clients is at &lt;a href="https://cmein.com/mcp" rel="noopener noreferrer"&gt;cmein.com/mcp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am interested in where other people drew this line. If you have shipped an MCP server for something consumer facing, what did you deliberately not expose?&lt;br&gt;
`&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
