---
title: 'Embedded MCP Server σε Production: AI Agents μέσα στο TypeScript Backend'
description: >-
  Το Model Context Protocol (MCP) συνήθως παρουσιάζεται ως standalone server, αλλά το πραγματικό production unlock είναι
  να ενσωματώσεις έναν MCP server μέσα στο υπάρχον TypeScript backend σου, ώστε οι Claude agents να κάνουν direct tool
  calls στα δεδομένα σου χωρίς ενδιάμεσο REST hop. Δύο πραγματικά MCP shipments, οι αποφάσεις στο
  @modelcontextprotocol/sdk, και πότε να ενσωματώνεις vs externalise-άρεις.
lang: el
source: https://jimrarras.com/embedded-mcp-servers-production-typescript/
fetched: '2026-08-24T14:13:23.872Z'
---

## Σε δύο σειρές

Έχω παραδώσει δύο production MCP servers σε TypeScript με το [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk):

- **[Heartbeat PIM](https://jimrarras.com/projects/heartbeat-pharmacy/)** — **embedded** MCP server που τρέχει μέσα στο ίδιο Express backend με το REST API του καταλόγου. Ο Claude agent συνδέεται μέσω stdio και κάνει direct tool calls στη βάση προϊόντων. Καμία HTTP overhead, καμία public επιφάνεια.
- **[Rorsa Tools](https://jimrarras.com/projects/rorsa-tools/)** — **standalone** MCP server, που διανέμεται ως Claude Code plugin. Ο ίδιος TypeScript κώδικας λειτουργεί και ως CLI binary για developers που θέλουν να χρησιμοποιήσουν το toolkit από το terminal.

Η απόφαση «embedded vs standalone» καθορίζει σχεδόν τα πάντα στο υπόλοιπο του build. Αυτό το άρθρο αναλύει και τα δύο σχήματα, την πραγματική αρχιτεκτονική, τα trade-offs, και πότε διαλέγεις το καθένα.

## Τι είναι πραγματικά ένας MCP server

Το [Model Context Protocol](https://modelcontextprotocol.io/) είναι ένα JSON-RPC-over-stdio (ή HTTP / SSE) protocol που εκδίδει η Anthropic, και επιτρέπει σε έναν AI agent (Claude, Claude Code, Continue, κ.λπ.) να καλεί **tools** που εσύ ορίζεις και να διαβάζει **resources** που εκθέτεις. Από την οπτική του agent, τα tools σου μοιάζουν απαράλλακτα από τα built-in.

Ένας μίνιμαλ TypeScript MCP server μοιάζει έτσι:

```
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new McpServer({ name: 'pim', version: '1.0.0' });

server.tool(
  'get_product',
  { sku: { type: 'string' } },
  async ({ sku }) => {
    const product = await db.products.findOne({ where: { sku } });
    return { content: [{ type: 'text', text: JSON.stringify(product) }] };
  }
);

await server.connect(new StdioServerTransport());
```

Αυτό είναι όλο το «API». Ο Claude agent στην άλλη άκρη παίρνει ένα tool με όνομα `get_product` που μπορεί να καλέσει με ένα SKU.

## Το standalone σχήμα: Rorsa Tools

Το [Rorsa Tools](https://jimrarras.com/projects/rorsa-tools/) είναι ένα published MCP toolkit (και Claude Code plugin) που εκθέτει 14 image-generation, editing, και processing tools σε AI agents. Είναι το toolkit που δημιούργησε κάθε pixel-art εικόνα σε αυτό το portfolio.

Η αρχιτεκτονική:

- **CLI binary** (`vertex-img`) — Commander-driven CLI για developers που θέλουν να τρέξουν τα ίδια tools από το terminal
- **MCP server** (`vertex-image-mcp`) — εκθέτει τα ίδια 14 tools σε Claude agents
- **Ένα codebase, δύο interfaces** — η κεντρική αρχιτεκτονική απόφαση

Παραδείγματα tool surface:

- `generate_image` — Vertex AI Nano Banana ή Gemini Flash, ανάλογα με ένα text-density heuristic
- `remove_background` — τρέχει σε Python sidecar process (RMBG-2.0 model), επειδή υπάρχει μόνο σε PyTorch
- `generate_responsive_sets` — Sharp pipeline, παράγει 640w/960w/1280w/1920w από ένα source
- `compress_images`, `convert_image_formats`, `resize_images`, `get_image_metadata` — Sharp πάλι
- `generate_favicon` — png-to-ico για το πλήρες favicon bundle

Ένας Claude Code agent μπορεί να αλυσιδώσει `generate_image → remove_background → generate_responsive_sets → compress_images` σε μία συνομιλία, χωρίς script.

**Το standalone είναι σωστό σχήμα όταν:**

- Τα tools είναι χρήσιμα σε πολλά projects
- Θες versioned distribution (Claude Code plugin manifest, npm package)
- Το tool surface είναι portable και δεν χρειάζεται τα ιδιωτικά σου δεδομένα

## Το embedded σχήμα: Heartbeat PIM

Το δεύτερο MCP shipment ζει **μέσα** στο product information management backend του Heartbeat. Το PIM είναι ένα Express 5 + TypeORM service που:

- Φιλοξενεί το REST API που χρησιμοποιεί το React admin για catalog browsing, editing, και order ops
- **Επιπλέον** τρέχει έναν `@modelcontextprotocol/sdk` server στο ίδιο Node process, μιλώντας στην ίδια Postgres βάση

Ο Claude agent συνδέεται μέσω stdio (local IPC, χωρίς HTTP) και μπορεί:

- `get_product(sku)` — διαβάζει product details
- `update_description(sku, description)` — ξαναγράφει μια περιγραφή μετά από AI enrichment
- `enrich_batch(skus, strategy)` — περνά μια λίστα SKUs μέσα από το enrichment pipeline

Το point είναι ότι **ο agent έχει το ίδιο data view με το REST API**. Δεν υπάρχει ξεχωριστό microservice, καμία δημόσια network egress, κανένα API key προς rotation.

### Shared state, shared transactions

Το δύσκολο κομμάτι ενός embedded MCP server είναι το **transaction scoping**. Και οι REST handlers και τα MCP tools χρησιμοποιούν την ίδια TypeORM data source. Αν ένα tool ξεκινήσει transaction για να ενημερώσει ένα product, και μια REST request έρθει μέσα στη μέση, και τα δύο πρέπει να είναι ασφαλή.

Δύο κανόνες το έκαναν tractable:

1. **Τα read-only tools παίρνουν read-only data source.** Τα `get_product`, `list_products`, `search_catalog` χρησιμοποιούν Postgres role που δεν έχει write privileges. Ο agent κυριολεκτικά δεν μπορεί να μεταλλάξει state μέσω αυτών.
2. **Τα write tools παίρνουν explicit transactions.** Το `update_description` τυλίγει τη δουλειά του σε ένα `dataSource.transaction(async (tx) => {...})` block. Αν το REST layer κάνει concurrent update, το Postgres serialize-άρει τα writes· αν ένα write αποτύχει, το transaction κάνει rollback· η MCP response επιστρέφει το error στον agent.

### Γιατί να μην χτυπήσει το REST API απευθείας;

Αυτή είναι η ερώτηση που ακούω συχνότερα. Γιατί ο Claude agent δεν καλεί απλά τα υπάρχοντα REST endpoints; Τρεις λόγοι:

1. **Καμία HTTP overhead.** Το stdio είναι local IPC. Κάθε 1ms κλήση αντικαθιστά ένα 30ms HTTP round-trip.
2. **Καμία public επιφάνεια.** Ο MCP server δεν είναι network-exposed. Δεν υπάρχει port να μπλοκάρεις, ούτε API key να διαχειριστείς, ούτε rate-limit να ρυθμίσεις.
3. **Tool semantics > endpoint semantics.** Ένα REST endpoint σχηματίζεται γύρω από HTTP verbs και resource paths. Ένα MCP tool σχηματίζεται γύρω από αυτό που ο AI agent χρειάζεται να *κάνει*. Το `enrich_batch(skus, strategy: 'descriptions' | 'keywords' | 'all')` είναι ρήμα. Το `POST /api/products/bulk-enrich` είναι route. Δεν είναι στο ίδιο επίπεδο abstraction.

**Το embedded είναι σωστό σχήμα όταν:**

- Τα δεδομένα είναι ιδιωτικά και δεν πρέπει να βγαίνουν εκτός process boundary
- Ο agent χρειάζεται high-frequency tool surface (σκέψου enrichment πάνω σε χιλιάδες προϊόντα)
- Τα tools είναι coupled με το business data model σου, με τρόπο που δεν θα είχε νόημα ως generic toolkit

## Δύο production decisions που αξίζει να αντιγράψεις

### 1\. Μη περνάς JSON blobs ως tool args

Ο πειρασμός είναι να φτιάξεις generic `query_product(filter: object)` tool που δέχεται αυθαίρετο filter. Μην το κάνεις. Όρισε narrow, named tools — `find_by_sku`, `find_by_vendor`, `list_low_stock` — καθένα με explicit args. Ο Claude είναι δραματικά καλύτερος στο να διαλέγει το σωστό tool παρά στο να κατασκευάζει το σωστό filter blob.

### 2\. Επιστρέφε text content, όχι raw JSON

Τα tools επιστρέφουν content μέσω `{ content: [{ type: 'text', text: '...' }] }`. Ο agent διαβάζει αυτό το text. Αν επιστρέφεις raw JSON, ο agent πρέπει να το κάνει parse. Αν επιστρέφεις μια παράγραφο summary συν το JSON, μπορεί και να διαβάσει το summary γρήγορα και να κάνει parse το detail αν χρειαστεί:

```
return {
  content: [
    { type: 'text', text: `Product ${product.sku}: ${product.name} — ${product.stock} in stock, last updated ${product.updatedAt}` },
    { type: 'text', text: JSON.stringify(product, null, 2) },
  ],
};
```

Αυτή είναι η μεγαλύτερη μεμονωμένη tool-quality νίκη που έχω μετρήσει. Το πρώτο text block είναι token-cheap summary· το δεύτερο είναι τα raw data που ο agent μπορεί να χρησιμοποιήσει ως fallback.

## Πότε standalone, πότε embedded

|  | Standalone (Rorsa Tools σχήμα) | Embedded (Heartbeat PIM σχήμα) |
| --- | --- | --- |
| Distribution | npm / Claude Code plugin | Κανένα — εσωτερικό στο host backend |
| Data | Public / χωρίς secrets | Private — business data |
| Tool design | Generic, reusable | Tied στο domain model σου |
| Transport | stdio + προαιρετικό HTTP | μόνο stdio |
| Auth | Μερικές φορές token | Το process boundary ΕΙΝΑΙ το auth |
| Κόστος shipping | Versioned package + docs | Άλλο ένα module στο backend σου |

Αν χτίζεις agentic features για το δικό σου προϊόν, embed. Αν παραδίδεις tools που άλλες ομάδες πρέπει να χρησιμοποιήσουν, standalone.

## Παρόμοια έργα

- [Heartbeat Pharmacy Platform](https://jimrarras.com/projects/heartbeat-pharmacy/) — πλήρες case study για το embedded MCP server build
- [Rorsa Tools](https://jimrarras.com/projects/rorsa-tools/) — πλήρες case study για το standalone MCP toolkit
