Linear MCP Server
by cpropster
- src
- graphql
import { LinearClient } from '@linear/sdk';
import { DocumentNode } from 'graphql';
import {
CreateIssueInput,
CreateIssueResponse,
CreateIssuesResponse,
UpdateIssueInput,
UpdateIssuesResponse,
SearchIssuesInput,
SearchIssuesResponse,
DeleteIssueResponse,
Issue,
IssueBatchResponse
} from '../features/issues/types/issue.types.js';
import {
ProjectInput,
ProjectResponse,
SearchProjectsResponse
} from '../features/projects/types/project.types.js';
import {
TeamResponse,
LabelInput,
LabelResponse
} from '../features/teams/types/team.types.js';
import {
UserResponse
} from '../features/users/types/user.types.js';
export class LinearGraphQLClient {
private linearClient: LinearClient;
constructor(linearClient: LinearClient) {
this.linearClient = linearClient;
}
async execute<T, V extends Record<string, unknown> = Record<string, unknown>>(
document: DocumentNode,
variables?: V
): Promise<T> {
const graphQLClient = this.linearClient.client;
try {
const response = await graphQLClient.rawRequest(
document.loc?.source.body || '',
variables
);
return response.data as T;
} catch (error) {
if (error instanceof Error) {
throw new Error(`GraphQL operation failed: ${error.message}`);
}
throw error;
}
}
// Create single issue
async createIssue(input: CreateIssueInput): Promise<CreateIssueResponse> {
const { CREATE_ISSUES_MUTATION } = await import('./mutations.js');
return this.execute<CreateIssueResponse>(CREATE_ISSUES_MUTATION, { input: [input] });
}
// Create multiple issues
async createIssues(issues: CreateIssueInput[]): Promise<CreateIssuesResponse> {
const { CREATE_ISSUES_MUTATION } = await import('./mutations.js');
return this.execute<CreateIssuesResponse>(CREATE_ISSUES_MUTATION, { input: issues });
}
// Create a project
async createProject(input: ProjectInput): Promise<ProjectResponse> {
const { CREATE_PROJECT } = await import('./mutations.js');
return this.execute<ProjectResponse>(CREATE_PROJECT, { input });
}
// Create batch of issues
async createBatchIssues(issues: CreateIssueInput[]): Promise<IssueBatchResponse> {
const { CREATE_BATCH_ISSUES } = await import('./mutations.js');
return this.execute<IssueBatchResponse>(CREATE_BATCH_ISSUES, {
input: { issues }
});
}
// Helper method to create a project with associated issues
async createProjectWithIssues(projectInput: ProjectInput, issues: CreateIssueInput[]): Promise<ProjectResponse> {
// Create project first
const projectResult = await this.createProject(projectInput);
if (!projectResult.projectCreate.success) {
throw new Error('Failed to create project');
}
// Then create issues with project ID
const issuesWithProject = issues.map(issue => ({
...issue,
projectId: projectResult.projectCreate.project.id
}));
const issuesResult = await this.createBatchIssues(issuesWithProject);
if (!issuesResult.issueBatchCreate.success) {
throw new Error('Failed to create issues');
}
return {
projectCreate: projectResult.projectCreate,
issueBatchCreate: issuesResult.issueBatchCreate
};
}
// Update a single issue
async updateIssue(id: string, input: UpdateIssueInput): Promise<UpdateIssuesResponse> {
const { UPDATE_ISSUES_MUTATION } = await import('./mutations.js');
return this.execute<UpdateIssuesResponse>(UPDATE_ISSUES_MUTATION, {
ids: [id],
input,
});
}
// Bulk update issues
async updateIssues(ids: string[], input: UpdateIssueInput): Promise<UpdateIssuesResponse> {
const { UPDATE_ISSUES_MUTATION } = await import('./mutations.js');
return this.execute<UpdateIssuesResponse>(UPDATE_ISSUES_MUTATION, { ids, input });
}
// Create multiple labels
async createIssueLabels(labels: LabelInput[]): Promise<LabelResponse> {
const { CREATE_ISSUE_LABELS } = await import('./mutations.js');
return this.execute<LabelResponse>(CREATE_ISSUE_LABELS, { labels });
}
// Search issues with pagination
async searchIssues(
filter: SearchIssuesInput['filter'],
first: number = 50,
after?: string,
orderBy: string = "updatedAt"
): Promise<SearchIssuesResponse> {
const { SEARCH_ISSUES_QUERY } = await import('./queries.js');
return this.execute<SearchIssuesResponse>(SEARCH_ISSUES_QUERY, {
filter,
first,
after,
orderBy,
});
}
// Get teams with their states and labels
async getTeams(): Promise<TeamResponse> {
const { GET_TEAMS_QUERY } = await import('./queries.js');
return this.execute<TeamResponse>(GET_TEAMS_QUERY);
}
// Get current user info
async getCurrentUser(): Promise<UserResponse> {
const { GET_USER_QUERY } = await import('./queries.js');
return this.execute<UserResponse>(GET_USER_QUERY);
}
// Get project info
async getProject(id: string): Promise<ProjectResponse> {
const { GET_PROJECT_QUERY } = await import('./queries.js');
return this.execute<ProjectResponse>(GET_PROJECT_QUERY, { id });
}
// Search projects
async searchProjects(filter: { name?: { eq: string } }): Promise<SearchProjectsResponse> {
const { SEARCH_PROJECTS_QUERY } = await import('./queries.js');
return this.execute<SearchProjectsResponse>(SEARCH_PROJECTS_QUERY, { filter });
}
// Delete a single issue
async deleteIssue(id: string): Promise<DeleteIssueResponse> {
const { DELETE_ISSUES_MUTATION } = await import('./mutations.js');
return this.execute<DeleteIssueResponse>(DELETE_ISSUES_MUTATION, { ids: [id] });
}
// Delete multiple issues
async deleteIssues(ids: string[]): Promise<DeleteIssueResponse> {
const { DELETE_ISSUES_MUTATION } = await import('./mutations.js');
return this.execute<DeleteIssueResponse>(DELETE_ISSUES_MUTATION, { ids });
}
}