Skip to content

feat(mcp): add routing attribute MCP tools for organizations#102

Closed
joeauyeung wants to merge 2 commits into
mainfrom
devin/1780510058-add-org-attributes-mcp-tools
Closed

feat(mcp): add routing attribute MCP tools for organizations#102
joeauyeung wants to merge 2 commits into
mainfrom
devin/1780510058-add-org-attributes-mcp-tools

Conversation

@joeauyeung
Copy link
Copy Markdown
Contributor

Summary

Adds 15 MCP tools wrapping the Cal.com API v2 organization attributes endpoints for reading and writing routing attributes per user.

New file src/tools/organizations/attributes.ts following existing patterns from memberships.ts / routing-forms.ts:

Attribute CRUDget_org_attributes, get_org_attribute, create_org_attribute, update_org_attribute, delete_org_attribute

Attribute option managementget_org_attribute_options, get_assigned_attribute_options, get_assigned_attribute_options_by_slug, create_org_attribute_option, update_org_attribute_option, delete_org_attribute_option

User attribute assignmentsget_user_attribute_options, assign_attribute_to_user, update_user_attribute_assignment, remove_attribute_from_user

All tools registered in register-tools.ts with appropriate MCP annotations (READ_ONLY, CREATE, UPDATE, DESTRUCTIVE). Descriptions follow existing style with hints like "Use get_me to obtain your organizationId — never guess." Unit tests cover success + error paths for every handler (60 tests).

Link to Devin session: https://app.devin.ai/sessions/8b5a7ba58d3241f5a2e23cc52155d1eb
Requested by: @joeauyeung

Add 15 MCP tools wrapping the Cal.com API v2 organization attributes
endpoints:

- get_org_attributes, get_org_attribute, create_org_attribute,
  update_org_attribute, delete_org_attribute
- get_org_attribute_options, get_assigned_attribute_options,
  get_assigned_attribute_options_by_slug, create_org_attribute_option,
  update_org_attribute_option, delete_org_attribute_option
- get_user_attribute_options, assign_attribute_to_user,
  update_user_attribute_assignment, remove_attribute_from_user

All tools are registered with appropriate MCP annotations (READ_ONLY,
CREATE, UPDATE, DESTRUCTIVE) and include unit tests.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@joeauyeung joeauyeung marked this pull request as ready for review June 3, 2026 18:45
@joeauyeung joeauyeung marked this pull request as draft June 3, 2026 18:45
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +40 to +47
export async function getOrgAttribute(params: { orgId: number; attributeId: string }) {
try {
const data = await calApi(`organizations/${params.orgId}/attributes/${params.attributeId}`);
return ok(data);
} catch (err) {
return handleError("get_org_attribute", err);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Missing path sanitization on string parameters interpolated into API URL paths

Every string parameter (attributeId, attributeSlug, optionId, attributeOptionId) is interpolated directly into URL path templates without calling sanitizePathSegment(). The rest of the codebase consistently sanitizes all user-supplied string path segments — bookings.ts sanitizes bookingUid (bookings.ts:86), routing-forms.ts sanitizes routingFormId (routing-forms.ts:90, routing-forms.ts:38) — to prevent path traversal attacks. A malicious attributeId like ../../admin would cause the API call to target an unintended endpoint. This affects 11 calApi() call sites across all functions that take string path parameters.

Affected call sites (11 instances)
  • Line 42: attributeId in getOrgAttribute
  • Line 126: attributeId in updateOrgAttribute
  • Line 150: attributeId in deleteOrgAttribute
  • Line 184: attributeId in getOrgAttributeOptions
  • Line 208: attributeId in getAssignedAttributeOptions
  • Line 234: attributeSlug in getAssignedAttributeOptionsBySlug
  • Line 267: attributeId in createOrgAttributeOption
  • Lines 305: attributeId + optionId in updateOrgAttributeOption
  • Lines 339: attributeId + optionId in deleteOrgAttributeOption
  • Line 440: attributeOptionId in updateUserAttributeAssignment
  • Line 477: attributeOptionId in removeAttributeFromUser
Prompt for agents
The new attributes.ts file does not use sanitizePathSegment() for any of its string path parameters (attributeId, attributeSlug, optionId, attributeOptionId), unlike the rest of the codebase which consistently sanitizes all user-supplied strings before interpolating into URL paths (see bookings.ts and routing-forms.ts for the established pattern).

To fix:
1. Import sanitizePathSegment from ../../utils/path-sanitizer.js
2. In every function that interpolates a string parameter into a URL path, call sanitizePathSegment() on it before interpolation. For example, in getOrgAttribute: const attrId = sanitizePathSegment(params.attributeId); then use attrId in the template literal.
3. Apply this to all 11 calApi() call sites that use attributeId, attributeSlug, optionId, or attributeOptionId in URL paths.

Note: orgId and userId are numbers (typed as z.number().int()) so they don't need sanitization.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in cd0fee2. Added sanitizePathSegment to all 11 string path param interpolation sites (attributeId, attributeSlug, optionId, attributeOptionId), matching the established pattern in bookings.ts and routing-forms.ts.

…ute tools

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cal-companion-chat Ignored Ignored Jun 3, 2026 6:49pm
cal-companion-mcp Ignored Ignored Jun 3, 2026 6:49pm

Request Review

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mcp-server/src/tools/organizations/attributes.ts">

<violation number="1" location="apps/mcp-server/src/tools/organizations/attributes.ts:259">
P2: `create_org_attribute_option` allows missing `slug`, but the Cal.com endpoint requires it, causing avoidable API validation failures.</violation>

<violation number="2" location="apps/mcp-server/src/tools/organizations/attributes.ts:410">
P1: `assign_attribute_to_user` omits required `attributeId` in request payload, so assignments can fail against the API contract.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}) {
try {
const body: Record<string, unknown> = {};
body.attributeOptionId = params.attributeOptionId;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: assign_attribute_to_user omits required attributeId in request payload, so assignments can fail against the API contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mcp-server/src/tools/organizations/attributes.ts, line 410:

<comment>`assign_attribute_to_user` omits required `attributeId` in request payload, so assignments can fail against the API contract.</comment>

<file context>
@@ -0,0 +1,499 @@
+}) {
+  try {
+    const body: Record<string, unknown> = {};
+    body.attributeOptionId = params.attributeOptionId;
+    if (params.value !== undefined) body.value = params.value;
+    const data = await calApi(`organizations/${params.orgId}/attributes/options/${params.userId}`, {
</file context>

.string()
.describe("Attribute ID. Use get_org_attributes to find this — never guess."),
value: z.string().describe("Value for the new option"),
slug: z.string().optional().describe("URL-friendly slug for the option"),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: create_org_attribute_option allows missing slug, but the Cal.com endpoint requires it, causing avoidable API validation failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mcp-server/src/tools/organizations/attributes.ts, line 259:

<comment>`create_org_attribute_option` allows missing `slug`, but the Cal.com endpoint requires it, causing avoidable API validation failures.</comment>

<file context>
@@ -0,0 +1,499 @@
+    .string()
+    .describe("Attribute ID. Use get_org_attributes to find this — never guess."),
+  value: z.string().describe("Value for the new option"),
+  slug: z.string().optional().describe("URL-friendly slug for the option"),
+};
+
</file context>

@joeauyeung joeauyeung closed this Jun 3, 2026
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.

1 participant