#
tokens: 3799/50000 7/7 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── mcp-config.json
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

build/

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

local.json
production.json
development.json

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# configs
config/local.json
workers/config/local.json

.npmrc

.vscode/
.DS_Store

# Dependencies
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log

# Environment variables
.env
.env.local
.env.*.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Build
dist/
build/
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Lithic MCP Server (TypeScript)

A TypeScript implementation of a Model Context Protocol (MCP) server for Lithic API that provides read-only access to Lithic banking and card services.

## Features

- Modern TypeScript implementation using the MCP SDK
- Read-only access to all Lithic API endpoints
- Type-safe tools for accessing Lithic resources
- Docker support
- Improved error handling and validation

## Quick Start

### Using Docker (Recommended)

1. Build the Docker image:
```bash
npm run docker:build
```

2. Run the server:
```bash
LITHIC_API_KEY=your_key_here npm run docker:run
```

### Manual Setup

1. Install dependencies:
```bash
npm install
```

2. Build the TypeScript code:
```bash
npm run build
```

3. Start the server:
```bash
LITHIC_API_KEY=your_key_here npm start
```

## MCP Configuration

Add this to your `.cursor/mcp.json` or Claude Desktop configuration:

```json
{
  "lithic": {
    "command": "docker",
    "args": [
      "run",
      "--rm",
      "-i",
      "-e", "LITHIC_API_KEY",
      "-e", "LITHIC_API_BASE_URL",
      "mcp/lithic"
    ],
    "env": {
      "LITHIC_API_KEY": "your_lithic_api_key_here",
      "LITHIC_API_BASE_URL": "https://api.lithic.com/v1"
    }
  }
}
```

## Available Tools

This MCP server provides the following tools:

- `get_resource`: Fetch a specific Lithic resource by ID/token
- `list_resources`: List resources of a given type

## Environment Variables

- `LITHIC_API_KEY` - Your Lithic API key (required)
- `LITHIC_API_BASE_URL` - Lithic API base URL (default: https://sandbox.lithic.com/v1)

## Supported Resource Types

- card
- account
- financial_account
- transaction
- event
- balance
- dispute
- external_bank_account
- report
- webhook
- account_holder

## Development

For development, you can use the watch mode:

```bash
npm run dev
```

This will automatically rebuild and restart the server when you make changes to the source code. 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
} 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM node:20-alpine

WORKDIR /app

# Install dependencies for the build
COPY package*.json ./
RUN npm install

# Copy TypeScript source and config
COPY tsconfig.json ./
COPY src/ ./src/
COPY mcp-config.json ./

# Build TypeScript code
RUN npm run build

# Clean up dev dependencies to reduce image size
RUN npm prune --production

# Set executable permissions
RUN chmod +x build/index.js

# Create a non-root user to run the app
RUN addgroup -S mcp && adduser -S mcp -G mcp
USER mcp

# Define default environment variables
ENV NODE_ENV=production
ENV LITHIC_API_BASE_URL=https://sandbox.lithic.com/v1

# Expose MCP server (stdin/stdout)
ENTRYPOINT ["node", "build/index.js"] 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "lithic-mcp-server",
  "version": "1.0.0",
  "description": "MCP Server for Lithic API - TypeScript implementation",
  "main": "build/index.js",
  "type": "module",
  "bin": {
    "lithic-mcp": "./build/index.js"
  },
  "scripts": {
    "build": "tsc && chmod 755 build/index.js",
    "start": "node build/index.js",
    "dev": "tsc-watch --onSuccess \"node build/index.js\"",
    "docker:build": "docker build -t mcp/lithic .",
    "docker:run": "docker run --rm -i -p 8080:8080 -e LITHIC_API_KEY mcp/lithic"
  },
  "files": [
    "build"
  ],
  "keywords": ["lithic", "api", "proxy", "mcp", "typescript"],
  "author": "",
  "license": "ISC",
  "engines": {
    "node": ">=18.0.0"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "axios": "^1.6.2",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^20.10.5",
    "typescript": "^5.3.3",
    "tsc-watch": "^6.0.4"
  }
}

