diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index f5e6388b..f37de52c 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -1357,6 +1357,18 @@ "authentication": "ON_INSTALL" }, "category": "Productivity" + }, + { + "name": "ramp", + "source": { + "source": "local", + "path": "./plugins/ramp" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" } ] } diff --git a/plugins/ramp/.codex-plugin/plugin.json b/plugins/ramp/.codex-plugin/plugin.json new file mode 100644 index 00000000..e54c2979 --- /dev/null +++ b/plugins/ramp/.codex-plugin/plugin.json @@ -0,0 +1,41 @@ +{ + "name": "ramp", + "version": "0.1.0", + "description": "Ramp skills and MCP server for vendor spend analysis and finance workflows.", + "author": { + "name": "Ramp", + "url": "https://ramp.com" + }, + "homepage": "https://docs.ramp.com/developer-api/v1/ramp-mcp", + "repository": "https://github.com/ramp-public/mcp-skills", + "license": "MIT", + "keywords": [ + "ramp", + "finance", + "vendor-analysis", + "spend", + "mcp" + ], + "skills": "./skills/", + "mcpServers": "./.mcp.json", + "interface": { + "displayName": "Ramp", + "shortDescription": "Analyze vendor spend and Ramp finance data", + "longDescription": "Use Ramp's MCP-backed skills to analyze vendor spend, enrich vendor ownership data, and answer finance workflow questions directly from Ramp data.", + "developerName": "Ramp", + "category": "Productivity", + "capabilities": [ + "Interactive", + "Write" + ], + "websiteURL": "https://ramp.com", + "privacyPolicyURL": "https://ramp.com/privacy-policy", + "termsOfServiceURL": "https://ramp.com/terms-of-service", + "defaultPrompt": [ + "Analyze vendor spend and renewal risk in Ramp" + ], + "composerIcon": "./assets/app-icon.png", + "logo": "./assets/app-icon.png", + "screenshots": [] + } +} diff --git a/plugins/ramp/.mcp.json b/plugins/ramp/.mcp.json new file mode 100644 index 00000000..b2fd2d05 --- /dev/null +++ b/plugins/ramp/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "ramp": { + "type": "streamable_http", + "url": "https://mcp.ramp.com/mcp" + } + } +} diff --git a/plugins/ramp/agents/openai.yaml b/plugins/ramp/agents/openai.yaml new file mode 100644 index 00000000..08551474 --- /dev/null +++ b/plugins/ramp/agents/openai.yaml @@ -0,0 +1,14 @@ +interface: + display_name: "Ramp" + short_description: "Analyze spend, approvals, transactions, and finance workflows" + icon_small: "./assets/app-icon.png" + icon_large: "./assets/app-icon.png" + default_prompt: "Use Ramp to analyze spend, review approvals, or clean up transactions." + +dependencies: + tools: + - type: "mcp" + value: "ramp" + description: "Ramp MCP server" + transport: "streamable_http" + url: "https://mcp.ramp.com/mcp" diff --git a/plugins/ramp/assets/app-icon.png b/plugins/ramp/assets/app-icon.png new file mode 100644 index 00000000..2df0a8bb Binary files /dev/null and b/plugins/ramp/assets/app-icon.png differ diff --git a/plugins/ramp/skills/approval-dashboard/SKILL.md b/plugins/ramp/skills/approval-dashboard/SKILL.md new file mode 100644 index 00000000..7c3c0852 --- /dev/null +++ b/plugins/ramp/skills/approval-dashboard/SKILL.md @@ -0,0 +1,256 @@ +--- +name: approval-dashboard +description: |- + Review and approve pending transactions, bills, reimbursements, and requests. + Use when: 'approve', 'pending approvals', 'what needs my approval', + 'review transactions', 'approve bills', 'reject', 'approval queue', + 'clear my approvals'. Do NOT use for: transaction analysis, receipt uploads, + or spend tracking. +--- + +## Non-Negotiables + +- Always show the item details before approving or rejecting. Never blind-approve. +- Rejections require a reason. Approvals do not, but a note is helpful. +- Confirm with the user before executing approvals — especially bulk operations. +- Present items sorted by priority: highest dollar amount first. +- Amounts vary by endpoint: bills are in **cents** (divide by 100), reimbursements are in **dollars**, transactions are formatted strings ("$135.40"). + +## Workflow + +### Step 1: Fetch all pending items + +Run these in parallel to build the full approval queue. **Paginate each endpoint until there are no more results** — a single page may not return everything. + +```python +# Pending transactions (paginate with next_page_cursor) +ramp_get_user_transactions({ + "rationale": "Retrieve transactions awaiting my approval", + "transactions_to_retrieve": "transactions_awaiting_my_approval", + "page_size": 50, + "include_count": true +}) + +# Pending bills (paginate with page_cursor) +ramp_get_bills_for_approval({ + "rationale": "Retrieve bills pending my approval", + "limit": 50 +}) + +# Pending reimbursements (no cursor pagination tool surface — use limit) +ramp_get_reimbursements_for_approval({ + "rationale": "Retrieve reimbursements pending my approval", + "limit": 50 +}) + +# Pending requests (paginate with start; thoughts is required) +ramp_get_requests_to_review({ + "rationale": "Retrieve all pending requests for review", + "thoughts": "Reviewing all pending requests", + "page_size": 50 +}) +``` + +For each endpoint, check the pagination cursor in the response envelope. If it is not null, re-run the call with that cursor value (`next_page_cursor` for transactions, `page_cursor` for bills, `start` for requests) until all pages are fetched. Note: `ramp_get_reimbursements_for_approval` does not expose cursor-based pagination in this MCP surface — increase `limit` if you need more results. Aggregate results before presenting. + +### Step 2: Present the queue + +Summarize what's pending: + +``` +Approval queue: 14 items ($23,450 total) + +Bills (4 items, $8,200): + $3,500 HighSpot Invoice #1234 Due 2026-03-28 + $2,500 Cometeer Invoice #5678 Due 2026-04-01 + ... + +Reimbursements (6 items, $2,100): + $ 520 Michael Scott Uber rides Submitted 2026-03-20 + $ 312 Oscar Martinez Office supplies Submitted 2026-03-22 + ... + +Transactions (3 items, $12,500): + $5,000 Dana Alhasawi AWS 2026-03-15 + ... + +Requests (1 item, $650): + ... +``` + +### Step 3: Review and act + +For each item the user wants to act on, get details first: + +```python +# Bill details +ramp_get_bill_details({ + "bill_id": "{bill_id}", + "rationale": "Retrieve bill details before approval review" +}) + +# Transaction details +ramp_get_full_transaction_metadata({ + "id": "{transaction_uuid}", + "rationale": "Retrieve full transaction details before approval review" +}) + +# Transaction missing items (if relevant) +ramp_get_transaction_missing_items({ + "id": "{transaction_uuid}", + "rationale": "Check whether the transaction is missing receipt, memo, or coding items" +}) + +# Reimbursement details (use get with specific UUID) +ramp_get_reimbursements({ + "reimbursement_uuids": ["{uuid}"], + "include_policy_assessment": true, + "rationale": "Retrieve reimbursement details for approval review" +}) +``` + +### Step 4: Execute approvals + +```python +# Approve a transaction +ramp_approve_or_reject_transaction({ + "transaction_id": "{transaction_uuid}", + "action": "APPROVE", + "thoughts": "Reviewed — within policy", + "rationale": "Approve the reviewed transaction" +}) + +# Reject a transaction (reason required) +ramp_approve_or_reject_transaction({ + "transaction_id": "{transaction_uuid}", + "action": "REJECT_AND_REQUEST_CHANGES", + "thoughts": "Missing receipt and over budget", + "user_reason": "Please attach the receipt and update the memo", + "rationale": "Reject the transaction and request changes" +}) + +# Approve a bill — not yet available via MCP tool. +# Bill approvals must be done in the Ramp app: https://app.ramp.com/bills + +# Approve a reimbursement +ramp_approve_or_reject_reimbursement({ + "reimbursement_id": "{reimbursement_uuid}", + "action": "approve", + "rationale": "Approve the reviewed reimbursement" +}) + +# Reject a reimbursement +ramp_approve_or_reject_reimbursement({ + "reimbursement_id": "{reimbursement_uuid}", + "action": "reject", + "user_reason": "Receipt doesn't match the claimed amount", + "rationale": "Reject the reimbursement with a clear reason" +}) + +# Approve a request +ramp_approve_or_reject_request({ + "request_uuid": "{request_uuid}", + "action": "APPROVE", + "thoughts": "Approved — within team budget", + "rationale": "Approve the reviewed request" +}) +``` + +### Step 5: Add comments (optional) + +Leave a comment on any Ramp object for context: + +```python +ramp_post_comment({ + "ramp_object_uuid": "{object_uuid}", + "ramp_object_type": "transaction", + "message": "Approved — this covers the Q1 offsite catering", + "rationale": "Leave context on the approved object" +}) +``` + +Valid object types: `bill`, `booking_request`, `card`, `payee`, `provisional_bill`, `reimbursement`, `spend_intent`, `spend_request`, `ticket`, `transaction`, `vendor_payment`. + +## Bulk Approval + +When the user wants to approve multiple items at once: + +1. Present all items with amounts and details +2. Ask: "Approve all N items, or would you like to review individually?" +3. If bulk: confirm once, then execute sequentially +4. Report results: "Approved 12/14 items. 2 failed: [reasons]" + +## Transaction Approval Actions + +| Action | When to use | +|---|---| +| `APPROVE` | Item is within policy, receipt/memo present | +| `REJECT_AND_REQUEST_CHANGES` | Needs corrections — missing memo, wrong coding, etc. | +| `REJECT_AND_REQUEST_FULL_REPAYMENT` | Personal expense or policy violation requiring repayment | + +## Example Session + +``` +User: What needs my approval? + +Agent: Let me check all your pending queues. +> ramp_get_user_transactions({ "rationale": "Checking approval queue", "transactions_to_retrieve": "transactions_awaiting_my_approval", "page_size": 50, "include_count": true }) +> ramp_get_bills_for_approval({ "rationale": "Checking approval queue", "limit": 50 }) +> ramp_get_reimbursements_for_approval({ "rationale": "Checking approval queue", "limit": 50 }) +> ramp_get_requests_to_review({ "rationale": "Checking approval queue", "thoughts": "Checking approval queue", "page_size": 50 }) + +Approval queue: 6 items ($4,350 total) + +Bills (2 items, $2,700): + $2,200 HighSpot Invoice #4401 Due 2026-03-28 + $ 500 Cometeer Invoice #882 Due 2026-04-01 + +Reimbursements (3 items, $1,430): + $ 520 Michael Scott Uber rides Submitted 2026-03-20 + $ 312 Oscar Martinez Office supplies Submitted 2026-03-22 + $ 598 Jan Levinson Flight change Submitted 2026-03-23 + +Transactions (1 item, $220): + $ 220 Dana Alhasawi DoorDash 2026-03-15 + +Want to review individually or approve all? + +User: Approve the bills and reimbursements. Let me see the transaction first. + +Agent: I can approve the reimbursements now. Bills must be approved in the Ramp app — I'll send you the links. +> ramp_approve_or_reject_reimbursement({ "reimbursement_id": "reimb-001", "action": "approve", "rationale": "Approve reviewed reimbursement" }) +> ramp_approve_or_reject_reimbursement({ "reimbursement_id": "reimb-002", "action": "approve", "rationale": "Approve reviewed reimbursement" }) +> ramp_approve_or_reject_reimbursement({ "reimbursement_id": "reimb-003", "action": "approve", "rationale": "Approve reviewed reimbursement" }) + +3 reimbursements approved. For the 2 bills, approve them here: + https://app.ramp.com/bills/bill-001 + https://app.ramp.com/bills/bill-002 + +Now let me get details on that DoorDash transaction: +> ramp_get_full_transaction_metadata({ "id": "txn-001", "rationale": "Retrieve transaction details before approval review" }) +> ramp_get_transaction_missing_items({ "id": "txn-001", "rationale": "Check whether the transaction is missing required items" }) + +DoorDash $220 — Dana Alhasawi, 2026-03-15 +Memo: "Team lunch" | Receipt: attached | Accounting: complete + +User: Approve it + +Agent: > ramp_approve_or_reject_transaction({ "transaction_id": "txn-001", "action": "APPROVE", "thoughts": "Team lunch — receipt and memo present", "rationale": "Approve the reviewed transaction" }) +Done. Approval queue is clear. +``` + +## When NOT to Use + +- **Editing transaction memos or categories** — use transaction-cleanup + +## Gotchas + +| Issue | Fix | +|---|---| +| Bill amounts are in cents | Divide by 100 for display | +| Reimbursement amounts are in dollars | Display as-is | +| Transaction amounts are formatted strings | Strip "$" and "," for sorting/totaling | +| `requests pending` requires `--thoughts` | Always include it — describe what you're doing | +| Bill approvals are not yet exposed in this MCP surface | Send the user to the Ramp app for bill approval | +| No undo for approvals | Confirm with user before executing. Consider dry-run style confirmation in chat before write calls. | +| Pagination varies | Check the response cursor fields. Pass them via `next_page_cursor` (transactions), `page_cursor` (bills), `start` (requests). Reimbursements: `limit` only in this MCP surface. | diff --git a/plugins/ramp/skills/approval-dashboard/agents/openai.yaml b/plugins/ramp/skills/approval-dashboard/agents/openai.yaml new file mode 100644 index 00000000..61de0027 --- /dev/null +++ b/plugins/ramp/skills/approval-dashboard/agents/openai.yaml @@ -0,0 +1,14 @@ +interface: + display_name: "Ramp Approval Dashboard" + short_description: "Review pending Ramp approvals" + icon_small: "./assets/app-icon.png" + icon_large: "./assets/app-icon.png" + default_prompt: "Use $approval-dashboard to review pending Ramp approvals and prepare approve or reject actions." + +dependencies: + tools: + - type: "mcp" + value: "ramp" + description: "Ramp MCP server" + transport: "streamable_http" + url: "https://mcp.ramp.com/mcp" diff --git a/plugins/ramp/skills/approval-dashboard/assets/app-icon.png b/plugins/ramp/skills/approval-dashboard/assets/app-icon.png new file mode 100644 index 00000000..2df0a8bb Binary files /dev/null and b/plugins/ramp/skills/approval-dashboard/assets/app-icon.png differ diff --git a/plugins/ramp/skills/transaction-cleanup/SKILL.md b/plugins/ramp/skills/transaction-cleanup/SKILL.md new file mode 100644 index 00000000..575315a8 --- /dev/null +++ b/plugins/ramp/skills/transaction-cleanup/SKILL.md @@ -0,0 +1,610 @@ +--- +name: transaction-cleanup +description: |- + Complete missing items on your transactions using Ramp MCP tools — memos, accounting categories, + funds, trips, attendees, and missing receipts. Use when: 'add memo', 'categorize transactions', + 'missing items', 'transaction cleanup', 'fix my transactions', 'set tracking category', + 'assign to fund', 'bulk memo update', 'get memo suggestions', 'which transactions are missing + receipts/memos/accounting', or similar transaction-maintenance requests. Do NOT use for + receipt image uploads (use receipt-compliance) or approvals/rejections (use approval-dashboard). +--- + +## Non-Negotiables + +- Scope to the user's own transactions by default using `ramp_get_user_transactions` with `transactions_to_retrieve: "my_transactions"`. +- Show the transaction details before editing. Never blind-edit. +- For bulk edits, present the plan and confirm before executing. +- Include `user_submitted_fields` when calling `ramp_edit_transaction` to distinguish user-provided vs agent-inferred edits. +- All MCP tool calls must include a `rationale`. +- Prefer the MCP transaction tools over generic SQL/database tools for user-facing cleanup tasks. + +## Workflow + +### Step 1: Find transactions needing attention + +List the user's recent transactions. Default to the last 30 days if the user doesn't specify a date range. + +```json +{ + "tool": "ramp_get_user_transactions", + "args": { + "transactions_to_retrieve": "my_transactions", + "from_date": "2026-03-15", + "page_size": 50, + "include_count": true, + "rationale": "Retrieve the current user's recent cleared transactions so I can identify missing items and show the transaction details before making any edits." + } +} +``` + +**Response shape:** +```json +{ + "transactions": [ + { + "transaction_uuid": "ab512db5-...", + "spent_by_user": "Jane Smith", + "amount": "$36.87", + "transaction_time": "2026-02-07T00:51:33+00:00", + "merchant_name": "Uber", + "merchant_category": "Taxi and Rideshare", + "reason_or_justification": "Team transportation", + "spend_allocation_name": "Marketing Budget", + "spend_allocation_id": "2946abc3-...", + "receipt_uuids": null, + "trip_uuid": null, + "trip_name": null, + "accounting_categories": null, + "attendees": null, + "suggested_memos": [], + "suggested_codings": [], + "suggested_funds": [], + "missing_items": null, + "transaction_link": "https://app.ramp.com/business-overview/transactions/ab512db5-..." + } + ], + "total_count": 92, + "next_page_cursor": "gAAAAABp..." +} +``` + +**Important:** The `missing_items`, `suggested_memos`, `suggested_codings`, and `suggested_funds` fields in the list response are often `null` or empty even when items are actually missing or suggestions exist. Always use the dedicated per-transaction tools (`ramp_get_transaction_missing_items`, `ramp_get_transaction_suggested_memos`) for reliable data. + +For any transaction, check what's missing: + +```json +{ + "tool": "ramp_get_transaction_missing_items", + "args": { + "id": "{transaction_uuid}", + "rationale": "Check whether this transaction is missing a receipt, memo, or accounting items before proposing any cleanup." + } +} +``` + +**Response shape:** +```json +{ + "transaction_id": "ab512db5-...", + "missing_receipt": false, + "missing_memo": false, + "missing_accounting_items": [] +} +``` + +Returns: +- `missing_receipt` (bool) +- `missing_memo` (bool) +- `missing_accounting_items` (array of category names) + +If the user asks which transactions are missing items, iterate through the surfaced transactions, check missing items on each, and group the results by memo, accounting items, and receipt. + +**Latency note:** Each call to `ramp_get_transaction_missing_items` is a separate MCP round-trip. For large transaction sets (20+), check the first 10–15, report results, and ask the user if they want to continue checking the rest rather than blocking on 50 sequential calls. + +### Step 2: Get AI-suggested memos + +Before writing memos manually, check if Ramp has suggestions: + +```json +{ + "tool": "ramp_get_transaction_suggested_memos", + "args": { + "id": "{transaction_uuid}", + "rationale": "Fetch Ramp's suggested memos for this transaction before proposing or applying a manual memo." + } +} +``` + +**Response shape:** +```json +{ + "id": "ab512db5-...", + "memos": [ + "CKO travel", + "Team transportation", + "Ground transportation for CKO" + ] +} +``` + +Returns `memos[]` — an array of suggested memo strings. May be empty if there isn't enough context for suggestions. + +### Step 3: Get full transaction details + +When you need the complete picture of a transaction before editing (current memo, fund, tracking categories, comments): + +```json +{ + "tool": "ramp_get_full_transaction_metadata", + "args": { + "id": "{transaction_uuid}", + "rationale": "View complete transaction details before proposing edits." + } +} +``` + +**Response shape:** +```json +{ + "id": "ab512db5-...", + "amount_cents": 3687, + "merchant_name": "Uber", + "memo": "Team transportation", + "memo_source": "RECURRING_MEMO_MERCHANT", + "created_at": "2026-02-07T00:51:33", + "personal_repayment_state": null, + "tracking_categories": [], + "fund_name": "Marketing Budget", + "user_name": "Jane Smith", + "comments": { + "count": 0, + "comments": [] + } +} +``` + +### Step 4: Edit transactions + +#### Set a memo + +```json +{ + "tool": "ramp_edit_transaction", + "args": { + "transaction_uuid": "{transaction_uuid}", + "memo": "Q2 team offsite catering", + "user_submitted_fields": ["memo"], + "rationale": "Set the memo on this transaction based on the user's explicit instruction." + } +} +``` + +To clear a memo, pass an empty string as the memo value. + +**Post-edit behavior:** The `ramp_edit_transaction` response includes updated `missing_items` and suggestions. After a successful edit, check this response to see if there are remaining missing items and prompt the user for the next one. The tool's own description specifies this order: receipt → memo → accounting categories → funds → trip → attendees. If all agent-fillable items are complete, provide the `transaction_link` URL so the user can finish any remaining items (like receipt upload) in the Ramp UI. + +#### Assign to a fund / spend allocation + +To find available funds for the current user: + +```json +{ + "tool": "ramp_get_user_funds", + "args": { + "funds_to_retrieve": "MY_FUNDS", + "include_balance": true, + "rationale": "List the current user's available funds so I can identify which fund to apply to the transaction." + } +} +``` + +**Response shape:** +```json +{ + "funds": [ + { + "id": "a974b0c1-...", + "name": "KYC (Know Your Customer)", + "state": "ACTIVE", + "balance_info": "$0.00 / $50.00 ($50.00 remaining)", + "restrictions": null, + "lock": null, + "member_locks": [], + "members": [] + } + ], + "total_count": 10 +} +``` + +You can also scope funds to a specific transaction using `for_transaction_id`, or search by name using `search_by_fund_display_name`. + +Then edit: + +```json +{ + "tool": "ramp_edit_transaction", + "args": { + "transaction_uuid": "{transaction_uuid}", + "fund_uuid": "{fund_id}", + "user_submitted_fields": ["fund_uuid"], + "rationale": "Assign the selected fund to this transaction based on the user's instruction." + } +} +``` + +#### Set tracking categories (accounting codes) + +First, get available categories: + +```json +{ + "tool": "ramp_get_tracking_categories", + "args": { + "transaction_uuid": "{transaction_uuid}", + "include_hidden": false, + "rationale": "Retrieve the active tracking categories available for this transaction so I can identify the correct accounting category to set." + } +} +``` + +**Response shape:** +```json +{ + "tracking_categories": [ + { + "tracking_category_uuid": "afe32ec7-...", + "name": "Department", + "type": "OTHER", + "input_type": "SINGLE_CHOICE", + "is_hidden": false + } + ] +} +``` + +Then get options for the selected category. Always pass `transaction_uuid` and set `include_hidden` to `false` when coding a specific transaction — this applies subsidiary/conditional filtering. Use `query_string` to search by name: + +```json +{ + "tool": "ramp_get_tracking_category_options", + "args": { + "tracking_category_uuid": "{tracking_category_uuid}", + "transaction_uuid": "{transaction_uuid}", + "include_hidden": false, + "query_string": "Marketing", + "page_size": 50, + "rationale": "Find the matching tracking category option to apply to this transaction." + } +} +``` + +**Response shape:** +```json +{ + "options": [ + { + "tracking_category_option_uuid": "4653156e-...", + "name": "Applied AI", + "code": null, + "is_hidden": false + } + ], + "next_page_cursor": 97337367 +} +``` + +Note: `next_page_cursor` is an **integer**, not a string. If present, pass it to get more results. Pagination is not supported when `query_string` is provided with semantic search enabled. + +Then edit using `category_uuid` and `option_selection` (not the response field names): + +```json +{ + "tool": "ramp_edit_transaction", + "args": { + "transaction_uuid": "{transaction_uuid}", + "tracking_category_selections": [ + { + "category_uuid": "{tracking_category_uuid}", + "option_selection": "{tracking_category_option_uuid}" + } + ], + "user_submitted_fields": ["tracking_category_selections"], + "rationale": "Apply the selected accounting category option to this transaction based on the user's instruction." + } +} +``` + +#### Set attendees + +If the user names attendees who are Ramp users, search for them first. The `transaction_id` param (note: **not** `transaction_uuid`) helps rank results by relevance: + +```json +{ + "tool": "ramp_search_user", + "args": { + "search": "Jane Smith", + "transaction_id": "{transaction_uuid}", + "rationale": "Find the matching user to attach as an attendee to this transaction." + } +} +``` + +**Response shape:** +```json +{ + "users": [ + { + "id": "ed735740-...", + "label": "Junho Choi", + "description": "jchoi@ramp.com • Forward Deployed Engineering" + } + ] +} +``` + +Then edit. `attendee_selections` is an object (not an array) with fields for Ramp and non-Ramp attendees: + +```json +{ + "tool": "ramp_edit_transaction", + "args": { + "transaction_uuid": "{transaction_uuid}", + "attendee_selections": { + "non_ramp_attendees": [ + {"attendee_name": "External Guest", "attendee_email": "guest@other.com"} + ], + "include_self_as_attendee": false + }, + "user_submitted_fields": ["attendee_selections"], + "rationale": "Add the attendees specified by the user to this transaction." + } +} +``` + +#### Set trip + +To find candidate trips for the current user. Note: this tool does **not** accept a `transaction_uuid` — it returns the user's ongoing, upcoming, and recently completed trips: + +```json +{ + "tool": "ramp_get_candidate_trips_for_transaction", + "args": { + "rationale": "Retrieve candidate trips that may be relevant to this transaction before assigning one." + } +} +``` + +**Response shape:** +```json +{ + "trips": [] +} +``` + +Returns an array of trip objects. May be empty if the user has no active or recent trips. + +Then edit with the chosen trip: + +```json +{ + "tool": "ramp_edit_transaction", + "args": { + "transaction_uuid": "{transaction_uuid}", + "trip_selection": { + "trip_uuid": "{trip_uuid}" + }, + "rationale": "Associate the selected trip with this transaction based on the user's instruction." + } +} +``` + +If the user needs a new trip first, create one: + +```json +{ + "tool": "ramp_create_trip", + "args": { + "name": "NYC Client Visit", + "are_trip_dates_from_booking_receipt_or_user_input": true, + "start_date": "2026-04-10", + "end_date": "2026-04-12", + "destinations": ["New York, NY"], + "trip_purpose": "Client meetings", + "rationale": "Create a new trip for the user to associate with their travel transactions." + } +} +``` + +Required params: `name` (string), `are_trip_dates_from_booking_receipt_or_user_input` (boolean — set to `true` if dates come from the user or a booking receipt, `false` otherwise), `rationale` (string). Optional: `start_date`, `end_date`, `destinations` (array), `trip_purpose`. + +### Step 5: Handle missing receipts + +If `missing_receipt` is true, there are two options: + +1. **Upload a receipt** — hand off to the receipt-compliance skill +2. **Mark as no receipt** + +If the user says there was no receipt or wants to flag the transaction: + +```json +{ + "tool": "ramp_mark_transaction_missing_receipt", + "args": { + "transaction_uuid": "{transaction_uuid}", + "rationale": "Flag this transaction as having no receipt because the user indicated no receipt is available." + } +} +``` + +If Ramp then asks for the reason (i.e., the user has already flagged it and now needs to explain why), provide it: + +```json +{ + "tool": "ramp_provide_no_receipt_reason", + "args": { + "transaction_uuid": "{transaction_uuid}", + "reason": "Digital subscription — no receipt issued", + "rationale": "Provide the user's no-receipt explanation for this transaction." + } +} +``` + +**Important:** Only call `ramp_provide_no_receipt_reason` when the user is responding to a prompt asking for a reason — not as a first step. Use `ramp_mark_transaction_missing_receipt` first to flag it. + +## Bulk Cleanup Workflow + +For cleaning up many transactions at once: + +1. Fetch all transactions in the requested period with `ramp_get_user_transactions` (paginate with `next_page_cursor` if needed) +2. Check `ramp_get_transaction_missing_items` on the first batch (10–15 transactions). If the set is large, report progress and ask before continuing +3. Group results by what's missing: memo, accounting items, receipt +4. Present summary: "12 transactions need memos, 5 need accounting categories, 3 need receipts" +5. For memos: fetch suggestions for each via `ramp_get_transaction_suggested_memos`, present them, let the user confirm or edit +6. For categories: if most transactions share a category, offer to bulk-apply +7. Execute edits sequentially with `ramp_edit_transaction`, then report results + +**Error handling:** If an edit fails mid-batch, report which transaction failed and why, then continue with the remaining transactions. Summarize successes and failures at the end. + +Do not execute bulk edits until the user confirms the proposed plan. + +## Provenance Tracking + +The `user_submitted_fields` field tells Ramp which fields the user explicitly provided. This matters for compliance and provenance. + +Include it when the user explicitly supplies or approves the value: +- `memo` +- `fund_uuid` +- `tracking_category_selections` +- `attendee_selections` + +If the agent is applying a suggested memo that the user explicitly approved, that still counts as user-submitted intent. + +## Example Session + +``` +User: I have a bunch of transactions that need memos + +Agent: Let me check what's missing on your recent transactions. +> ramp_get_user_transactions({ + "transactions_to_retrieve": "my_transactions", + "from_date": "2026-03-15", + "page_size": 50, + "include_count": true, + "rationale": "Retrieve the user's recent cleared transactions so I can identify missing memo items." + }) + +Found 8 transactions. Checking missing items on each... +> ramp_get_transaction_missing_items({"id": "{uuid}", "rationale": "Check missing items for cleanup."}) x8 + +Summary: + 5 need memos + 2 need accounting categories + 1 needs a receipt + +Let me get memo suggestions for the 5: +> ramp_get_transaction_suggested_memos({"id": "{uuid}", "rationale": "Fetch memo suggestions."}) x5 + +Transaction Suggested memo +$135 Facebook Ads 2026-03-02 "Q1 paid social campaign" +$220 AWS 2026-03-05 "March infrastructure costs" +$89 Uber 2026-03-10 (no suggestion) +$450 WeWork 2026-03-12 "March coworking space" +$65 Doordash 2026-03-15 (no suggestion) + +Want to accept these suggestions, edit them, or write your own? + +User: Accept the suggestions for Facebook and AWS. The Uber was a client dinner ride, WeWork is fine, skip DoorDash for now. + +Agent: Updating 4 transactions: +> ramp_edit_transaction({ + "transaction_uuid": "{fb-uuid}", + "memo": "Q1 paid social campaign", + "user_submitted_fields": ["memo"], + "rationale": "Apply the memo the user approved." + }) +> ramp_edit_transaction({ + "transaction_uuid": "{aws-uuid}", + "memo": "March infrastructure costs", + "user_submitted_fields": ["memo"], + "rationale": "Apply the memo the user approved." + }) +> ramp_edit_transaction({ + "transaction_uuid": "{uber-uuid}", + "memo": "Client dinner transportation", + "user_submitted_fields": ["memo"], + "rationale": "Apply the memo the user specified." + }) +> ramp_edit_transaction({ + "transaction_uuid": "{wework-uuid}", + "memo": "March coworking space", + "user_submitted_fields": ["memo"], + "rationale": "Apply the memo the user approved." + }) + +All 4 memos set. 2 transactions still need accounting categories. +Want to tackle those? + +User: Yes, they should both be under the "Marketing" department + +Agent: +> ramp_get_tracking_categories({ + "transaction_uuid": "{uuid1}", + "include_hidden": false, + "rationale": "Find the relevant tracking category for this transaction." + }) + +Found "Department" category (uuid: afe32ec7-...). Searching for Marketing option: + +> ramp_get_tracking_category_options({ + "tracking_category_uuid": "afe32ec7-...", + "transaction_uuid": "{uuid1}", + "include_hidden": false, + "query_string": "Marketing", + "page_size": 50, + "rationale": "Find the Marketing option for the Department tracking category." + }) + +Found "Marketing" option (uuid: mkt-123). Applying to both: +> ramp_edit_transaction({ + "transaction_uuid": "{uuid1}", + "tracking_category_selections": [{"category_uuid": "afe32ec7-...", "option_selection": "mkt-123"}], + "user_submitted_fields": ["tracking_category_selections"], + "rationale": "Apply the Marketing department the user selected." + }) +> ramp_edit_transaction({ + "transaction_uuid": "{uuid2}", + "tracking_category_selections": [{"category_uuid": "afe32ec7-...", "option_selection": "mkt-123"}], + "user_submitted_fields": ["tracking_category_selections"], + "rationale": "Apply the Marketing department the user selected." + }) + +Done. 1 transaction remaining needs a receipt — you can upload one (I'll hand off to receipt handling) or mark it as no receipt. What would you like to do? +``` + +## When NOT to Use + +- **Uploading receipt images/files** — use receipt-compliance +- **Approving or rejecting transactions/reimbursements/requests** — use approval-dashboard +- **Company-wide spend reporting or analysis** — use reporting/query workflows, not transaction cleanup +- **Card lock/unlock flows** — use the card-management skill +- **Reimbursement drafting/editing** — use `ramp_edit_reimbursement` (different tool, different params — notably `reimbursement_uuid` not `transaction_uuid`, and `trip_uuid` not `trip_selection`) + +## Gotchas + +| Issue | Fix | +|---|---| +| MCP tools require `rationale` | Include a concise reason on every tool call | +| List endpoint `missing_items` / `suggested_memos` fields are often null/empty | Always use `ramp_get_transaction_missing_items` and `ramp_get_transaction_suggested_memos` for reliable data | +| `ramp_get_candidate_trips_for_transaction` takes no `transaction_uuid` | It returns the user's recent/ongoing trips globally — match to the transaction by date yourself | +| Tracking category options `next_page_cursor` is an integer | Pass it as a number, not a string | +| Tracking category edit payload uses different field names than the response | Use `category_uuid` + `option_selection`, not `tracking_category_uuid` + `tracking_category_option_uuid` | +| `ramp_search_user` param is `transaction_id` | Not `transaction_uuid` — even though you pass a UUID value | +| `ramp_create_trip` requires `are_trip_dates_from_booking_receipt_or_user_input` | Boolean, required — set `true` if dates came from user or booking, `false` otherwise | +| Bulk missing-items checks are sequential | Each is a separate MCP call — batch in groups of 10–15 to avoid long waits | +| `ramp_provide_no_receipt_reason` is a follow-up tool | Only call it after `ramp_mark_transaction_missing_receipt`, when Ramp asks for a reason | +| `state` filter values are lowercase | Use `cleared`, `declined`, etc. | +| `transactions_to_retrieve` is required | Default to `"my_transactions"` for this skill | +| Text search across transactions | Use `reason_memo_merchant_or_user_name_text_search` param on `ramp_get_user_transactions` | +| Missing receipt requires separate tools | Use `ramp_mark_transaction_missing_receipt` and `ramp_provide_no_receipt_reason`, not `ramp_edit_transaction` | +| Failed edits in a bulk run | Report the failure, continue with remaining transactions, summarize at the end | diff --git a/plugins/ramp/skills/transaction-cleanup/agents/openai.yaml b/plugins/ramp/skills/transaction-cleanup/agents/openai.yaml new file mode 100644 index 00000000..5c60fa78 --- /dev/null +++ b/plugins/ramp/skills/transaction-cleanup/agents/openai.yaml @@ -0,0 +1,14 @@ +interface: + display_name: "Ramp Transaction Cleanup" + short_description: "Complete missing Ramp transaction details" + icon_small: "./assets/app-icon.png" + icon_large: "./assets/app-icon.png" + default_prompt: "Use $transaction-cleanup to find Ramp transactions with missing items and prepare safe updates." + +dependencies: + tools: + - type: "mcp" + value: "ramp" + description: "Ramp MCP server" + transport: "streamable_http" + url: "https://mcp.ramp.com/mcp" diff --git a/plugins/ramp/skills/transaction-cleanup/assets/app-icon.png b/plugins/ramp/skills/transaction-cleanup/assets/app-icon.png new file mode 100644 index 00000000..2df0a8bb Binary files /dev/null and b/plugins/ramp/skills/transaction-cleanup/assets/app-icon.png differ diff --git a/plugins/ramp/skills/vendor-analysis/SKILL.md b/plugins/ramp/skills/vendor-analysis/SKILL.md new file mode 100644 index 00000000..c2e4adf6 --- /dev/null +++ b/plugins/ramp/skills/vendor-analysis/SKILL.md @@ -0,0 +1,342 @@ +--- +name: ramp-vendor-analysis +description: Analyzes vendor spend data from Ramp and exports to connected systems. Use when user asks to "analyze vendor spend", "build vendor database", "update managed vendors", "top vendors report", "show vendors over X spend", "vendor spend analysis", "vendor renewals", "contract end dates", or asks about vendor spending patterns, vendor owners, purchase orders, or department-level vendor data. +metadata: + author: Ramp + version: 1.1.0 + mcp-server: ramp +--- + +# Ramp Vendor Spend Analysis + +## Overview + +This skill extracts comprehensive vendor data from Ramp, enriches it with owner/department information and contract data from purchase orders, and outputs structured results. Data can be displayed as text/tables or exported to connected integrations (Notion, Google Sheets, etc.). + +## When to Use This Skill + +- User asks to analyze vendor spend or build a vendor database +- User wants to see top vendors by spend (L365, L30, YTD, all-time) +- User asks about vendor owners or which departments own which vendors +- User asks about contract end dates, renewals, or purchase order status +- User wants vendor data exported to Notion, spreadsheets, or other systems +- User asks ad-hoc questions like "vendors with spend over $50k" or "upcoming renewals" + +## Prerequisites + +- Ramp MCP server must be connected +- For exports: Target integration (Notion, Google Sheets, etc.) should be connected + +## Data Collection Workflow + +### Step 1: Load Core Vendor Data + +**Tool:** `ramp-demo:load_vendors` + +This is the primary data source. Call with default parameters to load all vendors. + +``` +Parameters: {} +``` + +**Data extracted per vendor:** +- Vendor ID +- Vendor Name +- Vendor Owner ID +- Vendor Contacts (email, phone) +- Total spend L365 (last 365 days) +- Total spend L30 (last 30 days) +- Total spend YTD (year to date) +- Total spend all-time +- Billing frequency +- Tax information (W-9 status, tax ID) +- Active status + +### Step 2: Load Users for Owner Details + +**Tool:** `ramp-demo:load_users` + +Required to resolve Vendor Owner IDs to names and get their department assignments. + +``` +Parameters: {} +``` + +**Data extracted per user:** +- User ID +- First name, Last name +- Email +- Department ID +- Location ID +- Manager ID + +### Step 3: Load Departments + +**Tool:** `ramp-demo:load_departments` + +Required to map Department IDs to department names. + +``` +Parameters: {} +``` + +**Data extracted:** +- Department ID +- Department Name + +### Step 4: Load Bills for Payment Method Analysis + +**Tool:** `ramp-demo:load_spend_export` + +Query bills to determine preferred payment methods per vendor. + +``` +Parameters: + spend_export_type: "bills" + from_date: [365 days ago, YYYY-MM-DD format] + to_date: [today, YYYY-MM-DD format] +``` + +**Data extracted per bill:** +- Vendor ID (to join with vendor data) +- Payment method used +- Bill amount +- Bill date + +### Step 5: Load Purchase Orders for Contract Data + +**Tool:** `ramp-demo:load_purchase_orders` + +Purchase orders contain contract/renewal information. The spend end date on a PO maps to the contract end date for that vendor relationship. + +``` +Parameters: + from_date: [365 days ago, YYYY-MM-DD format] + to_date: [today, YYYY-MM-DD format] +``` + +**Data extracted per purchase order:** +- PO ID +- PO Number +- Vendor ID (to join with vendor data) +- Spend end date → **maps to contract end date** +- Total amount +- Amount paid/billed +- Receipt status (FULLY_RECEIVED, PARTIALLY_RECEIVED, OVER_RECEIVED, NOT_RECEIVED) +- Three-way match enabled flag +- Created date + +**Contract status logic:** +- If vendor has PO with spend_end_date → "Contract in place", use date as contract end +- If vendor has multiple POs → "Multiple contracts", use earliest upcoming end date +- If vendor has no POs → "No contract in place" +- If PO is nearing full billing (amount_paid approaching total_amount) → flag for renewal attention + +### Step 6: Join and Enrich Data + +After loading all data sources, perform the following joins using SQL queries: + +**Tool:** `ramp-demo:execute_query` + +```sql +-- Join vendors with owner names, departments, and contract info from POs +SELECT + v.vendor_name, + v.total_spend_l365, + v.total_spend_l30, + v.total_spend_ytd, + v.total_spend_all_time, + v.billing_frequency, + v.tax_status, + u.first_name || ' ' || u.last_name AS vendor_owner_name, + u.email AS vendor_owner_email, + d.name AS owner_department, + po.spend_end_date AS contract_end_date, + po.total_amount AS po_total, + po.amount_paid AS po_paid, + po.receipt_status, + CASE + WHEN po.id IS NULL THEN 'No contract in place' + WHEN po_count.cnt > 1 THEN 'Multiple contracts' + ELSE 'Contract' + END AS contract_status +FROM vendors v +LEFT JOIN users u ON v.vendor_owner_id = u.id +LEFT JOIN departments d ON u.department_id = d.id +LEFT JOIN purchase_orders po ON v.id = po.vendor_id +LEFT JOIN ( + SELECT vendor_id, COUNT(*) as cnt + FROM purchase_orders + GROUP BY vendor_id +) po_count ON v.id = po_count.vendor_id +ORDER BY v.total_spend_l365 DESC; +``` + +**Note:** Actual column names may vary. After loading data, query the schema: +```sql +PRAGMA table_info(vendors); +PRAGMA table_info(users); +PRAGMA table_info(departments); +PRAGMA table_info(purchase_orders); +PRAGMA table_info(bills); +``` + +## Output Schema + +The final enriched dataset should include these fields per vendor: + +| Field | Source | Description | +|-------|--------|-------------| +| Vendor Name | vendors | Company/vendor name | +| Vendor Owner | users (joined) | Full name of owner | +| Owner Email | users (joined) | Email of vendor owner | +| Owner Department | departments (joined) | Department name | +| Spend L365 | vendors | Last 365 days spend | +| Spend L30 | vendors | Last 30 days spend | +| Spend YTD | vendors | Year-to-date spend | +| Spend All-Time | vendors | Total historical spend | +| Billing Frequency | vendors | Monthly, annual, etc. | +| Tax Status | vendors | W-9 on file, tax ID | +| Preferred Payment Method | bills (aggregated) | Most common payment method | +| Vendor Contacts | vendors | Contact email/phone | +| Contract Status | purchase_orders | Contract / No contract / Multiple contracts | +| Contract End Date | purchase_orders.spend_end_date | Date contract/PO expires | +| PO Total Amount | purchase_orders | Total value of purchase order | +| PO Amount Paid | purchase_orders | Amount billed against PO | +| PO Receipt Status | purchase_orders | Fulfillment status | + +## Handling Ad-Hoc Queries + +For filtered queries like "show vendors over $50k spend": + +1. Load data using Steps 1-5 above +2. Apply filters in the SQL query: + +```sql +SELECT * FROM vendors +WHERE total_spend_l365 > 50000 +ORDER BY total_spend_l365 DESC; +``` + +Common filter patterns: +- Spend thresholds: `WHERE total_spend_l365 > [amount]` +- Department filter: `WHERE owner_department = '[dept_name]'` +- Active only: `WHERE is_active = 1` +- Top N vendors: `LIMIT [n]` +- Upcoming renewals: `WHERE contract_end_date BETWEEN date('now') AND date('now', '+90 days')` +- Missing contracts: `WHERE contract_status = 'No contract in place' AND total_spend_l365 > 25000` +- POs nearing completion: `WHERE po_paid / po_total > 0.8` + +## Output Options + +After data collection, ask the user how they want to receive the results: + +### Option 1: Text/Table Display (Default) + +Display results as a formatted markdown table directly in the conversation. + +### Option 2: Export to Connected Integration + +Check for available integrations and offer export: + +**For Notion:** +- Create or update a database with the output schema +- Map fields to Notion properties: + - Vendor Name → Title + - Spend fields → Number ($ format) + - Contract Status → Select (Contract | No contract in place | Multiple contracts) + - Contract End Date → Date + - Tax Status → Select (Tax details verified by Ramp | Missing | N/A) + - Payment Method → Select (Pay by card | Pay by Bill pay (ACH) | Mixed) + +**For Google Sheets:** +- Create a new sheet or append to existing +- Include headers matching the output schema + +**For other integrations:** +- Adapt the output schema to the target system's format + +## Example Usage + +**User:** "Build me a vendor database with our top vendors" + +**Workflow:** +1. Call `load_vendors` → loads vendor table +2. Call `load_users` → loads users table +3. Call `load_departments` → loads departments table +4. Call `load_spend_export` with type="bills" → loads bills table +5. Call `load_purchase_orders` → loads purchase orders table +6. Execute join query to create enriched dataset with contract info +7. Ask user: "I've compiled data on [X] vendors. Would you like me to display this as a table, or export to a connected system like Notion?" +8. Output based on user preference + +**User:** "Show me vendors with contracts expiring in the next 90 days" + +**Workflow:** +1. Load all data sources (Steps 1-5) +2. Execute filtered query: +```sql +SELECT vendor_name, vendor_owner_name, owner_department, + total_spend_l365, contract_end_date, + julianday(contract_end_date) - julianday('now') AS days_until_expiry +FROM enriched_vendors +WHERE contract_end_date BETWEEN date('now') AND date('now', '+90 days') +ORDER BY contract_end_date ASC; +``` +3. Display results as table + +**User:** "Which high-spend vendors don't have contracts?" + +**Workflow:** +1. Load all data sources (Steps 1-5) +2. Execute filtered query: +```sql +SELECT vendor_name, vendor_owner_name, total_spend_l365 +FROM enriched_vendors +WHERE contract_status = 'No contract in place' +AND total_spend_l365 > 25000 +ORDER BY total_spend_l365 DESC; +``` +3. Display results with recommendation to establish contracts + +**User:** "Show me purchase orders that are almost fully billed" + +**Workflow:** +1. Load purchase orders data +2. Execute query: +```sql +SELECT vendor_name, po_number, po_total, po_paid, + ROUND(po_paid * 100.0 / po_total, 1) AS percent_used, + contract_end_date +FROM enriched_vendors +WHERE po_paid / po_total > 0.8 +ORDER BY percent_used DESC; +``` +3. Display results - these vendors may need renewal attention + +## Error Handling + +### MCP Connection Failed +- Verify Ramp MCP is connected in Settings > Extensions +- Check API credentials are valid +- Try reconnecting the integration + +### No Vendors Returned +- Confirm the Ramp account has vendor data +- Check if filters are too restrictive +- Try loading without filters first + +### No Purchase Orders Found +- Not all vendors will have purchase orders +- Mark these vendors as "No contract in place" +- This is expected for many card-based or self-serve SaaS vendors + +### Join Failures +- Query table schemas first to verify column names +- Check for NULL values in join keys +- Use LEFT JOIN to preserve vendors without owners or POs + +### Export Failures +- Verify target integration is connected +- Check permissions on target database/sheet +- Confirm field mapping is valid for target system \ No newline at end of file diff --git a/plugins/ramp/skills/vendor-analysis/agents/openai.yaml b/plugins/ramp/skills/vendor-analysis/agents/openai.yaml new file mode 100644 index 00000000..63885738 --- /dev/null +++ b/plugins/ramp/skills/vendor-analysis/agents/openai.yaml @@ -0,0 +1,14 @@ +interface: + display_name: "Ramp Vendor Analysis" + short_description: "Analyze Ramp vendor spend and ownership data" + icon_small: "./assets/app-icon.png" + icon_large: "./assets/app-icon.png" + default_prompt: "Use $ramp-vendor-analysis to analyze vendor spend, identify top vendors, and summarize ownership and renewal risk." + +dependencies: + tools: + - type: "mcp" + value: "ramp" + description: "Ramp MCP server" + transport: "streamable_http" + url: "https://mcp.ramp.com/mcp" diff --git a/plugins/ramp/skills/vendor-analysis/assets/app-icon.png b/plugins/ramp/skills/vendor-analysis/assets/app-icon.png new file mode 100644 index 00000000..2df0a8bb Binary files /dev/null and b/plugins/ramp/skills/vendor-analysis/assets/app-icon.png differ