mcp-resource.e2e.spec.ts•6.86 kB
import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { Injectable } from '@nestjs/common';
import { McpModule } from '../src/mcp/mcp.module';
import { createSseClient } from './utils';
import { Resource, ResourceTemplate } from '../src';
@Injectable()
export class GreetingToolResource {
constructor() {}
@Resource({
name: 'hello-world',
description: 'A simple greeting resource',
mimeType: 'text/plain',
uri: 'mcp://hello-world-world',
})
async sayHello({ uri }) {
return {
contents: [
{
uri,
mimeType: 'text/plain',
text: 'Hello World',
},
],
};
}
@Resource({
name: 'hello-world-with-meta',
description: 'A simple greeting resource with meta',
mimeType: 'text/plain',
uri: 'mcp://hello-world-with-meta',
_meta: {
title: 'Say Hello Resource',
},
})
async sayHelloWithMeta({ uri }) {
return {
contents: [
{
uri,
mimeType: 'text/plain',
text: 'Hello World With Meta',
},
],
};
}
@ResourceTemplate({
name: 'hello-world-dynamic',
description: 'A simple greeting dynamic resource',
mimeType: 'text/plain',
uriTemplate: 'mcp://hello-world-dynamic/{userName}',
})
async sayHelloDynamic({ uri, userName }) {
return {
contents: [
{
uri: uri,
mimeType: 'text/plain',
text: `Hello ${userName}`,
},
],
};
}
@ResourceTemplate({
name: 'hello-world-template-with-meta',
description: 'A simple greeting dynamic resource with meta',
mimeType: 'text/plain',
uriTemplate: 'mcp://hello-world-template-with-meta/{id}',
_meta: {
title: 'Template With Meta',
},
})
async sayHelloTemplateWithMeta({ uri, id }) {
return {
contents: [
{
uri,
mimeType: 'text/plain',
text: `Hello ${id}`,
},
],
};
}
@ResourceTemplate({
name: 'hello-world-dynamic-multiple-paths',
description: 'A simple greeting dynamic resource with multiple paths',
mimeType: 'text/plain',
uriTemplate: 'mcp://hello-world-dynamic-multiple-paths/{userId}/{userName}',
})
async sayHelloMultiplePathsDynamic({ uri, userId, userName }) {
return {
contents: [
{
uri: uri,
mimeType: 'text/plain',
text: `Hello ${userName} from ${userId}`,
},
],
};
}
@ResourceTemplate({
name: 'hello-world-dynamic-multiple-paths-error',
description: 'A simple greeting dynamic resource with multiple paths',
mimeType: 'text/plain',
uriTemplate:
'mcp://hello-world-dynamic-multiple-paths-error/{userId}/{userName}',
})
async sayHelloMultiplePathsDynamicError() {
throw new Error('any error');
}
}
describe('E2E: MCP Resource Server', () => {
let app: INestApplication;
let testPort: number;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
McpModule.forRoot({
name: 'test-mcp-server',
version: '0.0.1',
guards: [],
}),
],
providers: [GreetingToolResource],
}).compile();
app = moduleFixture.createNestApplication();
await app.listen(0);
const server = app.getHttpServer();
testPort = server.address().port;
});
afterAll(async () => {
await app.close();
});
it('should list resources', async () => {
const client = await createSseClient(testPort);
const resources = await client.listResources();
const resourceTemplates = await client.listResourceTemplates();
expect(resources.resources.find((r) => r.name === 'hello-world')).toEqual({
name: 'hello-world',
uri: 'mcp://hello-world-world',
description: 'A simple greeting resource',
mimeType: 'text/plain',
});
const metaResource = resources.resources.find(
(r) => r.name === 'hello-world-with-meta',
);
expect(metaResource).toBeDefined();
expect(metaResource!._meta).toBeDefined();
expect(metaResource!._meta?.title).toBe('Say Hello Resource');
expect(
resourceTemplates.resourceTemplates.find(
(r) => r.name === 'hello-world-dynamic',
),
).toEqual({
name: 'hello-world-dynamic',
uriTemplate: 'mcp://hello-world-dynamic/{userName}',
description: 'A simple greeting dynamic resource',
mimeType: 'text/plain',
});
const metaTemplate = resourceTemplates.resourceTemplates.find(
(r) => r.name === 'hello-world-template-with-meta',
);
expect(metaTemplate).toBeDefined();
expect(metaTemplate!._meta).toBeDefined();
expect(metaTemplate!._meta?.title).toBe('Template With Meta');
await client.close();
});
it('should call the static resource', async () => {
const client = await createSseClient(testPort);
const result = await client.readResource({
uri: 'mcp://hello-world-world',
});
expect(result.contents[0].uri).toBe('mcp://hello-world-world');
expect(result.contents[0].mimeType).toBe('text/plain');
expect(result.contents[0].text).toBe('Hello World');
await client.close();
});
it('should call the dynamic resource', async () => {
const client = await createSseClient(testPort);
const result = await client.readResource({
uri: 'mcp://hello-world-dynamic/Raphael_John',
});
expect(result.contents[0].uri).toBe(
'mcp://hello-world-dynamic/Raphael_John',
);
expect(result.contents[0].mimeType).toBe('text/plain');
expect(result.contents[0].text).toBe('Hello Raphael_John');
await client.close();
});
it('should call the dynamic resource with multiple paths', async () => {
const client = await createSseClient(testPort);
const result = await client.readResource({
uri: 'mcp://hello-world-dynamic-multiple-paths/123/Raphael_John',
});
expect(result.contents[0].uri).toBe(
'mcp://hello-world-dynamic-multiple-paths/123/Raphael_John',
);
expect(result.contents[0].mimeType).toBe('text/plain');
expect(result.contents[0].text).toBe('Hello Raphael_John from 123');
await client.close();
});
it('should return an error when the resource is not found', async () => {
const client = await createSseClient(testPort);
const result = await client.readResource({
uri: 'mcp://hello-world-dynamic-multiple-paths-error/123/Raphael_John',
});
expect(result).toEqual({
contents: [
{
uri: 'mcp://hello-world-dynamic-multiple-paths-error/123/Raphael_John',
mimeType: 'text/plain',
text: 'any error',
},
],
isError: true,
});
await client.close();
});
});