Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
.vscode
.idea
.eslintcache
.eslintcache
.npmrc
2,361 changes: 1,224 additions & 1,137 deletions modelcontextprotocol/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion modelcontextprotocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
},
"dependencies": {
"@mastercard/developers-agent-toolkit": "0.1.3",
"@modelcontextprotocol/sdk": "^1.17.0"
"@modelcontextprotocol/sdk": "^1.27.1"
},
"lint-staged": {
"*.{mjs,js,jsx,ts,tsx}": [
Expand Down
3,194 changes: 1,473 additions & 1,721 deletions typescript/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions typescript/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.1.3",
"version": "0.1.4",
"name": "@mastercard/developers-agent-toolkit",
"homepage": "https://github.com/mastercard/developers-agent-toolkit",
"description": "Agent Toolkit for Mastercard Developers Platform",
Expand Down Expand Up @@ -57,9 +57,9 @@
"typescript": "^5.8.3"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.0",
"@modelcontextprotocol/sdk": "^1.27.1",
"node-fetch": "^2.7.0",
"zod": "^3.24.4"
"zod": "^3.25.67"
},
"lint-staged": {
"*.{mjs,js,jsx,ts,tsx}": [
Expand Down
104 changes: 74 additions & 30 deletions typescript/src/modelcontextprotocol/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,107 @@
import { MastercardDevelopersAgentToolkit, buildContext } from '../';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
import { tools } from '@/shared/tools';

const mockTool = jest.spyOn(McpServer.prototype, 'tool');

describe('MastercardDevelopersAgentToolkit', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('should register all tools with correct method/description when no config', () => {
new MastercardDevelopersAgentToolkit({});

function expectDefined<T>(value: T | undefined, message: string): T {
if (value === undefined) {
throw new Error(message);
}

return value;
}

async function listRegisteredTools(
config: ConstructorParameters<typeof MastercardDevelopersAgentToolkit>[0]
) {
const server = new MastercardDevelopersAgentToolkit(config);
const client = new Client(
{ name: 'test-client', version: '1.0.0' },
{ capabilities: {} }
);
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();

await Promise.all([
server.connect(serverTransport),
client.connect(clientTransport),
]);

try {
const result = await client.listTools();
return result.tools;
} finally {
await Promise.all([client.close(), server.close()]);
}
}

it('should list all tools with correct name/description when no config', async () => {
const registeredTools = await listRegisteredTools({});
const expectedTools = tools({});
expect(mockTool).toHaveBeenCalledTimes(expectedTools.length);
expect(registeredTools).toHaveLength(expectedTools.length);

expectedTools.forEach((expectedTool, index) => {
const [method, description] = mockTool.mock.calls[index];
expect(method).toBe(expectedTool.method);
expect(description).toBe(expectedTool.description);
const registeredTool = expectDefined(
registeredTools[index],
`Missing registered tool at index ${index}`
);
expect(registeredTool.name).toBe(expectedTool.name);
expect(registeredTool.title).toBe(expectedTool.title);
expect(registeredTool.description).toBe(expectedTool.description);
expect(registeredTool.annotations).toEqual(expectedTool.annotations);
expect(registeredTool.inputSchema).toBeDefined();
});
});

it('should register context-aware tools when apiSpecificationPath configured', () => {
new MastercardDevelopersAgentToolkit({
it('should list context-aware tools when apiSpecificationPath configured', async () => {
const registeredTools = await listRegisteredTools({
apiSpecification:
'https://static.developer.mastercard.com/content/service/swagger/path.yaml',
});

const expectedTools = tools({
serviceId: 'service',
apiSpecificationPath: '/service/swagger/path.yaml',
}).filter((tool) => tool.method !== 'get-services-list');
expect(mockTool).toHaveBeenCalledTimes(expectedTools.length);
}).filter((tool) => tool.name !== 'get-services-list');
expect(registeredTools).toHaveLength(expectedTools.length);

expectedTools.forEach((expectedTool, index) => {
const [method, description] = mockTool.mock.calls[index];
expect(method).toBe(expectedTool.method);
expect(description).toBe(expectedTool.description);
const registeredTool = expectDefined(
registeredTools[index],
`Missing registered tool at index ${index}`
);
expect(registeredTool.name).toBe(expectedTool.name);
expect(registeredTool.title).toBe(expectedTool.title);
expect(registeredTool.description).toBe(expectedTool.description);
expect(registeredTool.annotations).toEqual(expectedTool.annotations);
expect(registeredTool.inputSchema).toBeDefined();
});
});

it('should exclude get-services-list when serviceId configured', () => {
new MastercardDevelopersAgentToolkit({
it('should exclude get-services-list when serviceId configured', async () => {
const registeredTools = await listRegisteredTools({
service: 'https://developer.mastercard.com/test-service/documentation/',
});

const expectedTools = tools({ serviceId: 'test-service' }).filter(
(tool) => tool.method !== 'get-services-list'
(tool) => tool.name !== 'get-services-list'
);
const registeredMethods = mockTool.mock.calls.map((call) => call[0]);
const registeredNames = registeredTools.map((tool) => tool.name);

expect(registeredMethods).not.toContain('get-services-list');
expect(mockTool).toHaveBeenCalledTimes(expectedTools.length);
expect(registeredNames).not.toContain('get-services-list');
expect(registeredTools).toHaveLength(expectedTools.length);

expectedTools.forEach((expectedTool, index) => {
const [method, description] = mockTool.mock.calls[index];
expect(method).toBe(expectedTool.method);
expect(description).toBe(expectedTool.description);
const registeredTool = expectDefined(
registeredTools[index],
`Missing registered tool at index ${index}`
);
expect(registeredTool.name).toBe(expectedTool.name);
expect(registeredTool.title).toBe(expectedTool.title);
expect(registeredTool.description).toBe(expectedTool.description);
expect(registeredTool.annotations).toEqual(expectedTool.annotations);
expect(registeredTool.inputSchema).toBeDefined();
});
});
});
Expand Down
22 changes: 11 additions & 11 deletions typescript/src/modelcontextprotocol/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ export class MastercardDevelopersAgentToolkit extends McpServer {
super({
name: 'mastercard-developers-mcp',
version: version,
capabilities: {
tools: {},
},
});

this.registerAllTools(config);
Expand All @@ -27,18 +24,22 @@ export class MastercardDevelopersAgentToolkit extends McpServer {
const availableTools = tools(context);
const enabledTools = availableTools.filter((tool) => {
// If serviceId is provided, disable the services list tool
if (context.serviceId && tool.method === 'get-services-list') {
if (context.serviceId && tool.name === 'get-services-list') {
return false;
}

return true;
});

enabledTools.forEach((tool) => {
this.tool(
tool.method,
tool.description,
tool.parameters.shape,
this.registerTool(
tool.name,
{
title: tool.title,
description: tool.description,
inputSchema: tool.parameters,
annotations: tool.annotations,
},
async (params: any) => {
try {
const result = await tool.execute(params);
Expand All @@ -47,9 +48,8 @@ export class MastercardDevelopersAgentToolkit extends McpServer {
const message =
error instanceof Error ? error.message : String(error);
return {
content: [
{ type: 'text' as const, text: message, isError: true },
],
content: [{ type: 'text' as const, text: message }],
isError: true,
};
}
}
Expand Down
10 changes: 8 additions & 2 deletions typescript/src/shared/tools/documentation/getDocumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@ export const execute = async (
};

export const getDocumentation = (context: ToolContext): Tool => ({
method: 'get-documentation',
name: 'Get Documentation',
name: 'get-documentation',
title: 'Get Documentation',
description: getDescription(context),
parameters: getParameters(context),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
execute: (params) => execute(context, params),
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ export const execute = async (
};

export const getDocumentationPage = (context: ToolContext): Tool => ({
method: 'get-documentation-page',
name: 'Get Documentation Page',
name: 'get-documentation-page',
title: 'Get Documentation Page',
description: getDescription(context),
parameters: getParameters(context),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
execute: (params) => execute(context, params),
});
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,15 @@ export const execute = async (
};

export const getDocumentationSection = (context: ToolContext): Tool => ({
method: 'get-documentation-section-content',
name: 'Get Documentation Section Content',
name: 'get-documentation-section-content',
title: 'Get Documentation Section Content',
description: getDescription(context),
parameters: getParameters(context),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
execute: (params) => execute(context, params),
});
10 changes: 8 additions & 2 deletions typescript/src/shared/tools/documentation/getOAuth10aGuide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,15 @@ export const execute = async (
};

export const getOAuth10aGuide = (context: ToolContext): Tool => ({
method: 'get-oauth10a-integration-guide',
name: 'Get OAuth 1.0a Integration Guide',
name: 'get-oauth10a-integration-guide',
title: 'Get OAuth 1.0a Integration Guide',
description: getDescription(context),
parameters: getParameters(context),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
execute: (params) => execute(context, params),
});
10 changes: 8 additions & 2 deletions typescript/src/shared/tools/documentation/getOpenFinanceGuide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ export const execute = async (
};

export const getOpenFinanceGuide = (context: ToolContext): Tool => ({
method: 'get-openfinance-integration-guide',
name: 'Get Open Finance Integration Guide',
name: 'get-openfinance-integration-guide',
title: 'Get Open Finance Integration Guide',
description: getDescription(context),
parameters: getParameters(context),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
execute: (params) => execute(context, params),
});
10 changes: 8 additions & 2 deletions typescript/src/shared/tools/operations/getApiOperationDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,15 @@ export const execute = async (
};

export const getApiOperationDetails = (context: ToolContext): Tool => ({
method: 'get-api-operation-details',
name: 'Get API Operation Details',
name: 'get-api-operation-details',
title: 'Get API Operation Details',
description: getDescription(context),
parameters: getParameters(context),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
execute: (params) => execute(context, params),
});
10 changes: 8 additions & 2 deletions typescript/src/shared/tools/operations/getApiOperationList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ export const execute = async (
};

export const getApiOperationList = (context: ToolContext): Tool => ({
method: 'get-api-operation-list',
name: 'Get API Operation List',
name: 'get-api-operation-list',
title: 'Get API Operation List',
description: getDescription(context),
parameters: getParameters(context),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
execute: (params) => execute(context, params),
});
10 changes: 8 additions & 2 deletions typescript/src/shared/tools/services/getServicesList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ export const execute = async (
};

export const getServicesList = (context: ToolContext): Tool => ({
method: 'get-services-list',
name: 'Get Services List',
name: 'get-services-list',
title: 'Get Services List',
description: getDescription(context),
parameters: getParameters(context),
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
execute: (params) => execute(context, params),
});
4 changes: 3 additions & 1 deletion typescript/src/shared/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { z } from 'zod';
import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';

export interface Tool {
method: string;
name: string;
title: string;
description: string;
parameters: z.ZodObject<any>;
annotations: ToolAnnotations;
execute: (params: any) => Promise<string>;
}

Expand Down
Loading