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 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 Tool
71
+ ### Available Tools
71
72
 
72
- The server provides a single tool:
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
- console.log(`Headers from Linear:`, headers);
85
- // Convert headers array to object, being careful with header names
86
- const uploadHeaders = {};
87
- // Only include headers that Linear explicitly provided
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.1.1",
4
- "description": "MCP tools for Cyrus - including Linear file uploads and other utilities.",
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
- // Convert headers array to object, being careful with header names
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
- // Only include headers that Linear explicitly provided
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
 
@@ -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
+ }