```

--------------------------------------------------------------------------------
/mcp-config.json:
--------------------------------------------------------------------------------

```json
{
  "name": "lithic-mcp",
  "version": "1.0.0",
  "tools": [
    {
      "name": "get_resource",
      "description": "Get a specific Lithic resource by ID"
    },
    {
      "name": "list_resources",
      "description": "List resources of a specific type"
    }
  ],
  "resourceTypes": {
    "card": {
      "description": "Payment card resource"
    },
    "account": {
      "description": "User account resource"
    },
    "financial_account": {
      "description": "Financial account resource"
    },
    "transaction": {
      "description": "Transaction resource"
    },
    "event": {
      "description": "Event resource"
    },
    "balance": {
      "description": "Balance resource"
    },
    "dispute": {
      "description": "Dispute resource"
    },
    "external_bank_account": {
      "description": "External bank account resource"
    },
    "report": {
      "description": "Report resource"
    },
    "webhook": {
      "description": "Webhook resource"
    },
    "account_holder": {
      "description": "Account holder resource"
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { 
  CallToolRequestSchema, 
  ListToolsRequestSchema,
  InitializeRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import axios, { AxiosInstance } from "axios";

// Protocol version for MCP
const PROTOCOL_VERSION = "1.0.0";

// Create Lithic API client
const createLithicApiClient = (): AxiosInstance => {
  return axios.create({
    baseURL: process.env.LITHIC_API_BASE_URL || 'https://sandbox.lithic.com/v1',
    headers: {
      'Authorization': process.env.LITHIC_API_KEY || '',
      'Content-Type': 'application/json'
    },
    timeout: 30000 // 30 second timeout
  });
};

// Resource type mapping
const RESOURCE_MAP: Record<string, string> = {
  'card': 'card',
  'account': 'account',
  'financial_account': 'financial_account',
  'financial-account': 'financial_account',
  'credit': 'financial_account',
  'credit-account': 'financial_account',
  'transaction': 'transaction',
  'event': 'event',
  'balance': 'balance',
  'dispute': 'dispute',
  'bank': 'external_bank_account',
  'bank-account': 'external_bank_account',
  'external_bank_account': 'external_bank_account',
  'report': 'report',
  'webhook': 'webhook',
  'card_program': 'card_program',
  'account_holder': 'account_holder'
};

// Create the MCP server
const server = new Server({
  name: "lithic-mcp",
  version: PROTOCOL_VERSION
}, {
  capabilities: {
    tools: {}
  }
});

// Define resource schemas
const resourceIdSchema = z.string().describe("Resource ID or token");
const resourceTypeSchema = z.string().describe("Type of resource to fetch");

// Handle initialization requests
server.setRequestHandler(InitializeRequestSchema, async (request) => {
  console.error('Received initialize request with protocol version:', request.params.protocolVersion);
  return {
    serverInfo: {
      name: "lithic-mcp",
      version: PROTOCOL_VERSION
    },
    protocolVersion: request.params.protocolVersion,
    capabilities: {
      tools: {}
    }
  };
});

// Register available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get_resource",
        description: "Get a specific Lithic resource by ID",
        inputSchema: {
          type: "object",
          properties: {
            resourceType: {
              type: "string",
              description: "Type of resource (card, account, transaction, etc.)"
            },
            resourceId: {
              type: "string",
              description: "ID/token of the resource to fetch"
            }
          },
          required: ["resourceType", "resourceId"]
        }
      },
      {
        name: "list_resources",
        description: "List resources of a specific type",
        inputSchema: {
          type: "object",
          properties: {
            resourceType: {
              type: "string",
              description: "Type of resource to list (cards, accounts, transactions, etc.)"
            }
          },
          required: ["resourceType"]
        }
      }
    ]
  };
});

// Map resource type to API endpoint
function mapResourceTypeToEndpoint(resourceType: string): string {
  switch (resourceType) {
    case 'card':
      return '/cards';
    case 'account':
      return '/accounts';
    case 'financial_account':
      return '/financial_accounts';
    case 'transaction':
      return '/transactions';
    case 'external_bank_account':
      return '/external_bank_accounts';
    case 'event':
      return '/events';
    case 'balance':
      return '/balances';
    case 'card_program':
      return '/card_programs';
    case 'dispute':
      return '/disputes';
    case 'report':
      return '/reports';
    case 'webhook':
      return '/webhooks';
    case 'account_holder':
      return '/account_holders';
    default:
      return `/${resourceType}s`; // Default to pluralizing the resource type
  }
}

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  // Create Lithic API client for each request
  const lithicApi = createLithicApiClient();
  
  if (request.params.name === "get_resource") {
    try {
      // Parse and validate arguments
      const args = z.object({
        resourceType: resourceTypeSchema,
        resourceId: resourceIdSchema
      }).parse(request.params.arguments);

      // Map to standardized resource type
      const standardizedResourceType = RESOURCE_MAP[args.resourceType.toLowerCase()] || args.resourceType;
      
      // Map resource type to endpoint
      const endpoint = mapResourceTypeToEndpoint(standardizedResourceType);
      const response = await lithicApi.get(`${endpoint}/${args.resourceId}`);

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(response.data, null, 2)
          }
        ]
      };
    } catch (error: any) {
      console.error(`Error fetching resource:`, error.message);

      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Error fetching resource: ${error.message}`
          }
        ]
      };
    }
  } else if (request.params.name === "list_resources") {
    try {
      // Parse and validate arguments
      const args = z.object({
        resourceType: resourceTypeSchema
      }).parse(request.params.arguments);

      // Map to standardized resource type
      const standardizedResourceType = RESOURCE_MAP[args.resourceType.toLowerCase()] || args.resourceType;
      
      // Map resource type to endpoint
      const endpoint = mapResourceTypeToEndpoint(standardizedResourceType);
      const response = await lithicApi.get(endpoint);

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(response.data, null, 2)
          }
        ]
      };
    } catch (error: any) {
      console.error(`Error listing resources:`, error.message);

      return {
        isError: true,
        content: [
          {
            type: "text",
            text: `Error listing resources: ${error.message}`
          }
        ]
      };
    }
  }

  // Handle unknown tool
  return {
    isError: true,
    content: [
      {
        type: "text",
        text: `Unknown tool: ${request.params.name}`
      }
    ]
  };
});

// Start the server with stdio transport
async function main() {
  try {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error("Lithic MCP server running on stdio");
  } catch (error) {
    console.error("Failed to start server:", error);
    process.exit(1);
  }
}

// Keep the process alive and handle signals properly
process.on('SIGINT', () => {
  process.exit(0);
});

process.on('SIGTERM', () => {
  process.exit(0);
});

// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
  console.error('Uncaught exception:', err.message);
});

// Start the server
main().catch(err => {
  console.error("Fatal error:", err);
  process.exit(1);
}); 
```