Skip to content

Document Resources

Servers can expose documents as MCP resources: read-only content served over resources/list and resources/read. Three registration methods cover the different container shapes:

How it works

Registration Result
registerDocument(file) One static resource at file:///…/file.md, read lazily on resources/read
registerFolder(dir) Every matching file (default: all supported types) becomes a static resource
registerContentDocument(config) A dynamic resource at content://name, content fetched on every read

Folder scans are recursive and resource names are the file path relative to the registered folder root (e.g. notes.md, sub/guide.md). Files are read lazily on each resources/read, so changes on disk are picked up. A folder with no matching files registers nothing.

Registered files, folders, and content documents appear in the client's resources/list as normal static resources. No resource templates are used.

Content documents

Content documents serve in-memory strings without any file on disk. The content field accepts either a static string or a provider function called on every resources/read, so the content can be regenerated dynamically (e.g. from a registry or cache).

import { StdioMcpServer } from '@johannes.latzel/llm-chat-mcp';

const server = new StdioMcpServer({ name: 'doc-server', version: '1.0.0' });

// Static content
server.registerContentDocument({ name: 'greeting', content: 'Hello world' });

// Dynamic content: regenerated on every read
server.registerContentDocument({
    name: 'skills',
    description: 'Available skills',
    mimeType: 'text/yaml',
    content: () => generateSkillListing() // called on every resources/read
});

await server.start();

The provider may be async. Errors thrown inside the provider are wrapped in an MCP error and reported to the observer (if set).

Field Type Description
name string Unique resource name and URI segment
content string \| () => string \| Promise<string> Static string or provider called on every read
title string Resource title shown by clients
description string Resource description shown by clients
mimeType string MIME type (default: text/markdown)

URI scheme

Content documents use content:// URIs (e.g. content://skills), distinct from the file:// URIs of file and folder resources. This prevents collisions when a file path happens to match a content document name.

MIME types

The MIME type is inferred from the file extension (.mdtext/markdown, .jsonapplication/json, .pdfapplication/pdf, images, and more), falling back to application/octet-stream. Textual MIME types are served as text content; binary types as a base64 blob. Both the file and folder configs accept a mimeType override.

By default registerFolder picks up every supported extension (md, markdown, txt, text, json, yaml, yml, csv, html, htm, xml, svg, pdf, png, jpg, jpeg, gif, webp). Use extensions to narrow the scan.

Example

import { StdioMcpServer } from '@johannes.latzel/llm-chat-mcp';

const server = new StdioMcpServer({ name: 'doc-server', version: '1.0.0' });

server.registerDocument('./README.md');
server.registerFolder('./docs'); // every supported type, recursively
server.registerFolder({ path: './data', extensions: ['md', 'txt'] });
server.registerContentDocument({ name: 'status', content: 'ok' });

await server.start();

Clients then call client.listResources() to see every document and client.readResource({ uri }) to fetch any of them.

Constraints

  • Registering the same file twice (directly or via two overlapping folders) throws, since resource URIs must be unique, mirroring duplicate tool names.
  • Registering a path that does not exist, or registering a directory via registerDocument / a file via registerFolder, throws at registration time.
  • Reading a file that has been deleted after registration throws an MCP error.
  • Content document names must be unique across all registered content documents.

See also: BaseMcpServer, Architecture, Quickstart