Skip to content

feat: Add support to Submittals#3

Open
pabloderen wants to merge 1 commit intomainfrom
feature/Submitalls
Open

feat: Add support to Submittals#3
pabloderen wants to merge 1 commit intomainfrom
feature/Submitalls

Conversation

@pabloderen
Copy link
Collaborator

@pabloderen pabloderen commented Feb 12, 2026

Adds a set of tools for interacting with the ACC Submittals API, including raw request, item listing, package listing, specs listing, and attachment retrieval.

Provides helper functions for constructing API paths, summarizing responses, and validating input.

Also includes quick-reference documentation for the Submittals API.

Summary by CodeRabbit

  • New Features
    • Added seven new ACC Submittals API tools for managing project submittals
    • List submittal items, packages, and specifications with filtering and pagination support
    • Retrieve detailed submittal information and attachment metadata
    • Raw API access for advanced users requiring full control
    • Integrated quick-reference documentation for Submittals workflows

Adds a set of tools for interacting with the ACC Submittals API, including raw request, item listing, package listing, specs listing, and attachment retrieval.

Provides helper functions for constructing API paths, summarizing responses, and validating input.

Also includes quick-reference documentation for the Submittals API.
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

Walkthrough

This PR introduces comprehensive ACC Submittals support to the APS MCP server, adding seven new tools with corresponding helper types, validators, summarizers, and documentation. All changes are additive with no removal of existing functionality.

Changes

Cohort / File(s) Summary
Manifest Tool Declarations
manifest.json
Added seven new tool entries to the APS MCP manifest: aps\_submittals\_request (raw API access), aps\_list\_submittal\_items, aps\_get\_submittal\_item, aps\_list\_submittal\_packages, aps\_list\_submittal\_specs, aps\_get\_submittal\_item\_attachments, and aps\_submittals\_docs (quick reference).
Submittals Helpers & Types
src/aps-helpers.ts
Added four new interfaces (SubmittalItemSummary, SubmittalPackageSummary, SubmittalSpecSummary, SubmittalAttachmentSummary), utility functions for project ID normalization (toAccProjectId) and path construction (submittalPath), four summarizer functions for submittals endpoints, three validators (validateSubmittalProjectId, validateSubmittalItemId, validateSubmittalPath), and comprehensive SUBMITTALS\_DOCS markdown reference.
Tool Handlers & Exports
src/index.ts
Integrated seven new tool handlers into handleTool dispatcher, added re-exports of submittals helpers and types from aps-helpers, defined input schemas for each submittal tool with validation and pagination support, and extended public tool catalog with new submittal tool declarations.

Sequence Diagram

sequenceDiagram
    participant Client as MCP Client
    participant Handler as Tool Handler
    participant Validator as Validator
    participant APS as APS API
    participant Summarizer as Response Summarizer
    
    Client->>Handler: Call aps_list_submittal_items<br/>(projectId, filters, pagination)
    Handler->>Validator: validateSubmittalProjectId(projectId)
    Validator-->>Handler: Validation result
    alt Validation passes
        Handler->>APS: GET /projects/{projectId}/submittals/items<br/>(with filters & pagination)
        APS-->>Handler: Raw JSON response
        Handler->>Summarizer: summarizeSubmittalItems(raw)
        Summarizer-->>Handler: Structured response<br/>(pagination + items)
        Handler-->>Client: Summarized submittal items
    else Validation fails
        Handler-->>Client: Error message
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐰 Submittals now flow through our warren bright,
Seven new tools to make the API right—
With validators strong and summarizers swift,
We hop through projects, a data-bound gift! ✨📋

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add support to Submittals' directly and clearly describes the main change—adding Submittals API support with seven new tools and helper functions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/Submitalls

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 712-783: The handlers aps_get_submittal_item and
aps_get_submittal_item_attachments currently interpolate raw args.item_id into
submittalPath, which can produce malformed paths for values with "/" or other
reserved chars; URL-encode the itemId (e.g., const itemId =
encodeURIComponent(args.item_id as string)) before calling submittalPath (and
keep existing validateSubmittalItemId checks), so both submittalPath(projectId,
`items/${itemId}`) and submittalPath(projectId, `items/${itemId}/attachments`)
use the encoded id.
🧹 Nitpick comments (1)
src/aps-helpers.ts (1)

