@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.
@@ -0,0 +1,58 @@
1
+ const html = [
2
+ '<!doctype html>',
3
+ '<html lang="en">',
4
+ ' <head>',
5
+ ' <meta charset="utf-8" />',
6
+ ' <title>MCP Dashboard</title>',
7
+ ' </head>',
8
+ ' <body>',
9
+ ' <main>',
10
+ ' <h1>Dashboard</h1>',
11
+ ' <p>UI resource served via ui:// scheme.</p>',
12
+ ' </main>',
13
+ ' </body>',
14
+ '</html>',
15
+ ''
16
+ ].join('\n');
17
+
18
+ const uri = 'ui://dashboard/app.html';
19
+
20
+ /**
21
+ * Register the MCP App UI resource.
22
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
23
+ */
24
+ export function registerUi(server) {
25
+ /**
26
+ * Return HTML for the dashboard UI.
27
+ * @param {URL} url
28
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').ReadResourceResult>}
29
+ */
30
+ async function ui(url) {
31
+ return {
32
+ contents: [
33
+ {
34
+ uri: url.href,
35
+ mimeType: 'text/html',
36
+ text: html
37
+ }
38
+ ]
39
+ };
40
+ }
41
+
42
+ server.registerResource(
43
+ 'dashboard-ui',
44
+ uri,
45
+ {
46
+ title: 'Dashboard UI',
47
+ description: 'MCP App UI for the dashboard tool.',
48
+ mimeType: 'text/html',
49
+ _meta: {
50
+ ui: {
51
+ csp: "default-src 'self'",
52
+ permissions: ['clipboard-read']
53
+ }
54
+ }
55
+ },
56
+ ui
57
+ );
58
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Build a capability checker bound to the current server connection.
3
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
4
+ * @returns {(capability: 'sampling' | 'elicitation' | 'roots') => boolean}
5
+ */
6
+ export function createCapabilityChecker(server) {
7
+ /**
8
+ * Determine whether the connected client declared the requested capability.
9
+ * @param {'sampling' | 'elicitation' | 'roots'} capability
10
+ * @returns {boolean}
11
+ */
12
+ function hasCapability(capability) {
13
+ const caps = server.server.getClientCapabilities();
14
+ return Boolean(caps && caps[capability]);
15
+ }
16
+
17
+ return hasCapability;
18
+ }
@@ -0,0 +1,70 @@
1
+ import { ErrorCode, McpError, SubscribeRequestSchema, UnsubscribeRequestSchema } from '@modelcontextprotocol/sdk/types.js';
2
+
3
+ /**
4
+ * Configure basic resource subscriptions on the underlying server transport.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ * @param {{ isSubscribable: (uri: string) => boolean }} options
7
+ * @returns {{ notifyResourceUpdated: (uri: string) => Promise<void> }}
8
+ */
9
+ export function registerResourceSubscriptions(server, options) {
10
+ const subscribers = new Map();
11
+
12
+ server.server.assertCanSetRequestHandler(SubscribeRequestSchema.shape.method.value);
13
+ server.server.assertCanSetRequestHandler(UnsubscribeRequestSchema.shape.method.value);
14
+ server.server.registerCapabilities({
15
+ resources: {
16
+ subscribe: true
17
+ }
18
+ });
19
+
20
+ server.server.setRequestHandler(SubscribeRequestSchema, async (request, extra) => {
21
+ const uri = request.params.uri;
22
+ if (!options.isSubscribable(uri)) {
23
+ throw new McpError(ErrorCode.InvalidParams, `Cannot subscribe to ${uri}`);
24
+ }
25
+ const sessionId = extra.sessionId ?? 'stdio';
26
+ const existing = subscribers.get(sessionId) ?? new Set();
27
+ existing.add(uri);
28
+ subscribers.set(sessionId, existing);
29
+ return {};
30
+ });
31
+
32
+ server.server.setRequestHandler(UnsubscribeRequestSchema, async (request, extra) => {
33
+ const sessionId = extra.sessionId ?? 'stdio';
34
+ if (!sessionId) {
35
+ return {};
36
+ }
37
+ const set = subscribers.get(sessionId);
38
+ if (set) {
39
+ set.delete(request.params.uri);
40
+ if (set.size === 0) {
41
+ subscribers.delete(sessionId);
42
+ }
43
+ }
44
+ return {};
45
+ });
46
+
47
+ server.server.onclose = () => {
48
+ subscribers.clear();
49
+ };
50
+
51
+ /**
52
+ * Notify subscribed clients that a resource has been updated.
53
+ * @param {string} uri
54
+ * @returns {Promise<void>}
55
+ */
56
+ async function notifyResourceUpdated(uri) {
57
+ let hasAudience = false;
58
+ for (const set of subscribers.values()) {
59
+ if (set.has(uri)) {
60
+ hasAudience = true;
61
+ break;
62
+ }
63
+ }
64
+ if (hasAudience) {
65
+ await server.server.sendResourceUpdated({ uri });
66
+ }
67
+ }
68
+
69
+ return { notifyResourceUpdated };
70
+ }
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Register the add tool returning structured totals.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ * @returns {ReturnType<import('@modelcontextprotocol/sdk/server/mcp.js').McpServer['registerTool']>}
7
+ */
8
+ export function registerAdd(server) {
9
+ const inputSchema = {
10
+ first: z.number(),
11
+ second: z.number()
12
+ };
13
+
14
+ /**
15
+ * Sum two numbers to demonstrate structured responses.
16
+ * @param {{ first: number, second: number }} args
17
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').CallToolResult>}
18
+ */
19
+ async function addTool(args) {
20
+ const total = args.first + args.second;
21
+ return {
22
+ content: [
23
+ {
24
+ type: 'text',
25
+ text: `Total: ${total}`
26
+ }
27
+ ],
28
+ structuredContent: { total }
29
+ };
30
+ }
31
+
32
+ return server.registerTool(
33
+ 'add',
34
+ {
35
+ title: 'Add',
36
+ description: 'Combine two numbers and return the sum.',
37
+ inputSchema,
38
+ outputSchema: {
39
+ total: z.number()
40
+ }
41
+ },
42
+ addTool
43
+ );
44
+ }
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Register a tool that exercises annotations and _meta without output schema.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ * @returns {ReturnType<import('@modelcontextprotocol/sdk/server/mcp.js').McpServer['registerTool']>}
7
+ */
8
+ export function registerAnnotated(server) {
9
+ const inputSchema = {
10
+ label: z.string().min(1, 'Label is required')
11
+ };
12
+
13
+ /**
14
+ * Return a plain text response for annotation coverage.
15
+ * @param {{ label: string }} args
16
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').CallToolResult>}
17
+ */
18
+ async function annotatedTool(args) {
19
+ return {
20
+ content: [
21
+ {
22
+ type: 'text',
23
+ text: `Annotated tool saw ${args.label}.`
24
+ }
25
+ ]
26
+ };
27
+ }
28
+
29
+ return server.registerTool(
30
+ 'annotated',
31
+ {
32
+ description: 'Tool used to exercise annotation/title fallback and _meta extraction.',
33
+ inputSchema,
34
+ annotations: {
35
+ title: 'Annotated Tool',
36
+ readOnlyHint: true
37
+ },
38
+ _meta: {
39
+ owner: 'mcp-layer-schema'
40
+ }
41
+ },
42
+ annotatedTool
43
+ );
44
+ }
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Register a tool that accepts array/object inputs.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ * @returns {ReturnType<import('@modelcontextprotocol/sdk/server/mcp.js').McpServer['registerTool']>}
7
+ */
8
+ export function registerBatch(server) {
9
+ const inputSchema = {
10
+ items: z.array(z.string()).min(1, 'Provide at least one item'),
11
+ meta: z.object({ tag: z.string().min(1, 'Tag is required') })
12
+ };
13
+
14
+ /**
15
+ * Summarize array/object inputs.
16
+ * @param {{ items: string[], meta: { tag: string } }} args
17
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').CallToolResult>}
18
+ */
19
+ async function batchTool(args) {
20
+ return {
21
+ content: [
22
+ {
23
+ type: 'text',
24
+ text: `Batch ${args.meta.tag} contains ${args.items.length} items.`
25
+ }
26
+ ],
27
+ structuredContent: {
28
+ count: args.items.length,
29
+ tag: args.meta.tag
30
+ }
31
+ };
32
+ }
33
+
34
+ return server.registerTool(
35
+ 'batch',
36
+ {
37
+ title: 'Batch',
38
+ description: 'Accepts array and object inputs for CLI parsing tests.',
39
+ inputSchema
40
+ },
41
+ batchTool
42
+ );
43
+ }
@@ -0,0 +1,107 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Register the booking tool that triggers elicitation workflows.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ * @param {{ hasCapability: (capability: 'elicitation') => boolean }} capabilities
7
+ */
8
+ export function registerBooking(server, capabilities) {
9
+ const inputSchema = {
10
+ restaurant: z.string().min(1),
11
+ date: z.string().min(1),
12
+ guests: z.number().int().min(1).max(12)
13
+ };
14
+
15
+ /**
16
+ * Demonstrate elicitation by asking for alternate booking dates.
17
+ * @param {{ restaurant: string, date: string, guests: number }} args
18
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').CallToolResult>}
19
+ */
20
+ async function bookingTool(args) {
21
+ const baseMessage = `No tables available at ${args.restaurant} on ${args.date}.`;
22
+ if (!capabilities.hasCapability('elicitation')) {
23
+ return {
24
+ content: [
25
+ {
26
+ type: 'text',
27
+ text: `${baseMessage} Client did not declare elicitation capability.`
28
+ }
29
+ ],
30
+ structuredContent: {
31
+ confirmed: false,
32
+ alternateDate: null,
33
+ message: `${baseMessage} Client lacks elicitation support.`
34
+ }
35
+ };
36
+ }
37
+
38
+ try {
39
+ const result = await server.server.elicitInput({
40
+ message: `${baseMessage} Provide an alternate date to continue.`,
41
+ requestedSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ confirmAlternate: {
45
+ type: 'boolean',
46
+ title: 'Confirm alternate date',
47
+ description: 'Confirm whether we should book an alternate date.'
48
+ },
49
+ alternateDate: {
50
+ type: 'string',
51
+ title: 'Preferred alternate date',
52
+ description: 'ISO-8601 date string for the alternate booking.'
53
+ }
54
+ },
55
+ required: ['confirmAlternate']
56
+ }
57
+ });
58
+
59
+ const confirmed = result.action === 'accept' && Boolean(result.content?.confirmAlternate);
60
+ const alternate = confirmed ? result.content?.alternateDate ?? args.date : null;
61
+ const message = confirmed ? `Reservation confirmed for ${alternate ?? args.date}.` : `${baseMessage} Request declined.`;
62
+ return {
63
+ content: [
64
+ {
65
+ type: 'text',
66
+ text: message
67
+ }
68
+ ],
69
+ structuredContent: {
70
+ confirmed,
71
+ alternateDate: alternate,
72
+ message
73
+ }
74
+ };
75
+ } catch (error) {
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ return {
78
+ content: [
79
+ {
80
+ type: 'text',
81
+ text: message
82
+ }
83
+ ],
84
+ structuredContent: {
85
+ confirmed: false,
86
+ alternateDate: null,
87
+ message
88
+ }
89
+ };
90
+ }
91
+ }
92
+
93
+ server.registerTool(
94
+ 'booking',
95
+ {
96
+ title: 'Booking',
97
+ description: 'Trigger elicitation/create to confirm alternate dates.',
98
+ inputSchema,
99
+ outputSchema: {
100
+ confirmed: z.boolean(),
101
+ alternateDate: z.string().nullable(),
102
+ message: z.string()
103
+ }
104
+ },
105
+ bookingTool
106
+ );
107
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+
3
+ const uri = 'ui://dashboard/app.html';
4
+ const csp = "default-src 'self'";
5
+ const permissions = ['clipboard-read'];
6
+
7
+ /**
8
+ * Register a tool that declares an MCP App UI resource.
9
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
10
+ * @returns {ReturnType<import('@modelcontextprotocol/sdk/server/mcp.js').McpServer['registerTool']>}
11
+ */
12
+ export function registerDashboard(server) {
13
+ const inputSchema = {
14
+ name: z.string().min(1, 'Name is required')
15
+ };
16
+
17
+ /**
18
+ * Return a simple payload while the host renders the UI resource.
19
+ * @param {{ name: string }} args
20
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').CallToolResult>}
21
+ */
22
+ async function dashboardTool(args) {
23
+ return {
24
+ content: [
25
+ {
26
+ type: 'text',
27
+ text: `Dashboard ready for ${args.name}.`
28
+ }
29
+ ],
30
+ structuredContent: {
31
+ name: args.name,
32
+ resourceUri: uri
33
+ }
34
+ };
35
+ }
36
+
37
+ return server.registerTool(
38
+ 'dashboard',
39
+ {
40
+ title: 'Dashboard',
41
+ description: 'Return a dashboard with an MCP App UI resource.',
42
+ inputSchema,
43
+ _meta: {
44
+ ui: {
45
+ resourceUri: uri,
46
+ csp,
47
+ permissions
48
+ }
49
+ }
50
+ },
51
+ dashboardTool
52
+ );
53
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Register the echo tool that optionally uppercases responses.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ * @returns {ReturnType<import('@modelcontextprotocol/sdk/server/mcp.js').McpServer['registerTool']>}
7
+ */
8
+ export function registerEcho(server) {
9
+ const inputSchema = {
10
+ text: z.string().min(1, 'Text must be provided'),
11
+ loud: z.boolean().default(false)
12
+ };
13
+
14
+ /**
15
+ * Repeat supplied text and optionally uppercase the response.
16
+ * @param {{ text: string, loud: boolean }} args
17
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').CallToolResult>}
18
+ */
19
+ async function echoTool(args) {
20
+ const text = args.loud ? args.text.toUpperCase() : args.text;
21
+ return {
22
+ content: [
23
+ {
24
+ type: 'text',
25
+ text
26
+ }
27
+ ],
28
+ structuredContent: { text, loud: args.loud }
29
+ };
30
+ }
31
+
32
+ return server.registerTool(
33
+ 'echo',
34
+ {
35
+ title: 'Echo',
36
+ description: 'Repeat supplied text; uppercase when loud is true.',
37
+ inputSchema,
38
+ outputSchema: {
39
+ text: z.string(),
40
+ loud: z.boolean()
41
+ }
42
+ },
43
+ echoTool
44
+ );
45
+ }
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Register the files tool that emits ResourceLinks referencing shared assets.
5
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
6
+ * @param {{ references: Array<{ uri: string, name: string, description: string, mimeType: string }> }} context
7
+ */
8
+ export function registerFiles(server, context) {
9
+ const inputSchema = {
10
+ filter: z.string().min(1).default('*'),
11
+ limit: z.number().int().min(1).max(context.references.length).default(context.references.length)
12
+ };
13
+
14
+ /**
15
+ * Emit ResourceLinks referencing manual and note resources.
16
+ * @param {{ filter: string, limit: number }} args
17
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').CallToolResult>}
18
+ */
19
+ async function filesTool(args) {
20
+ const term = args.filter === '*' ? '' : args.filter.toLowerCase();
21
+ const matches = context.references
22
+ .filter(function filterReferences(entry) {
23
+ return entry.name.toLowerCase().includes(term);
24
+ })
25
+ .slice(0, args.limit);
26
+
27
+ const payload = {
28
+ count: matches.length,
29
+ files: matches.map(function mapMatches(entry) {
30
+ return {
31
+ uri: entry.uri,
32
+ name: entry.name,
33
+ description: entry.description,
34
+ mimeType: entry.mimeType
35
+ };
36
+ })
37
+ };
38
+
39
+ return {
40
+ content: [
41
+ {
42
+ type: 'text',
43
+ text: `Found ${payload.count} references.`
44
+ },
45
+ ...matches.map(function linkReferences(entry) {
46
+ return {
47
+ type: 'resource_link',
48
+ uri: entry.uri,
49
+ name: entry.name,
50
+ description: entry.description,
51
+ mimeType: entry.mimeType
52
+ };
53
+ })
54
+ ],
55
+ structuredContent: payload
56
+ };
57
+ }
58
+
59
+ server.registerTool(
60
+ 'files',
61
+ {
62
+ title: 'Files',
63
+ description: 'List ResourceLinks for manual and note resources.',
64
+ inputSchema,
65
+ outputSchema: {
66
+ count: z.number().int(),
67
+ files: z.array(
68
+ z.object({
69
+ uri: z.string(),
70
+ name: z.string(),
71
+ description: z.string(),
72
+ mimeType: z.string()
73
+ })
74
+ )
75
+ }
76
+ },
77
+ filesTool
78
+ );
79
+ }
@@ -0,0 +1,37 @@
1
+ import { registerAdd } from './add.js';
2
+ import { registerAnnotated } from './annotated.js';
3
+ import { registerBatch } from './batch.js';
4
+ import { registerBooking } from './booking.js';
5
+ import { registerDashboard } from './dashboard.js';
6
+ import { registerEcho } from './echo.js';
7
+ import { registerFiles } from './files.js';
8
+ import { registerLogs } from './logs.js';
9
+ import { registerNoteUpdate } from './note-update.js';
10
+ import { registerProgress } from './progress.js';
11
+ import { registerPresent } from './present.js';
12
+ import { registerRebalance } from './rebalance.js';
13
+ import { registerRoots } from './roots.js';
14
+ import { registerSummaries } from './summaries.js';
15
+
16
+ /**
17
+ * Register all server tools.
18
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
19
+ * @param {{ references: Array<{ uri: string, name: string, description: string, mimeType: string }> }} context
20
+ * @param {{ hasCapability: (capability: 'sampling' | 'elicitation' | 'roots') => boolean }} capabilities
21
+ */
22
+ export function registerTools(server, context, capabilities, notifier) {
23
+ const echoHandle = registerEcho(server);
24
+ const addHandle = registerAdd(server);
25
+ registerBatch(server);
26
+ registerAnnotated(server);
27
+ registerDashboard(server);
28
+ registerFiles(server, context);
29
+ registerSummaries(server, capabilities);
30
+ registerBooking(server, capabilities);
31
+ registerRoots(server, capabilities);
32
+ registerNoteUpdate(server, context, notifier);
33
+ registerLogs(server);
34
+ registerPresent(server);
35
+ registerProgress(server);
36
+ registerRebalance(server, { echo: echoHandle, add: addHandle });
37
+ }
@@ -0,0 +1,56 @@
1
+ import { LoggingLevelSchema } from '@modelcontextprotocol/sdk/types.js';
2
+ import { z } from 'zod';
3
+
4
+ const levels = LoggingLevelSchema.options;
5
+
6
+ /**
7
+ * Register the logs tool that emits notifications/message entries.
8
+ * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} server
9
+ */
10
+ export function registerLogs(server) {
11
+ const inputSchema = {
12
+ level: z.enum(levels),
13
+ message: z.string().min(1),
14
+ logger: z.string().default('diagnostics')
15
+ };
16
+
17
+ /**
18
+ * Emit a logging notification honoring the client's configured level filter.
19
+ * @param {{ level: typeof levels[number], message: string, logger: string }} args
20
+ * @returns {Promise<import('@modelcontextprotocol/sdk/types.js').CallToolResult>}
21
+ */
22
+ async function logsTool(args) {
23
+ await server.sendLoggingMessage({
24
+ level: args.level,
25
+ logger: args.logger,
26
+ data: args.message
27
+ });
28
+
29
+ return {
30
+ content: [
31
+ {
32
+ type: 'text',
33
+ text: `Logged ${args.level} message.`
34
+ }
35
+ ],
36
+ structuredContent: {
37
+ level: args.level,
38
+ logger: args.logger
39
+ }
40
+ };
41
+ }
42
+
43
+ server.registerTool(
44
+ 'logs',
45
+ {
46
+ title: 'Logs',
47
+ description: 'Send notifications/message payloads at the requested level.',
48
+ inputSchema,
49
+ outputSchema: {
50
+ level: z.enum(levels),
51
+ logger: z.string()
52
+ }
53
+ },
54
+ logsTool
55
+ );
56
+ }