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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The toolkit provides the following tools for agents to use:
* `get-documentation-section-content`: Retrieves the complete content for a specific documentation section.
* `get-documentation-page`: Retrieves the complete content of a specific documentation page.
* `get-oauth10a-integration-guide`: Retrieves the comprehensive OAuth 1.0a integration guide.
* `get-oauth20-integration-guide`: Retrieves the comprehensive OAuth 2.0 integration guide.
* `get-openfinance-integration-guide`: Retrieves the comprehensive Open Finance integration guide.

### API Operations
Expand Down
4 changes: 3 additions & 1 deletion modelcontextprotocol/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This MCP server acts as a bridge between MCP clients and Mastercard Developers r
- List available Mastercard services
- Query API specifications and their operations
- Access documentation for each service
- Retrieve integration guides for OAuth 1.0a and Open Finance
- Retrieve integration guides for OAuth 1.0a, OAuth 2.0, and Open Finance

## Available Tools

Expand All @@ -40,6 +40,8 @@ This MCP server acts as a bridge between MCP clients and Mastercard Developers r

- **`get-oauth10a-integration-guide`**: Retrieves the comprehensive OAuth 1.0a integration guide including step-by-step instructions, code examples, and best practices for Mastercard APIs.

- **`get-oauth20-integration-guide`**: Retrieves the comprehensive OAuth 2.0 integration guide including step-by-step instructions, code examples, and best practices for Mastercard APIs.

- **`get-openfinance-integration-guide`**: Retrieves the comprehensive Open Finance integration guide including setup instructions, API usage examples, and implementation best practices.

