Skip to content

Commit 22791c3

Browse files
authored
fix(mcp): align org team pagination with openapi (#107)
1 parent 913cc1b commit 22791c3

4 files changed

Lines changed: 26 additions & 8 deletions

File tree

apps/mcp-server/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,8 @@ Each tool exposes MCP [tool annotations](https://modelcontextprotocol.io/specifi
274274
### Organizations: Teams (2)
275275
| Tool | Title | Hint | Description |
276276
|---|---|---|---|
277-
| `get_org_teams` | List All Org Teams | Read | List all teams in an organization; requires org admin access |
278-
| `get_my_teams` | List My Teams | Read | List teams the authenticated user belongs to |
277+
| `get_org_teams` | List All Org Teams | Read | List all teams in an organization; requires org admin access; supports `take`/`skip` pagination (`take` max 250) |
278+
| `get_my_teams` | List My Teams | Read | List teams the authenticated user belongs to; supports `take`/`skip` pagination (`take` max 250) |
279279

280280
### Organizations: Routing Forms (2)
281281
| Tool | Title | Hint | Description |

apps/mcp-server/src/register-tools.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ export function registerTools(server: McpServer): void {
647647
{
648648
title: "List All Org Teams",
649649
description:
650-
"List all teams in an organization. Requires ORG_ADMIN role. Use get_me to obtain your organizationId. Returns team IDs, names, and slugs needed by other team-scoped tools. If this fails with 403, use get_my_teams instead.",
650+
"List all teams in an organization. Supports take/skip pagination (take max 250). Requires ORG_ADMIN role. Use get_me to obtain your organizationId. Returns team IDs, names, and slugs needed by other team-scoped tools. If this fails with 403, use get_my_teams instead.",
651651
inputSchema: getOrgTeamsSchema,
652652
annotations: READ_ONLY,
653653
},
@@ -658,7 +658,7 @@ export function registerTools(server: McpServer): void {
658658
{
659659
title: "List My Teams",
660660
description:
661-
"List teams the authenticated user belongs to. Works for any org member (org admins see all teams). Use get_me to obtain your organizationId. Returns team IDs, names, and slugs needed by other team-scoped tools.",
661+
"List teams the authenticated user belongs to. Supports take/skip pagination (take max 250). Works for any org member (org admins see all teams). Use get_me to obtain your organizationId. Returns team IDs, names, and slugs needed by other team-scoped tools.",
662662
inputSchema: getMyTeamsSchema,
663663
annotations: READ_ONLY,
664664
},

apps/mcp-server/src/tools/organizations/teams.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ describe("getOrgTeams", () => {
1717
expect(getOrgTeamsSchema).toBeDefined();
1818
});
1919

20+
it("enforces OpenAPI pagination bounds", () => {
21+
expect(getOrgTeamsSchema.take.safeParse(1.5).success).toBe(false);
22+
expect(getOrgTeamsSchema.take.safeParse(0).success).toBe(false);
23+
expect(getOrgTeamsSchema.take.safeParse(250).success).toBe(true);
24+
expect(getOrgTeamsSchema.take.safeParse(251).success).toBe(false);
25+
expect(getOrgTeamsSchema.skip.safeParse(1.5).success).toBe(false);
26+
expect(getOrgTeamsSchema.skip.safeParse(-1).success).toBe(false);
27+
});
28+
2029
it("calls the admin teams endpoint", async () => {
2130
mockCalApi.mockResolvedValueOnce({ status: "success", data: [{ id: 1, name: "Engineering" }] });
2231
const result = await getOrgTeams({ orgId: 1 });
@@ -49,6 +58,15 @@ describe("getMyTeams", () => {
4958
expect(getMyTeamsSchema).toBeDefined();
5059
});
5160

61+
it("enforces OpenAPI pagination bounds", () => {
62+
expect(getMyTeamsSchema.take.safeParse(1.5).success).toBe(false);
63+
expect(getMyTeamsSchema.take.safeParse(0).success).toBe(false);
64+
expect(getMyTeamsSchema.take.safeParse(250).success).toBe(true);
65+
expect(getMyTeamsSchema.take.safeParse(251).success).toBe(false);
66+
expect(getMyTeamsSchema.skip.safeParse(1.5).success).toBe(false);
67+
expect(getMyTeamsSchema.skip.safeParse(-1).success).toBe(false);
68+
});
69+
5270
it("calls the /me teams endpoint", async () => {
5371
mockCalApi.mockResolvedValueOnce({ status: "success", data: [{ id: 2, name: "Sales" }] });
5472
const result = await getMyTeams({ orgId: 1 });

apps/mcp-server/src/tools/organizations/teams.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ export const getOrgTeamsSchema = {
77
.number()
88
.int()
99
.describe("Organization ID. Use get_me to obtain your organizationId — never guess."),
10-
take: z.number().optional().describe("Max results to return (default 250)"),
11-
skip: z.number().optional().describe("Results to skip (offset)"),
10+
take: z.number().int().min(1).max(250).optional().describe("Max results to return (1-250)"),
11+
skip: z.number().int().min(0).optional().describe("Results to skip (offset, min 0)"),
1212
};
1313

1414
export async function getOrgTeams(params: { orgId: number; take?: number; skip?: number }) {
@@ -28,8 +28,8 @@ export const getMyTeamsSchema = {
2828
.number()
2929
.int()
3030
.describe("Organization ID. Use get_me to obtain your organizationId — never guess."),
31-
take: z.number().optional().describe("Max results to return (default 250)"),
32-
skip: z.number().optional().describe("Results to skip (offset)"),
31+
take: z.number().int().min(1).max(250).optional().describe("Max results to return (1-250)"),
32+
skip: z.number().int().min(0).optional().describe("Results to skip (offset, min 0)"),
3333
};
3434

3535
export async function getMyTeams(params: { orgId: number; take?: number; skip?: number }) {

0 commit comments

Comments
 (0)