551-568: Encode the project ID in submittalPath for safer URL construction.

Even if project IDs are expected to be UUIDs, encoding/trim guards against stray whitespace or unexpected characters that would break the path.

🔧 Suggested change
 export function submittalPath(projectId: string, subPath: string): string {
-  const pid = toAccProjectId(projectId);
+  const pid = encodeURIComponent(toAccProjectId(projectId).trim());
   const sub = subPath.replace(/^\//, "");
   return `${SUBMITTALS_BASE}/projects/${pid}/${sub}`;
 }

Comment on lines +712 to +783
// ── aps_get_submittal_item ──────────────────────────────────
if (name === "aps_get_submittal_item") {
const projectId = args.project_id as string;
const itemId = args.item_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const e2 = validateSubmittalItemId(itemId);
if (e2) return fail(e2);

const t = await token();
const raw = await apsDmRequest("GET", submittalPath(projectId, `items/${itemId}`), t, {
headers: { "Content-Type": "application/json" },
});
return json(raw);
}

// ── aps_list_submittal_packages ─────────────────────────────
if (name === "aps_list_submittal_packages") {
const projectId = args.project_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);

const query: Record<string, string> = {};
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 200);
query.limit = String(limit);
if (args.offset != null) query.offset = String(args.offset);

const t = await token();
const raw = await apsDmRequest("GET", submittalPath(projectId, "packages"), t, {
query,
headers: { "Content-Type": "application/json" },
});
return json(summarizeSubmittalPackages(raw));
}

// ── aps_list_submittal_specs ────────────────────────────────
if (name === "aps_list_submittal_specs") {
const projectId = args.project_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);

const query: Record<string, string> = {};
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 200);
query.limit = String(limit);
if (args.offset != null) query.offset = String(args.offset);

const t = await token();
const raw = await apsDmRequest("GET", submittalPath(projectId, "specs"), t, {
query,
headers: { "Content-Type": "application/json" },
});
return json(summarizeSubmittalSpecs(raw));
}

