iflow-mcp_giuseppe-coco-google-workspace-mcp-server 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .venv
2
+ .env
3
+ __pycache__
4
+ desktop_client_secrets.json
5
+ token.json
3063_process.log ADDED
@@ -0,0 +1,2 @@
1
+ [2026-02-05 02:49:06] [INFO] 步骤2: 阅读代码完成
2
+ [2026-02-05 02:54:12] [INFO] 步骤3: 本地测试完成,共12个工具
LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Giuseppe Coco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
PKG-INFO ADDED
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: iflow-mcp_giuseppe-coco-google-workspace-mcp-server
3
+ Version: 1.0.0
4
+ Summary: A Model Context Protocol (MCP) server for Google Workspace
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: google-api-python-client
8
+ Requires-Dist: google-auth-oauthlib
9
+ Requires-Dist: mcp[cli]
10
+ Description-Content-Type: text/markdown
11
+
12
+ # Google Workspace MCP Server
13
+
14
+ A Model Context Protocol (MCP) server that acts as a secure bridge between your personal Google Workspace account (Gmail, Calendar, etc.) and any MCP-compatible AI client, such as Claude Desktop.
15
+
16
+ ## Features
17
+
18
+ * **Google Calendar**:
19
+ * Effortlessly list and search for events on your primary calendar within a specific date range.
20
+ * Create new events with detailed information like title, description, start, and end times.
21
+ * Update existing events, allowing for partial modifications such as changing the title or time.
22
+ * Delete events directly from your calendar.
23
+ * **Gmail**:
24
+ * Read the content of your most recent email to stay up-to-date.
25
+ * Search for specific emails by their subject line to find important conversations.
26
+ * Compose and send new emails directly from your account.
27
+ * **Google Drive**:
28
+ * Search for files and folders using powerful query strings.
29
+ * Create new Google Docs with a specified title and initial content.
30
+ * Update the entire content of an existing Google Doc.
31
+ * Manage your files by moving them to the bin or deleting them permanently.
32
+
33
+ ## Getting Started
34
+
35
+ Follow these steps to set up the server and run the example AI agent.
36
+
37
+ ### Prerequisites
38
+
39
+ * Python 3.9+ and `uv` (or `pip`).
40
+ * A Google Cloud project with the necessary APIs enabled.
41
+ * Claude Desktop.
42
+
43
+ ### Step 1: Configure your Google Cloud Project
44
+
45
+ You need to authorize this application to access your Google data. This is a one-time setup.
46
+
47
+ 1. **Go to the Google Cloud Console**: [https://console.cloud.google.com/](https://console.cloud.google.com/)
48
+ 2. **Create a new project** (or use an existing one).
49
+ 3. **Enable APIs**:
50
+ * Go to "APIs & Services" -> "Library".
51
+ * Search for and **Enable** the **Gmail API**.
52
+ * Search for and **Enable** the **Google Calendar API**.
53
+ * Search for and **Enable** the **Google Drive API**.
54
+ 4. **Create OAuth Credentials**:
55
+ * Go to "APIs & Services" -> "Credentials".
56
+ * Click "Create Credentials" -> "OAuth client ID".
57
+ * If prompted, configure the "OAuth consent screen". Choose **External** and provide a name for the app. You can skip most other fields for personal use. Add your Google account email as a Test User.
58
+ * For "Application type", select **Desktop app**.
59
+ * Give it a name (e.g., "GSuite MCP Client").
60
+ 5. **Download Credentials**:
61
+ * After creating the client ID, click the "Download JSON" icon.
62
+ * Rename the downloaded file to `client_secrets.json` and place it in the **root directory of this project**.
63
+
64
+ ### Step 2: Install Dependencies
65
+
66
+ Clone this repository and install the required Python packages for both the server and the client.
67
+
68
+ ```bash
69
+ git clone <your-repo-url>
70
+ cd <your-repo-name>
71
+ uv venv # Create a virtual environment
72
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
73
+ # Install server dependencies
74
+ uv install -r requirements.txt
75
+ ```
76
+
77
+ ### Step 3: Run the One-Time Authorization
78
+
79
+ Before you can run the server, you need to authorize it with your Google account. Run the `get_credentials.py` script from your terminal:
80
+
81
+ ```bash
82
+ python get_credentials.py
83
+ ```
84
+
85
+ * This will open a browser window.
86
+ * Log in with your Google account and grant the requested permissions.
87
+ * After you approve, the script will automatically create a `token.json` file in the project directory. This file stores your authorization tokens so you don't have to log in every time.
88
+
89
+ ### Step 4: Set Up and Run the AI Agent Client
90
+
91
+ As an example, I'll show you how to configure Claude Desktop as an MCP Client. However, you can use whatever MCP Client available on Internet.
92
+
93
+ 1. **Configure Claude Desktop**:
94
+
95
+ (Windows) Open `C:\Users\<user>\AppData\Roaming\Claude\claude_desktop_config.json` and add
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "GsuiteMCPServer": {
100
+ "command": "absolute-path-to-your-python-executable-in-virtual-environment",
101
+ "args": [
102
+ "<mcp_server.py-abs-path>"
103
+ ]
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ 2. **Use the available tools**:
110
+ Ask Claude something like:
111
+
112
+ - Create a Google Calendar Event based on the content of the last mail being sent to my inbox.
113
+ If you cannot create an event, create a sort of 'reminder event' in order to remind me to check that email.
114
+
115
+ - Create a Google Docs drafting a trip plan in San Francisco.
116
+
117
+ - Check what are my availabilities next week for a two-hours call with a customer.
118
+
119
+ - Edit the start time of the meeting with the VCs to 10 A.M.
120
+
121
+ - Search for new e-mails in my inbox talking about AI news.
122
+
123
+ - Send an email to my supplier telling him he's late and I need the next lot as soon as possible.
124
+
125
+ - Much more.
126
+
127
+
128
+ ## Roadmap & Future Plans
129
+
130
+ This server is the foundation for a much larger vision. The goal is to provide a comprehensive MCP server for the entire Google Workspace suite. Future additions will include tools for:
131
+
132
+ * 📝 **Google Docs**: More granular document manipulation, such as appending text or reading specific sections instead of overwriting the whole file.
133
+ * 📊 **Google Sheets**: Read data from sheets, append new rows, update cells, and even perform calculations.
134
+ * 📨 New functionalities for **Gmail**.
135
+ * 📅 New functionalities for **Google Calendar**.
136
+ * 🗂️ New functionalities for **Google Drive**.
137
+
138
+ Contributions are welcome!
139
+
140
+ ## Contributing
141
+
142
+ If you'd like to contribute, please feel free to fork the repository and submit a pull request. For major changes, please open an issue first to discuss what you would like to change.
143
+
144
+ ## License
145
+
146
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
README.md ADDED
@@ -0,0 +1,135 @@
1
+ # Google Workspace MCP Server
2
+
3
+ A Model Context Protocol (MCP) server that acts as a secure bridge between your personal Google Workspace account (Gmail, Calendar, etc.) and any MCP-compatible AI client, such as Claude Desktop.
4
+
5
+ ## Features
6
+
7
+ * **Google Calendar**:
8
+ * Effortlessly list and search for events on your primary calendar within a specific date range.
9
+ * Create new events with detailed information like title, description, start, and end times.
10
+ * Update existing events, allowing for partial modifications such as changing the title or time.
11
+ * Delete events directly from your calendar.
12
+ * **Gmail**:
13
+ * Read the content of your most recent email to stay up-to-date.
14
+ * Search for specific emails by their subject line to find important conversations.
15
+ * Compose and send new emails directly from your account.
16
+ * **Google Drive**:
17
+ * Search for files and folders using powerful query strings.
18
+ * Create new Google Docs with a specified title and initial content.
19
+ * Update the entire content of an existing Google Doc.
20
+ * Manage your files by moving them to the bin or deleting them permanently.
21
+
22
+ ## Getting Started
23
+
24
+ Follow these steps to set up the server and run the example AI agent.
25
+
26
+ ### Prerequisites
27
+
28
+ * Python 3.9+ and `uv` (or `pip`).
29
+ * A Google Cloud project with the necessary APIs enabled.
30
+ * Claude Desktop.
31
+
32
+ ### Step 1: Configure your Google Cloud Project
33
+
34
+ You need to authorize this application to access your Google data. This is a one-time setup.
35
+
36
+ 1. **Go to the Google Cloud Console**: [https://console.cloud.google.com/](https://console.cloud.google.com/)
37
+ 2. **Create a new project** (or use an existing one).
38
+ 3. **Enable APIs**:
39
+ * Go to "APIs & Services" -> "Library".
40
+ * Search for and **Enable** the **Gmail API**.
41
+ * Search for and **Enable** the **Google Calendar API**.
42
+ * Search for and **Enable** the **Google Drive API**.
43
+ 4. **Create OAuth Credentials**:
44
+ * Go to "APIs & Services" -> "Credentials".
45
+ * Click "Create Credentials" -> "OAuth client ID".
46
+ * If prompted, configure the "OAuth consent screen". Choose **External** and provide a name for the app. You can skip most other fields for personal use. Add your Google account email as a Test User.
47
+ * For "Application type", select **Desktop app**.
48
+ * Give it a name (e.g., "GSuite MCP Client").
49
+ 5. **Download Credentials**:
50
+ * After creating the client ID, click the "Download JSON" icon.
51
+ * Rename the downloaded file to `client_secrets.json` and place it in the **root directory of this project**.
52
+
53
+ ### Step 2: Install Dependencies
54
+
55
+ Clone this repository and install the required Python packages for both the server and the client.
56
+
57
+ ```bash
58
+ git clone <your-repo-url>
59
+ cd <your-repo-name>
60
+ uv venv # Create a virtual environment
61
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
62
+ # Install server dependencies
63
+ uv install -r requirements.txt
64
+ ```
65
+
66
+ ### Step 3: Run the One-Time Authorization
67
+
68
+ Before you can run the server, you need to authorize it with your Google account. Run the `get_credentials.py` script from your terminal:
69
+
70
+ ```bash
71
+ python get_credentials.py
72
+ ```
73
+
74
+ * This will open a browser window.
75
+ * Log in with your Google account and grant the requested permissions.
76
+ * After you approve, the script will automatically create a `token.json` file in the project directory. This file stores your authorization tokens so you don't have to log in every time.
77
+
78
+ ### Step 4: Set Up and Run the AI Agent Client
79
+
80
+ As an example, I'll show you how to configure Claude Desktop as an MCP Client. However, you can use whatever MCP Client available on Internet.
81
+
82
+ 1. **Configure Claude Desktop**:
83
+
84
+ (Windows) Open `C:\Users\<user>\AppData\Roaming\Claude\claude_desktop_config.json` and add
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "GsuiteMCPServer": {
89
+ "command": "absolute-path-to-your-python-executable-in-virtual-environment",
90
+ "args": [
91
+ "<mcp_server.py-abs-path>"
92
+ ]
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ 2. **Use the available tools**:
99
+ Ask Claude something like:
100
+
101
+ - Create a Google Calendar Event based on the content of the last mail being sent to my inbox.
102
+ If you cannot create an event, create a sort of 'reminder event' in order to remind me to check that email.
103
+
104
+ - Create a Google Docs drafting a trip plan in San Francisco.
105
+
106
+ - Check what are my availabilities next week for a two-hours call with a customer.
107
+
108
+ - Edit the start time of the meeting with the VCs to 10 A.M.
109
+
110
+ - Search for new e-mails in my inbox talking about AI news.
111
+
112
+ - Send an email to my supplier telling him he's late and I need the next lot as soon as possible.
113
+
114
+ - Much more.
115
+
116
+
117
+ ## Roadmap & Future Plans
118
+
119
+ This server is the foundation for a much larger vision. The goal is to provide a comprehensive MCP server for the entire Google Workspace suite. Future additions will include tools for:
120
+
121
+ * 📝 **Google Docs**: More granular document manipulation, such as appending text or reading specific sections instead of overwriting the whole file.
122
+ * 📊 **Google Sheets**: Read data from sheets, append new rows, update cells, and even perform calculations.
123
+ * 📨 New functionalities for **Gmail**.
124
+ * 📅 New functionalities for **Google Calendar**.
125
+ * 🗂️ New functionalities for **Google Drive**.
126
+
127
+ Contributions are welcome!
128
+
129
+ ## Contributing
130
+
131
+ If you'd like to contribute, please feel free to fork the repository and submit a pull request. For major changes, please open an issue first to discuss what you would like to change.
132
+
133
+ ## License
134
+
135
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
config.py ADDED
@@ -0,0 +1,13 @@
1
+ import os
2
+
3
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
4
+ TOKEN_PATH = os.path.join(SCRIPT_DIR, "token.json")
5
+ CLIENT_SECRETS_PATH = os.path.join(SCRIPT_DIR, "desktop_client_secrets.json")
6
+
7
+ # --- IMPORTANT --- After updating this, you MUST delete your old token.json and re-run get_credentials.py
8
+ SCOPES = [
9
+ 'https://www.googleapis.com/auth/gmail.readonly',
10
+ 'https://www.googleapis.com/auth/gmail.send',
11
+ 'https://www.googleapis.com/auth/calendar.events',
12
+ 'https://www.googleapis.com/auth/drive',
13
+ ]
get_credentials.py ADDED
@@ -0,0 +1,29 @@
1
+ import os
2
+ import google.oauth2.credentials
3
+ from google.auth.transport.requests import Request
4
+ from google_auth_oauthlib.flow import InstalledAppFlow
5
+ from config import TOKEN_PATH, SCOPES, CLIENT_SECRETS_PATH
6
+
7
+ def main():
8
+ creds = None
9
+ if os.path.exists(TOKEN_PATH):
10
+ print(f"Token file '{TOKEN_PATH}' already exists. Loading credentials.")
11
+ creds = google.oauth2.credentials.Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
12
+
13
+ if not creds or not creds.valid:
14
+ if creds and creds.expired and creds.refresh_token:
15
+ print("Credentials have expired. Refreshing...")
16
+ creds.refresh(Request())
17
+ else:
18
+ print("No valid credentials found. Starting authorization flow...")
19
+ flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_PATH, SCOPES)
20
+ creds = flow.run_local_server(port=0, prompt='consent')
21
+
22
+ with open(TOKEN_PATH, 'w') as token_file:
23
+ token_file.write(creds.to_json())
24
+ print(f"Credentials successfully saved to '{TOKEN_PATH}'")
25
+ else:
26
+ print("Credentials are valid and ready to use.")
27
+
28
+ if __name__ == '__main__':
29
+ main()
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: iflow-mcp_giuseppe-coco-google-workspace-mcp-server
3
+ Version: 1.0.0
4
+ Summary: A Model Context Protocol (MCP) server for Google Workspace
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: google-api-python-client
8
+ Requires-Dist: google-auth-oauthlib
9
+ Requires-Dist: mcp[cli]
10
+ Description-Content-Type: text/markdown
11
+
12
+ # Google Workspace MCP Server
13
+
14
+ A Model Context Protocol (MCP) server that acts as a secure bridge between your personal Google Workspace account (Gmail, Calendar, etc.) and any MCP-compatible AI client, such as Claude Desktop.
15
+
16
+ ## Features
17
+
18
+ * **Google Calendar**:
19
+ * Effortlessly list and search for events on your primary calendar within a specific date range.
20
+ * Create new events with detailed information like title, description, start, and end times.
21
+ * Update existing events, allowing for partial modifications such as changing the title or time.
22
+ * Delete events directly from your calendar.
23
+ * **Gmail**:
24
+ * Read the content of your most recent email to stay up-to-date.
25
+ * Search for specific emails by their subject line to find important conversations.
26
+ * Compose and send new emails directly from your account.
27
+ * **Google Drive**:
28
+ * Search for files and folders using powerful query strings.
29
+ * Create new Google Docs with a specified title and initial content.
30
+ * Update the entire content of an existing Google Doc.
31
+ * Manage your files by moving them to the bin or deleting them permanently.
32
+
33
+ ## Getting Started
34
+
35
+ Follow these steps to set up the server and run the example AI agent.
36
+
37
+ ### Prerequisites
38
+
39
+ * Python 3.9+ and `uv` (or `pip`).
40
+ * A Google Cloud project with the necessary APIs enabled.
41
+ * Claude Desktop.
42
+
43
+ ### Step 1: Configure your Google Cloud Project
44
+
45
+ You need to authorize this application to access your Google data. This is a one-time setup.
46
+
47
+ 1. **Go to the Google Cloud Console**: [https://console.cloud.google.com/](https://console.cloud.google.com/)
48
+ 2. **Create a new project** (or use an existing one).
49
+ 3. **Enable APIs**:
50
+ * Go to "APIs & Services" -> "Library".
51
+ * Search for and **Enable** the **Gmail API**.
52
+ * Search for and **Enable** the **Google Calendar API**.
53
+ * Search for and **Enable** the **Google Drive API**.
54
+ 4. **Create OAuth Credentials**:
55
+ * Go to "APIs & Services" -> "Credentials".
56
+ * Click "Create Credentials" -> "OAuth client ID".
57
+ * If prompted, configure the "OAuth consent screen". Choose **External** and provide a name for the app. You can skip most other fields for personal use. Add your Google account email as a Test User.
58
+ * For "Application type", select **Desktop app**.
59
+ * Give it a name (e.g., "GSuite MCP Client").
60
+ 5. **Download Credentials**:
61
+ * After creating the client ID, click the "Download JSON" icon.
62
+ * Rename the downloaded file to `client_secrets.json` and place it in the **root directory of this project**.
63
+
64
+ ### Step 2: Install Dependencies
65
+
66
+ Clone this repository and install the required Python packages for both the server and the client.
67
+
68
+ ```bash
69
+ git clone <your-repo-url>
70
+ cd <your-repo-name>
71
+ uv venv # Create a virtual environment
72
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
73
+ # Install server dependencies
74
+ uv install -r requirements.txt
75
+ ```
76
+
77
+ ### Step 3: Run the One-Time Authorization
78
+
79
+ Before you can run the server, you need to authorize it with your Google account. Run the `get_credentials.py` script from your terminal:
80
+
81
+ ```bash
82
+ python get_credentials.py
83
+ ```
84
+
85
+ * This will open a browser window.
86
+ * Log in with your Google account and grant the requested permissions.
87
+ * After you approve, the script will automatically create a `token.json` file in the project directory. This file stores your authorization tokens so you don't have to log in every time.
88
+
89
+ ### Step 4: Set Up and Run the AI Agent Client
90
+
91
+ As an example, I'll show you how to configure Claude Desktop as an MCP Client. However, you can use whatever MCP Client available on Internet.
92
+
93
+ 1. **Configure Claude Desktop**:
94
+
95
+ (Windows) Open `C:\Users\<user>\AppData\Roaming\Claude\claude_desktop_config.json` and add
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "GsuiteMCPServer": {
100
+ "command": "absolute-path-to-your-python-executable-in-virtual-environment",
101
+ "args": [
102
+ "<mcp_server.py-abs-path>"
103
+ ]
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ 2. **Use the available tools**:
110
+ Ask Claude something like:
111
+
112
+ - Create a Google Calendar Event based on the content of the last mail being sent to my inbox.
113
+ If you cannot create an event, create a sort of 'reminder event' in order to remind me to check that email.
114
+
115
+ - Create a Google Docs drafting a trip plan in San Francisco.
116
+
117
+ - Check what are my availabilities next week for a two-hours call with a customer.
118
+
119
+ - Edit the start time of the meeting with the VCs to 10 A.M.
120
+
121
+ - Search for new e-mails in my inbox talking about AI news.
122
+
123
+ - Send an email to my supplier telling him he's late and I need the next lot as soon as possible.
124
+
125
+ - Much more.
126
+
127
+
128
+ ## Roadmap & Future Plans
129
+
130
+ This server is the foundation for a much larger vision. The goal is to provide a comprehensive MCP server for the entire Google Workspace suite. Future additions will include tools for:
131
+
132
+ * 📝 **Google Docs**: More granular document manipulation, such as appending text or reading specific sections instead of overwriting the whole file.
133
+ * 📊 **Google Sheets**: Read data from sheets, append new rows, update cells, and even perform calculations.
134
+ * 📨 New functionalities for **Gmail**.
135
+ * 📅 New functionalities for **Google Calendar**.
136
+ * 🗂️ New functionalities for **Google Drive**.
137
+
138
+ Contributions are welcome!
139
+
140
+ ## Contributing
141
+
142
+ If you'd like to contribute, please feel free to fork the repository and submit a pull request. For major changes, please open an issue first to discuss what you would like to change.
143
+
144
+ ## License
145
+
146
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
@@ -0,0 +1,20 @@
1
+ ./.gitignore,sha256=N9zl_cFCO8DF3sakl3Wo9MJ-HgNThnTgt9k6KtPm_dY,61
2
+ ./3063_process.log,sha256=cla9J25fh_bZNwJZihrpoNeCjey09eAGeicFkNymehU,131
3
+ ./LICENSE,sha256=0dcirjuQAYBlwjKwINfvuhNVqCHTr1nPE5oSqKJtX1o,1070
4
+ ./PKG-INFO,sha256=_4r3qFHYVhxWDr1RHpcFGLhcHwaWpo6O8Mu_pCD3h_U,6136
5
+ ./README.md,sha256=O71qymZU6Qmb7Qh0i8vGJWwKnjff6CmH0Jxsb8t6SlE,5787
6
+ ./config.py,sha256=gJMyjCLsu9_oI2P9How_MHI22D-tsTo8w_-g9t5QG2E,521
7
+ ./get_credentials.py,sha256=eL-c1pnrievABwS2mNdq23W9HTMpA7l5zLAbjfwmIDQ,1179
8
+ ./language.json,sha256=J-z0poNcsv31IHB413--iOY8LoHBKiTHeybHX3abokI,7
9
+ ./mcp_client.py,sha256=QIAQQpqniH5honHovdRZFH05rkDOF0xjCR62OPLSSJI,870
10
+ ./mcp_server.py,sha256=GDF44XGzm_fQ6Fjs1L_WUbO2lbqM28C56NuVVW3OQ48,21843
11
+ ./notes.md,sha256=UlXsfWxZMghW1MBdg8WGiVUQH_gL3aQ1ftGB5PrMPCc,451
12
+ ./package_name,sha256=n8VuDuWtmqJzvyQXNN2q-uxpoHhY1adZJT-0B3Pcbz8,52
13
+ ./push_info.json,sha256=-He-JBVTh2IDLB7j3iwkM5SIIZXmJ06Ga75NxmczA9A,145
14
+ ./pyproject.toml,sha256=AzMm7t5LNfrJYnLYVo0CAJVwXDa_rUlBrMSkogK8LYo,494
15
+ ./requirements.txt,sha256=tn_YxuKZEPPj7_Vxj1IEK842NzjDdxHvINeicIAZk2I,54
16
+ iflow_mcp_giuseppe_coco_google_workspace_mcp_server-1.0.0.dist-info/METADATA,sha256=_4r3qFHYVhxWDr1RHpcFGLhcHwaWpo6O8Mu_pCD3h_U,6136
17
+ iflow_mcp_giuseppe_coco_google_workspace_mcp_server-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
+ iflow_mcp_giuseppe_coco_google_workspace_mcp_server-1.0.0.dist-info/entry_points.txt,sha256=-61zsVKG3AA_beVPNXktcVJ-PSWAFuw_3wSbAfsA-go,60
19
+ iflow_mcp_giuseppe_coco_google_workspace_mcp_server-1.0.0.dist-info/licenses/LICENSE,sha256=0dcirjuQAYBlwjKwINfvuhNVqCHTr1nPE5oSqKJtX1o,1070
20
+ iflow_mcp_giuseppe_coco_google_workspace_mcp_server-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gsuite-mcp-server = mcp_server:server.run
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Giuseppe Coco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
language.json ADDED
@@ -0,0 +1 @@
1
+ python
mcp_client.py ADDED
@@ -0,0 +1,34 @@
1
+ import os
2
+ import asyncio
3
+ from dotenv import load_dotenv
4
+ from langchain_openai import ChatOpenAI
5
+ from mcp_use import MCPAgent, MCPClient
6
+
7
+ async def main():
8
+ load_dotenv()
9
+ config = {
10
+ "mcpServers": {
11
+ "gsuite": {
12
+ "command": "python",
13
+ "args": [f"{os.getenv("MCP_SERVER_ABS_PATH")}"],
14
+ }
15
+ }
16
+ }
17
+
18
+ client = MCPClient.from_dict(config)
19
+
20
+ llm = ChatOpenAI(model="gpt-4o")
21
+
22
+ # Create agent with the client
23
+ agent = MCPAgent(llm=llm, client=client, max_steps=30)
24
+
25
+ result = await agent.run(
26
+ """
27
+ Create a Google Calendar Event based on the content of the last mail being sent to my inbox.
28
+ If you cannot create an event, create a sort of "reminder event" in order to remind me to check that email.
29
+ """,
30
+ )
31
+ print(f"\nResult: {result}")
32
+
33
+ if __name__ == "__main__":
34
+ asyncio.run(main())
mcp_server.py ADDED
@@ -0,0 +1,514 @@
1
+ # mcp_server.py
2
+ import base64
3
+ import os
4
+ import sys
5
+ from typing import Dict, Any, AsyncIterator, Optional, List
6
+ from contextlib import asynccontextmanager
7
+ import io
8
+ from email.message import EmailMessage
9
+
10
+ import google.oauth2.credentials
11
+ import googleapiclient.discovery
12
+ from google.auth.transport.requests import Request
13
+ from googleapiclient.errors import HttpError
14
+ from googleapiclient.http import MediaIoBaseUpload
15
+
16
+ from mcp.server.fastmcp import FastMCP, Context
17
+ from pydantic import BaseModel, Field
18
+
19
+ from config import TOKEN_PATH, SCOPES
20
+
21
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
22
+
23
+ class EventDetails(BaseModel):
24
+ summary: str = Field(description="The title or summary of the calendar event.")
25
+ start_time: str = Field(description="The start time of the event in ISO 8601 format (e.g., '2025-07-05T15:00:00').")
26
+ end_time: str = Field(description="The end time of the event in ISO 8601 format (e.g., '2025-07-05T16:00:00').")
27
+ description: Optional[str] = Field(None, description="A detailed description for the event. Can include notes from the source email.")
28
+
29
+ """
30
+ EventUpdateDetails is for updating an event. When you update an event, you often only want to change one or two things (e.g., just the title,
31
+ or just the end time). If we used the original EventDetails model, the agent would be forced to provide values for all fields, even the ones
32
+ it wasn't changing. By making all fields in EventUpdateDetails Optional, we allow for partial updates. The agent can provide only the fields
33
+ it wants to change, making the tool much more flexible and easier to use.
34
+ """
35
+ class EventUpdateDetails(BaseModel):
36
+ summary: Optional[str] = Field(None, description="The new title for the event.")
37
+ start_time: Optional[str] = Field(None, description="The new start time in ISO 8601 format.")
38
+ end_time: Optional[str] = Field(None, description="The new end time in ISO 8601 format.")
39
+ description: Optional[str] = Field(None, description="The new description for the event.")
40
+
41
+ class EmailContent(BaseModel):
42
+ to: str = Field(description="The recipient's email address.")
43
+ subject: str = Field(description="The subject line of the email.")
44
+ body: str = Field(description="The plain text body of the email.")
45
+
46
+ class ListedEvent(BaseModel):
47
+ id: str = Field(description="The unique ID of the event.")
48
+ summary: str = Field(description="The title of the event.")
49
+ start_time: str = Field(description="The start time of the event in ISO 8601 format.")
50
+ end_time: str = Field(description="The end time of the event in ISO 8601 format.")
51
+
52
+ class ListedDriveFile(BaseModel):
53
+ id: str = Field(description="The unique ID of the file.")
54
+ name: str = Field(description="The name of the file.")
55
+ mime_type: str = Field(description="The MIME type of the file (e.g., 'application/vnd.google-apps.document').")
56
+
57
+
58
+ # --- Lifespan Management for Credentials ---
59
+
60
+ @asynccontextmanager
61
+ async def credential_manager(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
62
+ """
63
+ Manages loading and refreshing Google API credentials on server startup.
64
+ """
65
+ creds = None
66
+ # For testing purposes, allow server to start without real credentials
67
+ if not os.path.exists(TOKEN_PATH):
68
+ print(f"WARNING: Token file '{TOKEN_PATH}' not found. Running in demo mode.", file=sys.stderr)
69
+ print("Please run 'python get_credentials.py' first to authorize the application for full functionality.", file=sys.stderr)
70
+ # Create a mock credential object for testing
71
+ yield {"creds": "mock"}
72
+ return
73
+
74
+ print(f"Loading credentials from {TOKEN_PATH}", file=sys.stderr)
75
+ creds = google.oauth2.credentials.Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
76
+
77
+ if creds and creds.expired and creds.refresh_token:
78
+ print("Credentials expired. Refreshing...", file=sys.stderr)
79
+ try:
80
+ creds.refresh(Request())
81
+ # Re-save the refreshed token
82
+ with open(TOKEN_PATH, 'w') as token_file:
83
+ token_file.write(creds.to_json())
84
+ print("Token refreshed and saved.", file=sys.stderr)
85
+ except Exception as e:
86
+ print(f"ERROR: Failed to refresh token: {e}", file=sys.stderr)
87
+ creds = None # Mark credentials as invalid
88
+
89
+ # Make credentials available to all tool handlers via context
90
+ yield {"creds": creds}
91
+ print("Server shutting down.", file=sys.stderr)
92
+
93
+ # Initialize the server with the lifespan manager
94
+ server = FastMCP(
95
+ "GsuiteMCPServer",
96
+ lifespan=credential_manager
97
+ )
98
+
99
+ def get_creds_from_context(ctx: Context) -> google.oauth2.credentials.Credentials:
100
+ """Helper to get credentials from the context and handle errors."""
101
+ creds = ctx.request_context.lifespan_context.get("creds")
102
+ if not creds or (creds != "mock" and not creds.valid):
103
+ raise Exception(
104
+ "Google API credentials are not available or invalid. "
105
+ "Please run 'python get_credentials.py' to authenticate."
106
+ )
107
+ return creds
108
+
109
+ def get_email_body(payload: Dict[str, Any]) -> Optional[str]:
110
+ """Recursively finds the 'text/plain' part of an email."""
111
+ if 'parts' in payload:
112
+ for part in payload['parts']:
113
+ if part['mimeType'] == 'text/plain' and 'data' in part['body']:
114
+ return base64.urlsafe_b64decode(part['body']['data']).decode('utf-8')
115
+ # Recurse to check nested parts
116
+ body = get_email_body(part)
117
+ if body:
118
+ return body
119
+ elif payload.get('mimeType') == 'text/plain' and 'data' in payload.get('body', {}):
120
+ return base64.urlsafe_b64decode(payload['body']['data']).decode('utf-8')
121
+ return None
122
+
123
+ # --- GMAIL TOOLS ---
124
+
125
+ @server.tool()
126
+ def read_latest_gmail_email(ctx: Context) -> Dict[str, str]:
127
+ """Reads the most recent email from the user's Gmail inbox."""
128
+ creds = get_creds_from_context(ctx)
129
+ try:
130
+ gmail_service = googleapiclient.discovery.build('gmail', 'v1', credentials=creds)
131
+ messages_list = gmail_service.users().messages().list(userId='me', maxResults=1).execute()
132
+
133
+ if not messages_list.get('messages'):
134
+ raise Exception("No emails found.")
135
+
136
+ msg_id = messages_list['messages'][0]['id']
137
+ message = gmail_service.users().messages().get(userId='me', id=msg_id, format='full').execute()
138
+
139
+ email_body = get_email_body(message['payload'])
140
+ if not email_body:
141
+ email_body = "Could not find the body in the last email."
142
+ # raise Exception("Could not find the body in the last email.")
143
+
144
+ return {'snippet': message.get('snippet', ''), 'body': email_body}
145
+ except HttpError as e:
146
+ raise Exception(f"API Gmail error (HTTP {e.status_code}): {e.reason}")
147
+ except Exception as e:
148
+ if creds == "mock":
149
+ # Return mock data for testing
150
+ return {
151
+ 'snippet': 'This is a demo email snippet...',
152
+ 'body': 'This is a demo email body for testing purposes without real Google API credentials.'
153
+ }
154
+ raise
155
+
156
+ @server.tool()
157
+ def read_email_by_subject(subject: str, ctx: Context) -> List[Dict[str, str]]:
158
+ """
159
+ Searches for emails by subject and returns the body and snippet of the most recent matches.
160
+
161
+ Args:
162
+ subject: The subject line to search for.
163
+ """
164
+ creds = get_creds_from_context(ctx)
165
+ try:
166
+ gmail_service = googleapiclient.discovery.build('gmail', 'v1', credentials=creds)
167
+ # Search for messages with the given subject, get the most recent 5
168
+ results = gmail_service.users().messages().list(userId='me', q=f'subject:"{subject}"', maxResults=5).execute()
169
+ messages = results.get('messages', [])
170
+
171
+ if not messages:
172
+ return [{"status": f"No emails found with subject: '{subject}'"}]
173
+
174
+ emails = []
175
+ for msg_info in messages:
176
+ msg = gmail_service.users().messages().get(userId='me', id=msg_info['id'], format='full').execute()
177
+ body = get_email_body(msg['payload']) or "Could not extract plain text body."
178
+ emails.append({'id': msg['id'], 'snippet': msg.get('snippet', ''), 'body': body})
179
+ return emails
180
+ except HttpError as e:
181
+ raise Exception(f"API Gmail error (HTTP {e.status_code}): {e.reason}")
182
+ except Exception as e:
183
+ if creds == "mock":
184
+ return [{
185
+ "id": "demo_email_123",
186
+ "snippet": f"Demo email with subject: {subject}",
187
+ "body": f"This is a demo email body for testing purposes. Subject matched: {subject}"
188
+ }]
189
+ raise
190
+
191
+ @server.tool()
192
+ def send_email(email_content: EmailContent, ctx: Context) -> Dict[str, str]:
193
+ """
194
+ Sends an email from the user's Gmail account.
195
+
196
+ Args:
197
+ email_content: A structured object containing the recipient, subject, and body.
198
+ """
199
+ creds = get_creds_from_context(ctx)
200
+ try:
201
+ gmail_service = googleapiclient.discovery.build('gmail', 'v1', credentials=creds)
202
+ message = EmailMessage()
203
+ message.set_content(email_content.body)
204
+ message['To'] = email_content.to
205
+ message['Subject'] = email_content.subject
206
+
207
+ encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
208
+ create_message = {'raw': encoded_message}
209
+
210
+ send_message = gmail_service.users().messages().send(userId="me", body=create_message).execute()
211
+ return {"status": "Email sent successfully", "messageId": send_message['id']}
212
+ except HttpError as e:
213
+ raise Exception(f"API Gmail error (HTTP {e.status_code}): {e.reason}")
214
+ except Exception as e:
215
+ if creds == "mock":
216
+ return {"status": "Email sent successfully (demo mode)", "messageId": "demo_msg_123"}
217
+ raise
218
+
219
+ # --- CALENDAR TOOLS ---
220
+
221
+ @server.tool()
222
+ def list_calendar_events(ctx: Context, start_time: str, end_time: str, query: Optional[str] = None) -> List[ListedEvent]:
223
+ """
224
+ Lists calendar events within a specified time range, optionally filtering by a search query.
225
+
226
+ Args:
227
+ start_time: The start of the time range in ISO 8601 format (e.g., '2025-07-05T00:00:00Z').
228
+ end_time: The end of the time range in ISO 8601 format (e.g., '2025-07-06T00:00:00Z').
229
+ query: An optional text query to filter events by (e.g., 'meeting').
230
+ """
231
+ creds = get_creds_from_context(ctx)
232
+ try:
233
+ calendar_service = googleapiclient.discovery.build('calendar', 'v3', credentials=creds)
234
+ events_result = calendar_service.events().list(
235
+ calendarId='primary',
236
+ timeMin=start_time,
237
+ timeMax=end_time,
238
+ q=query, # TODO: study it
239
+ maxResults=20, # Limit results to a reasonable number
240
+ singleEvents=True,
241
+ orderBy='startTime'
242
+ ).execute()
243
+
244
+ events = events_result.get('items', [])
245
+ if not events:
246
+ return []
247
+
248
+ listed_events = []
249
+ for event in events:
250
+ # Handle all-day events which have 'date' instead of 'dateTime'
251
+ start = event['start'].get('dateTime', event['start'].get('date'))
252
+ end = event['end'].get('dateTime', event['end'].get('date'))
253
+ listed_events.append(
254
+ ListedEvent(
255
+ id=event['id'],
256
+ summary=event.get('summary', 'No Title'),
257
+ start_time=start,
258
+ end_time=end
259
+ )
260
+ )
261
+ return listed_events
262
+ except HttpError as e:
263
+ raise Exception(f"API Calendar error (HTTP {e.status_code}): {e.reason}")
264
+ except Exception as e:
265
+ if creds == "mock":
266
+ return [
267
+ ListedEvent(
268
+ id="demo_event_1",
269
+ summary="Demo Meeting",
270
+ start_time=start_time,
271
+ end_time=end_time
272
+ )
273
+ ]
274
+ raise
275
+
276
+ @server.tool()
277
+ def create_calendar_event(event_details: EventDetails, ctx: Context) -> Dict[str, Any]:
278
+ """
279
+ Creates a Google Calendar event from structured event details.
280
+
281
+ Args:
282
+ event_details: A structured object containing the summary, start time, end time, and description.
283
+ """
284
+ creds = get_creds_from_context(ctx)
285
+ event_body = {
286
+ 'summary': event_details.summary,
287
+ 'description': event_details.description or f'Created from an email automation.',
288
+ 'start': {'dateTime': event_details.start_time, 'timeZone': 'Europe/Rome'},
289
+ 'end': {'dateTime': event_details.end_time, 'timeZone': 'Europe/Rome'},
290
+ }
291
+
292
+ try:
293
+ calendar_service = googleapiclient.discovery.build('calendar', 'v3', credentials=creds)
294
+ created_event = calendar_service.events().insert(calendarId='primary', body=event_body).execute()
295
+ return created_event
296
+ except HttpError as e:
297
+ raise Exception(f"API Calendar error (HTTP {e.status_code}): {e.reason}")
298
+ except Exception as e:
299
+ if creds == "mock":
300
+ return {
301
+ 'id': 'demo_event_123',
302
+ 'summary': event_details.summary,
303
+ 'start': {'dateTime': event_details.start_time},
304
+ 'end': {'dateTime': event_details.end_time}
305
+ }
306
+ raise
307
+
308
+ @server.tool()
309
+ def delete_calendar_event(event_id: str, ctx: Context) -> Dict[str, str]:
310
+ """
311
+ Deletes a calendar event by its ID. To get an event ID, first list or search for events.
312
+
313
+ Args:
314
+ event_id: The unique ID of the event to delete.
315
+ """
316
+ creds = get_creds_from_context(ctx)
317
+ try:
318
+ calendar_service = googleapiclient.discovery.build('calendar', 'v3', credentials=creds)
319
+ calendar_service.events().delete(calendarId='primary', eventId=event_id).execute()
320
+ return {"status": "Event deleted successfully"}
321
+ except HttpError as e:
322
+ raise Exception(f"API Calendar error (HTTP {e.status_code}): {e.reason}")
323
+ except Exception as e:
324
+ if creds == "mock":
325
+ return {"status": "Event deleted successfully (demo mode)"}
326
+ raise
327
+
328
+ @server.tool()
329
+ def update_calendar_event(event_id: str, update_details: EventUpdateDetails, ctx: Context) -> Dict[str, Any]:
330
+ """
331
+ Updates an existing calendar event by its ID. Only provided fields will be updated.
332
+
333
+ Args:
334
+ event_id: The ID of the event to update.
335
+ update_details: A structured object with the fields to update.
336
+ """
337
+ creds = get_creds_from_context(ctx)
338
+ try:
339
+ calendar_service = googleapiclient.discovery.build('calendar', 'v3', credentials=creds)
340
+ # First, get the existing event to ensure it exists and to merge updates
341
+ event = calendar_service.events().get(calendarId='primary', eventId=event_id).execute()
342
+
343
+ # Create the update body with only the fields that are provided
344
+ update_body = update_details.model_dump(exclude_unset=True)
345
+ if 'start_time' in update_body:
346
+ event['start']['dateTime'] = update_body['start_time']
347
+ if 'end_time' in update_body:
348
+ event['end']['dateTime'] = update_body['end_time']
349
+ if 'summary' in update_body:
350
+ event['summary'] = update_body['summary']
351
+ if 'description' in update_body:
352
+ event['description'] = update_body['description']
353
+
354
+ updated_event = calendar_service.events().update(calendarId='primary', eventId=event['id'], body=event).execute()
355
+ return updated_event
356
+ except HttpError as e:
357
+ raise Exception(f"API Calendar error (HTTP {e.status_code}): {e.reason}")
358
+ except Exception as e:
359
+ if creds == "mock":
360
+ return {
361
+ 'id': event_id,
362
+ 'summary': update_details.summary or 'Updated Demo Event',
363
+ 'start': {'dateTime': update_details.start_time or '2025-07-05T10:00:00'},
364
+ 'end': {'dateTime': update_details.end_time or '2025-07-05T11:00:00'}
365
+ }
366
+ raise
367
+
368
+ # --- GOOGLE DRIVE TOOLS ---
369
+
370
+ @server.tool()
371
+ def list_drive_files(query: str, ctx: Context) -> List[ListedDriveFile]:
372
+ """
373
+ Searches for files in Google Drive using a query string.
374
+
375
+ Args:
376
+ query: The search query. Examples: "name contains 'report'", "mimeType='application/vnd.google-apps.spreadsheet'".
377
+ See Google Drive API docs for full query syntax.
378
+ """
379
+ creds = get_creds_from_context(ctx)
380
+ try:
381
+ drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=creds)
382
+
383
+ # TODO: Considering adding https://developers.google.com/workspace/drive/api/guides/search-files as a guide for q parameter
384
+ results = drive_service.files().list(
385
+ q=query,
386
+ pageSize=20, # Limit results
387
+ fields="nextPageToken, files(id, name, mimeType)"
388
+ ).execute()
389
+
390
+ files = results.get('files', [])
391
+ if not files:
392
+ return []
393
+
394
+ return [
395
+ ListedDriveFile(
396
+ id=file['id'],
397
+ name=file['name'],
398
+ mime_type=file['mimeType']
399
+ ) for file in files
400
+ ]
401
+ except HttpError as e:
402
+ raise Exception(f"API Drive error (HTTP {e.status_code}): {e.reason}")
403
+ except Exception as e:
404
+ if creds == "mock":
405
+ return [
406
+ ListedDriveFile(
407
+ id="demo_file_1",
408
+ name="Demo Document.docx",
409
+ mime_type="application/vnd.google-apps.document"
410
+ )
411
+ ]
412
+ raise
413
+
414
+ @server.tool()
415
+ def create_drive_document(ctx: Context, title: str, content: Optional[str] = "") -> Dict[str, str]:
416
+ """
417
+ Creates a new Google Document in the user's Drive with the given title and content.
418
+
419
+ Args:
420
+ title: The title of the new document.
421
+ content: The initial text content for the document.
422
+ """
423
+ creds = get_creds_from_context(ctx)
424
+ try:
425
+ drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=creds)
426
+ file_metadata = {
427
+ 'name': title,
428
+ 'mimeType': 'application/vnd.google-apps.document'
429
+ }
430
+
431
+ media = MediaIoBaseUpload(io.BytesIO((content or "").encode()), mimetype='text/plain', resumable=True)
432
+ file = drive_service.files().create(body=file_metadata, media_body=media, fields='id,name,webViewLink').execute()
433
+ return {"status": "Document created", "id": file['id'], "name": file['name'], "link": file['webViewLink']}
434
+ except HttpError as e:
435
+ raise Exception(f"API Drive error (HTTP {e.status_code}): {e.reason}")
436
+ except Exception as e:
437
+ if creds == "mock":
438
+ return {
439
+ "status": "Document created (demo mode)",
440
+ "id": "demo_doc_123",
441
+ "name": title,
442
+ "link": "https://docs.google.com/document/d/demo123"
443
+ }
444
+ raise
445
+
446
+ @server.tool()
447
+ def update_drive_document(file_id: str, content: str, ctx: Context) -> Dict[str, str]:
448
+ """
449
+ Overwrites the content of an existing Google Document.
450
+
451
+ Args:
452
+ file_id: The ID of the document to update.
453
+ content: The new text content to write to the document.
454
+ """
455
+ creds = get_creds_from_context(ctx)
456
+ try:
457
+ drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=creds)
458
+ media = MediaIoBaseUpload(io.BytesIO(content.encode()), mimetype='text/plain', resumable=True)
459
+ updated_file = drive_service.files().update(fileId=file_id, media_body=media, fields='id,name').execute()
460
+ return {"status": "Document updated", "id": updated_file['id'], "name": updated_file['name']}
461
+ except HttpError as e:
462
+ raise Exception(f"API Drive error (HTTP {e.status_code}): {e.reason}")
463
+ except Exception as e:
464
+ if creds == "mock":
465
+ return {"status": "Document updated (demo mode)", "id": file_id, "name": "Demo Document"}
466
+ raise
467
+
468
+ # Dangerous. Use with caution.
469
+ @server.tool()
470
+ def delete_drive_file(file_id: str, ctx: Context) -> Dict[str, str]:
471
+ """
472
+ Permanently deletes a file from Google Drive. This action cannot be undone.
473
+
474
+ Args:
475
+ file_id: The ID of the file to delete.
476
+ """
477
+ creds = get_creds_from_context(ctx)
478
+ try:
479
+ drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=creds)
480
+ drive_service.files().delete(fileId=file_id).execute()
481
+ return {"status": f"File with ID '{file_id}' deleted successfully."}
482
+ except HttpError as e:
483
+ raise Exception(f"API Drive error (HTTP {e.status_code}): {e.reason}")
484
+ except Exception as e:
485
+ if creds == "mock":
486
+ return {"status": f"File with ID '{file_id}' deleted successfully (demo mode)"}
487
+ raise
488
+
489
+ @server.tool()
490
+ def move_drive_file_to_bin(file_id: str, ctx: Context) -> Dict[str, str]:
491
+ """
492
+ Moves a file to the Google Drive bin (trash). The file can be restored from the bin later.
493
+
494
+ Args:
495
+ file_id: The ID of the file to move to the bin.
496
+ """
497
+ creds = get_creds_from_context(ctx)
498
+ try:
499
+ drive_service = googleapiclient.discovery.build('drive', 'v3', credentials=creds)
500
+
501
+ # To move a file to the bin, we update its metadata to set 'trashed' to True.
502
+ body = {'trashed': True}
503
+ drive_service.files().update(fileId=file_id, body=body).execute()
504
+
505
+ return {"status": f"File with ID '{file_id}' moved to bin successfully."}
506
+ except HttpError as e:
507
+ raise Exception(f"API Drive error (HTTP {e.status_code}): {e.reason}")
508
+ except Exception as e:
509
+ if creds == "mock":
510
+ return {"status": f"File with ID '{file_id}' moved to bin successfully (demo mode)"}
511
+ raise
512
+
513
+ if __name__ == "__main__":
514
+ server.run()
notes.md ADDED
@@ -0,0 +1,25 @@
1
+ ## Tests
2
+
3
+ - list_calendar_events: ✅
4
+
5
+ - list_drive_files: ✅
6
+
7
+ - read_latest_gmail_email: ✅
8
+
9
+ - create_calendar_event: ✅
10
+
11
+ - read_email_by_subject: ✅
12
+
13
+ - send_email: ✅
14
+
15
+ - delete_calendar_event: ✅
16
+
17
+ - update_calendar_event: ✅
18
+
19
+ - create_drive_document: (creates only google docs) ✅
20
+
21
+ - update_drive_document: (overwriting only, no append mode nor edit mode like in Google Docs App) ✅
22
+
23
+ - delete_drive_file: ✅
24
+
25
+ - move_drive_file_to_bin: ✅
package_name ADDED
@@ -0,0 +1 @@
1
+ iflow-mcp_giuseppe-coco-google-workspace-mcp-server
push_info.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "push_platform": "github",
3
+ "fork_url": "https://github.com/iflow-mcp/giuseppe-coco-google-workspace-mcp-server",
4
+ "fork_branch": "iflow"
5
+ }
pyproject.toml ADDED
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "iflow-mcp_giuseppe-coco-google-workspace-mcp-server"
3
+ version = "1.0.0"
4
+ description = "A Model Context Protocol (MCP) server for Google Workspace"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ dependencies = [
8
+ "mcp[cli]",
9
+ "google-api-python-client",
10
+ "google-auth-oauthlib",
11
+ ]
12
+
13
+ [project.scripts]
14
+ gsuite-mcp-server = "mcp_server:server.run"
15
+
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["."]
requirements.txt ADDED
@@ -0,0 +1,3 @@
1
+ mcp[cli]
2
+ google-api-python-client
3
+ google-auth-oauthlib