Compare commits
11 Commits
FEAT-readm
...
25ac1ec68f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25ac1ec68f | ||
|
|
e7feb61e6f | ||
| dac6f37331 | |||
|
|
54c6f7e81b | ||
|
|
55d0e77788 | ||
| aaef1b31c4 | |||
| c76725b111 | |||
| e88a0226b6 | |||
| cfef93a374 | |||
| 48017d958e | |||
| 7a6112479f |
20
README.md
20
README.md
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
# deClawed
|
# deClawed
|
||||||
|
|
||||||
**LLM-powered email triage and workflow automation via the Model Context Protocol**
|
**LLM-powered email, meeting, teleconference and event triage and workflow automation via the Model Context Protocol**
|
||||||
|
|
||||||
deClawed is an MCP server that connects your Gmail inbox to any LLM-capable client (such as Claude Desktop). It classifies incoming emails, automates routine actions (delete, archive, summarize), and integrates with Google Sheets and Calendar to keep your job search—or any high-volume email workflow—organized and actionable.
|
deClawed is an MCP server that connects email inbox to an LLM-capable client (such as Claude Desktop), within an orchestration harness: Agents have access to tools to classify incoming emails, automate routine actions (delete, archive, summarize). The framwork integrates with Google Mail, Sheets and Calendar to keep your high-volume email workflow organized and actionable.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -97,10 +97,10 @@ The server fetches unread emails, classifies them using your configured prompts,
|
|||||||
|
|
||||||
| Category | Description | Default Action |
|
| Category | Description | Default Action |
|
||||||
|----------|-------------|----------------|
|
|----------|-------------|----------------|
|
||||||
| **A** | Acknowledgements, auto-replies | Delete |
|
| **A** | Acknowledgements, auto-replies | Star and mark read |
|
||||||
| **B** | Advancement (interview requests, next steps) | Log + Calendar event |
|
| **B** | Advancement (interview requests, next steps) | Log + Calendar event (never star) |
|
||||||
| **C** | Rejections | Delete |
|
| **C** | Rejections | Star and mark read |
|
||||||
| **D** | Other/uncategorized | Log for review |
|
| **D** | Other/uncategorized | Star, mark read, and log for review |
|
||||||
|
|
||||||
### Customizing Prompts
|
### Customizing Prompts
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ Changes take effect immediately without rebuilding.
|
|||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `fetch_new_emails` | Fetch unread emails with classification instructions appended |
|
| `fetch_new_emails` | Fetch unread emails with classification instructions appended |
|
||||||
| `delete_emails` | Move specified message IDs to trash |
|
| `delete_emails` | Move specified message IDs to trash |
|
||||||
| `star_emails` | Star messages and mark as read |
|
| `star_emails` | Star Category A/C/D messages and mark as read |
|
||||||
| `append_to_summary` | Log email metadata to local JSON (auto-purges after 30 days) |
|
| `append_to_summary` | Log email metadata to local JSON (auto-purges after 30 days) |
|
||||||
| `log_recruiter_contact` | Append or update a row in Google Sheets |
|
| `log_recruiter_contact` | Append or update a row in Google Sheets |
|
||||||
| `create_calendar_event` | Create a Google Calendar event (skips past dates) |
|
| `create_calendar_event` | Create a Google Calendar event (skips past dates) |
|
||||||
@@ -130,6 +130,12 @@ Changes take effect immediately without rebuilding.
|
|||||||
fetch_new_emails(account: "work", maxResults: 50)
|
fetch_new_emails(account: "work", maxResults: 50)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Example: Star and Mark Read (Categories A, C, D only)
|
||||||
|
|
||||||
|
```
|
||||||
|
star_emails(account: "work", messageIds: ["msg-id-1", "msg-id-2"])
|
||||||
|
```
|
||||||
|
|
||||||
### Example: Create Calendar Event
|
### Example: Create Calendar Event
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,80 +1,35 @@
|
|||||||
You are an expert Executive Assistant AI specialized in email management. Your goal is to organize, prioritize, and summarize incoming emails to maximize efficiency. I have been applying for jobs, which generates a large volume of responses.
|
You are an expert Executive Assistant AI specialized in email management and productivity. Your goal is to organize, prioritize, and summarize incoming emails to maximize user efficiency.
|
||||||
|
|
||||||
Review the first 200 inbox emails. Apply the following instructions.
|
I have been applying for jobs, which generates a large volume of responses. Review my inbox and apply the following instructions to all new emails.
|
||||||
|
|
||||||
STEP 1: Constraint
|
STEP 1: Constraints
|
||||||
- NEVER make up information.
|
- Do not make up information.
|
||||||
|
|
||||||
STEP 2: CLASSIFY each email into exactly one category.
|
STEP 2: CLASSIFY each email into exactly one category.
|
||||||
|
|
||||||
Category A - Acknowledgement Only
|
Category A - Acknowledgement Only
|
||||||
The employer confirms receipt of my application but requires no action from me. Typical language: "We received your application," "Your application is under review," "We will contact you if selected."
|
The employer confirms receipt of my application but requires no action from me. Typical language: "We received your application," "Your application is under review," "We will contact you if selected." No next steps are requested.
|
||||||
|
Action you should take: Call star_emails with this message's ID (same account used for fetch_new_emails). That stars the email and marks it read.
|
||||||
|
|
||||||
Category B - Advancement to a Next Step
|
Category B - Advancement to Next Step
|
||||||
The company wants to move forward. This includes, for example:
|
The employer wants to move forward. This includes: invitations to schedule a phone screen or interview, requests to complete an assessment or assignment, requests for additional information or documents, or any communication that requires a response or action from me. If an email both acknowledges receipt AND requests action, classify it as B.
|
||||||
|
Actions you should take: 1) add a calendar event with all pertinent info: a) person I will be meeting with,
|
||||||
|
b) company c) role I will be discussing d) method for joining meeting - ie, telephone? If yes, will the representative all me, and is the number correct (718) 749-8292 c) or, is it Zoom, Google Meet, Webex or another video format, if so, include the link for joining
|
||||||
|
|
||||||
1. Invitations to schedule a phone screen or interview
|
NEVER, EVER ADD A STAR TO A CATEGORY B EMAIL. Do not pass Category B message IDs to star_emails.
|
||||||
2. Requests to complete an assessment or assignment
|
|
||||||
3. Requests for additional information or documents, from a human. This item is a bit more fine grained than others, because it does not include, i.e. automated requests to provide EEOC demographic information, verify identity, create an account password, etc. An important factor here is automated messaging versus a "human in the loop" on the company side seeking to connect. Use your judgment. Do not stop the process to ask questions about any emails. Rather, when in doubt, err on the side of inclusion and move on.
|
|
||||||
4. Emails seeking to schedule a "second round", "third round" interview, further discussion or screening.
|
|
||||||
5. Emails that state that I missed a prior email, appointment or request *and* come from a human, not automation. Again, when in doubt err on the side of inclusion.
|
|
||||||
6. If an email both acknowledges receipt AND requests action, classify it as B.
|
|
||||||
|
|
||||||
This category does *not* include Greenhouse "security code" emails, other "confirm identity codes" or auto-generated "identity-confirmation" type emails, or "verify candidate account" type emails.
|
|
||||||
|
|
||||||
Category C - Rejection
|
Category C - Rejection
|
||||||
The employer declines to move forward. Typical language: "We have decided to pursue other candidates," "Unfortunately, you were not selected," "We will not be moving forward with your application."
|
The employer declines to move forward. Typical language: "We have decided to pursue other candidates," "Unfortunately, you were not selected," "We will not be moving forward with your application."
|
||||||
|
Action you should take: Call star_emails with this message's ID (same account used for fetch_new_emails). That stars the email and marks it read.
|
||||||
|
|
||||||
Category D - Other
|
Category D - Other
|
||||||
The email does not fit into categories A, B, or C. This includes non-job-application emails, newsletters, promotional content, or ambiguous messages that do not clearly match another category.
|
The email does not fit into categories A, B, or C. This includes non-job-application emails, newsletters, promotional content, or ambiguous messages that do not clearly match another category.
|
||||||
|
Action you should take: Call star_emails with this message's ID (same account used for fetch_new_emails). That stars the email and marks it read.
|
||||||
|
|
||||||
STEP 3: STAR REVIEWED EMAILS
|
STEP 3: SUMMARIZE emails classified as Category B and Category D in the chat in an organized list with headings. Example format for this:
|
||||||
|
|
||||||
For every Category A, C, and D email, apply the Gmail "STARRED" system label or use your email tool to add a star to the message.
|
|
||||||
DO NOT add a star to any Category B email.
|
|
||||||
|
|
||||||
STEP 4: Confirm, via the chat window, the total number of emails fetched.
|
|
||||||
|
|
||||||
STEP 5: SUMMARIZE emails classified as Category B and Category D, under separate headers, in the chat.
|
|
||||||
For each, state:
|
|
||||||
1. Sender name and email address
|
1. Sender name and email address
|
||||||
2. Email subject line
|
2. Email subject line
|
||||||
3. Date and time received
|
3. Date and time received
|
||||||
4. Category (B or D)
|
4. Category (B or D)
|
||||||
5. Summary of body
|
5. Suggested action I might take
|
||||||
|
|
||||||
STEP 6: Process check **STRICT COMPLIANCE REQUIRED** **DO NOT FORGET***:
|
|
||||||
|
|
||||||
Summarize all "Category B" and "Category D" emails in the chat.
|
|
||||||
|
|
||||||
**STRICT COMPLIANCE REQUIRED** **DO NOT FORGET***
|
|
||||||
Inlude headers:
|
|
||||||
From (sender)
|
|
||||||
Subject (one line)
|
|
||||||
Date
|
|
||||||
Time (IN EASTERN STANDARD TIME)
|
|
||||||
Body Summary
|
|
||||||
|
|
||||||
Star all emails reviewed and classified as Category A, C or D.
|
|
||||||
Do not star emails reviewed and classified as Category B.
|
|
||||||
Do not star unreviewed emails.
|
|
||||||
|
|
||||||
STEP 7: CREATE CALENDAR EVENTS
|
|
||||||
Perform the following actions for each "Category B" email:
|
|
||||||
Immediately after logging to the spreadsheet, check whether ANY of these fields were populated or updated:
|
|
||||||
- Recruiter Call Scheduled
|
|
||||||
- Company First Interview
|
|
||||||
- Company Second Interview
|
|
||||||
|
|
||||||
For EACH of the above fields that contains a date/time, create a Google Calendar event using the create_calendar_event tool:
|
|
||||||
- Title format: "[<Recruiter Name> Call] Company - Role" or "[First Interview] Company - Role" or "[Second Interview] Company - Role"
|
|
||||||
- Start date/time: extracted from the email (convert to ISO 8601)
|
|
||||||
- Duration: 30 minutes unless otherwise specified in the email
|
|
||||||
- Location: the video meeting link, phone number, or platform name
|
|
||||||
- Description: include recruiter or company contact name/email/phone, and note whether they have your cell number
|
|
||||||
|
|
||||||
STRICT CONSTRAINTS:
|
|
||||||
- Do not make up phone numbers, links, dates, or anything at all. If information does not exist in text within a verifiable email message, IT DOES NOT EXIST.
|
|
||||||
- Before creating a calendar event: 1) verify today's date from an external, reliable source; 2) compare the proposed meeting date/time against today's date; 3) if the meeting date has already passed, do NOT create a calendar event for it.
|
|
||||||
- Always use the same account for calendar events as was used for email fetching.
|
|
||||||
- Do not create duplicate calendar events for the same meeting (same company representative && same job description | same company && same date time)
|
|
||||||
@@ -55,8 +55,9 @@ server.registerTool(
|
|||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Fetch unread emails from a Gmail inbox. Returns sender, date, " +
|
"Fetch unread emails from a Gmail inbox. Returns sender, date, " +
|
||||||
"subject, message ID, and body text for each message. The message IDs " +
|
"subject, message ID, and body text for each message. After evaluation, " +
|
||||||
"must be passed to star_emails after evaluation. Specify which account to fetch from.",
|
"pass Category A, C, and D message IDs to star_emails. Never pass " +
|
||||||
|
"Category B IDs to star_emails. Specify which account to fetch from.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
account: accountSchema,
|
account: accountSchema,
|
||||||
maxResults: z
|
maxResults: z
|
||||||
@@ -310,8 +311,9 @@ server.registerTool(
|
|||||||
"star_emails",
|
"star_emails",
|
||||||
{
|
{
|
||||||
description: "Mark reviewed emails with a star and mark them as read in Gmail. " +
|
description: "Mark reviewed emails with a star and mark them as read in Gmail. " +
|
||||||
"Use this for all reviewed messages (Categories A, B, C, and D) " +
|
"Use this for Category A (acknowledgements), C (rejections), and D (other) " +
|
||||||
"to signal they have been processed. Specify which account the emails belong to.",
|
"messages to signal they have been processed. Never use this for Category B " +
|
||||||
|
"(advancement) emails. Specify which account the emails belong to.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
account: accountSchema,
|
account: accountSchema,
|
||||||
messageIds: z
|
messageIds: z
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { MOCK_MESSAGE_LIST, MOCK_MESSAGES, MOCK_EMPTY_LIST } from "../fixtures/m
|
|||||||
const mockGmailList = vi.fn();
|
const mockGmailList = vi.fn();
|
||||||
const mockGmailGet = vi.fn();
|
const mockGmailGet = vi.fn();
|
||||||
const mockGmailTrash = vi.fn();
|
const mockGmailTrash = vi.fn();
|
||||||
|
const mockGmailBatchModify = vi.fn();
|
||||||
let tmpDir: string;
|
let tmpDir: string;
|
||||||
|
|
||||||
vi.mock("../../src/loaders/prompt-config-loaders.js", () => {
|
vi.mock("../../src/loaders/prompt-config-loaders.js", () => {
|
||||||
@@ -45,6 +46,7 @@ vi.mock("../../src/loaders/prompt-config-loaders.js", () => {
|
|||||||
list: mockGmailList,
|
list: mockGmailList,
|
||||||
get: mockGmailGet,
|
get: mockGmailGet,
|
||||||
trash: mockGmailTrash,
|
trash: mockGmailTrash,
|
||||||
|
batchModify: mockGmailBatchModify,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -207,6 +209,51 @@ describe("Phase 1: Email Review Workflow", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ----- Step 2b: Star A, C, and D emails -----
|
||||||
|
describe("star_emails", () => {
|
||||||
|
it("stars and marks read the specified message IDs", async () => {
|
||||||
|
mockGmailBatchModify.mockResolvedValue({});
|
||||||
|
|
||||||
|
const text = await callTool("star_emails", {
|
||||||
|
account: "work",
|
||||||
|
messageIds: ["msg-cat-a-001", "msg-cat-c-001", "msg-cat-d-001"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockGmailBatchModify).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockGmailBatchModify).toHaveBeenCalledWith({
|
||||||
|
userId: "me",
|
||||||
|
requestBody: {
|
||||||
|
ids: ["msg-cat-a-001", "msg-cat-c-001", "msg-cat-d-001"],
|
||||||
|
addLabelIds: ["STARRED"],
|
||||||
|
removeLabelIds: ["UNREAD"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(text).toContain("Successfully starred and marked read 3 email(s).");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns early when no message IDs are provided", async () => {
|
||||||
|
const text = await callTool("star_emails", {
|
||||||
|
account: "work",
|
||||||
|
messageIds: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockGmailBatchModify).not.toHaveBeenCalled();
|
||||||
|
expect(text).toBe("No message IDs provided.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles Gmail API errors gracefully", async () => {
|
||||||
|
mockGmailBatchModify.mockRejectedValue(new Error("Quota exceeded"));
|
||||||
|
|
||||||
|
const text = await callTool("star_emails", {
|
||||||
|
account: "work",
|
||||||
|
messageIds: ["msg-cat-a-001"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(text).toContain("Error starring emails");
|
||||||
|
expect(text).toContain("Quota exceeded");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ----- Step 3: Summarize B and D -----
|
// ----- Step 3: Summarize B and D -----
|
||||||
describe("append_to_summary", () => {
|
describe("append_to_summary", () => {
|
||||||
it("creates a summary file and appends entries", async () => {
|
it("creates a summary file and appends entries", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user