// ── aps_get_submittal_item_attachments ──────────────────────
if (name === "aps_get_submittal_item_attachments") {
const projectId = args.project_id as string;
const itemId = args.item_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const e2 = validateSubmittalItemId(itemId);
if (e2) return fail(e2);

const t = await token();
const raw = await apsDmRequest(
"GET",
submittalPath(projectId, `items/${itemId}/attachments`),
t,
{ headers: { "Content-Type": "application/json" } },
);
return json(summarizeSubmittalAttachments(raw));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Encode item_id before interpolating into submittals paths.

item_id is user input; if it contains / or other reserved characters, the path becomes malformed or targets the wrong endpoint. Encoding (or stricter validation) avoids this.

🔧 Suggested change
   if (name === "aps_get_submittal_item") {
     const projectId = args.project_id as string;
     const itemId = args.item_id as string;
@@
-    const raw = await apsDmRequest("GET", submittalPath(projectId, `items/${itemId}`), t, {
+    const encodedItemId = encodeURIComponent(itemId);
+    const raw = await apsDmRequest("GET", submittalPath(projectId, `items/${encodedItemId}`), t, {
       headers: { "Content-Type": "application/json" },
     });
     return json(raw);
   }
@@
   if (name === "aps_get_submittal_item_attachments") {
     const projectId = args.project_id as string;
     const itemId = args.item_id as string;
@@
-    const raw = await apsDmRequest(
-      "GET",
-      submittalPath(projectId, `items/${itemId}/attachments`),
+    const encodedItemId = encodeURIComponent(itemId);
+    const raw = await apsDmRequest(
+      "GET",
+      submittalPath(projectId, `items/${encodedItemId}/attachments`),
       t,
       { headers: { "Content-Type": "application/json" } },
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// ── aps_get_submittal_item ──────────────────────────────────
if (name === "aps_get_submittal_item") {
const projectId = args.project_id as string;
const itemId = args.item_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const e2 = validateSubmittalItemId(itemId);
if (e2) return fail(e2);
const t = await token();
const raw = await apsDmRequest("GET", submittalPath(projectId, `items/${itemId}`), t, {
headers: { "Content-Type": "application/json" },
});
return json(raw);
}
// ── aps_list_submittal_packages ─────────────────────────────
if (name === "aps_list_submittal_packages") {
const projectId = args.project_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const query: Record<string, string> = {};
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 200);
query.limit = String(limit);
if (args.offset != null) query.offset = String(args.offset);
const t = await token();
const raw = await apsDmRequest("GET", submittalPath(projectId, "packages"), t, {
query,
headers: { "Content-Type": "application/json" },
});
return json(summarizeSubmittalPackages(raw));
}
// ── aps_list_submittal_specs ────────────────────────────────
if (name === "aps_list_submittal_specs") {
const projectId = args.project_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const query: Record<string, string> = {};
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 200);
query.limit = String(limit);
if (args.offset != null) query.offset = String(args.offset);
const t = await token();
const raw = await apsDmRequest("GET", submittalPath(projectId, "specs"), t, {
query,
headers: { "Content-Type": "application/json" },
});
return json(summarizeSubmittalSpecs(raw));
}
// ── aps_get_submittal_item_attachments ──────────────────────
if (name === "aps_get_submittal_item_attachments") {
const projectId = args.project_id as string;
const itemId = args.item_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const e2 = validateSubmittalItemId(itemId);
if (e2) return fail(e2);
const t = await token();
const raw = await apsDmRequest(
"GET",
submittalPath(projectId, `items/${itemId}/attachments`),
t,
{ headers: { "Content-Type": "application/json" } },
);
return json(summarizeSubmittalAttachments(raw));
}
// ── aps_get_submittal_item ──────────────────────────────────
if (name === "aps_get_submittal_item") {
const projectId = args.project_id as string;
const itemId = args.item_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const e2 = validateSubmittalItemId(itemId);
if (e2) return fail(e2);
const t = await token();
const encodedItemId = encodeURIComponent(itemId);
const raw = await apsDmRequest("GET", submittalPath(projectId, `items/${encodedItemId}`), t, {
headers: { "Content-Type": "application/json" },
});
return json(raw);
}
// ── aps_list_submittal_packages ─────────────────────────────
if (name === "aps_list_submittal_packages") {
const projectId = args.project_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const query: Record<string, string> = {};
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 200);
query.limit = String(limit);
if (args.offset != null) query.offset = String(args.offset);
const t = await token();
const raw = await apsDmRequest("GET", submittalPath(projectId, "packages"), t, {
query,
headers: { "Content-Type": "application/json" },
});
return json(summarizeSubmittalPackages(raw));
}
// ── aps_list_submittal_specs ────────────────────────────────
if (name === "aps_list_submittal_specs") {
const projectId = args.project_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const query: Record<string, string> = {};
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 200);
query.limit = String(limit);
if (args.offset != null) query.offset = String(args.offset);
const t = await token();
const raw = await apsDmRequest("GET", submittalPath(projectId, "specs"), t, {
query,
headers: { "Content-Type": "application/json" },
});
return json(summarizeSubmittalSpecs(raw));
}
// ── aps_get_submittal_item_attachments ──────────────────────
if (name === "aps_get_submittal_item_attachments") {
const projectId = args.project_id as string;
const itemId = args.item_id as string;
const e1 = validateSubmittalProjectId(projectId);
if (e1) return fail(e1);
const e2 = validateSubmittalItemId(itemId);
if (e2) return fail(e2);
const t = await token();
const encodedItemId = encodeURIComponent(itemId);
const raw = await apsDmRequest(
"GET",
submittalPath(projectId, `items/${encodedItemId}/attachments`),
t,
{ headers: { "Content-Type": "application/json" } },
);
return json(summarizeSubmittalAttachments(raw));
}
🤖 Prompt for AI Agents
In `@src/index.ts` around lines 712 - 783, The handlers aps_get_submittal_item and
aps_get_submittal_item_attachments currently interpolate raw args.item_id into
submittalPath, which can produce malformed paths for values with "/" or other
reserved chars; URL-encode the itemId (e.g., const itemId =
encodeURIComponent(args.item_id as string)) before calling submittalPath (and
keep existing validateSubmittalItemId checks), so both submittalPath(projectId,
`items/${itemId}`) and submittalPath(projectId, `items/${itemId}/attachments`)
use the encoded id.

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.

2 participants