@vineethnkrishnan/linear-mcp 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/common/types.d.ts +10 -0
- package/build/common/types.js +3 -0
- package/build/common/types.js.map +1 -0
- package/build/common/utils.d.ts +38 -0
- package/build/common/utils.js +155 -0
- package/build/common/utils.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +94 -0
- package/build/index.js.map +1 -0
- package/build/services/linear.service.d.ts +12 -0
- package/build/services/linear.service.js +165 -0
- package/build/services/linear.service.js.map +1 -0
- package/build/tools/issue.tools.d.ts +32 -0
- package/build/tools/issue.tools.js +51 -0
- package/build/tools/issue.tools.js.map +1 -0
- package/build/tools/project.tools.d.ts +22 -0
- package/build/tools/project.tools.js +35 -0
- package/build/tools/project.tools.js.map +1 -0
- package/build/tools/team.tools.d.ts +15 -0
- package/build/tools/team.tools.js +25 -0
- package/build/tools/team.tools.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { McpToolResponse } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Converts GraphQL connection structures ({nodes: [...], pageInfo: {...}})
|
|
4
|
+
* to plain arrays. Handles nested connections recursively.
|
|
5
|
+
*/
|
|
6
|
+
export declare function flattenConnection(data: unknown): unknown;
|
|
7
|
+
/**
|
|
8
|
+
* Simplifies nested references on a node by stripping __typename fields.
|
|
9
|
+
* Preserves structure like {assignee: {id, name}} but removes GraphQL metadata.
|
|
10
|
+
*/
|
|
11
|
+
export declare function flattenNode(node: unknown): unknown;
|
|
12
|
+
/**
|
|
13
|
+
* Maps numeric priority values to human-readable labels.
|
|
14
|
+
* Returns the label string, or the original value if not a known priority.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mapPriority(priority: number): string;
|
|
17
|
+
/**
|
|
18
|
+
* Recursively maps priority fields in an object from numeric to labeled format.
|
|
19
|
+
*/
|
|
20
|
+
export declare function mapPriorities(data: unknown): unknown;
|
|
21
|
+
/**
|
|
22
|
+
* Recursively removes __typename, pageInfo, and other GraphQL metadata
|
|
23
|
+
* from all levels of an object.
|
|
24
|
+
*/
|
|
25
|
+
export declare function stripGraphQLMetadata(obj: unknown, keys?: string[]): unknown;
|
|
26
|
+
/**
|
|
27
|
+
* Composes all Linear-specific transformations for LLM optimization.
|
|
28
|
+
* Order: flatten connections -> strip metadata -> map priorities
|
|
29
|
+
*/
|
|
30
|
+
export declare function transformLinearResponse(data: unknown): unknown;
|
|
31
|
+
/**
|
|
32
|
+
* Transforms Linear data and wraps it in the MCP tool response format.
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatMcpResponse(data: unknown): McpToolResponse;
|
|
35
|
+
/**
|
|
36
|
+
* Wraps an error in the MCP tool error response format.
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatMcpError(error: unknown): McpToolResponse;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flattenConnection = flattenConnection;
|
|
4
|
+
exports.flattenNode = flattenNode;
|
|
5
|
+
exports.mapPriority = mapPriority;
|
|
6
|
+
exports.mapPriorities = mapPriorities;
|
|
7
|
+
exports.stripGraphQLMetadata = stripGraphQLMetadata;
|
|
8
|
+
exports.transformLinearResponse = transformLinearResponse;
|
|
9
|
+
exports.formatMcpResponse = formatMcpResponse;
|
|
10
|
+
exports.formatMcpError = formatMcpError;
|
|
11
|
+
const LINEAR_STRIP_KEYS = [
|
|
12
|
+
"__typename",
|
|
13
|
+
"pageInfo",
|
|
14
|
+
"archivedAt",
|
|
15
|
+
"trashed",
|
|
16
|
+
"autoArchivedAt",
|
|
17
|
+
"autoClosedAt",
|
|
18
|
+
"snoozedUntilAt",
|
|
19
|
+
"sortOrder",
|
|
20
|
+
"boardOrder",
|
|
21
|
+
"subIssueSortOrder",
|
|
22
|
+
];
|
|
23
|
+
const PRIORITY_MAP = {
|
|
24
|
+
0: "No Priority",
|
|
25
|
+
1: "Urgent",
|
|
26
|
+
2: "High",
|
|
27
|
+
3: "Medium",
|
|
28
|
+
4: "Low",
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Converts GraphQL connection structures ({nodes: [...], pageInfo: {...}})
|
|
32
|
+
* to plain arrays. Handles nested connections recursively.
|
|
33
|
+
*/
|
|
34
|
+
function flattenConnection(data) {
|
|
35
|
+
if (data === null || data === undefined)
|
|
36
|
+
return data;
|
|
37
|
+
if (Array.isArray(data))
|
|
38
|
+
return data.map(flattenConnection);
|
|
39
|
+
if (typeof data !== "object")
|
|
40
|
+
return data;
|
|
41
|
+
const record = data;
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const [key, value] of Object.entries(record)) {
|
|
44
|
+
if (value !== null &&
|
|
45
|
+
typeof value === "object" &&
|
|
46
|
+
!Array.isArray(value) &&
|
|
47
|
+
"nodes" in value) {
|
|
48
|
+
const connection = value;
|
|
49
|
+
const nodes = connection.nodes;
|
|
50
|
+
result[key] = nodes.map(flattenConnection);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
result[key] = flattenConnection(value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Simplifies nested references on a node by stripping __typename fields.
|
|
60
|
+
* Preserves structure like {assignee: {id, name}} but removes GraphQL metadata.
|
|
61
|
+
*/
|
|
62
|
+
function flattenNode(node) {
|
|
63
|
+
if (node === null || node === undefined)
|
|
64
|
+
return node;
|
|
65
|
+
if (Array.isArray(node))
|
|
66
|
+
return node.map(flattenNode);
|
|
67
|
+
if (typeof node !== "object")
|
|
68
|
+
return node;
|
|
69
|
+
const record = node;
|
|
70
|
+
const result = {};
|
|
71
|
+
for (const [key, value] of Object.entries(record)) {
|
|
72
|
+
if (key === "__typename")
|
|
73
|
+
continue;
|
|
74
|
+
result[key] = flattenNode(value);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Maps numeric priority values to human-readable labels.
|
|
80
|
+
* Returns the label string, or the original value if not a known priority.
|
|
81
|
+
*/
|
|
82
|
+
function mapPriority(priority) {
|
|
83
|
+
return PRIORITY_MAP[priority] ?? `Unknown (${priority})`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Recursively maps priority fields in an object from numeric to labeled format.
|
|
87
|
+
*/
|
|
88
|
+
function mapPriorities(data) {
|
|
89
|
+
if (data === null || data === undefined)
|
|
90
|
+
return data;
|
|
91
|
+
if (Array.isArray(data))
|
|
92
|
+
return data.map(mapPriorities);
|
|
93
|
+
if (typeof data !== "object")
|
|
94
|
+
return data;
|
|
95
|
+
const record = data;
|
|
96
|
+
const result = {};
|
|
97
|
+
for (const [key, value] of Object.entries(record)) {
|
|
98
|
+
if (key === "priority" && typeof value === "number") {
|
|
99
|
+
result[key] = { level: value, label: mapPriority(value) };
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
result[key] = mapPriorities(value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Recursively removes __typename, pageInfo, and other GraphQL metadata
|
|
109
|
+
* from all levels of an object.
|
|
110
|
+
*/
|
|
111
|
+
function stripGraphQLMetadata(obj, keys = LINEAR_STRIP_KEYS) {
|
|
112
|
+
if (obj === null || obj === undefined)
|
|
113
|
+
return obj;
|
|
114
|
+
if (Array.isArray(obj))
|
|
115
|
+
return obj.map((item) => stripGraphQLMetadata(item, keys));
|
|
116
|
+
if (typeof obj !== "object")
|
|
117
|
+
return obj;
|
|
118
|
+
const result = {};
|
|
119
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
120
|
+
if (keys.includes(key))
|
|
121
|
+
continue;
|
|
122
|
+
result[key] = stripGraphQLMetadata(value, keys);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Composes all Linear-specific transformations for LLM optimization.
|
|
128
|
+
* Order: flatten connections -> strip metadata -> map priorities
|
|
129
|
+
*/
|
|
130
|
+
function transformLinearResponse(data) {
|
|
131
|
+
let result = flattenConnection(data);
|
|
132
|
+
result = stripGraphQLMetadata(result);
|
|
133
|
+
result = mapPriorities(result);
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Transforms Linear data and wraps it in the MCP tool response format.
|
|
138
|
+
*/
|
|
139
|
+
function formatMcpResponse(data) {
|
|
140
|
+
const transformed = transformLinearResponse(data);
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: JSON.stringify(transformed, null, 2) }],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Wraps an error in the MCP tool error response format.
|
|
147
|
+
*/
|
|
148
|
+
function formatMcpError(error) {
|
|
149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
152
|
+
isError: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/common/utils.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
const linear_service_1 = require("./services/linear.service");
|
|
9
|
+
const issue_tools_1 = require("./tools/issue.tools");
|
|
10
|
+
const project_tools_1 = require("./tools/project.tools");
|
|
11
|
+
const team_tools_1 = require("./tools/team.tools");
|
|
12
|
+
// Validate required config
|
|
13
|
+
const LINEAR_API_KEY = process.env.LINEAR_API_KEY;
|
|
14
|
+
if (!LINEAR_API_KEY) {
|
|
15
|
+
console.error("LINEAR_API_KEY environment variable is required.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const linearService = new linear_service_1.LinearService({
|
|
19
|
+
apiKey: LINEAR_API_KEY,
|
|
20
|
+
});
|
|
21
|
+
// Initialize tool classes
|
|
22
|
+
const tools = {
|
|
23
|
+
issues: new issue_tools_1.IssueTools(linearService),
|
|
24
|
+
projects: new project_tools_1.ProjectTools(linearService),
|
|
25
|
+
teams: new team_tools_1.TeamTools(linearService),
|
|
26
|
+
};
|
|
27
|
+
// Combine all schemas
|
|
28
|
+
const AllToolSchemas = {
|
|
29
|
+
...issue_tools_1.IssueToolSchemas,
|
|
30
|
+
...project_tools_1.ProjectToolSchemas,
|
|
31
|
+
...team_tools_1.TeamToolSchemas,
|
|
32
|
+
};
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
|
+
const { version } = require("../package.json");
|
|
35
|
+
const server = new index_js_1.Server({
|
|
36
|
+
name: "linear-mcp",
|
|
37
|
+
version,
|
|
38
|
+
}, {
|
|
39
|
+
capabilities: {
|
|
40
|
+
tools: {},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Handler for listing available tools.
|
|
45
|
+
*/
|
|
46
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
47
|
+
return {
|
|
48
|
+
tools: Object.entries(AllToolSchemas).map(([name, config]) => ({
|
|
49
|
+
name,
|
|
50
|
+
description: config.description,
|
|
51
|
+
inputSchema: zod_1.z.toJSONSchema(config.schema),
|
|
52
|
+
})),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
const toolRegistry = {};
|
|
56
|
+
for (const name of Object.keys(issue_tools_1.IssueToolSchemas)) {
|
|
57
|
+
toolRegistry[name] = (args) => tools.issues[name](args);
|
|
58
|
+
}
|
|
59
|
+
for (const name of Object.keys(project_tools_1.ProjectToolSchemas)) {
|
|
60
|
+
toolRegistry[name] = (args) => tools.projects[name](args);
|
|
61
|
+
}
|
|
62
|
+
for (const name of Object.keys(team_tools_1.TeamToolSchemas)) {
|
|
63
|
+
toolRegistry[name] = (args) => tools.teams[name](args);
|
|
64
|
+
}
|
|
65
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
66
|
+
try {
|
|
67
|
+
const { name, arguments: args } = request.params;
|
|
68
|
+
const handler = toolRegistry[name];
|
|
69
|
+
if (!handler) {
|
|
70
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Tool not found: ${name}`);
|
|
71
|
+
}
|
|
72
|
+
return await handler(args ?? {});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
78
|
+
isError: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* Start the server using Stdio transport.
|
|
84
|
+
*/
|
|
85
|
+
async function main() {
|
|
86
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
87
|
+
await server.connect(transport);
|
|
88
|
+
console.error("Linear MCP Server running on stdio");
|
|
89
|
+
}
|
|
90
|
+
main().catch((error) => {
|
|
91
|
+
console.error("Server error:", error);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LinearConfig } from "../common/types";
|
|
2
|
+
export declare class LinearService {
|
|
3
|
+
private apiKey;
|
|
4
|
+
constructor(config: LinearConfig);
|
|
5
|
+
query<T>(graphqlQuery: string, variables?: Record<string, unknown>): Promise<T>;
|
|
6
|
+
listIssues(teamId?: string, status?: string, limit?: number): Promise<unknown>;
|
|
7
|
+
getIssue(issueId: string): Promise<unknown>;
|
|
8
|
+
searchIssues(searchQuery: string, limit?: number): Promise<unknown>;
|
|
9
|
+
listProjects(limit?: number): Promise<unknown>;
|
|
10
|
+
getProject(projectId: string): Promise<unknown>;
|
|
11
|
+
listTeams(limit?: number): Promise<unknown>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LinearService = void 0;
|
|
4
|
+
const GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
|
|
5
|
+
// ===========================================================================
|
|
6
|
+
// GraphQL query fragments
|
|
7
|
+
// ===========================================================================
|
|
8
|
+
const ISSUE_FIELDS = `
|
|
9
|
+
id
|
|
10
|
+
identifier
|
|
11
|
+
title
|
|
12
|
+
description
|
|
13
|
+
priority
|
|
14
|
+
state { name type }
|
|
15
|
+
assignee { name email }
|
|
16
|
+
team { name key }
|
|
17
|
+
labels { nodes { name } }
|
|
18
|
+
createdAt
|
|
19
|
+
updatedAt
|
|
20
|
+
url
|
|
21
|
+
`;
|
|
22
|
+
const PROJECT_FIELDS = `
|
|
23
|
+
id
|
|
24
|
+
name
|
|
25
|
+
description
|
|
26
|
+
state
|
|
27
|
+
progress
|
|
28
|
+
startDate
|
|
29
|
+
targetDate
|
|
30
|
+
teams { nodes { name } }
|
|
31
|
+
lead { name }
|
|
32
|
+
url
|
|
33
|
+
`;
|
|
34
|
+
const TEAM_FIELDS = `
|
|
35
|
+
id
|
|
36
|
+
name
|
|
37
|
+
key
|
|
38
|
+
description
|
|
39
|
+
members { nodes { name email } }
|
|
40
|
+
`;
|
|
41
|
+
// ===========================================================================
|
|
42
|
+
// Service
|
|
43
|
+
// ===========================================================================
|
|
44
|
+
class LinearService {
|
|
45
|
+
apiKey;
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.apiKey = config.apiKey;
|
|
48
|
+
}
|
|
49
|
+
async query(graphqlQuery, variables) {
|
|
50
|
+
const response = await fetch(GRAPHQL_ENDPOINT, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: this.apiKey,
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({ query: graphqlQuery, variables }),
|
|
57
|
+
});
|
|
58
|
+
// Handle HTTP-level errors
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const errorBody = await response.text().catch(() => "");
|
|
61
|
+
switch (response.status) {
|
|
62
|
+
case 401:
|
|
63
|
+
throw new Error("Authentication failed. Check your LINEAR_API_KEY.");
|
|
64
|
+
case 429: {
|
|
65
|
+
const retryAfter = response.headers.get("Retry-After") ?? "unknown";
|
|
66
|
+
throw new Error(`Rate limited by Linear. Retry after ${retryAfter} seconds.`);
|
|
67
|
+
}
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(`Linear API error (${response.status}): ${errorBody || response.statusText}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const json = (await response.json());
|
|
73
|
+
// Handle GraphQL-level errors
|
|
74
|
+
if (json.errors && json.errors.length > 0) {
|
|
75
|
+
const messages = json.errors.map((e) => e.message).join("; ");
|
|
76
|
+
throw new Error(`Linear GraphQL error: ${messages}`);
|
|
77
|
+
}
|
|
78
|
+
if (!json.data) {
|
|
79
|
+
throw new Error("Linear API returned no data.");
|
|
80
|
+
}
|
|
81
|
+
return json.data;
|
|
82
|
+
}
|
|
83
|
+
// ===========================================================================
|
|
84
|
+
// Issues
|
|
85
|
+
// ===========================================================================
|
|
86
|
+
async listIssues(teamId, status, limit = 25) {
|
|
87
|
+
const filterParts = [];
|
|
88
|
+
if (teamId)
|
|
89
|
+
filterParts.push(`team: { id: { eq: "${teamId}" } }`);
|
|
90
|
+
if (status)
|
|
91
|
+
filterParts.push(`state: { name: { eqCaseInsensitive: "${status}" } }`);
|
|
92
|
+
const filterArg = filterParts.length > 0 ? `, filter: { ${filterParts.join(", ")} }` : "";
|
|
93
|
+
const graphqlQuery = `
|
|
94
|
+
query ListIssues {
|
|
95
|
+
issues(first: ${limit}${filterArg}) {
|
|
96
|
+
nodes { ${ISSUE_FIELDS} }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
`;
|
|
100
|
+
const data = await this.query(graphqlQuery);
|
|
101
|
+
return data.issues;
|
|
102
|
+
}
|
|
103
|
+
async getIssue(issueId) {
|
|
104
|
+
const graphqlQuery = `
|
|
105
|
+
query GetIssue($id: String!) {
|
|
106
|
+
issue(id: $id) { ${ISSUE_FIELDS} }
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
const data = await this.query(graphqlQuery, { id: issueId });
|
|
110
|
+
return data.issue;
|
|
111
|
+
}
|
|
112
|
+
async searchIssues(searchQuery, limit = 25) {
|
|
113
|
+
const graphqlQuery = `
|
|
114
|
+
query SearchIssues($query: String!, $first: Int) {
|
|
115
|
+
searchIssues(query: $query, first: $first) {
|
|
116
|
+
nodes { ${ISSUE_FIELDS} }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
const data = await this.query(graphqlQuery, {
|
|
121
|
+
query: searchQuery,
|
|
122
|
+
first: limit,
|
|
123
|
+
});
|
|
124
|
+
return data.searchIssues;
|
|
125
|
+
}
|
|
126
|
+
// ===========================================================================
|
|
127
|
+
// Projects
|
|
128
|
+
// ===========================================================================
|
|
129
|
+
async listProjects(limit = 25) {
|
|
130
|
+
const graphqlQuery = `
|
|
131
|
+
query ListProjects($first: Int) {
|
|
132
|
+
projects(first: $first) {
|
|
133
|
+
nodes { ${PROJECT_FIELDS} }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
`;
|
|
137
|
+
const data = await this.query(graphqlQuery, { first: limit });
|
|
138
|
+
return data.projects;
|
|
139
|
+
}
|
|
140
|
+
async getProject(projectId) {
|
|
141
|
+
const graphqlQuery = `
|
|
142
|
+
query GetProject($id: String!) {
|
|
143
|
+
project(id: $id) { ${PROJECT_FIELDS} }
|
|
144
|
+
}
|
|
145
|
+
`;
|
|
146
|
+
const data = await this.query(graphqlQuery, { id: projectId });
|
|
147
|
+
return data.project;
|
|
148
|
+
}
|
|
149
|
+
// ===========================================================================
|
|
150
|
+
// Teams
|
|
151
|
+
// ===========================================================================
|
|
152
|
+
async listTeams(limit = 25) {
|
|
153
|
+
const graphqlQuery = `
|
|
154
|
+
query ListTeams($first: Int) {
|
|
155
|
+
teams(first: $first) {
|
|
156
|
+
nodes { ${TEAM_FIELDS} }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
const data = await this.query(graphqlQuery, { first: limit });
|
|
161
|
+
return data.teams;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.LinearService = LinearService;
|
|
165
|
+
//# sourceMappingURL=linear.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear.service.js","sourceRoot":"","sources":["../../src/services/linear.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LinearService } from "../services/linear.service";
|
|
3
|
+
export declare const IssueToolSchemas: {
|
|
4
|
+
list_issues: {
|
|
5
|
+
description: string;
|
|
6
|
+
schema: z.ZodObject<{
|
|
7
|
+
team_id: z.ZodOptional<z.ZodString>;
|
|
8
|
+
status: z.ZodOptional<z.ZodString>;
|
|
9
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
};
|
|
12
|
+
get_issue: {
|
|
13
|
+
description: string;
|
|
14
|
+
schema: z.ZodObject<{
|
|
15
|
+
issue_id: z.ZodString;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
};
|
|
18
|
+
search_issues: {
|
|
19
|
+
description: string;
|
|
20
|
+
schema: z.ZodObject<{
|
|
21
|
+
query: z.ZodString;
|
|
22
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export declare class IssueTools {
|
|
27
|
+
private linearService;
|
|
28
|
+
constructor(linearService: LinearService);
|
|
29
|
+
list_issues(args: z.infer<typeof IssueToolSchemas.list_issues.schema>): Promise<import("../common/types").McpToolResponse>;
|
|
30
|
+
get_issue(args: z.infer<typeof IssueToolSchemas.get_issue.schema>): Promise<import("../common/types").McpToolResponse>;
|
|
31
|
+
search_issues(args: z.infer<typeof IssueToolSchemas.search_issues.schema>): Promise<import("../common/types").McpToolResponse>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IssueTools = exports.IssueToolSchemas = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const utils_1 = require("../common/utils");
|
|
6
|
+
exports.IssueToolSchemas = {
|
|
7
|
+
list_issues: {
|
|
8
|
+
description: "Lists issues from Linear, optionally filtered by team and status. Returns issue title, identifier, priority, state, assignee, and labels.",
|
|
9
|
+
schema: zod_1.z.object({
|
|
10
|
+
team_id: zod_1.z.string().optional().describe("Team ID to filter issues by."),
|
|
11
|
+
status: zod_1.z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Workflow state name to filter by (e.g., 'In Progress', 'Done')."),
|
|
15
|
+
limit: zod_1.z.number().optional().default(25).describe("Number of issues to return (max 50)."),
|
|
16
|
+
}),
|
|
17
|
+
},
|
|
18
|
+
get_issue: {
|
|
19
|
+
description: "Retrieves full details for a specific Linear issue including title, description, state, priority, assignee, labels, and timestamps. Accepts both UUID and human-readable identifier (e.g., 'ENG-123').",
|
|
20
|
+
schema: zod_1.z.object({
|
|
21
|
+
issue_id: zod_1.z.string().describe("The issue ID (UUID) or identifier (e.g., 'ENG-123')."),
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
search_issues: {
|
|
25
|
+
description: "Searches Linear issues by text query across all teams. Returns matching issues with title, identifier, priority, state, and assignee.",
|
|
26
|
+
schema: zod_1.z.object({
|
|
27
|
+
query: zod_1.z.string().describe("Search query to find issues by title or description."),
|
|
28
|
+
limit: zod_1.z.number().optional().default(25).describe("Number of issues to return (max 50)."),
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
class IssueTools {
|
|
33
|
+
linearService;
|
|
34
|
+
constructor(linearService) {
|
|
35
|
+
this.linearService = linearService;
|
|
36
|
+
}
|
|
37
|
+
async list_issues(args) {
|
|
38
|
+
const issues = await this.linearService.listIssues(args.team_id, args.status, args.limit);
|
|
39
|
+
return (0, utils_1.formatMcpResponse)(issues);
|
|
40
|
+
}
|
|
41
|
+
async get_issue(args) {
|
|
42
|
+
const issue = await this.linearService.getIssue(args.issue_id);
|
|
43
|
+
return (0, utils_1.formatMcpResponse)(issue);
|
|
44
|
+
}
|
|
45
|
+
async search_issues(args) {
|
|
46
|
+
const issues = await this.linearService.searchIssues(args.query, args.limit);
|
|
47
|
+
return (0, utils_1.formatMcpResponse)(issues);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.IssueTools = IssueTools;
|
|
51
|
+
//# sourceMappingURL=issue.tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.tools.js","sourceRoot":"","sources":["../../src/tools/issue.tools.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB,2CAAoD;AAEvC,QAAA,gBAAgB,GAAG;IAC9B,WAAW,EAAE;QACX,WAAW,EACT,2IAA2I;QAC7I,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;YACvE,MAAM,EAAE,OAAC;iBACN,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,iEAAiE,CAAC;YAC9E,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,sCAAsC,CAAC;SAC1F,CAAC;KACH;IACD,SAAS,EAAE;QACT,WAAW,EACT,wMAAwM;QAC1M,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sDAAsD,CAAC;SACtF,CAAC;KACH;IACD,aAAa,EAAE;QACb,WAAW,EACT,uIAAuI;QACzI,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sDAAsD,CAAC;YAClF,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,sCAAsC,CAAC;SAC1F,CAAC;KACH;CACF,CAAC;AAEF,MAAa,UAAU;IACD;IAApB,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAEpD,KAAK,CAAC,WAAW,CAAC,IAAyD;QACzE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1F,OAAO,IAAA,yBAAiB,EAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAuD;QACrE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/D,OAAO,IAAA,yBAAiB,EAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,IAA2D;QAC7E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7E,OAAO,IAAA,yBAAiB,EAAC,MAAM,CAAC,CAAC;IACnC,CAAC;CACF;AAjBD,gCAiBC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LinearService } from "../services/linear.service";
|
|
3
|
+
export declare const ProjectToolSchemas: {
|
|
4
|
+
list_projects: {
|
|
5
|
+
description: string;
|
|
6
|
+
schema: z.ZodObject<{
|
|
7
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
};
|
|
10
|
+
get_project: {
|
|
11
|
+
description: string;
|
|
12
|
+
schema: z.ZodObject<{
|
|
13
|
+
project_id: z.ZodString;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export declare class ProjectTools {
|
|
18
|
+
private linearService;
|
|
19
|
+
constructor(linearService: LinearService);
|
|
20
|
+
list_projects(args: z.infer<typeof ProjectToolSchemas.list_projects.schema>): Promise<import("../common/types").McpToolResponse>;
|
|
21
|
+
get_project(args: z.infer<typeof ProjectToolSchemas.get_project.schema>): Promise<import("../common/types").McpToolResponse>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProjectTools = exports.ProjectToolSchemas = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const utils_1 = require("../common/utils");
|
|
6
|
+
exports.ProjectToolSchemas = {
|
|
7
|
+
list_projects: {
|
|
8
|
+
description: "Lists all projects in the Linear workspace. Returns project name, state, progress percentage, lead, start/target dates, and associated teams.",
|
|
9
|
+
schema: zod_1.z.object({
|
|
10
|
+
limit: zod_1.z.number().optional().default(25).describe("Number of projects to return (max 50)."),
|
|
11
|
+
}),
|
|
12
|
+
},
|
|
13
|
+
get_project: {
|
|
14
|
+
description: "Retrieves full details for a specific Linear project including name, description, state, progress, lead, start/target dates, and associated teams.",
|
|
15
|
+
schema: zod_1.z.object({
|
|
16
|
+
project_id: zod_1.z.string().describe("The project ID (UUID)."),
|
|
17
|
+
}),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
class ProjectTools {
|
|
21
|
+
linearService;
|
|
22
|
+
constructor(linearService) {
|
|
23
|
+
this.linearService = linearService;
|
|
24
|
+
}
|
|
25
|
+
async list_projects(args) {
|
|
26
|
+
const projects = await this.linearService.listProjects(args.limit);
|
|
27
|
+
return (0, utils_1.formatMcpResponse)(projects);
|
|
28
|
+
}
|
|
29
|
+
async get_project(args) {
|
|
30
|
+
const project = await this.linearService.getProject(args.project_id);
|
|
31
|
+
return (0, utils_1.formatMcpResponse)(project);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.ProjectTools = ProjectTools;
|
|
35
|
+
//# sourceMappingURL=project.tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.tools.js","sourceRoot":"","sources":["../../src/tools/project.tools.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB,2CAAoD;AAEvC,QAAA,kBAAkB,GAAG;IAChC,aAAa,EAAE;QACb,WAAW,EACT,+IAA+I;QACjJ,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,wCAAwC,CAAC;SAC5F,CAAC;KACH;IACD,WAAW,EAAE;QACX,WAAW,EACT,oJAAoJ;QACtJ,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;SAC1D,CAAC;KACH;CACF,CAAC;AAEF,MAAa,YAAY;IACH;IAApB,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAEpD,KAAK,CAAC,aAAa,CAAC,IAA6D;QAC/E,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnE,OAAO,IAAA,yBAAiB,EAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAA2D;QAC3E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACrE,OAAO,IAAA,yBAAiB,EAAC,OAAO,CAAC,CAAC;IACpC,CAAC;CACF;AAZD,oCAYC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LinearService } from "../services/linear.service";
|
|
3
|
+
export declare const TeamToolSchemas: {
|
|
4
|
+
list_teams: {
|
|
5
|
+
description: string;
|
|
6
|
+
schema: z.ZodObject<{
|
|
7
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export declare class TeamTools {
|
|
12
|
+
private linearService;
|
|
13
|
+
constructor(linearService: LinearService);
|
|
14
|
+
list_teams(args: z.infer<typeof TeamToolSchemas.list_teams.schema>): Promise<import("../common/types").McpToolResponse>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TeamTools = exports.TeamToolSchemas = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const utils_1 = require("../common/utils");
|
|
6
|
+
exports.TeamToolSchemas = {
|
|
7
|
+
list_teams: {
|
|
8
|
+
description: "Lists all teams in the Linear workspace including name, key (prefix for issue identifiers), description, and members.",
|
|
9
|
+
schema: zod_1.z.object({
|
|
10
|
+
limit: zod_1.z.number().optional().default(25).describe("Number of teams to return (max 50)."),
|
|
11
|
+
}),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
class TeamTools {
|
|
15
|
+
linearService;
|
|
16
|
+
constructor(linearService) {
|
|
17
|
+
this.linearService = linearService;
|
|
18
|
+
}
|
|
19
|
+
async list_teams(args) {
|
|
20
|
+
const teams = await this.linearService.listTeams(args.limit);
|
|
21
|
+
return (0, utils_1.formatMcpResponse)(teams);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.TeamTools = TeamTools;
|
|
25
|
+
//# sourceMappingURL=team.tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team.tools.js","sourceRoot":"","sources":["../../src/tools/team.tools.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB,2CAAoD;AAEvC,QAAA,eAAe,GAAG;IAC7B,UAAU,EAAE;QACV,WAAW,EACT,uHAAuH;QACzH,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC;YACf,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,qCAAqC,CAAC;SACzF,CAAC;KACH;CACF,CAAC;AAEF,MAAa,SAAS;IACA;IAApB,YAAoB,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAEpD,KAAK,CAAC,UAAU,CAAC,IAAuD;QACtE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7D,OAAO,IAAA,yBAAiB,EAAC,KAAK,CAAC,CAAC;IAClC,CAAC;CACF;AAPD,8BAOC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vineethnkrishnan/linear-mcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A read-only MCP server for Linear API, enabling AI assistants to query issues, projects, and teams via GraphQL.",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"linear-mcp": "build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"build/"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"start": "node build/index.js",
|
|
15
|
+
"test": "jest --coverage"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"linear",
|
|
23
|
+
"ai",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"project-management",
|
|
26
|
+
"graphql"
|
|
27
|
+
],
|
|
28
|
+
"author": "Vineeth Krishnan",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"type": "commonjs",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36
|
+
"zod": "^4.3.6"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/jest": "^30.0.0",
|
|
40
|
+
"@types/node": "^25.5.0",
|
|
41
|
+
"jest": "^30.3.0",
|
|
42
|
+
"ts-jest": "^29.4.6",
|
|
43
|
+
"typescript": "^5.7.0"
|
|
44
|
+
}
|
|
45
|
+
}
|