How My AI Agents Talk to My Thermal Printer (Using an MCP Server)
Connecting thermal printers to an MCP server so AI-agents can print labels for my home inventory system.
Background
I set up a Notion-based Home Inventory Database and concected it to a Thermal Printer to Easily & Cheaply Print Labels.
Why?
Where is the wrapping paper? Can you find the crayons? Do we have a US-to-EU AC adaptor? Do we need to buy more paper towels?
Instead of someone manually looking these up in our home inventory, I wanted AI to quickly find answers to these types of questions.
Basic Architecture
- Node.js (Nest.js) API Server
with required MCP auth & tool endpoints - Notion Database
this is the data store for our home inventory system
One endpoint, sessions included
The whole thing runs through a single /mcp HTTP endpoint. When an agent connects for the first time, the server spins up a session — basically a transport layer (from the @modelcontextprotocol/sdk) paired with a server instance.
The transport generates a session ID and hands it back in a response header. After that, the agent includes the session ID on every request, and the server routes it to the right session. The MCP SDK handles all the JSON-RPC framing, so the controller is just plumbing — it forwards raw request/response objects to the transport and gets out of the way.
Tools Are Just Functions with Schemas
For each domain in my server — printing, inventory, image recognition — the available tools are registered via registerTools. This method receives the MCP server and calls server.tool() to declare what it can do. This is the Strategy Pattern in practice.
Each tool gets:
- Name
- Plain-English description
- Input schema (written in Zod)
- Handler function
For example, the "print a general label" tool declares inputs like title, subtitle, languages, and size – all typed and validated. When an agent sees this tool, it knows exactly what parameters to provide and what they mean. No prompt engineering gymnastics required. The agent reads the schema, fills in the fields, and calls the tool.
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
/**
* Interface that MCP tool registration services must implement.
* Each provider registers its own set of tools on the McpServer.
*/
export interface McpToolsProvider {
registerTools(server: McpServer): void | Promise<void>;
}The MCP Service
@Injectable()
export class McpService implements OnModuleDestroy {
constructor(
@Inject(MCP_TOOLS_PROVIDERS)
private readonly toolsProviders: McpToolsProvider[],
) {}
...
}Each tool handler is a textbook Service Activator.
The printer doesn't know or care who's asking
This is the nice part. The tool handlers are thin. They validate the input, maybe call out to an external service (a database lookup for inventory data, an LLM call for translation), and then hand everything off to the same print service the web UI uses. Same Handlebars template rendering, same ESC/POS byte generation, same write to the printer. An agent-printed label and a human-printed label are identical.
What it actually took
Honestly, not much. The MCP SDK does the heavy lifting for protocol negotiation and session lifecycle. The tool provider pattern keeps things modular — adding a new printable label type means writing one server.tool() call with a Zod schema and a handler that calls the print service. The hard part was already done: having a clean print pipeline that accepts a template and some data and produces a label. MCP just gave agents a front door to it.
Once the tools were registered, I stopped thinking about the agent integration as something separate. It's just another caller. The same way a button in the UI triggers a print, an agent deciding "this box needs a label" triggers the same print.
That's the whole point: not building AI features, but making existing features available to AI.
Comments ()