Skip to content

Implement saved view tools#1

Merged
itz4blitz merged 2 commits intomainfrom
feat/saved-views
Apr 18, 2026
Merged

Implement saved view tools#1
itz4blitz merged 2 commits intomainfrom
feat/saved-views

Conversation

@itz4blitz
Copy link
Copy Markdown
Owner

Summary

  • add MCP tools for Linear saved views backed by the CustomView API, including definitions, handlers, registration, and Smithery manifest updates
  • tighten saved view validation and update semantics so nullable fields can be cleared, list args are constrained, and docs/schemas match the actual tool outputs
  • work around the current Linear SDK favorites() query mismatch by issuing a minimal supported favorites query, with unit coverage plus live smoke tests against a real workspace

Testing

  • npm test
  • npm run build
  • live smoke test: linear_getSavedViews
  • live smoke test: linear_getFavoriteViews
  • live smoke test: create/update/clear/delete saved view

Add CustomView-backed saved view operations and favorite view support so local MCP clients can manage and validate Linear views against the current API schema.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds MCP tool support for Linear “saved views” (CustomView) and “favorite views”, wiring up tool definitions, handlers, and LinearService methods, plus associated validation and tests.

Changes:

  • Added saved view & favorite view MCP tool definitions and registered their handlers.
  • Implemented LinearService methods for listing/creating/updating/deleting saved views and querying favorites via a custom GraphQL query.
  • Added unit tests for the new type guards and service behavior; updated Smithery tool list and TOOLS.md docs.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/tools/type-guards.ts Adds validation/type guards for saved view & favorite view tool inputs.
src/tools/handlers/view-handlers.ts New handlers delegating to LinearService for saved/favorite view tools.
src/tools/handlers/index.ts Registers and re-exports the new view handlers.
src/tools/definitions/view-tools.ts New tool schemas for saved views & favorite views.
src/tools/definitions/index.ts Adds view tool definitions to the exported/registered list.
src/services/linear-service.ts Implements CustomView CRUD + favorites GraphQL query + normalization helpers.
src/tests/view-type-guards.test.ts Adds coverage for the new view-related type guards.
src/tests/linear-service.test.ts Adds coverage for saved view sanitization, pagination forwarding, and favorites filtering.
package.json Adds the new tools to the Smithery manifest tool list.
TOOLS.md Documents the new “Views and Filters” tool set and removes them from “Planned”.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/tools/type-guards.ts
Comment on lines +1034 to 1046
export function isGetFavoriteViewsArgs(args: unknown): args is {
limit?: number;
includeArchived?: boolean;
orderBy?: string;
} {
return (
typeof args === 'object' &&
args !== null &&
(!('limit' in args) || isPositiveInteger((args as { limit: unknown }).limit)) &&
(!('includeArchived' in args) ||
typeof (args as { includeArchived: boolean }).includeArchived === 'boolean') &&
(!('orderBy' in args) || isSavedViewOrderBy((args as { orderBy: unknown }).orderBy))
);
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isGetFavoriteViewsArgs has the same issue as isGetSavedViewsArgs: arrays like [] will pass because only typeof args === 'object' is checked. Consider validating with isJsonObject(args) / !Array.isArray(args) and narrowing the type predicate for orderBy to 'createdAt' | 'updatedAt' since other values are rejected.

Copilot uses AI. Check for mistakes.
Comment thread src/tools/type-guards.ts Outdated
typeof (args as { initiativeId: string }).initiativeId === 'string' &&
'projectId' in args &&
typeof (args as { projectId: string }).projectId === 'string'
typeof (args as { projectId: string }).projectId === 'string'
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be a formatting/indentation regression in this guard: the typeof (args as { projectId: string }).projectId === 'string' line is indented as if it were nested under nothing, making the condition block harder to read and potentially failing lint/prettier checks. Reformatting this expression to match surrounding guards would improve consistency.

Suggested change
typeof (args as { projectId: string }).projectId === 'string'
typeof (args as { projectId: string }).projectId === 'string'

Copilot uses AI. Check for mistakes.
Comment thread src/tools/definitions/view-tools.ts Outdated
properties: {
id: { type: 'string' },
name: { type: 'string' },
slugId: { type: 'string' },
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the linear_getFavoriteViews output schema, customView.slugId is declared as { type: 'string' }, but the service-side type for favorites models it as slugId?: string | null and the handler forwards whatever comes back from GraphQL. To avoid schema/tooling mismatches when slugId is missing/null, consider making this field nullable (or at least optional+nullable) in the output schema (e.g., align it with nullableStringSchema).

Suggested change
slugId: { type: 'string' },
slugId: nullableStringSchema,

Copilot uses AI. Check for mistakes.
description: 'Updated project filter data for the saved view. Pass null to clear it.',
},
},
required: ['id'],
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linear_updateSavedView rejects calls that only provide id (both in the type guard and in LinearService.updateSavedView), but the JSON schema only requires id and doesn't express the "at least one other field" requirement. If consumers rely on the schema for client-side validation, they may construct requests that always fail at runtime. Consider adding a schema constraint such as minProperties: 2 or an anyOf requiring one mutable field alongside id.

Suggested change
required: ['id'],
required: ['id'],
anyOf: [
{ required: ['name'] },
{ required: ['description'] },
{ required: ['shared'] },
{ required: ['icon'] },
{ required: ['color'] },
{ required: ['teamId'] },
{ required: ['projectId'] },
{ required: ['ownerId'] },
{ required: ['filters'] },
{ required: ['filterData'] },
{ required: ['projectFilterData'] },
],

Copilot uses AI. Check for mistakes.
Comment thread src/tools/type-guards.ts
Comment on lines +911 to +924
export function isGetSavedViewsArgs(args: unknown): args is {
limit?: number;
includeArchived?: boolean;
orderBy?: string;
} {
return (
typeof args === 'object' &&
args !== null &&
(!('limit' in args) || isPositiveInteger((args as { limit: unknown }).limit)) &&
(!('includeArchived' in args) ||
typeof (args as { includeArchived: boolean }).includeArchived === 'boolean') &&
(!('orderBy' in args) || isSavedViewOrderBy((args as { orderBy: unknown }).orderBy))
);
}
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isGetSavedViewsArgs accepts arrays because it only checks typeof args === 'object' / args !== null. For example, [] will pass validation and be treated as an empty options object. Consider reusing isJsonObject(args) (or adding !Array.isArray(args)) so only plain objects are accepted. Also, since the guard only allows 'createdAt' | 'updatedAt', the type predicate should narrow orderBy to that union instead of string to preserve type safety downstream.

Copilot uses AI. Check for mistakes.
@itz4blitz
Copy link
Copy Markdown
Owner Author

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown

Copilot AI commented Apr 18, 2026

@copilot apply changes based on the comments in this thread

Implemented the requested review-thread fixes in commit 79ee29a: tightened saved/favorite args guards to reject arrays and narrow orderBy, fixed the indentation regression, made favorite customView.slugId nullable in the output schema, and added anyOf constraints to linear_updateSavedView input schema to require at least one mutable field.

@itz4blitz itz4blitz merged commit 9b184f9 into main Apr 18, 2026
@itz4blitz itz4blitz deleted the feat/saved-views branch April 18, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants