cyrus-mcp-tools 0.1.1 → 0.2.0
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/README.md +16 -2
- package/dist/services/linear-service.js +2 -2
- package/dist/tools/definitions/agent-session-tool.js +39 -0
- package/dist/tools/definitions/index.js +3 -1
- package/dist/tools/handlers/agent-session-handler.js +51 -0
- package/dist/tools/handlers/index.js +3 -1
- package/dist/tools/handlers/upload-handler.js +7 -5
- package/dist/tools/type-guards.js +15 -0
- package/package.json +5 -4
- package/src/services/linear-service.ts +2 -2
- package/src/tools/definitions/agent-session-tool.ts +41 -0
- package/src/tools/definitions/index.ts +3 -1
- package/src/tools/handlers/agent-session-handler.ts +61 -0
- package/src/tools/handlers/index.ts +3 -1
- package/src/tools/handlers/upload-handler.ts +7 -5
- package/src/tools/type-guards.ts +25 -0
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ MCP tools for Cyrus - including Linear file uploads and other utilities.
|
|
|
9
9
|
|
|
10
10
|
This MCP server provides tools for Cyrus, currently including:
|
|
11
11
|
- File uploads to Linear's cloud storage for use in issues, comments, and other content
|
|
12
|
+
- Agent session creation for tracking AI/bot activity on Linear issues
|
|
12
13
|
|
|
13
14
|
## Features
|
|
14
15
|
|
|
@@ -67,9 +68,9 @@ Add the following to your MCP settings file:
|
|
|
67
68
|
|
|
68
69
|
## Usage
|
|
69
70
|
|
|
70
|
-
### Available
|
|
71
|
+
### Available Tools
|
|
71
72
|
|
|
72
|
-
The server provides
|
|
73
|
+
The server provides the following tools:
|
|
73
74
|
|
|
74
75
|
#### `linear_upload_file`
|
|
75
76
|
|
|
@@ -87,6 +88,19 @@ Upload a file to Linear and get an asset URL.
|
|
|
87
88
|
- `size`: File size in bytes
|
|
88
89
|
- `contentType`: MIME type of the uploaded file
|
|
89
90
|
|
|
91
|
+
#### `linear_agent_session_create`
|
|
92
|
+
|
|
93
|
+
Create an agent session on a Linear issue to track AI/bot activity.
|
|
94
|
+
|
|
95
|
+
**Parameters:**
|
|
96
|
+
- `issueId` (required): The ID or identifier of the Linear issue (e.g., "ABC-123" or UUID)
|
|
97
|
+
- `externalLink` (optional): URL of an external agent-hosted page associated with this session
|
|
98
|
+
|
|
99
|
+
**Returns:**
|
|
100
|
+
- `success`: Whether the operation was successful
|
|
101
|
+
- `agentSessionId`: The ID of the created agent session
|
|
102
|
+
- `lastSyncId`: The identifier of the last sync operation
|
|
103
|
+
|
|
90
104
|
### Example Usage
|
|
91
105
|
|
|
92
106
|
Once configured, you can use prompts like:
|
|
@@ -457,7 +457,7 @@ export class LinearService {
|
|
|
457
457
|
description: args.description,
|
|
458
458
|
content: args.content,
|
|
459
459
|
teamIds: teamIds,
|
|
460
|
-
state: args.state,
|
|
460
|
+
// state: args.state, // TODO: v58 removed state, need to use statusId instead
|
|
461
461
|
startDate: args.startDate ? new Date(args.startDate) : undefined,
|
|
462
462
|
targetDate: args.targetDate ? new Date(args.targetDate) : undefined,
|
|
463
463
|
leadId: args.leadId,
|
|
@@ -941,7 +941,7 @@ export class LinearService {
|
|
|
941
941
|
name: args.name,
|
|
942
942
|
description: args.description,
|
|
943
943
|
content: args.content,
|
|
944
|
-
state: args.state,
|
|
944
|
+
// state: args.state as any, // TODO: v58 removed state, need to use statusId instead
|
|
945
945
|
startDate: args.startDate ? new Date(args.startDate) : undefined,
|
|
946
946
|
targetDate: args.targetDate ? new Date(args.targetDate) : undefined,
|
|
947
947
|
leadId: args.leadId,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for creating an agent session on a Linear issue.
|
|
3
|
+
* This creates a new agent session that tracks AI/bot activity on an issue.
|
|
4
|
+
*/
|
|
5
|
+
export const createAgentSessionToolDefinition = {
|
|
6
|
+
name: 'linear_agent_session_create',
|
|
7
|
+
description: 'Create an agent session on a Linear issue to track AI/bot activity.',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
issueId: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'The ID or identifier of the Linear issue (e.g., "ABC-123" or UUID)'
|
|
14
|
+
},
|
|
15
|
+
externalLink: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Optional URL of an external agent-hosted page associated with this session'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
required: ['issueId']
|
|
21
|
+
},
|
|
22
|
+
output_schema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
success: {
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
description: 'Whether the operation was successful'
|
|
28
|
+
},
|
|
29
|
+
agentSessionId: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'The ID of the created agent session'
|
|
32
|
+
},
|
|
33
|
+
lastSyncId: {
|
|
34
|
+
type: 'number',
|
|
35
|
+
description: 'The identifier of the last sync operation'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { uploadFileToolDefinition } from "./upload-tool.js";
|
|
2
|
+
import { createAgentSessionToolDefinition } from "./agent-session-tool.js";
|
|
2
3
|
// All tool definitions
|
|
3
4
|
export const allToolDefinitions = [
|
|
4
5
|
uploadFileToolDefinition,
|
|
6
|
+
createAgentSessionToolDefinition,
|
|
5
7
|
];
|
|
6
8
|
// Export all tool definitions individually
|
|
7
|
-
export { uploadFileToolDefinition };
|
|
9
|
+
export { uploadFileToolDefinition, createAgentSessionToolDefinition };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { isCreateAgentSessionArgs } from '../type-guards.js';
|
|
2
|
+
/**
|
|
3
|
+
* Handler for creating an agent session on a Linear issue
|
|
4
|
+
*/
|
|
5
|
+
export function handleCreateAgentSession(linearService) {
|
|
6
|
+
return async (args) => {
|
|
7
|
+
if (!isCreateAgentSessionArgs(args)) {
|
|
8
|
+
throw new Error('Invalid arguments for agent_session_create');
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
// Use raw GraphQL through the Linear client
|
|
12
|
+
// Access the underlying GraphQL client
|
|
13
|
+
const graphQLClient = linearService.client.client;
|
|
14
|
+
const mutation = `
|
|
15
|
+
mutation AgentSessionCreateOnIssue($input: AgentSessionCreateOnIssue!) {
|
|
16
|
+
agentSessionCreateOnIssue(input: $input) {
|
|
17
|
+
success
|
|
18
|
+
lastSyncId
|
|
19
|
+
agentSession {
|
|
20
|
+
id
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
const variables = {
|
|
26
|
+
input: {
|
|
27
|
+
issueId: args.issueId,
|
|
28
|
+
...(args.externalLink && { externalLink: args.externalLink })
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
console.log(`Creating agent session for issue ${args.issueId}`);
|
|
32
|
+
const response = await graphQLClient.rawRequest(mutation, variables);
|
|
33
|
+
const result = response.data.agentSessionCreateOnIssue;
|
|
34
|
+
if (!result.success) {
|
|
35
|
+
throw new Error('Failed to create agent session');
|
|
36
|
+
}
|
|
37
|
+
console.log(`Agent session created successfully: ${result.agentSession.id}`);
|
|
38
|
+
return {
|
|
39
|
+
success: result.success,
|
|
40
|
+
agentSessionId: result.agentSession.id,
|
|
41
|
+
lastSyncId: result.lastSyncId
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof Error) {
|
|
46
|
+
throw new Error(`Failed to create agent session: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { handleUploadFile } from "./upload-handler.js";
|
|
2
|
+
import { handleCreateAgentSession } from "./agent-session-handler.js";
|
|
2
3
|
/**
|
|
3
4
|
* Registers all tool handlers for the MCP Linear uploads
|
|
4
5
|
* @param linearService The Linear service instance
|
|
@@ -7,7 +8,8 @@ import { handleUploadFile } from "./upload-handler.js";
|
|
|
7
8
|
export function registerToolHandlers(linearService) {
|
|
8
9
|
return {
|
|
9
10
|
linear_upload_file: handleUploadFile(linearService),
|
|
11
|
+
linear_agent_session_create: handleCreateAgentSession(linearService),
|
|
10
12
|
};
|
|
11
13
|
}
|
|
12
14
|
// Export all handlers individually
|
|
13
|
-
export { handleUploadFile };
|
|
15
|
+
export { handleUploadFile, handleCreateAgentSession };
|
|
@@ -81,12 +81,14 @@ export function handleUploadFile(linearService) {
|
|
|
81
81
|
const { uploadUrl, headers, assetUrl } = uploadPayload.uploadFile;
|
|
82
82
|
// Step 2: Upload the file to the provided URL
|
|
83
83
|
console.log(`Uploading file to Linear cloud storage...`);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
// Create headers following Linear's documentation exactly
|
|
85
|
+
const uploadHeaders = {
|
|
86
|
+
"Content-Type": contentType,
|
|
87
|
+
"Cache-Control": "public, max-age=31536000",
|
|
88
|
+
};
|
|
89
|
+
// Then add the headers from Linear's response
|
|
90
|
+
// These override any defaults we set above
|
|
88
91
|
for (const header of headers) {
|
|
89
|
-
// Use the exact key provided by Linear
|
|
90
92
|
uploadHeaders[header.key] = header.value;
|
|
91
93
|
}
|
|
92
94
|
console.log(`Headers being sent:`, uploadHeaders);
|
|
@@ -22,3 +22,18 @@ export function isUploadFileArgs(args) {
|
|
|
22
22
|
}
|
|
23
23
|
return true;
|
|
24
24
|
}
|
|
25
|
+
export function isCreateAgentSessionArgs(args) {
|
|
26
|
+
if (typeof args !== "object" || args === null) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const obj = args;
|
|
30
|
+
// Required field
|
|
31
|
+
if (typeof obj.issueId !== "string") {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// Optional field
|
|
35
|
+
if (obj.externalLink !== undefined && typeof obj.externalLink !== "string") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cyrus-mcp-tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP tools for Cyrus -
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP tools for Cyrus - Linear file uploads, agent session creation, and other utilities.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
"smithery": {
|
|
26
26
|
"name": "cyrus-mcp-tools",
|
|
27
27
|
"displayName": "Cyrus MCP Tools",
|
|
28
|
-
"description": "MCP tools for Cyrus including Linear file uploads",
|
|
28
|
+
"description": "MCP tools for Cyrus including Linear file uploads and agent session management",
|
|
29
29
|
"tools": [
|
|
30
|
-
"linear_upload_file"
|
|
30
|
+
"linear_upload_file",
|
|
31
|
+
"linear_agent_session_create"
|
|
31
32
|
]
|
|
32
33
|
},
|
|
33
34
|
"keywords": [
|
|
@@ -571,7 +571,7 @@ export class LinearService {
|
|
|
571
571
|
description: args.description,
|
|
572
572
|
content: args.content,
|
|
573
573
|
teamIds: teamIds,
|
|
574
|
-
state: args.state,
|
|
574
|
+
// state: args.state, // TODO: v58 removed state, need to use statusId instead
|
|
575
575
|
startDate: args.startDate ? new Date(args.startDate) : undefined,
|
|
576
576
|
targetDate: args.targetDate ? new Date(args.targetDate) : undefined,
|
|
577
577
|
leadId: args.leadId,
|
|
@@ -1125,7 +1125,7 @@ export class LinearService {
|
|
|
1125
1125
|
name: args.name,
|
|
1126
1126
|
description: args.description,
|
|
1127
1127
|
content: args.content,
|
|
1128
|
-
state: args.state as any,
|
|
1128
|
+
// state: args.state as any, // TODO: v58 removed state, need to use statusId instead
|
|
1129
1129
|
startDate: args.startDate ? new Date(args.startDate) : undefined,
|
|
1130
1130
|
targetDate: args.targetDate ? new Date(args.targetDate) : undefined,
|
|
1131
1131
|
leadId: args.leadId,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { MCPToolDefinition } from '../../types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tool for creating an agent session on a Linear issue.
|
|
5
|
+
* This creates a new agent session that tracks AI/bot activity on an issue.
|
|
6
|
+
*/
|
|
7
|
+
export const createAgentSessionToolDefinition: MCPToolDefinition = {
|
|
8
|
+
name: 'linear_agent_session_create',
|
|
9
|
+
description: 'Create an agent session on a Linear issue to track AI/bot activity.',
|
|
10
|
+
input_schema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
issueId: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'The ID or identifier of the Linear issue (e.g., "ABC-123" or UUID)'
|
|
16
|
+
},
|
|
17
|
+
externalLink: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Optional URL of an external agent-hosted page associated with this session'
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
required: ['issueId']
|
|
23
|
+
},
|
|
24
|
+
output_schema: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
success: {
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
description: 'Whether the operation was successful'
|
|
30
|
+
},
|
|
31
|
+
agentSessionId: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'The ID of the created agent session'
|
|
34
|
+
},
|
|
35
|
+
lastSyncId: {
|
|
36
|
+
type: 'number',
|
|
37
|
+
description: 'The identifier of the last sync operation'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { MCPToolDefinition } from "../../types.js";
|
|
2
2
|
import { uploadFileToolDefinition } from "./upload-tool.js";
|
|
3
|
+
import { createAgentSessionToolDefinition } from "./agent-session-tool.js";
|
|
3
4
|
|
|
4
5
|
// All tool definitions
|
|
5
6
|
export const allToolDefinitions: MCPToolDefinition[] = [
|
|
6
7
|
uploadFileToolDefinition,
|
|
8
|
+
createAgentSessionToolDefinition,
|
|
7
9
|
];
|
|
8
10
|
|
|
9
11
|
// Export all tool definitions individually
|
|
10
|
-
export { uploadFileToolDefinition };
|
|
12
|
+
export { uploadFileToolDefinition, createAgentSessionToolDefinition };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { LinearService } from '../../services/linear-service.js';
|
|
2
|
+
import { isCreateAgentSessionArgs } from '../type-guards.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handler for creating an agent session on a Linear issue
|
|
6
|
+
*/
|
|
7
|
+
export function handleCreateAgentSession(linearService: LinearService) {
|
|
8
|
+
return async (args: unknown) => {
|
|
9
|
+
if (!isCreateAgentSessionArgs(args)) {
|
|
10
|
+
throw new Error('Invalid arguments for agent_session_create');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
// Use raw GraphQL through the Linear client
|
|
15
|
+
// Access the underlying GraphQL client
|
|
16
|
+
const graphQLClient = (linearService as any).client.client;
|
|
17
|
+
|
|
18
|
+
const mutation = `
|
|
19
|
+
mutation AgentSessionCreateOnIssue($input: AgentSessionCreateOnIssue!) {
|
|
20
|
+
agentSessionCreateOnIssue(input: $input) {
|
|
21
|
+
success
|
|
22
|
+
lastSyncId
|
|
23
|
+
agentSession {
|
|
24
|
+
id
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const variables = {
|
|
31
|
+
input: {
|
|
32
|
+
issueId: args.issueId,
|
|
33
|
+
...(args.externalLink && { externalLink: args.externalLink })
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
console.log(`Creating agent session for issue ${args.issueId}`);
|
|
38
|
+
|
|
39
|
+
const response = await graphQLClient.rawRequest(mutation, variables);
|
|
40
|
+
|
|
41
|
+
const result = response.data.agentSessionCreateOnIssue;
|
|
42
|
+
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
throw new Error('Failed to create agent session');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(`Agent session created successfully: ${result.agentSession.id}`);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
success: result.success,
|
|
51
|
+
agentSessionId: result.agentSession.id,
|
|
52
|
+
lastSyncId: result.lastSyncId
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
throw new Error(`Failed to create agent session: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LinearService } from "../../services/linear-service.js";
|
|
2
2
|
import { handleUploadFile } from "./upload-handler.js";
|
|
3
|
+
import { handleCreateAgentSession } from "./agent-session-handler.js";
|
|
3
4
|
|
|
4
5
|
// Define the handler function type
|
|
5
6
|
type ToolHandler = (args: unknown) => Promise<unknown>;
|
|
@@ -14,8 +15,9 @@ export function registerToolHandlers(
|
|
|
14
15
|
): Record<string, ToolHandler> {
|
|
15
16
|
return {
|
|
16
17
|
linear_upload_file: handleUploadFile(linearService),
|
|
18
|
+
linear_agent_session_create: handleCreateAgentSession(linearService),
|
|
17
19
|
};
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
// Export all handlers individually
|
|
21
|
-
export { handleUploadFile };
|
|
23
|
+
export { handleUploadFile, handleCreateAgentSession };
|
|
@@ -106,14 +106,16 @@ export function handleUploadFile(linearService: LinearService) {
|
|
|
106
106
|
|
|
107
107
|
// Step 2: Upload the file to the provided URL
|
|
108
108
|
console.log(`Uploading file to Linear cloud storage...`);
|
|
109
|
-
console.log(`Headers from Linear:`, headers);
|
|
110
109
|
|
|
111
|
-
//
|
|
112
|
-
const uploadHeaders: Record<string, string> = {
|
|
110
|
+
// Create headers following Linear's documentation exactly
|
|
111
|
+
const uploadHeaders: Record<string, string> = {
|
|
112
|
+
"Content-Type": contentType,
|
|
113
|
+
"Cache-Control": "public, max-age=31536000",
|
|
114
|
+
};
|
|
113
115
|
|
|
114
|
-
//
|
|
116
|
+
// Then add the headers from Linear's response
|
|
117
|
+
// These override any defaults we set above
|
|
115
118
|
for (const header of headers) {
|
|
116
|
-
// Use the exact key provided by Linear
|
|
117
119
|
uploadHeaders[header.key] = header.value;
|
|
118
120
|
}
|
|
119
121
|
|
package/src/tools/type-guards.ts
CHANGED
|
@@ -36,3 +36,28 @@ export function isUploadFileArgs(args: unknown): args is UploadFileArgs {
|
|
|
36
36
|
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
export interface CreateAgentSessionArgs {
|
|
41
|
+
issueId: string;
|
|
42
|
+
externalLink?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isCreateAgentSessionArgs(args: unknown): args is CreateAgentSessionArgs {
|
|
46
|
+
if (typeof args !== "object" || args === null) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const obj = args as any;
|
|
51
|
+
|
|
52
|
+
// Required field
|
|
53
|
+
if (typeof obj.issueId !== "string") {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Optional field
|
|
58
|
+
if (obj.externalLink !== undefined && typeof obj.externalLink !== "string") {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return true;
|
|
63
|
+
}
|