Skip to main content
Glama
Jing-yilin

LinkedIn MCP Server

by Jing-yilin
index.test.ts23.5 kB
import { describe, test, expect } from 'bun:test'; describe('LinkedIn MCP Server', () => { describe('Tool Schemas', () => { // Profile Tools test('get_profile schema accepts url, publicIdentifier, or profileId', () => { const validParams = [ { url: 'https://www.linkedin.com/in/satyanadella/' }, { publicIdentifier: 'satyanadella' }, { profileId: 'ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc' }, ]; for (const params of validParams) { expect(Object.keys(params).length).toBeGreaterThan(0); } }); test('search_profiles requires search parameter', () => { const requiredFields = ['search']; const params = { search: 'software engineer', currentCompany: 'google', location: 'San Francisco', }; for (const field of requiredFields) { expect(params).toHaveProperty(field); } }); test('get_profile_posts accepts profile, profileId, or profilePublicIdentifier', () => { const validParams = [ { profile: 'https://www.linkedin.com/in/satyanadella/' }, { profileId: 'ACoAAA8BYqEBCGLg' }, { profilePublicIdentifier: 'satyanadella' }, ]; for (const params of validParams) { expect(Object.keys(params).length).toBeGreaterThan(0); } }); test('get_profile_comments supports postedLimit filter', () => { const validPostedLimits = ['24h', 'week', 'month']; const params = { profileId: 'test123', postedLimit: '24h' }; expect(validPostedLimits).toContain(params.postedLimit); }); test('get_profile_reactions uses pagination params', () => { const params = { profile: 'https://www.linkedin.com/in/test/', page: 1, paginationToken: 'next_token', }; expect(params).toHaveProperty('page'); expect(params).toHaveProperty('paginationToken'); }); // Company Tools test('get_company accepts url, universalName, or search', () => { const validParams = [ { url: 'https://www.linkedin.com/company/google/' }, { universalName: 'google' }, { search: 'Google' }, ]; for (const params of validParams) { expect(Object.keys(params).length).toBeGreaterThan(0); } }); test('search_companies requires search and supports filters', () => { const params = { search: 'technology', location: 'California', geoId: '102095887', companySize: '1001-5000,5001-10000', page: 1, }; expect(params).toHaveProperty('search'); expect(params).toHaveProperty('companySize'); }); test('get_company_posts supports company identifiers', () => { const validParams = [ { company: 'https://www.linkedin.com/company/google/' }, { companyId: '1441' }, { companyUniversalName: 'google' }, ]; for (const params of validParams) { expect(Object.keys(params).length).toBeGreaterThan(0); } }); // Job Tools test('get_job accepts jobId or url', () => { const validParams = [ { jobId: '4153069088' }, { url: 'https://www.linkedin.com/jobs/view/4153069088/' }, ]; for (const params of validParams) { expect(Object.keys(params).length).toBeGreaterThan(0); } }); test('search_jobs supports comprehensive filters', () => { const params = { search: 'software engineer', companyId: '1441', location: 'San Francisco', geoId: '102095887', sortBy: 'date', workplaceType: 'remote', employmentType: 'full-time', salary: '100k+', postedLimit: '24h', experienceLevel: 'mid-senior', industryId: '96', functionId: '8', under10Applicants: true, easyApply: true, page: 1, }; expect(params).toHaveProperty('search'); expect(params).toHaveProperty('workplaceType'); expect(params).toHaveProperty('employmentType'); expect(params).toHaveProperty('salary'); expect(params).toHaveProperty('experienceLevel'); expect(params).toHaveProperty('easyApply'); }); test('search_jobs sortBy accepts relevance or date', () => { const validSortBy = ['relevance', 'date']; expect(validSortBy).toContain('relevance'); expect(validSortBy).toContain('date'); }); test('search_jobs workplaceType accepts valid values', () => { const validWorkplaceTypes = ['office', 'hybrid', 'remote']; expect(validWorkplaceTypes).toContain('remote'); expect(validWorkplaceTypes).toContain('hybrid'); expect(validWorkplaceTypes).toContain('office'); }); test('search_jobs employmentType accepts valid values', () => { const validEmploymentTypes = ['full-time', 'part-time', 'contract', 'temporary', 'volunteer', 'internship']; expect(validEmploymentTypes).toContain('full-time'); expect(validEmploymentTypes).toContain('contract'); expect(validEmploymentTypes).toContain('internship'); }); test('search_jobs experienceLevel accepts valid values', () => { const validExperienceLevels = ['internship', 'entry', 'associate', 'mid-senior', 'director', 'executive']; expect(validExperienceLevels).toContain('entry'); expect(validExperienceLevels).toContain('mid-senior'); expect(validExperienceLevels).toContain('director'); }); // Post Tools test('get_post requires url parameter', () => { const params = { url: 'https://www.linkedin.com/posts/microsoft-events_microsoft-build-ugcPost-7329991434395160578-GnK7', }; expect(params).toHaveProperty('url'); expect(params.url).toBeTruthy(); }); test('search_posts supports various filters', () => { const params = { search: 'AI innovation', profile: 'https://www.linkedin.com/in/satyanadella/', profileId: 'ACoAAA8BYqEBCGLg', company: 'Microsoft', companyId: '1035', authorsCompany: 'Google', authorsCompanyId: '1441', group: 'AI Professionals', postedLimit: '24h', sortBy: 'relevance', page: 1, paginationToken: 'next_token', }; expect(params).toHaveProperty('search'); expect(params).toHaveProperty('authorsCompany'); }); test('get_post_comments requires post URL', () => { const params = { post: 'https://www.linkedin.com/posts/test-post/', sortBy: 'relevance', page: 1, paginationToken: 'token123', }; expect(params).toHaveProperty('post'); expect(params.post).toBeTruthy(); }); test('get_post_reactions requires post URL', () => { const params = { post: 'https://www.linkedin.com/posts/test-post/', page: 1, }; expect(params).toHaveProperty('post'); }); // Group Tools test('get_group accepts url or groupId', () => { const validParams = [ { url: 'https://www.linkedin.com/groups/12345/' }, { groupId: '12345' }, ]; for (const params of validParams) { expect(Object.keys(params).length).toBeGreaterThan(0); } }); test('search_groups requires search parameter', () => { const params = { search: 'Sales', page: 1, }; expect(params).toHaveProperty('search'); expect(params.search).toBeTruthy(); }); // Geo ID Tool test('search_geo_id requires search parameter', () => { const params = { search: 'New York', }; expect(params).toHaveProperty('search'); expect(params.search).toBeTruthy(); }); }); describe('API Endpoint Paths', () => { // Profile endpoints test('profile endpoint path is correct', () => { const endpoint = '/profile'; expect(endpoint).toBe('/profile'); }); test('profile-search endpoint path is correct', () => { const endpoint = '/profile-search'; expect(endpoint).toBe('/profile-search'); }); test('profile-posts endpoint path is correct', () => { const endpoint = '/profile-posts'; expect(endpoint).toBe('/profile-posts'); }); test('profile-comments endpoint path is correct', () => { const endpoint = '/profile-comments'; expect(endpoint).toBe('/profile-comments'); }); test('profile-reactions endpoint path is correct', () => { const endpoint = '/profile-reactions'; expect(endpoint).toBe('/profile-reactions'); }); // Company endpoints test('company endpoint path is correct', () => { const endpoint = '/company'; expect(endpoint).toBe('/company'); }); test('company-search endpoint path is correct', () => { const endpoint = '/company-search'; expect(endpoint).toBe('/company-search'); }); test('company-posts endpoint path is correct', () => { const endpoint = '/company-posts'; expect(endpoint).toBe('/company-posts'); }); // Job endpoints test('job endpoint path is correct', () => { const endpoint = '/job'; expect(endpoint).toBe('/job'); }); test('job-search endpoint path is correct', () => { const endpoint = '/job-search'; expect(endpoint).toBe('/job-search'); }); // Post endpoints test('post endpoint path is correct', () => { const endpoint = '/post'; expect(endpoint).toBe('/post'); }); test('post-search endpoint path is correct', () => { const endpoint = '/post-search'; expect(endpoint).toBe('/post-search'); }); test('post-comments endpoint path is correct', () => { const endpoint = '/post-comments'; expect(endpoint).toBe('/post-comments'); }); test('post-reactions endpoint path is correct', () => { const endpoint = '/post-reactions'; expect(endpoint).toBe('/post-reactions'); }); // Group endpoints test('group endpoint path is correct', () => { const endpoint = '/group'; expect(endpoint).toBe('/group'); }); test('group-search endpoint path is correct', () => { const endpoint = '/group-search'; expect(endpoint).toBe('/group-search'); }); // Geo ID endpoint test('geo-id-search endpoint path is correct', () => { const endpoint = '/geo-id-search'; expect(endpoint).toBe('/geo-id-search'); }); }); describe('Parameter Validation', () => { test('page should default to 1', () => { const defaultPage = 1; const params: { page?: number } = {}; const actualPage = params.page ?? defaultPage; expect(actualPage).toBe(1); }); test('postedLimit should be valid enum value', () => { const validValues = ['24h', 'week', 'month']; const testValue = '24h'; expect(validValues).toContain(testValue); }); test('sortBy should be relevance or date', () => { const validValues = ['relevance', 'date']; expect(validValues).toContain('relevance'); expect(validValues).toContain('date'); }); test('companySize should be valid format', () => { const validSizes = ['1-10', '11-50', '51-200', '201-500', '501-1000', '1001-5000', '5001-10000', '10001+']; expect(validSizes).toContain('1001-5000'); expect(validSizes).toContain('10001+'); }); test('salary filters should be valid format', () => { const validSalaries = ['40k+', '60k+', '80k+', '100k+', '120k+', '140k+', '160k+', '180k+', '200k+']; expect(validSalaries).toContain('100k+'); expect(validSalaries).toContain('200k+'); }); test('profile identifier validation', () => { // URL format const urlPattern = /^https:\/\/www\.linkedin\.com\/in\/[\w-]+\/?$/; expect(urlPattern.test('https://www.linkedin.com/in/satyanadella/')).toBe(true); // Public identifier format const identifierPattern = /^[\w-]+$/; expect(identifierPattern.test('satyanadella')).toBe(true); }); test('company URL validation', () => { const urlPattern = /^https:\/\/www\.linkedin\.com\/company\/[\w-]+\/?$/; expect(urlPattern.test('https://www.linkedin.com/company/google/')).toBe(true); }); test('job URL validation', () => { const urlPattern = /^https:\/\/www\.linkedin\.com\/jobs\/view\/\d+\/?$/; expect(urlPattern.test('https://www.linkedin.com/jobs/view/4153069088/')).toBe(true); }); }); describe('Response Format Validation', () => { test('profile response has element field', () => { const mockResponse = { element: { id: 'ACoAAA8BYqEBCGLg', publicIdentifier: 'satyanadella', name: 'Satya Nadella', }, status: 'success', error: '', }; expect(mockResponse).toHaveProperty('element'); expect(mockResponse.element).toHaveProperty('id'); }); test('search response has elements array and pagination', () => { const mockResponse = { elements: [ { id: '1', name: 'Test User' }, { id: '2', name: 'Another User' }, ], pagination: { totalPages: 10, totalElements: 100, pageNumber: 1, pageSize: 10, paginationToken: 'next_token', }, status: 'success', error: '', }; expect(mockResponse).toHaveProperty('elements'); expect(mockResponse.elements).toBeInstanceOf(Array); expect(mockResponse).toHaveProperty('pagination'); expect(mockResponse.pagination).toHaveProperty('totalPages'); expect(mockResponse.pagination).toHaveProperty('pageNumber'); expect(mockResponse.pagination).toHaveProperty('paginationToken'); }); test('profile posts response has engagement metrics', () => { const mockPost = { id: 'post123', content: 'Test post content', linkedinUrl: 'https://www.linkedin.com/posts/test/', engagement: { likes: 100, comments: 25, shares: 10, reactions: [ { type: 'LIKE', count: 80 }, { type: 'CELEBRATE', count: 20 }, ], }, }; expect(mockPost).toHaveProperty('engagement'); expect(mockPost.engagement).toHaveProperty('likes'); expect(mockPost.engagement).toHaveProperty('comments'); expect(mockPost.engagement).toHaveProperty('shares'); expect(mockPost.engagement.reactions).toBeInstanceOf(Array); }); test('company response has expected fields', () => { const mockCompany = { id: '1441', universalName: 'google', name: 'Google', linkedinUrl: 'https://www.linkedin.com/company/google/', logo: 'https://media.licdn.com/...', description: 'A technology company', employeeCount: 150000, followerCount: 30000000, industries: ['Internet', 'Computer Software'], }; expect(mockCompany).toHaveProperty('id'); expect(mockCompany).toHaveProperty('universalName'); expect(mockCompany).toHaveProperty('name'); expect(mockCompany).toHaveProperty('employeeCount'); expect(mockCompany).toHaveProperty('industries'); }); test('job response has expected fields', () => { const mockJob = { id: '4153069088', title: 'Software Engineer', linkedinUrl: 'https://www.linkedin.com/jobs/view/4153069088/', jobState: 'LISTED', postedDate: '2024-01-15T00:00:00Z', location: { linkedinText: 'San Francisco, CA', city: 'San Francisco', state: 'California', country: 'United States', }, employmentType: 'FULL_TIME', workplaceType: 'REMOTE', companyName: 'Google', salary: { min: 150000, max: 250000, currency: 'USD', }, easyApplyUrl: 'https://www.linkedin.com/jobs/apply/...', }; expect(mockJob).toHaveProperty('id'); expect(mockJob).toHaveProperty('title'); expect(mockJob).toHaveProperty('location'); expect(mockJob).toHaveProperty('salary'); expect(mockJob.salary).toHaveProperty('min'); expect(mockJob.salary).toHaveProperty('max'); }); test('group response has expected fields', () => { const mockGroup = { id: '12345', linkedinUrl: 'https://www.linkedin.com/groups/12345/', name: 'Sales Professionals', memberCount: 500000, description: 'A group for sales professionals', logo: { url: 'https://media.licdn.com/...' }, }; expect(mockGroup).toHaveProperty('id'); expect(mockGroup).toHaveProperty('name'); expect(mockGroup).toHaveProperty('memberCount'); }); test('geo ID response has expected fields', () => { const mockGeoResponse = { elements: [ { geoId: '102095887', title: 'California, United States' }, { geoId: '90000070', title: 'San Francisco Bay Area' }, ], status: 'success', }; expect(mockGeoResponse.elements[0]).toHaveProperty('geoId'); expect(mockGeoResponse.elements[0]).toHaveProperty('title'); }); }); describe('Error Handling', () => { test('missing required params for get_profile should throw error', () => { const hasUrl = false; const hasPublicIdentifier = false; const hasProfileId = false; if (!hasUrl && !hasPublicIdentifier && !hasProfileId) { expect(() => { throw new Error('At least one of url, publicIdentifier, or profileId is required'); }).toThrow('At least one of url, publicIdentifier, or profileId is required'); } }); test('missing required params for get_profile_posts should throw error', () => { const hasProfile = false; const hasProfileId = false; const hasProfilePublicIdentifier = false; if (!hasProfile && !hasProfileId && !hasProfilePublicIdentifier) { expect(() => { throw new Error('At least one of profile, profileId, or profilePublicIdentifier is required'); }).toThrow('At least one of profile, profileId, or profilePublicIdentifier is required'); } }); test('missing required params for get_company should throw error', () => { const hasUrl = false; const hasUniversalName = false; const hasSearch = false; if (!hasUrl && !hasUniversalName && !hasSearch) { expect(() => { throw new Error('At least one of url, universalName, or search is required'); }).toThrow('At least one of url, universalName, or search is required'); } }); test('missing required params for get_job should throw error', () => { const hasJobId = false; const hasUrl = false; if (!hasJobId && !hasUrl) { expect(() => { throw new Error('At least one of jobId or url is required'); }).toThrow('At least one of jobId or url is required'); } }); test('missing required params for get_group should throw error', () => { const hasUrl = false; const hasGroupId = false; if (!hasUrl && !hasGroupId) { expect(() => { throw new Error('At least one of url or groupId is required'); }).toThrow('At least one of url or groupId is required'); } }); test('missing post URL for get_post_comments should throw error', () => { const post = ''; if (!post) { expect(() => { throw new Error('post URL is required'); }).toThrow('post URL is required'); } }); }); describe('API Key Configuration', () => { test('accepts HARVESTAPI_API_KEY environment variable', () => { const apiKey = process.env.HARVESTAPI_API_KEY || ''; expect(typeof apiKey).toBe('string'); }); test('accepts LINKEDIN_API_KEY as alternative', () => { const apiKey = process.env.LINKEDIN_API_KEY || ''; expect(typeof apiKey).toBe('string'); }); test('X-API-Key header is used for authentication', () => { const headers = { 'X-API-Key': 'test_api_key', 'Content-Type': 'application/json', }; expect(headers).toHaveProperty('X-API-Key'); }); }); }); describe('MCP Protocol Compliance', () => { test('tools/list returns valid tool array', () => { const toolsResponse = { tools: [ { name: 'get_profile', description: 'Get LinkedIn profile information' }, { name: 'search_profiles', description: 'Search LinkedIn profiles' }, { name: 'get_company', description: 'Get LinkedIn company information' }, { name: 'search_jobs', description: 'Search LinkedIn jobs' }, { name: 'get_post', description: 'Get LinkedIn post details' }, { name: 'search_geo_id', description: 'Search LinkedIn Geo ID' }, ], }; expect(toolsResponse.tools).toBeInstanceOf(Array); expect(toolsResponse.tools.length).toBeGreaterThan(0); expect(toolsResponse.tools[0]).toHaveProperty('name'); expect(toolsResponse.tools[0]).toHaveProperty('description'); }); test('tool count should be 17', () => { const expectedTools = [ 'get_profile', 'search_profiles', 'get_profile_posts', 'get_profile_comments', 'get_profile_reactions', 'get_company', 'search_companies', 'get_company_posts', 'get_job', 'search_jobs', 'get_post', 'search_posts', 'get_post_comments', 'get_post_reactions', 'get_group', 'search_groups', 'search_geo_id', ]; expect(expectedTools.length).toBe(17); }); test('tool call returns CallToolResult format', () => { const result = { content: [ { type: 'text', text: JSON.stringify({ element: {}, status: 'success' }, null, 2), }, ], }; expect(result).toHaveProperty('content'); expect(result.content[0]).toHaveProperty('type'); expect(result.content[0]).toHaveProperty('text'); expect(result.content[0].type).toBe('text'); }); test('error response uses McpError format', () => { const errorResponse = { code: -32602, // InvalidParams message: 'Missing arguments', }; expect(errorResponse).toHaveProperty('code'); expect(errorResponse).toHaveProperty('message'); }); test('error codes are correct', () => { const ErrorCodes = { ParseError: -32700, InvalidRequest: -32600, MethodNotFound: -32601, InvalidParams: -32602, InternalError: -32603, }; expect(ErrorCodes.InvalidParams).toBe(-32602); expect(ErrorCodes.MethodNotFound).toBe(-32601); expect(ErrorCodes.InternalError).toBe(-32603); }); }); describe('Base URL Configuration', () => { test('base URL is correct', () => { const baseURL = 'https://api.harvest-api.com/linkedin'; expect(baseURL).toBe('https://api.harvest-api.com/linkedin'); }); test('proxy support is available', () => { // Verify proxy environment variables are checked (they may or may not be set) const proxyEnvVars = ['PROXY_URL', 'HTTP_PROXY', 'HTTPS_PROXY']; for (const envVar of proxyEnvVars) { // Just verify the env var lookup works (value can be undefined or string) const value = process.env[envVar]; expect(value === undefined || typeof value === 'string').toBe(true); } }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Jing-yilin/linkedin-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server