Skip to main content
Glama
iceener

Linear Streamable MCP Server

by iceener
metadata.ts12.1 kB
/** * Centralized tool metadata for the Linear MCP server. */ export interface ToolMetadata { name: string; title: string; description: string; } export const serverMetadata = { title: 'Linear', instructions: `Use these tools to list and manage Linear issues, projects, teams, and users. Quick start - Call 'workspace_metadata' first to fetch canonical identifiers you will reuse across tools. It will include your viewer id: use that as 'assigneeId' when you want to assign items to yourself. - Then use 'list_issues' with teamId/projectId and filters (or q/keywords) to locate targets. Use 'list_issues' for both discovery and precise lookups (including by id/identifier) — prefer orderBy='updatedAt'. - To modify, use 'update_issues' / 'update_projects', then verify with 'list_issues'. - For teams with cycles enabled, use 'list_cycles' to browse planning cycles. Default recency window - If a date range is not provided when listing issues, default to the current week in the viewer's timezone (Mon 00:00 → Sun 23:59:59.999), using updatedAt for recency. Mention the timezone surfaced by the client in your reasoning and outputs. Account identifiers (returned by 'workspace_metadata') - viewer: { id, name, email, displayName, avatarUrl, timezone, createdAt } - teams: Array<{ id, key, name, description?, defaultIssueEstimate? }> - workflowStatesByTeam: Record<teamId, Array<{ id, name, type }>> - labelsByTeam: Record<teamId, Array<{ id, name, color?, description? }>> - projects: Array<{ id, name, state, teamId?, leadId?, targetDate?, createdAt }> How to chain safely - teamId: filter in 'list_issues'; pass to 'create_issues'; use workflowStatesByTeam[teamId] to find stateId for 'update_issues'. - projectId: filter in 'list_issues'; pass to create/update issue payloads. - stateId: set via 'update_issues'. Resolve by name → id using workflowStatesByTeam. - labelIds: pass to create/update; resolve from labelsByTeam. Handling assignees and failures - To assign to yourself, prefer using your viewer id from 'workspace_metadata' as 'assigneeId'. - If a create or update fails with 'assigneeId ... could not be found', either: - Re-run 'workspace_metadata' and verify the correct team/project and user id, or - Use 'list_users' to fetch users and pick the right id. Filtering (list_issues) - 'filter' supports GraphQL-style comparators and relationship fields. Comparators: { eq, neq, lt, lte, gt, gte, in, nin, containsIgnoreCase, startsWith, endsWith, null }. Common examples: - Team/project: { team: { id: { eq: teamId } } } or { project: { id: { eq: projectId } } } - State type: { state: { type: { eq: "started" } } } - Assignee email: { assignee: { email: { eqIgnoreCase: "name@acme.com" } } } - Title case-insensitive contains: { title: { containsIgnoreCase: "search" } } - Labels (use names from your workspace): { labels: { name: { in: ["LabelA", "LabelB"] } } } - Or use q/keywords for keyword search (default matchMode='all' requires ALL tokens; use 'any' for broad search). - Use workspace_metadata first to discover team/project/label/user names and IDs. Pagination - List tools return { cursor, nextCursor, limit }. Pass nextCursor to fetch the next page. - Prefer small limits and refine filters instead of broad scans. Safety & writes - Do not guess ids. Always take ids from 'workspace_metadata' or a read tool. - Batch writes default to sequential; keep batches small and verify with 'list_issues'. - 'update_issues' ignores empty strings (e.g., dueDate: ""): only valid fields are sent. `, } as const; export const toolsMetadata = { workspace_metadata: { name: 'workspace_metadata', title: 'Discover IDs (Use First)', description: "Use this to discover workspace entities and canonical IDs (viewer, teams, workflow states, labels, projects, favorites). Use this FIRST whenever you don't know ids. Inputs: include? (profile|teams|workflow_states|labels|projects|favorites), teamIds?, project_limit?, label_limit?.\nReturns: viewer, teams[] (with estimation settings and cyclesEnabled), workflowStatesByTeam, labelsByTeam, projects[], favorites?. Next: Use teamId/projectId to filter 'list_issues'; use workflowStatesByTeam[teamId][].id as stateId for 'update_issues'; use labelsByTeam ids for label operations. If a team has cyclesEnabled=false, avoid cycle-related tools.", }, list_issues: { name: 'list_issues', title: 'List Issues', description: 'List issues with filtering. Inputs: teamId?, projectId?, filter?, q?, keywords?, matchMode?, includeArchived?, orderBy?(updatedAt|createdAt), detail?(minimal|standard|full), limit?, cursor?, assignedToMe?.\n\n⚠️ orderBy only supports updatedAt or createdAt. DO NOT use orderBy:"priority" - use filter instead!\n\nKEYWORD SEARCH (q/keywords):\n- q: Extract 2-4 significant keywords from user intent. Avoid short/common words.\n- matchMode: \'all\' (default, precise) requires ALL tokens; \'any\' (broad) requires at least ONE.\n- Example: user says "find cursor workshop task" → q: "cursor workshop"\n\nFILTERING:\n- High priority: filter: { priority: { lte: 2 } } (1=Urgent, 2=High, 3=Medium, 4=Low)\n- Active issues: filter: { state: { type: { neq: \'completed\' } } }\n- In progress: filter: { state: { type: { eq: \'started\' } } }\n- My issues: assignedToMe: true\n\nDETAIL LEVELS: minimal (id,title,state), standard (default, +priority,assignee,project), full (+labels,description).\n\nReturns: { items[], pagination, meta }.', }, get_issues: { name: 'get_issues', title: 'Get Issues (Batch)', description: "Fetch detailed issues in batch by ids (UUIDs or short ids like ENG-123). Inputs: { ids: string[] }.\nReturns: { results: Array<{ index, ok, id?, identifier?, issue? }>, summary }. Each issue includes assignee, state, project, labels, attachments, and branchName when available. Next: Call 'update_issues' to modify fields or 'list_issues' to discover more.", }, create_issues: { name: 'create_issues', title: 'Create Issues (Batch)', description: "Create multiple issues in one call. Inputs: { items: Array<{ teamId: string; title: string; description?; stateId?; stateName?; stateType?; labelIds?; labelNames?; assigneeId?; assigneeName?; assigneeEmail?; projectId?; projectName?; priority?; estimate?; dueDate?; parentId?; allowZeroEstimate? }>; parallel?; dry_run? }.\n\nHUMAN-READABLE INPUTS (use workspace_metadata to discover valid values):\n- priority: 0-4 or \"Urgent\"/\"High\"/\"Medium\"/\"Low\" (standardized)\n- stateType: \"completed\"/\"started\"/\"backlog\"/\"unstarted\"/\"canceled\" (standardized)\n- stateName/labelNames/assigneeName/projectName: workspace-specific\n\nBehavior: Only send fields you intend to set. If 'assigneeId' is omitted, defaults to current viewer. Invalid numbers are ignored (priority<0 dropped; estimate<=0 dropped unless allowZeroEstimate=true). Returns: per-item results with id/identifier. Next: verify with 'list_issues'.", }, update_issues: { name: 'update_issues', title: 'Update Issues (Batch)', description: "Update issues in batch (state, labels, assignee, metadata). Supports up to 50 items per call — always batch all updates together. Inputs: { items: Array<{ id: string; title?; description?; stateId?; stateName?; stateType?; labelIds?; labelNames?; addLabelIds?; addLabelNames?; removeLabelIds?; removeLabelNames?; assigneeId?; assigneeName?; assigneeEmail?; projectId?; projectName?; priority?; estimate?; dueDate?; parentId?; archived?; allowZeroEstimate? }>; parallel?; dry_run? }.\n\nHUMAN-READABLE INPUTS (use workspace_metadata to discover valid values):\n- priority: 0-4 or \"Urgent\"/\"High\"/\"Medium\"/\"Low\" (standardized)\n- stateType: \"completed\"/\"started\"/\"canceled\" (standardized)\n- stateName/labelNames/assigneeName/projectName: workspace-specific\n\nBehavior: Only send fields you intend to change. Empty strings ignored; estimate<=0 ignored unless allowZeroEstimate=true. add/removeLabelIds/Names adjust labels incrementally.\nExample: { items: [{ id: 'ABC-123', stateType: 'completed', priority: 'High' }] }. Returns: per-item results. Next: 'get_issues' for verification.", }, list_projects: { name: 'list_projects', title: 'List Projects', description: "List projects with filtering and pagination. Inputs: filter? (ProjectFilter: id/state/team/lead/targetDate), includeArchived?, limit?, cursor?. For a single project, set filter.id.eq and limit=1.\nReturns: { items[], cursor?, nextCursor?, limit? } where items include id, name, state, leadId?, teamId?, targetDate?, description?. Next: Use 'update_projects' to modify or 'list_issues' with projectId to find issues.", }, create_projects: { name: 'create_projects', title: 'Create Projects (Batch)', description: 'Create multiple projects in one call. Inputs: { items: Array<{ name: string; teamId?: string; leadId?: string; description?: string; targetDate?: string; state?: string }> }.\nNotes: team association uses teamIds internally; provide teamId to attach initially. Returns: per-item results and a summary.', }, update_projects: { name: 'update_projects', title: 'Update Projects (Batch)', description: "Update multiple projects in one call. Inputs: { items: Array<{ id: string; name?: string; description?: string; targetDate?: string; state?: string; leadId?: string; archived?: boolean }> }.\nReturns: per-item results and a summary. Next: verify with 'list_projects' (filter.id.eq, limit=1); discover via 'list_projects'.", }, list_teams: { name: 'list_teams', title: 'List Teams', description: "List teams in the workspace. Inputs: limit?, cursor?.\nReturns: { items: Array<{ id, key?, name }>, cursor?, nextCursor?, limit? }. Next: Use team ids with 'workspace_metadata' (workflowStatesByTeam) and 'list_issues'.", }, list_users: { name: 'list_users', title: 'List Users', description: "List users in the workspace. Inputs: limit?, cursor?.\nReturns: { items: Array<{ id, name?, email?, displayName?, avatarUrl? }>, cursor?, nextCursor?, limit? }. Next: Use user ids in 'update_issues' (assigneeId).", }, list_comments: { name: 'list_comments', title: 'List Comments', description: 'List comments for an issue. Inputs: { issueId, limit?, cursor? }.\nReturns: { items[], cursor?, nextCursor?, limit? } where items include id, body, url?, createdAt, updatedAt?, user{id,name?}. Next: Use add_comments to add context or mention teammates.', }, add_comments: { name: 'add_comments', title: 'Add Comments (Batch)', description: 'Add one or more comments to issues. Inputs: { items: Array<{ issueId: string; body: string }>, parallel?, dry_run? }.\nReturns: per-item results and a summary. Next: Use list_comments to verify and retrieve the comment URLs.', }, update_comments: { name: 'update_comments', title: 'Update Comments (Batch)', description: 'Update existing comment bodies. Cannot delete comments (by design). Inputs: { items: Array<{ id: string; body: string }> }.\nReturns: per-item results and a summary. Next: Use list_comments to verify changes.', }, list_cycles: { name: 'list_cycles', title: 'List Cycles', description: "List cycles for a team (only if team.cyclesEnabled=true). Inputs: { teamId, includeArchived?, orderBy?(updatedAt|createdAt), limit?, cursor? }.\nReturns: { items[], cursor?, nextCursor?, limit? } where items include id, name?, number?, startsAt?, endsAt?, completedAt?, teamId, status?. Next: Use teamId from 'workspace_metadata' to target the right team; avoid this tool if cyclesEnabled=false.", }, } as const satisfies Record<string, ToolMetadata>; /** * Type-safe helper to get metadata for a tool. */ export function getToolMetadata(toolName: keyof typeof toolsMetadata): ToolMetadata { return toolsMetadata[toolName]; } /** * Get all registered tool names. */ export function getToolNames(): string[] { return Object.keys(toolsMetadata); }

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/iceener/linear-streamable-mcp-server'

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