Safeguard
AI Security•

How do you build a Node.js MCP server that exposes data to an LLM?

Expose read-only data to an LLM through MCP resources: pick URIs, declare the capability, validate every read, and keep scopes narrow. Steps for Node.js.

Safeguard Security Team
5 min read

To expose data to an LLM from Node.js, build an MCP server that declares the resources capability, lists each item under a unique URI, and returns content when the client sends resources/read. Install the official TypeScript SDK, keep the data read-only, validate every URI, and check permissions before returning anything.

What is an MCP resource, and how is it different from a tool?

The MCP specification describes resources as a standard way for servers to share data that gives a language model context: files, database schemas, application-specific information. Each one has a URI.

A tool is something the model asks the server to do. A resource is something the host application chooses to load. The spec calls resources application-driven. The host might show a picker, let the user search, or include items automatically by some rule. That split matters for security. A resource read should have no side effects, so a mistake in what gets loaded costs you disclosure, not damage.

What does the wire protocol look like?

A server that supports resources declares it during initialization. Two optional features exist: subscribe, so a client can be told when one resource changes, and listChanged, so it is told when the set of resources changes. You can support neither, either or both.

The client then asks for the list, with pagination through a cursor:

{ "jsonrpc": "2.0", "id": 1, "method": "resources/list", "params": {} }

Each entry carries a uri, a name, and optionally a title, description, mimeType and size. To fetch content, the client sends resources/read with the uri, and the result holds a contents array with either text or a base64 blob.

For data that is parameterised, such as one record per product, you publish a resource template with a URI template and the client fills in the variable. Resources can also carry annotations: audience (user, assistant or both), a priority from 0.0 to 1.0, and lastModified. Hosts can use these to decide what goes into context first.

How do you set it up in Node.js?

The official TypeScript SDK publishes server libraries as @modelcontextprotocol/server, and its README says it runs on Node.js, Bun and Deno. Install it with npm install @modelcontextprotocol/server. Transports include stdio for a local process and Streamable HTTP for a remote one. The README also lists thin adapters for Express, Fastify, Hono and the plain Node HTTP server.

The README example registers a tool with a Zod schema. Resources follow the same pattern of registering a handler against a URI or template. Check the SDK's own resources guide for the exact method names in the version you install, because the project has moved between major versions and the spec itself has dated revisions.

A constructed example

Say a support team wants an assistant to read its product catalogue. You publish a resource template whose URI is catalog://products/ followed by a SKU variable, plus a list endpoint that pages through SKUs. The read handler does four things in order: parse the URI, reject anything that does not match the pattern, look up the caller's permissions, then fetch the row from a read-only database user.

The assistant now sees catalogue rows and nothing else. It cannot reach an orders table, because no URI maps there and the database account could not read it anyway.

What can go wrong?

The spec's own security notes for resources are short: servers MUST validate all resource URIs, SHOULD implement access controls for sensitive resources, and SHOULD check permissions before operations. Treat the first as path traversal prevention. A file URI template that accepts parent directory segments hands out your filesystem.

The MCP security best practices page covers the next layer. It warns against token passthrough, where a server accepts a client's token and forwards it to a downstream API without checking it was issued to that server. It also describes confused deputy problems in proxy servers and session hijacking when several stateful servers share session IDs. On scopes, it points out that granting broad scopes up front raises the damage if a token leaks.

Resource content is also untrusted input to the model. A document stored in your database may contain instructions written by someone else. Mark what you can, keep sensitive fields out, and assume the model may repeat whatever you hand it.

How to check or apply this

  1. List every dataset you plan to expose and decide which fields the model never needs.
  2. Give each dataset a URI scheme and a template, and write the validation pattern first.
  3. Connect the server to the data with a read-only account scoped to those datasets.
  4. Check the caller's permission inside the read handler, not only at connection time.
  5. Use narrow scopes in your authorization flow and never forward client tokens downstream.
  6. Log each read with the URI and the identity that asked.
  7. Test with a malformed URI, a traversal attempt and a user who lacks access.

If you run MCP servers from several vendors and teams, Safeguard's MCP Guardrail is built to put policy between agents and the tools and data they reach. It does not replace validating inside your own server.

Quick check

Should data go in a resource or a tool?

Use a resource when the host application should decide what context to load and the read has no side effects. Use a tool when the model needs to trigger an action or run a query with arguments.

Do I need subscribe and listChanged?

No. Both are optional. Add them only if your data changes while a session is open and the client needs to know.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.