UIFlowchartCreator
by umshere
- src
- handlers
import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import axios from 'axios';
import { BaseResourceHandler, Resource, ResourceTemplate, ResourceContent } from '../types/resource.js';
/**
* Handles resource requests for Postman API resources
*/
export class McpResourceHandler extends BaseResourceHandler {
// Set of valid direct resource types (no parameters)
private static readonly DIRECT_RESOURCES = new Set([
'workspaces',
'user',
'apis',
'collections',
'environments',
'mocks',
'monitors',
'collection-roles',
'workspace-roles',
'collection-access-keys',
'pan-elements',
'webhooks',
'tagged-elements',
'workspace-tags',
'api-tags'
]);
// Rate limiting configuration
private static readonly RATE_LIMIT = {
maxRequests: Number(process.env.POSTMAN_RATE_LIMIT_MAX) || 60,
windowMs: Number(process.env.POSTMAN_RATE_LIMIT_WINDOW) || 60000 // 1 minute
};
private requestCount: number = 0;
private windowStart: number = Date.now();
constructor(private server: Server) {
super();
this.setupHandlers();
}
private setupHandlers() {
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: await this.listResources()
}));
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
resourceTemplates: await this.listResourceTemplates()
}));
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => ({
contents: await this.readResource(request.params.uri)
}));
}
/**
* List all available direct resources
*/
async listResources(): Promise<Resource[]> {
return [
// Core Resources
{
uri: 'postman://workspaces',
name: 'Postman Workspaces',
description: 'List of all available Postman workspaces',
mimeType: 'application/json',
},
{
uri: 'postman://user',
name: 'Current User',
description: 'Information about the currently authenticated user',
mimeType: 'application/json',
},
// API Development Resources
{
uri: 'postman://apis',
name: 'Postman APIs',
description: 'List of all available API definitions',
mimeType: 'application/json',
},
{
uri: 'postman://collections',
name: 'Postman Collections',
description: 'List of all available request collections',
mimeType: 'application/json',
},
{
uri: 'postman://environments',
name: 'Postman Environments',
description: 'List of all available environments',
mimeType: 'application/json',
},
// Testing & Monitoring Resources
{
uri: 'postman://mocks',
name: 'Postman Mocks',
description: 'List of all available mock servers',
mimeType: 'application/json',
},
{
uri: 'postman://monitors',
name: 'Postman Monitors',
description: 'List of all available collection monitors',
mimeType: 'application/json',
},
// Security & Access Control Resources
{
uri: 'postman://collection-roles',
name: 'Collection Roles',
description: 'List of roles and permissions for collections',
mimeType: 'application/json',
},
{
uri: 'postman://workspace-roles',
name: 'Workspace Roles',
description: 'List of roles and permissions for workspaces',
mimeType: 'application/json',
},
{
uri: 'postman://collection-access-keys',
name: 'Collection Access Keys',
description: 'List of access keys for collections',
mimeType: 'application/json',
},
// Additional Features
{
uri: 'postman://pan-elements',
name: 'Private API Network Elements',
description: 'List of elements in the Private API Network',
mimeType: 'application/json',
},
{
uri: 'postman://webhooks',
name: 'Webhooks',
description: 'List of configured webhooks',
mimeType: 'application/json',
},
{
uri: 'postman://tagged-elements',
name: 'Tagged Elements',
description: 'List of elements with specific tags',
mimeType: 'application/json',
},
{
uri: 'postman://workspace-tags',
name: 'Workspace Tags',
description: 'List of tags used in workspaces',
mimeType: 'application/json',
},
{
uri: 'postman://api-tags',
name: 'API Tags',
description: 'List of tags used in APIs',
mimeType: 'application/json',
}
];
}
/**
* List all available resource templates
*/
async listResourceTemplates(): Promise<ResourceTemplate[]> {
return [
// Workspace Resources
{
uriTemplate: 'postman://workspaces/{workspaceId}/collections',
name: 'Workspace Collections',
description: 'List of collections in a specific workspace',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://workspaces/{workspaceId}/environments',
name: 'Workspace Environments',
description: 'List of environments in a specific workspace',
mimeType: 'application/json',
},
// API Resources
{
uriTemplate: 'postman://apis/{apiId}',
name: 'API Details',
description: 'Details of a specific API',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://apis/{apiId}/versions',
name: 'API Versions',
description: 'List of versions for a specific API',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://apis/{apiId}/versions/{versionId}',
name: 'API Version Details',
description: 'Details of a specific API version',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://apis/{apiId}/schemas',
name: 'API Schemas',
description: 'List of schemas for a specific API',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://apis/{apiId}/schemas/{schemaId}/files',
name: 'API Schema Files',
description: 'List of files in an API schema',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://apis/{apiId}/comments',
name: 'API Comments',
description: 'List of comments on a specific API',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://apis/{apiId}/tags',
name: 'API Tags',
description: 'Tags associated with a specific API',
mimeType: 'application/json',
},
// Collection Resources
{
uriTemplate: 'postman://collections/{collectionId}',
name: 'Collection Details',
description: 'Details of a specific collection',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://collections/{collectionId}/requests',
name: 'Collection Requests',
description: 'List of requests in a specific collection',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://collections/{collectionId}/folders',
name: 'Collection Folders',
description: 'List of folders in a specific collection',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://collections/{collectionId}/responses',
name: 'Collection Responses',
description: 'List of saved responses in a specific collection',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://collections/{collectionId}/forks',
name: 'Collection Forks',
description: 'List of forks of a specific collection',
mimeType: 'application/json',
},
// Environment Resources
{
uriTemplate: 'postman://environments/{environmentId}',
name: 'Environment Details',
description: 'Details of a specific environment',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://environments/{environmentId}/forks',
name: 'Environment Forks',
description: 'List of forks of a specific environment',
mimeType: 'application/json',
},
// Mock Resources
{
uriTemplate: 'postman://mocks/{mockId}',
name: 'Mock Server Details',
description: 'Details of a specific mock server',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://mocks/{mockId}/serverResponses',
name: 'Mock Server Responses',
description: 'List of server responses for a specific mock',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://mocks/{mockId}/callLogs',
name: 'Mock Call Logs',
description: 'Call logs for a specific mock server',
mimeType: 'application/json',
},
// Monitor Resources
{
uriTemplate: 'postman://monitors/{monitorId}',
name: 'Monitor Details',
description: 'Details of a specific monitor',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://monitors/{monitorId}/runs',
name: 'Monitor Runs',
description: 'List of runs for a specific monitor',
mimeType: 'application/json',
},
// Role Resources
{
uriTemplate: 'postman://collections/{collectionId}/roles',
name: 'Collection Roles',
description: 'List of roles for a specific collection',
mimeType: 'application/json',
},
{
uriTemplate: 'postman://workspaces/{workspaceId}/roles',
name: 'Workspace Roles',
description: 'List of roles for a specific workspace',
mimeType: 'application/json',
},
// PAN Resources
{
uriTemplate: 'postman://pan/folders/{folderId}/elements',
name: 'PAN Folder Elements',
description: 'List of elements in a specific Private API Network folder',
mimeType: 'application/json',
}
];
}
/**
* Read a resource's contents
*/
async readResource(uri: string): Promise<ResourceContent[]> {
await this.checkRateLimit();
const { protocol, resourceType, params } = this.parseUri(uri);
if (protocol !== 'postman') {
throw new McpError(
ErrorCode.InvalidRequest,
`Invalid protocol: ${protocol}. Expected: postman`
);
}
// For direct resources, validate against DIRECT_RESOURCES
if (Object.keys(params).length === 0 && !McpResourceHandler.DIRECT_RESOURCES.has(resourceType)) {
throw new McpError(
ErrorCode.InvalidRequest,
`Unknown direct resource type: ${resourceType}`
);
}
try {
const endpoint = this.buildEndpoint(resourceType, params);
const response = await this.makePostmanRequest(endpoint);
return [{
uri,
mimeType: 'application/json',
text: JSON.stringify(response.data, null, 2)
}];
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 429) {
throw new McpError(
ErrorCode.InvalidRequest,
'Postman API rate limit exceeded'
);
}
throw new McpError(
ErrorCode.InternalError,
`Postman API error: ${error.response?.data?.error?.message || error.message}`
);
}
throw error;
}
}
private async checkRateLimit() {
const now = Date.now();
if (now - this.windowStart >= McpResourceHandler.RATE_LIMIT.windowMs) {
this.requestCount = 0;
this.windowStart = now;
}
if (this.requestCount >= McpResourceHandler.RATE_LIMIT.maxRequests) {
throw new McpError(
ErrorCode.InvalidRequest,
`Rate limit exceeded. Maximum ${McpResourceHandler.RATE_LIMIT.maxRequests} requests per ${McpResourceHandler.RATE_LIMIT.windowMs / 1000} seconds.`
);
}
this.requestCount++;
}
private async makePostmanRequest(endpoint: string) {
if (!process.env.POSTMAN_API_KEY) {
throw new McpError(ErrorCode.InternalError, 'Postman API key not configured');
}
return await axios.get(`https://api.getpostman.com${endpoint}`, {
headers: {
'X-Api-Key': process.env.POSTMAN_API_KEY
}
});
}
private buildEndpoint(resourceType: string, params: Record<string, string>): string {
// Map resource types to Postman API endpoints
switch (resourceType) {
// Direct resources
case 'workspaces':
return '/workspaces';
case 'user':
return '/me';
case 'apis':
return '/apis';
case 'collections':
return '/collections';
case 'environments':
return '/environments';
case 'mocks':
return '/mocks';
case 'monitors':
return '/monitors';
case 'collection-roles':
return '/roles/collections';
case 'workspace-roles':
return '/roles/workspaces';
case 'collection-access-keys':
return '/collection-access-keys';
case 'pan-elements':
return '/pan/elements';
case 'webhooks':
return '/webhooks';
case 'tagged-elements':
return '/tagged-elements';
case 'workspace-tags':
return '/workspace-tags';
case 'api-tags':
return '/api-tags';
// Workspace resources
case 'workspaces-collections':
return `/workspaces/${params.workspaceId}/collections`;
case 'workspaces-environments':
return `/workspaces/${params.workspaceId}/environments`;
// API resources
case 'apis-details':
return `/apis/${params.apiId}`;
case 'apis-versions':
return `/apis/${params.apiId}/versions`;
case 'apis-version-details':
return `/apis/${params.apiId}/versions/${params.versionId}`;
case 'apis-schemas':
return `/apis/${params.apiId}/schemas`;
case 'apis-schema-files':
return `/apis/${params.apiId}/schemas/${params.schemaId}/files`;
case 'apis-comments':
return `/apis/${params.apiId}/comments`;
case 'apis-tags':
return `/apis/${params.apiId}/tags`;
// Collection resources
case 'collections-details':
return `/collections/${params.collectionId}`;
case 'collections-requests':
return `/collections/${params.collectionId}/requests`;
case 'collections-folders':
return `/collections/${params.collectionId}/folders`;
case 'collections-responses':
return `/collections/${params.collectionId}/responses`;
case 'collections-forks':
return `/collections/${params.collectionId}/forks`;
case 'collections-roles':
return `/collections/${params.collectionId}/roles`;
// Environment resources
case 'environments-details':
return `/environments/${params.environmentId}`;
case 'environments-forks':
return `/environments/${params.environmentId}/forks`;
// Mock resources
case 'mocks-details':
return `/mocks/${params.mockId}`;
case 'mocks-server-responses':
return `/mocks/${params.mockId}/serverResponses`;
case 'mocks-call-logs':
return `/mocks/${params.mockId}/callLogs`;
// Monitor resources
case 'monitors-details':
return `/monitors/${params.monitorId}`;
case 'monitors-runs':
return `/monitors/${params.monitorId}/runs`;
// PAN resources
case 'pan-folder-elements':
return `/pan/folders/${params.folderId}/elements`;
default:
throw new McpError(
ErrorCode.InvalidRequest,
`Unsupported resource type: ${resourceType}`
);
}
}
}