@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 +21 -0
- package/README.md +138 -0
- package/package.json +40 -0
- package/src/bin.js +14 -0
- package/src/config.js +12 -0
- package/src/data/manual.js +22 -0
- package/src/data/notes.js +72 -0
- package/src/data/references.js +20 -0
- package/src/http.js +6 -0
- package/src/index.js +54 -0
- package/src/prompts/index.js +9 -0
- package/src/prompts/welcome.js +63 -0
- package/src/resources/index.js +14 -0
- package/src/resources/manual.js +43 -0
- package/src/resources/notes.js +104 -0
- package/src/resources/ui.js +58 -0
- package/src/shared/capabilities.js +18 -0
- package/src/subscriptions/resources.js +70 -0
- package/src/tools/add.js +44 -0
- package/src/tools/annotated.js +44 -0
- package/src/tools/batch.js +43 -0
- package/src/tools/booking.js +107 -0
- package/src/tools/dashboard.js +53 -0
- package/src/tools/echo.js +45 -0
- package/src/tools/files.js +79 -0
- package/src/tools/index.js +37 -0
- package/src/tools/logs.js +56 -0
- package/src/tools/note-update.js +56 -0
- package/src/tools/present.js +76 -0
- package/src/tools/progress.js +85 -0
- package/src/tools/rebalance.js +49 -0
- package/src/tools/roots.js +98 -0
- package/src/tools/summaries.js +96 -0
- package/src/transport/http.js +77 -0
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
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
|
+
}
|