## Configuration Options
Expand Down
2 changes: 1 addition & 1 deletion typescript/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion typescript/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.1.4",
"version": "0.1.5",
"name": "@mastercard/developers-agent-toolkit",
"homepage": "https://github.com/mastercard/developers-agent-toolkit",
"description": "Agent Toolkit for Mastercard Developers Platform",
Expand Down
58 changes: 35 additions & 23 deletions typescript/src/modelcontextprotocol/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('MastercardDevelopersAgentToolkit', () => {

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

expectedTools.forEach((expectedTool, index) => {
Expand All @@ -60,10 +60,12 @@ describe('MastercardDevelopersAgentToolkit', () => {
'https://static.developer.mastercard.com/content/service/swagger/path.yaml',
});

const expectedTools = tools({
serviceId: 'service',
apiSpecificationPath: '/service/swagger/path.yaml',
}).filter((tool) => tool.name !== 'get-services-list');
const expectedTools = tools(
buildContext({
apiSpecification:
'https://static.developer.mastercard.com/content/service/swagger/path.yaml',
})
).filter((tool) => tool.name !== 'get-services-list');
expect(registeredTools).toHaveLength(expectedTools.length);

expectedTools.forEach((expectedTool, index) => {
Expand All @@ -84,9 +86,11 @@ describe('MastercardDevelopersAgentToolkit', () => {
service: 'https://developer.mastercard.com/test-service/documentation/',
});

const expectedTools = tools({ serviceId: 'test-service' }).filter(
(tool) => tool.name !== 'get-services-list'
);
const expectedTools = tools(
buildContext({
service: 'https://developer.mastercard.com/test-service/documentation/',
})
).filter((tool) => tool.name !== 'get-services-list');
const registeredNames = registeredTools.map((tool) => tool.name);

expect(registeredNames).not.toContain('get-services-list');
Expand All @@ -110,49 +114,57 @@ describe('buildContext function', () => {
describe('success cases', () => {
it('should return empty context when no config provided', () => {
const result = buildContext({});
expect(result).toEqual({});
expect(result).toEqual(expect.objectContaining({}));
});

it('should parse service URL and extract serviceId', () => {
const result = buildContext({
service:
'https://developer.mastercard.com/open-finance-us/documentation/',
});
expect(result).toEqual({
serviceId: 'open-finance-us',
});
expect(result).toEqual(
expect.objectContaining({
serviceId: 'open-finance-us',
})
);
});

it('should parse API specification URL and extract serviceId and apiSpecificationPath', () => {
const result = buildContext({
apiSpecification:
'https://static.developer.mastercard.com/content/test-service/swagger/api.yaml',
});
expect(result).toEqual({
serviceId: 'test-service',
apiSpecificationPath: '/test-service/swagger/api.yaml',
});
expect(result).toEqual(
expect.objectContaining({
serviceId: 'test-service',
apiSpecificationPath: '/test-service/swagger/api.yaml',
})
);
});

it('should handle nested API specification paths', () => {
const result = buildContext({
apiSpecification:
'https://static.developer.mastercard.com/content/payment-gateway/swagger/nested/spec.yaml',
});
expect(result).toEqual({
serviceId: 'payment-gateway',
apiSpecificationPath: '/payment-gateway/swagger/nested/spec.yaml',
});
expect(result).toEqual(
expect.objectContaining({
serviceId: 'payment-gateway',
apiSpecificationPath: '/payment-gateway/swagger/nested/spec.yaml',
})
);
});

it('should handle service IDs with hyphens and numbers', () => {
const result = buildContext({
service:
'https://developer.mastercard.com/open-finance-us-v2/documentation/',
});
expect(result).toEqual({
serviceId: 'open-finance-us-v2',
});
expect(result).toEqual(
expect.objectContaining({
serviceId: 'open-finance-us-v2',
})
);
});
});

Expand Down
6 changes: 5 additions & 1 deletion typescript/src/modelcontextprotocol/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { defaultDevelopersApi } from '@/shared/api';
import { tools } from '@/shared/tools';
import { ToolContext } from '@/shared/types';
import { version } from '../../package.json';

export type { DevelopersApi, Tool, ToolContext } from '@/shared/types';
export { tools } from '@/shared/tools';

export interface MastercardDevelopersAgentToolkitConfig {
service?: string;
apiSpecification?: string;
Expand Down Expand Up @@ -61,7 +65,7 @@ export class MastercardDevelopersAgentToolkit extends McpServer {
export function buildContext(
config: MastercardDevelopersAgentToolkitConfig
): ToolContext {
const context: ToolContext = {};
const context: ToolContext = { client: defaultDevelopersApi };
if (config.service != null) {
const serviceId = parseServiceIdFromUrl(config.service);
if (serviceId == null) {
Expand Down
8 changes: 7 additions & 1 deletion typescript/src/shared/api/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MastercardAPIClient } from '@/shared/api';
import { defaultDevelopersApi, MastercardAPIClient } from '@/shared/api';
import fetch, { RequestInfo, Response } from 'node-fetch';

const mcd = (path: string) => {
Expand All @@ -8,6 +8,12 @@ const mcd = (path: string) => {
const mockFetch = fetch as jest.MockedFunction<typeof fetch>;
jest.mock('node-fetch');

describe('defaultDevelopersApi', () => {
it('uses the default MastercardAPIClient implementation', () => {
expect(defaultDevelopersApi).toBeInstanceOf(MastercardAPIClient);
});
});

describe('MastercardAPIClient', () => {
let client: MastercardAPIClient;

Expand Down
5 changes: 3 additions & 2 deletions typescript/src/shared/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { z } from 'zod';
import fetch from 'node-fetch';

import type { DevelopersApi } from '@/shared/types';

const PathSchema = z
.string()
.min(1, 'Path must be a non-empty string')
Expand Down Expand Up @@ -106,5 +108,4 @@ export class MastercardAPIClient {
}
}

const api = new MastercardAPIClient();
export default api;
export const defaultDevelopersApi: DevelopersApi = new MastercardAPIClient();
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import {
execute,
getParameters,
} from '@/shared/tools/documentation/getDocumentation';
import api from '@/shared/api';
import { createMockApi } from '@/tests/mockDevelopersApi';

jest.mock<typeof api>('@/shared/api');

const mockApi = api as jest.Mocked<typeof api>;
const mockApi = createMockApi();

describe('execute', () => {
beforeEach(() => {
Expand All @@ -17,7 +15,10 @@ describe('execute', () => {
const mockResult = 'mock documentation';
mockApi.getDocumentation.mockResolvedValue(mockResult);

const result = await execute({}, { serviceId: 'test-service' });
const result = await execute(
{ client: mockApi },
{ serviceId: 'test-service' }
);

expect(mockApi.getDocumentation).toHaveBeenCalledWith('test-service');
expect(result).toBe(mockResult);
Expand All @@ -27,7 +28,10 @@ describe('execute', () => {
const mockResult = 'mock documentation';
mockApi.getDocumentation.mockResolvedValue(mockResult);

const result = await execute({ serviceId: 'context-service' }, {});
const result = await execute(
{ client: mockApi, serviceId: 'context-service' },
{}
);

expect(mockApi.getDocumentation).toHaveBeenCalledWith('context-service');
expect(result).toBe(mockResult);
Expand All @@ -36,15 +40,18 @@ describe('execute', () => {

describe('getParameters', () => {
it('should return the correct parameters if no context', () => {
const parameters = getParameters({});
const parameters = getParameters({ client: mockApi });

const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['serviceId']);
expect(fields.length).toBe(1);
});

it('should return the correct parameters if serviceId is specified in context', () => {
const parameters = getParameters({ serviceId: 'test-service' });
const parameters = getParameters({
client: mockApi,
serviceId: 'test-service',
});

const fields = Object.keys(parameters.shape);
expect(fields).toEqual([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import {
execute,
getParameters,
} from '@/shared/tools/documentation/getDocumentationPage';
import api from '@/shared/api';
import { createMockApi } from '@/tests/mockDevelopersApi';

jest.mock<typeof api>('@/shared/api');

const mockApi = api as jest.Mocked<typeof api>;
const mockApi = createMockApi();

describe('execute', () => {
beforeEach(() => {
Expand All @@ -17,7 +15,10 @@ describe('execute', () => {
const mockResult = 'mock documentation page content';
mockApi.getDocumentationPage.mockResolvedValue(mockResult);

const result = await execute({}, { pagePath: '/test/page.md' });
const result = await execute(
{ client: mockApi },
{ pagePath: '/test/page.md' }
);

expect(mockApi.getDocumentationPage).toHaveBeenCalledWith('/test/page.md');
expect(result).toBe(mockResult);
Expand All @@ -26,7 +27,7 @@ describe('execute', () => {

describe('getParameters', () => {
it('should return the correct parameters if no context', () => {
const parameters = getParameters({});
const parameters = getParameters({ client: mockApi });

const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['pagePath']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import {
execute,
getParameters,
} from '@/shared/tools/documentation/getDocumentationSection';
import api from '@/shared/api';
import { createMockApi } from '@/tests/mockDevelopersApi';

jest.mock<typeof api>('@/shared/api');

const mockApi = api as jest.Mocked<typeof api>;
const mockApi = createMockApi();

describe('execute', () => {
beforeEach(() => {
Expand All @@ -18,8 +16,11 @@ describe('execute', () => {
mockApi.getDocumentationSection.mockResolvedValue(mockResult);

const result = await execute(
{},
{ serviceId: 'test-service', sectionId: 'test-section' }
{ client: mockApi },
{
serviceId: 'test-service',
sectionId: 'test-section',
}
);

expect(mockApi.getDocumentationSection).toHaveBeenCalledWith(
Expand All @@ -34,8 +35,10 @@ describe('execute', () => {
mockApi.getDocumentationSection.mockResolvedValue(mockResult);

const result = await execute(
{ serviceId: 'context-service' },
{ sectionId: 'test-section' }
{ client: mockApi, serviceId: 'context-service' },
{
sectionId: 'test-section',
}
);

expect(mockApi.getDocumentationSection).toHaveBeenCalledWith(
Expand All @@ -48,15 +51,18 @@ describe('execute', () => {

describe('getParameters', () => {
it('should return the correct parameters if no context', () => {
const parameters = getParameters({});
const parameters = getParameters({ client: mockApi });

const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['serviceId', 'sectionId']);
expect(fields.length).toBe(2);
});

it('should return the correct parameters if serviceId is specified in context', () => {
const parameters = getParameters({ serviceId: 'test-service' });
const parameters = getParameters({
client: mockApi,
serviceId: 'test-service',
});

const fields = Object.keys(parameters.shape);
expect(fields).toEqual(['sectionId']);
Expand Down
Loading
Loading