@mcp-layer/test-server 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Arnout Kazemier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @mcp-layer/test-server
2
+
3
+ `@mcp-layer/test-server` is a feature-complete MCP server used for integration tests and local experiments. It mirrors the tool, resource, prompt, sampling, elicitation, notification, and MCP Apps features expected by the official SDK so clients can validate end-to-end behavior against a single, real server.
4
+
5
+ ## Why this exists
6
+
7
+ The rest of this workspace depends on a real MCP server to validate protocols. This server:
8
+ - Runs over stdio (and optional HTTP/SSE transports)
9
+ - Exercises all major MCP capabilities
10
+ - Exposes stable fixtures for tests and demos
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ # npm
16
+ npm install @mcp-layer/test-server
17
+ # pnpm
18
+ pnpm add @mcp-layer/test-server
19
+ # yarn
20
+ yarn add @mcp-layer/test-server
21
+ ```
22
+
23
+ Requirements: Node.js 20+ (or Deno/Bun with Node compatibility).
24
+
25
+ ## Quick start
26
+
27
+ ### Stdio transport
28
+
29
+ ```bash
30
+ npx mcp-test-server
31
+ ```
32
+
33
+ ### Streamable HTTP + SSE transport
34
+
35
+ ```bash
36
+ npx mcp-test-server-http --port 3333
37
+ ```
38
+
39
+ This exposes:
40
+ - Streamable HTTP on `/mcp`
41
+ - SSE compatibility on `/sse` and `/sse/messages`
42
+
43
+ ## What it provides
44
+
45
+ ### Tools
46
+
47
+ - `echo` -- returns text, structured output
48
+ - `add` -- arithmetic + structured output
49
+ - `annotated` -- tool annotations + `_meta` coverage
50
+ - `dashboard` -- MCP Apps UI metadata via `_meta.ui`
51
+ - `files` -- emits `resource_link` payloads
52
+ - `present` -- mixed content types (text/markdown/image/audio/resource/resource_link)
53
+ - `summaries` -- sampling support
54
+ - `booking` -- elicitation support
55
+ - `roots` -- roots capability
56
+ - `note-update` -- mutable resources
57
+ - `logs` -- logging notifications
58
+ - `progress` -- progress notifications
59
+ - `rebalance` -- debounced list updates
60
+
61
+ ### Resources
62
+
63
+ - `resource://manual` -- markdown manual
64
+ - `note://{topic}/{detail}` -- template-backed resources with completions
65
+ - `ui://dashboard/app.html` -- MCP Apps HTML UI
66
+
67
+ ### Prompts
68
+
69
+ - `welcome` -- prompt arguments with completions
70
+
71
+ ### MCP Apps
72
+
73
+ - `dashboard` tool advertises `_meta.ui.resourceUri`
74
+ - `ui://dashboard/app.html` serves HTML and `_meta.ui` settings (`csp`, `permissions`)
75
+
76
+ ## Structure
77
+
78
+ - `src/tools/*` -- tool implementations
79
+ - `src/resources/*` -- resource + template handlers
80
+ - `src/prompts/*` -- prompt definitions
81
+ - `src/data/*` -- shared manual text and fixtures
82
+ - `src/shared/*` -- utilities and capability checks
83
+
84
+ ## Spec support matrix
85
+
86
+ | Feature | Status | Notes |
87
+ | --- | --- | --- |
88
+ | Tools (structured outputs, resource links) | yes | `echo`, `add`, `files`, `summaries`, `booking`, `roots`, `logs`, `progress`, `rebalance`. |
89
+ | Tool annotations + metadata | yes | `annotated` exposes `annotations` + `_meta`. |
90
+ | MCP Apps (UI resources) | yes | `dashboard` tool exposes `_meta.ui.resourceUri`, `ui://dashboard/app.html` serves HTML. |
91
+ | Mixed content outputs | yes | `present` emits text/markdown/image/audio/resource/resource_link. |
92
+ | Resources & dynamic templates | yes | `resource://manual`, `note://{topic}/{detail}` with completions. |
93
+ | Prompts & completion API | yes | `welcome` prompt + `completion/complete`. |
94
+ | Sampling (`sampling/createMessage`) | yes | `summaries` tool proxies sampling and validates responses. |
95
+ | Elicitation (`elicitation/create`) | yes | `booking` tool requests alternate booking info. |
96
+ | Roots (`roots/list`) | yes | `roots` tool lists file URIs from capable clients. |
97
+ | Resource subscriptions/updates | yes | `resources/subscribe` + `note-update` tool emit `notifications/resources/updated`. |
98
+ | Logging notifications | yes | `logs` tool emits `notifications/message`. |
99
+ | Progress notifications | yes | `progress` tool streams `notifications/progress`. |
100
+ | Debounced list notifications | yes | `rebalance` tool coalesces updates. |
101
+ | Alternate transports (Streamable HTTP / SSE) | yes | `mcp-test-server-http` bootstraps both. |
102
+ | Auth/OAuth proxying | no | Out of scope for this fixture. |
103
+
104
+ ## Usage in tests
105
+
106
+ Use it as the real server target for integration tests:
107
+
108
+ ```js
109
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
110
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
111
+
112
+ const transport = new StdioClientTransport({
113
+ command: process.execPath,
114
+ args: [require.resolve('@mcp-layer/test-server/src/bin.js')]
115
+ });
116
+
117
+ const client = new Client({ name: 'demo', version: '0.0.0' });
118
+ await client.connect(transport);
119
+ ```
120
+
121
+ ## Testing
122
+
123
+ ```bash
124
+ pnpm test --filter @mcp-layer/test-server
125
+ ```
126
+
127
+ The suite boots the real binary, exercises all tools/resources/prompts, verifies notifications, and asserts MCP Apps metadata so this fixture remains trustworthy.
128
+
129
+ ## Extending the server
130
+
131
+ When adding capabilities:
132
+ 1) Implement the feature in `src/*`.
133
+ 2) Add or update a test in `test/index.test.js`.
134
+ 3) Document it in this README.
135
+
136
+ ## License
137
+
138
+ MIT
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@mcp-layer/test-server",
3
+ "version": "0.0.0",
4
+ "description": "Feature-rich MCP server for integration testing and local development.",
5
+ "type": "module",
6
+ "keywords": [
7
+ "mcp",
8
+ "server",
9
+ "testing",
10
+ "integration",
11
+ "model-context-protocol"
12
+ ],
13
+ "scripts": {
14
+ "test": "node --test --experimental-test-coverage ./test/**/*.js"
15
+ },
16
+ "bin": {
17
+ "mcp-test-server": "src/bin.js",
18
+ "mcp-test-server-http": "src/http.js"
19
+ },
20
+ "main": "src/index.js",
21
+ "exports": {
22
+ ".": {
23
+ "import": "./src/index.js",
24
+ "default": "./src/index.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "src",
30
+ "LICENSE"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.20.2",
37
+ "express": "^5.0.1",
38
+ "zod": "^3.25.76"
39
+ }
40
+ }
package/src/bin.js ADDED
@@ -0,0 +1,14 @@
1
+ import { start } from './index.js';
2
+
3
+ /**
4
+ * Launch the test MCP server over stdio.
5
+ * @returns {Promise<void>}
6
+ */
7
+ async function main() {
8
+ await start();
9
+ }
10
+
11
+ main().catch(function handle(error) {
12
+ console.error(error);
13
+ process.exitCode = 1;
14
+ });
package/src/config.js ADDED
@@ -0,0 +1,12 @@
1
+ export const info = { name: 'mcp-test-server', version: '0.1.0' };
2
+
3
+ export const instructions = [
4
+ 'MCP test server exercises tools, resources, prompts, completions, sampling, elicitation, roots, and debounced notifications',
5
+ 'so every integration path described in the TypeScript SDK README is available for automated verification.'
6
+ ].join(' ');
7
+
8
+ export const debouncedNotifications = [
9
+ 'notifications/tools/list_changed',
10
+ 'notifications/resources/list_changed',
11
+ 'notifications/prompts/list_changed'
12
+ ];
@@ -0,0 +1,22 @@
1
+ export const manual = [
2
+ '# MCP Test Server Manual',
3
+ '',
4
+ '## Tools',
5
+ '- echo: text echo with loud mode for uppercase coverage.',
6
+ '- add: deterministic math returning structured totals.',
7
+ '- files: emits ResourceLinks for manual and note resources.',
8
+ '- summaries: requests sampling/createMessage from the client.',
9
+ '- booking: demonstrates elicitation/create workflows.',
10
+ '- roots: requests roots/list from capable clients.',
11
+ '- logs: streams notifications/message entries at configurable levels.',
12
+ '- progress: emits notifications/progress updates tied to request tokens.',
13
+ '- note-update: mutates note:// resources and emits notifications/resources/updated.',
14
+ '- rebalance: toggles tool registrations to showcase notification debouncing.',
15
+ '',
16
+ '## Resources',
17
+ '- resource://manual exposes this manual.',
18
+ '- note://{topic}/{detail} template lists summaries and usage guidance for every feature.',
19
+ '',
20
+ '## Prompts',
21
+ '- welcome prompt provides name and tone completions for guided prompting.'
22
+ ].join('\n');
@@ -0,0 +1,72 @@
1
+ export const notes = new Map([
2
+ [
3
+ 'echo',
4
+ {
5
+ summary: 'Echo repeats provided text and can force uppercase responses with the loud flag.',
6
+ usage: 'Call echo with loud true to validate structured responses stay in sync with text output.'
7
+ }
8
+ ],
9
+ [
10
+ 'add',
11
+ {
12
+ summary: 'Add combines two numbers and reports totals inside structuredContent for schema validation demos.',
13
+ usage: 'Use add to confirm numeric handling prior to driving more complex tools.'
14
+ }
15
+ ],
16
+ [
17
+ 'files',
18
+ {
19
+ summary: 'Files returns resource_link entries so clients can lazily fetch manual and note content.',
20
+ usage: 'Provide a filter string to narrow which ResourceLinks appear in the response.'
21
+ }
22
+ ],
23
+ [
24
+ 'summaries',
25
+ {
26
+ summary: 'Summaries requests sampling/createMessage and relays the returned completion back to the caller.',
27
+ usage: 'Declare sampling capability before connecting so the server can proxy summarization requests.'
28
+ }
29
+ ],
30
+ [
31
+ 'booking',
32
+ {
33
+ summary: 'Booking elicits alternate dates when the requested reservation is full.',
34
+ usage: 'Clients respond to elicitation/create by echoing confirmAlternate and alternateDate fields.'
35
+ }
36
+ ],
37
+ [
38
+ 'roots',
39
+ {
40
+ summary: 'Roots fetches file:// locations from the client by issuing roots/list.',
41
+ usage: 'Declare roots capability and provide a handler that returns accessible filesystem URIs.'
42
+ }
43
+ ],
44
+ [
45
+ 'logs',
46
+ {
47
+ summary: 'Logs forwards structured payloads via notifications/message respecting client log levels.',
48
+ usage: 'Set logging/setLevel before invoking logs to ensure notifications arrive.'
49
+ }
50
+ ],
51
+ [
52
+ 'progress',
53
+ {
54
+ summary: 'Progress sends notifications/progress tied to supplied request tokens during long tasks.',
55
+ usage: 'Call the progress tool with a _meta.progressToken to observe incremental updates.'
56
+ }
57
+ ],
58
+ [
59
+ 'note-update',
60
+ {
61
+ summary: 'Note-update mutates note resources and triggers notifications/resources/updated.',
62
+ usage: 'Subscribe to note:// URIs before calling this tool to receive updates.'
63
+ }
64
+ ],
65
+ [
66
+ 'rebalance',
67
+ {
68
+ summary: 'Rebalance flips tool registrations repeatedly to prove notification debouncing works.',
69
+ usage: 'Expect only one notifications/tools/list_changed message even if multiple toggles run.'
70
+ }
71
+ ]
72
+ ]);
@@ -0,0 +1,20 @@
1
+ export const references = [
2
+ {
3
+ uri: 'resource://manual',
4
+ name: 'manual',
5
+ description: 'Server manual covering every feature.',
6
+ mimeType: 'text/markdown'
7
+ },
8
+ {
9
+ uri: 'note://echo/summary',
10
+ name: 'echo-note',
11
+ description: 'Echo behavior summary.',
12
+ mimeType: 'text/plain'
13
+ },
14
+ {
15
+ uri: 'note://summaries/usage',
16
+ name: 'sampling-note',
17
+ description: 'Instructions for sampling-aware clients.',
18
+ mimeType: 'text/plain'
19
+ }
20
+ ];
package/src/http.js ADDED
@@ -0,0 +1,6 @@
1
+ import { startHttpServer } from './transport/http.js';
2
+
3
+ startHttpServer().catch(error => {
4
+ console.error(error);
5
+ process.exitCode = 1;
6
+ });
package/src/index.js ADDED
@@ -0,0 +1,54 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { debouncedNotifications, info, instructions } from './config.js';
4
+ import { manual } from './data/manual.js';
5
+ import { notes } from './data/notes.js';
6
+ import { references } from './data/references.js';
7
+ import { registerPrompts } from './prompts/index.js';
8
+ import { registerResources } from './resources/index.js';
9
+ import { createCapabilityChecker } from './shared/capabilities.js';
10
+ import { registerResourceSubscriptions } from './subscriptions/resources.js';
11
+ import { registerTools } from './tools/index.js';
12
+
13
+ /**
14
+ * Build an MCP server instance pre-configured with tools, resources, and prompts.
15
+ * @returns {McpServer}
16
+ */
17
+ export function build() {
18
+ const server = new McpServer(info, {
19
+ instructions,
20
+ debouncedNotificationMethods: debouncedNotifications,
21
+ capabilities: {
22
+ logging: {}
23
+ }
24
+ });
25
+
26
+ const context = { manual, notes, references };
27
+ const hasCapability = createCapabilityChecker(server);
28
+ const subscriptions = registerResourceSubscriptions(server, {
29
+ isSubscribable(uri) {
30
+ return uri.startsWith('note://');
31
+ }
32
+ });
33
+
34
+ registerTools(server, context, { hasCapability }, subscriptions);
35
+ registerResources(server, context);
36
+ registerPrompts(server);
37
+
38
+ return server;
39
+ }
40
+
41
+ /**
42
+ * Start the test server using stdio transport for integration scenarios.
43
+ * @param {{ server?: McpServer, transport?: StdioServerTransport }} [opts]
44
+ * @returns {Promise<{ server: McpServer, transport: StdioServerTransport }>}
45
+ */
46
+ export async function start(opts = {}) {
47
+ const server = opts.server ?? build();
48
+ const transport = opts.transport ?? new StdioServerTransport();
49
+
50
+ // Use stdio transport so callers can spawn this server as a subprocess during tests.
51
+ await server.connect(transport);
52
+
53
+ return { server, transport };
54
+ }
@@ -0,0 +1,9 @@
1
+ import { registerWelcomePrompt } from './welcome.js';
2
+
3
+ /**
4
+ * Register all prompts exposed by the server.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ */
7
+ export function registerPrompts(server) {
8
+ registerWelcomePrompt(server);
9
+ }
@@ -0,0 +1,63 @@
1
+ import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
2
+ import { z } from 'zod';
3
+
4
+ const greeters = ['Ada', 'Casey', 'Echo', 'Mira'];
5
+ const tones = ['casual', 'formal', 'cheerful'];
6
+
7
+ /**
8
+ * Register the welcome prompt with completable arguments.
9
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
10
+ */
11
+ export function registerWelcomePrompt(server) {
12
+ const argsSchema = {
13
+ name: completable(z.string().min(1, 'Name is required'), function completeName(value) {
14
+ const match = value.toLowerCase();
15
+ return greeters.filter(function filterGreeters(entry) {
16
+ return entry.toLowerCase().startsWith(match);
17
+ });
18
+ }),
19
+ tone: completable(z.enum(tones).default('casual'), function completeTone(value, context) {
20
+ const focus = context?.arguments?.name?.toLowerCase();
21
+ const pool = focus === 'ada' ? ['formal', 'casual'] : tones;
22
+ return pool.filter(function filterTone(entry) {
23
+ return entry.startsWith(value);
24
+ });
25
+ })
26
+ };
27
+
28
+ /**
29
+ * Produce a greeting message tailored to the provided tone.
30
+ * @param {{ name: string, tone: 'casual' | 'formal' | 'cheerful' }} args
31
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').GetPromptResult>}
32
+ */
33
+ async function welcomePrompt(args) {
34
+ const base =
35
+ args.tone === 'formal'
36
+ ? `Greetings ${args.name}, welcome to the MCP test server.`
37
+ : args.tone === 'cheerful'
38
+ ? `Hi ${args.name}! Enjoy the feature-complete MCP test server!`
39
+ : `Hey ${args.name}! Enjoy exploring the test server.`;
40
+
41
+ return {
42
+ messages: [
43
+ {
44
+ role: 'assistant',
45
+ content: {
46
+ type: 'text',
47
+ text: base
48
+ }
49
+ }
50
+ ]
51
+ };
52
+ }
53
+
54
+ server.registerPrompt(
55
+ 'welcome',
56
+ {
57
+ title: 'Welcome Prompt',
58
+ description: 'Generate a greeting message with customizable tone and completions.',
59
+ argsSchema
60
+ },
61
+ welcomePrompt
62
+ );
63
+ }
@@ -0,0 +1,14 @@
1
+ import { registerManualResource } from './manual.js';
2
+ import { registerNotesResource } from './notes.js';
3
+ import { registerUi } from './ui.js';
4
+
5
+ /**
6
+ * Register all resources exposed by the server.
7
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
8
+ * @param {{ manual: string, notes: Map<string, Record<string, string>> }} context
9
+ */
10
+ export function registerResources(server, context) {
11
+ registerManualResource(server, context);
12
+ registerNotesResource(server, context);
13
+ registerUi(server);
14
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Register the markdown manual resource.
3
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
4
+ * @param {{ manual: string }} context
5
+ */
6
+ export function registerManualResource(server, context) {
7
+ /**
8
+ * Return a markdown manual describing server features.
9
+ * @param {URL} uri
10
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').ReadResourceResult>}
11
+ */
12
+ async function manualRead(uri) {
13
+ return {
14
+ contents: [
15
+ {
16
+ uri: uri.href,
17
+ text: context.manual
18
+ }
19
+ ]
20
+ };
21
+ }
22
+
23
+ server.registerResource(
24
+ 'manual',
25
+ 'resource://manual',
26
+ {
27
+ title: 'Server Manual',
28
+ description: 'Describes how to interact with the test server.',
29
+ mimeType: 'text/markdown',
30
+ icons: [
31
+ {
32
+ src: 'https://example.test/assets/manual.png',
33
+ mimeType: 'image/png',
34
+ sizes: ['48x48']
35
+ }
36
+ ],
37
+ _meta: {
38
+ owner: 'mcp-layer-schema'
39
+ }
40
+ },
41
+ manualRead
42
+ );
43
+ }
@@ -0,0 +1,104 @@
1
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+
3
+ /**
4
+ * Register the dynamic notes resource template with completions.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ * @param {{ notes: Map<string, Record<string, string>> }} context
7
+ */
8
+ export function registerNotesResource(server, context) {
9
+ const template = new ResourceTemplate('note://{topic}/{detail}', {
10
+ list: async function listNotes() {
11
+ return {
12
+ resources: Array.from(context.notes.entries()).flatMap(function mapNotes([topic, detailSet]) {
13
+ return Object.keys(detailSet).map(function mapDetail(detail) {
14
+ return {
15
+ uri: `note://${topic}/${detail}`,
16
+ name: `${topic}-${detail}`,
17
+ description: `Notes covering ${topic} (${detail}).`
18
+ };
19
+ });
20
+ })
21
+ };
22
+ },
23
+ complete: {
24
+ topic: async function completeTopic(value) {
25
+ const match = value.toLowerCase();
26
+ return Array.from(context.notes.keys()).filter(function filterTopics(topic) {
27
+ return topic.startsWith(match);
28
+ });
29
+ },
30
+ detail: async function completeDetail(value, completionContext) {
31
+ const topic = completionContext?.arguments?.topic;
32
+ const pool = topic && context.notes.get(topic) ? Object.keys(context.notes.get(topic)) : ['summary', 'usage'];
33
+ return pool.filter(function filterDetail(entry) {
34
+ return entry.startsWith(value);
35
+ });
36
+ }
37
+ }
38
+ });
39
+
40
+ /**
41
+ * Derive note topic and detail for a URI or template variable set.
42
+ * @param {URL} uri
43
+ * @param {{ topic?: string, detail?: string }} [variables]
44
+ * @returns {{ topic: string, detail: string }}
45
+ */
46
+ function noteParts(uri, variables = {}) {
47
+ const topic = variables.topic ?? uri.hostname ?? uri.pathname.replace(/^\//, '');
48
+ const rawDetail = variables.detail ?? uri.pathname.replace(/^\//, '');
49
+ const detail = rawDetail || 'summary';
50
+ return { topic, detail };
51
+ }
52
+
53
+ /**
54
+ * Read a dynamic note resource for the requested topic/detail pair.
55
+ * @param {URL} uri
56
+ * @param {{ topic?: string, detail?: string }} [variables]
57
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').ReadResourceResult>}
58
+ */
59
+ async function noteRead(uri, variables = {}) {
60
+ const parts = noteParts(uri, variables);
61
+ const topicNotes = context.notes.get(parts.topic);
62
+ const text = topicNotes?.[parts.detail];
63
+ if (!text) {
64
+ return {
65
+ contents: [
66
+ {
67
+ uri: uri.href,
68
+ text: `No ${parts.detail} note is available for ${parts.topic}.`
69
+ }
70
+ ]
71
+ };
72
+ }
73
+
74
+ return {
75
+ contents: [
76
+ {
77
+ uri: uri.href,
78
+ text
79
+ }
80
+ ]
81
+ };
82
+ }
83
+
84
+ server.registerResource(
85
+ 'notes',
86
+ template,
87
+ {
88
+ title: 'Feature Notes',
89
+ description: 'Dynamic notes for each feature, including summaries and usage guidance.',
90
+ mimeType: 'text/plain',
91
+ icons: [
92
+ {
93
+ src: 'https://example.test/assets/notes.svg',
94
+ mimeType: 'image/svg+xml',
95
+ sizes: ['any']
96
+ }
97
+ ],
98
+ _meta: {
99
+ owner: 'mcp-layer-schema'
100
+ }
101
+ },
102
+ noteRead
103
+ );
104
+ }