Skip to main content
Glama

MCP Server for Things3

� �,Zh;I��H�ddlZddlmZmZmZmZddlZddlZGd�d�Zy)�N)�List�Dict�Any�Optionalc�4�eZdZdZededefd��Zededefd��Zededeee ffd��Z ede eee ffd ��Z ede eee ffd ��Z ede eeeffd ��Zedefd ��Zed edefd��Zedede eee ffd��Zy)�AppleScriptHandlerz9Handles AppleScript execution for Things3 data retrieval.�script�returnc��� tjdd|gddd��}|jj�S#tj$r}t d|����d}~wwxYw)zA Executes an AppleScript and returns its output. � osascriptz-eT)�check�capture_output�textzFailed to execute AppleScript: N)� subprocess�run�stdout�strip�CalledProcessError� RuntimeError)r �result�es �V/Users/drjforrest/dev/github/mcp-things3/src/mcp_server_things3/applescript_handler.py� run_scriptzAppleScriptHandler.run_script si�� F��^�^��d�F�+��#�� �F� �=�=�&�&�(� (���,�,� F��!@���D�E� E�� F�s�58�A� A�Arc��|sy|jdd�}|jdd�}|jdd�}|jdd �}|S) zj Safely escape a string for use in AppleScript by handling quotes and special characters. ��\z\\�"z\"� z\n� z\r)�replace)rs r�safe_string_for_applescriptz.AppleScriptHandler.safe_string_for_applescriptsQ�� ���|�|�D�&�)���|�|�C��'���|�|�D�%�(���|�|�D�%�(��� �� record_strc�F�i}|j�}|jd�r|jd�r|dd}g}d}d}|D]I}|dk(r|dz }n7|dk(r|dz}n,|dk(r'|dk(r"|j|j��d}�E||z }�K|j�r|j|j��|D]_}d|vs�|j dd�\}}|j�jd �}|j�jd �}|||<�a|S) z� Parse an AppleScript record string into a Python dictionary. More robust than string concatenation for JSON. �{�}������rr�,�:z"')r� startswith�endswith�append�split) r#r�parts� current_part� brace_count�char�part�key�values r�parse_applescript_recordz+AppleScriptHandler.parse_applescript_record+s8�� �� �%�%�'� � � � �� %�*�*=�*=�c�*B�#�A�b�)�J���� �� ��D��s�{��q� � �����q� � ������!1�� � �\�/�/�1�2�!� �� �D� �L�� � � � � �L�L��+�+�-� .��D��d�{�!�Z�Z��Q�/� ��U��i�i�k�'�'��.��� � � �+�+�E�2��#��s� � �� r"c ��d} tj|�}|r |dk(s|dk(rgSg}|jd�r�|jd�r�|dd}t j d|�}|D]�}|j �}|jd �sd |z}|jd �s|d z}tj|�}|jd d�|jd d�|jd d�|jdd�|jdd�d�}|j|���|S#t$r}td|���gcYd}~Sd}~wwxYw)z_ Retrieves tasks from the Inbox using AppleScript with improved data handling. a tell application "Things3" set inboxTasks to to dos of list "Inbox" set resultList to {} repeat with t in inboxTasks set taskTitle to name of t set taskNotes to "" if notes of t is not missing value then set taskNotes to notes of t end if set dueDate to "" if due date of t is not missing value then set dueDate to ((due date of t) as string) end if set whenDate to "" if activation date of t is not missing value then set whenDate to ((activation date of t) as string) end if set tagText to "" try set tagList to tag names of t if tagList is not {} then set AppleScript's text item delimiters to "," set tagText to tagList as string set AppleScript's text item delimiters to "" end if end try -- Create a record for this task set taskRecord to {title:taskTitle, notes:taskNotes, due_date:dueDate, when_date:whenDate, tags:tagText} set end of resultList to taskRecord end repeat return resultList end tell �{}r�{{�}}�������\}, \{r%r&�title�notes�due_date� when_date�tags)r>r?r@�whenrBzError retrieving inbox tasks: N� rrr+r,�rer.rr6�getr-� Exception�print� r r�tasks� records_str�record_stringsr#� task_data�taskrs r�get_inbox_tasksz"AppleScriptHandler.get_inbox_tasksUsU�� ( ��T* �'�2�2�6�:�F��V�t�^�v��|�� ��E�� � ��&�6�?�?�4�+@�$�Q�r�l� �!#���)�[�!A��"0�J�!+�!1�!1�!3�J�%�0�0��5�%(�:�%5� �%�.�.�s�3�%/�#�%5� �!3� K� K�J� W�I�"+���w��!;�!*���w��!;�$-�M�M�*�b�$A� )� � �k�2� >� )� � �f�b� 9� �D��L�L��&�'#1�*�L��� � �2�1�#�6� 7��I�� �s#�"D)�DD)�) E �2E�E �E c ���d} tj|�}|r |dk(s|dk(rgSg}|jd�r�|jd�r�|dd}t j d|�}|D]�}|j �}|jd �sd |z}|jd �s|d z}tj|�}|jd d�|jd d�|jd d�|jdd�|jdd�|jdd�d�}|j|���|S#t$r}td|���gcYd}~Sd}~wwxYw)ze Retrieves today's tasks from Things3 using AppleScript with improved data handling. ai tell application "Things3" set todayTasks to to dos of list "Today" set resultList to {} repeat with t in todayTasks set taskTitle to name of t set taskNotes to "" if notes of t is not missing value then set taskNotes to notes of t end if set dueDate to "" if due date of t is not missing value then set dueDate to ((due date of t) as string) end if set startDate to "" try if start date of t is not missing value then set startDate to ((start date of t) as string) end if on error set startDate to "" end try set whenDate to "" if activation date of t is not missing value then set whenDate to ((activation date of t) as string) end if set tagText to "" try set tagList to tag names of t if tagList is not {} then set AppleScript's text item delimiters to "," set tagText to tagList as string set AppleScript's text item delimiters to "" end if end try -- Create a record for this task set taskRecord to {title:taskTitle, notes:taskNotes, due_date:dueDate, start_date:startDate, when_date:whenDate, tags:tagText} set end of resultList to taskRecord end repeat return resultList end tell r8rr9r:r;r<r=r%r&r>r?r@� start_daterArB)r>r?r@rQrCrBz Error retrieving today's tasks: NrDrIs r�get_todays_tasksz#AppleScriptHandler.get_todays_tasks�sa�� 1 ��f& �'�2�2�6�:�F��V�t�^�v��|�� ��E�� � ��&�6�?�?�4�+@�$�Q�r�l� �!#���)�[�!A��"0�J�!+�!1�!1�!3�J�%�0�0��5�%(�:�%5� �%�.�.�s�3�%/�#�%5� � 2� K� K�J� W�I�"+���w��!;�!*���w��!;�$-�M�M�*�b�$A�&/�m�m�L�"�&E� )� � �k�2� >� )� � �f�b� 9� �D��L�L��&�##1�&�L��� � �4�Q�C�8� 9��I�� �s#�"D:�DD:�: E�E�E�Ec�8�d} tj|�}|r |dk(s|dk(rgSg}|jd�r�|jd�r�|dd}t j d|�}|D]�}|j �}|jd �sd |z}|jd �s|d z}tj|�}|jd d�|jd d�d �}|j|���|S#t$r}td|���gcYd}~Sd}~wwxYw)zd Retrieves all projects from Things3 using AppleScript with improved data handling. a� tell application "Things3" set projectList to projects set resultList to {} repeat with p in projectList set projectTitle to name of p set projectNotes to "" if notes of p is not missing value then set projectNotes to notes of p end if -- Create a record for this project set projectRecord to {title:projectTitle, notes:projectNotes} set end of resultList to projectRecord end repeat return resultList end tell r8rr9r:r;r<r=r%r&r>r?)r>r?zError retrieving projects: NrD) r r�projectsrKrLr#� project_data�projectrs r� get_projectszAppleScriptHandler.get_projectss(��  ��* �'�2�2�6�:�F��V�t�^�v��|�� ��H�� � ��&�6�?�?�4�+@�$�Q�r�l� �!#���)�[�!A��"0�J�!+�!1�!1�!3�J�%�0�0��5�%(�:�%5� �%�.�.�s�3�%/�#�%5� �#5�#N�#N�z�#Z�L�".�!1�!1�'�2�!>�!-�!1�!1�'�2�!>��G��O�O�G�,�#1��O��� � �/��s�3� 4��I�� �s#�"C6�CC6�6 D�?D�D�Dc�X� d}tj|�}d|vS#t$rYywxYw)zE Validate that Things3 is accessible and responsive. z~ tell application "Things3" return name of application "Things3" end tell �Things3F)rrrG)r rs r�validate_things3_accessz*AppleScriptHandler.validate_things3_accessLs=�� ��F� (�2�2�6�:�F���&� &��� �� �s �� )�)� title_searchc���dtj|��dtj|��dtj|��d�} tj|�}|jd�S#t$rYywxYw)zF Mark a todo as completed by searching for its title. a tell application "Things3" set foundTodo to missing value -- Search in Today list set todayTodos to to dos of list "Today" repeat with t in todayTodos if name of t contains "a~" then set foundTodo to t exit repeat end if end repeat -- If not found in Today, search in Inbox if foundTodo is missing value then set inboxTodos to to dos of list "Inbox" repeat with t in inboxTodos if name of t contains "a�" then set foundTodo to t exit repeat end if end repeat end if -- If not found in standard lists, search all todos if foundTodo is missing value then set allTodos to to dos repeat with t in allTodos if name of t contains "a�" and status of t is not completed then set foundTodo to t exit repeat end if end repeat end if -- Complete the todo if found if foundTodo is not missing value then set status of foundTodo to completed return "COMPLETED:" & name of foundTodo else return "NOT_FOUND" end if end tell z COMPLETED:F)rr!rr+rG)r[r rs r�complete_todo_by_titlez)AppleScriptHandler.complete_todo_by_title\s��� (�);�(V�(V�Wc�(d�'e� f,�-?�,Z�,Z�[g�,h�+i� j,�-?�,Z�,Z�[g�,h�+i�j �9+ ��Z �'�2�2�6�:�F��$�$�\�2� 2��� �� �s�%A-�- A9�8A9�queryc ��dtj|��d�} tj|�}|r |dk(s|dk(rgSg}|jd��r |j d�r�|dd}t j d |�}|D]�}|j�}|jd �sd |z}|j d �s|d z}|d d }tjd|zdz�}|jdd�|jdd�|jdd�|jdd�|jdd�|jdd�d�} |j| ���|S#t$r} td| ���gcYd} ~ Sd} ~ wwxYw)z7 Search for todos by title or content. zD tell application "Things3" set searchQuery to "ax" set foundTodos to {} set allTodos to to dos repeat with t in allTodos set taskTitle to name of t set taskNotes to "" if notes of t is not missing value then set taskNotes to notes of t end if -- Check if query matches title or notes if taskTitle contains searchQuery or taskNotes contains searchQuery then set taskStatus to "incomplete" if status of t is completed then set taskStatus to "completed" end if set dueDate to "" if due date of t is not missing value then set dueDate to ((due date of t) as string) end if set whenDate to "" if activation date of t is not missing value then set whenDate to ((activation date of t) as string) end if set tagText to "" try set tagList to tag names of t if tagList is not {} then set AppleScript's text item delimiters to "," set tagText to tagList as string set AppleScript's text item delimiters to "" end if end try set taskRecord to {title:taskTitle, notes:taskNotes, status:taskStatus, due_date:dueDate, when_date:whenDate, tags:tagText} set end of foundTodos to taskRecord end if end repeat return foundTodos end tell z{{}}rz{{{{z}}}}������z \}\}, \{\{r9r:r;r<r%r&r>r?�status�unknownr@rArB)r>r?rbr@rCrBzError searching todos: N) rr!rr+r,rEr.rr6rFr-rGrH) r^r r�todosrKrLr#� inner_recordrM�todors r� search_todoszAppleScriptHandler.search_todos�s��� !�!3�!O�!O�PU�!V� W�-X �/ ��b& �'�2�2�6�:�F��V�v�-��2��� ��E�� � ��(�V�_�_�V�-D�$�Q�r�l� �!#���-��!E��"0�J�!+�!1�!1�!3�J�%�0�0��6�%)�J�%6� �%�.�.�t�4�%/�$�%6� �$.�a��#3�L� 2� K� K�C�R^�L^�ad�Ld� e�I�"+���w��!;�!*���w��!;�"+�-�-��)�"D�$-�M�M�*�b�$A� )� � �k�2� >� )� � �f�b� 9� �D��L�L��&�'#1�*�L��� � �+�A�3�/� 0��I�� �s#�"E�DE� F�&E;�5F�;FN)�__name__� __module__� __qualname__�__doc__� staticmethod�strrr!rrr6rrOrRrW�boolrZr]rg�r"rrrsh��C�� F�3� F�3� F�� F���#��#����"�'�S�'�T�#�s�(�^�'��'�R�X�T�$�s�C�x�.�1�X��X�t�]�d�4��S��>�2�]��]�~�9�$�t�C��H�~�.�9��9�v� �T� �� ��5�S�5�T�5��5�n�[�C�[�D��c�3�h��$8�[��[r"r) r�typingrrrr�jsonrError"r�<module>rrs ���,�,� � �j�jr"

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/drjforrest/mcp-things3'

If you have feedback or need assistance with the MCP directory API, please join our Discord server