Commit732f089fRecorded25 Mar 2026Repositoryparlor
Add MCP tools for user-by-email, topic invite, and extended admin invite
Message
Expose new sigil-discourse functions as parlor MCP tools and CLI commands:
- parlor/users-by-email: find user by email address (admin endpoint) - parlor/topics-invite: invite a user to a topic by email or username - parlor/admin-invite: now supports topicid and groupnames parameters for creating invite links that land on a specific topic
Also adds CLI commands (users by-email, topics invite), tests for the new tool exports, and updates the README.
Changed
README.md | 6 ++++--
src/parlor/admin.sgl | 14 +++++++++++---
src/parlor/main.sgl | 15 +++++++++++++++
src/parlor/topics.sgl | 28 ++++++++++++++++++++++++++--
src/parlor/users.sgl | 24 ++++++++++++++++++++++++
test/test-tools.sgl | 45 +++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 125 insertions(+), 7 deletions(-)Diff
README.mdmodified
@@ -13,6 +13,7 @@ parlor topics close 42 # Close topic
13
parlor topics open 42 # Reopen topic 14
parlor topics pin 42 # Pin topic 15
parlor topics delete 42 # Delete topic+16
parlor topics invite 42 "[email protected]" # Invite user to topic 17
parlor posts get 123 # Get post 18
parlor posts create 42 "Reply body" # Reply to topic 19
parlor posts update 123 "Edited body" # Edit post@@ -25,6 +26,7 @@ parlor categories get 5 # Get category
26
parlor categories create "Name" "0088CC" # Create category 27
parlor users get username # Get user 28
parlor users list # List users+29
parlor users by-email [email protected] # Find user by email 30
parlor users create "Name" "[email protected]" "pw" un # Create user 31
parlor groups list # List groups 32
parlor groups get group-name # Get group@@ -35,12 +37,12 @@ parlor groups create new-group # Create group
37
38
Start with `parlor serve`. Exposes these tools: 39
−38
- `parlor/topics-latest`, `parlor/topics-top`, `parlor/topics-by-category`, `parlor/topics-get`, `parlor/topics-create`, `parlor/topics-update`, `parlor/topics-close`, `parlor/topics-open`, `parlor/topics-pin`, `parlor/topics-delete`+40
- `parlor/topics-latest`, `parlor/topics-top`, `parlor/topics-by-category`, `parlor/topics-get`, `parlor/topics-create`, `parlor/topics-update`, `parlor/topics-close`, `parlor/topics-open`, `parlor/topics-pin`, `parlor/topics-delete`, `parlor/topics-invite` 41
- `parlor/posts-get`, `parlor/posts-create`, `parlor/posts-update`, `parlor/posts-delete` 42
- `parlor/messages-send`, `parlor/messages-list`, `parlor/messages-reply` 43
- `parlor/search` 44
- `parlor/categories-list`, `parlor/categories-get`, `parlor/categories-create`−43
- `parlor/users-get`, `parlor/users-list`, `parlor/users-create`, `parlor/users-suspend`, `parlor/users-unsuspend`+45
- `parlor/users-get`, `parlor/users-list`, `parlor/users-by-email`, `parlor/users-create`, `parlor/users-suspend`, `parlor/users-unsuspend` 46
- `parlor/groups-list`, `parlor/groups-get`, `parlor/groups-create`, `parlor/groups-add-members`, `parlor/groups-remove-members` 47
- `parlor/admin-settings`, `parlor/admin-update-setting`, `parlor/admin-invite` 48
src/parlor/admin.sglmodified
@@ -38,7 +38,11 @@
38
(properties . ((email . ((type . "string") 39
(description . "Email address to invite"))) 40
(group_ids . ((type . "string")−41
(description . "Comma-separated group IDs (optional)")))))+41
(description . "Comma-separated group IDs (optional)")))+42
(topic_id . ((type . "integer")+43
(description . "Topic ID to land on after accepting invite (optional)")))+44
(group_names . ((type . "string")+45
(description . "Comma-separated group names (optional, alternative to group_ids)"))))) 46
(required . ("email")))) 47
48
;; ============================================================@@ -86,10 +90,14 @@
90
91
(define (tool-admin-invite args) 92
(let* ((email (dict-ref args email:))−89
(group-ids (dict-ref args group_ids: "")))+93
(group-ids (dict-ref args group_ids: ""))+94
(topic-id (dict-ref args topic_id: #f))+95
(group-names (dict-ref args group_names: #f))) 96
(discourse-create-invite 97
(parlor-base-url) (parlor-api-key) (parlor-username)−92
email group-ids)+98
email group-ids+99
topic-id: topic-id+100
group-names: group-names) 101
(string-append "Sent invite to " email "."))) 102
103
;; ============================================================src/parlor/main.sglmodified
@@ -53,6 +53,7 @@
53
(display " parlor topics open <id> Reopen topic\n") 54
(display " parlor topics pin <id> Pin topic\n") 55
(display " parlor topics delete <id> Delete topic\n")+56
(display " parlor topics invite <id> <user> Invite user to topic\n") 57
(display " parlor posts get <id> Get post\n") 58
(display " parlor posts create <topic-id> <body> Reply to topic\n") 59
(display " parlor posts update <id> <body> Edit post\n")@@ -65,6 +66,7 @@
66
(display " parlor categories create <name> <color> Create category\n") 67
(display " parlor users get <username> Get user\n") 68
(display " parlor users list List users\n")+69
(display " parlor users by-email <email> Find user by email\n") 70
(display " parlor users create <name> <email> <pw> <un> Create user\n") 71
(display " parlor groups list List groups\n") 72
(display " parlor groups get <name> Get group\n")@@ -128,6 +130,13 @@
130
#{ topic_id: (string->number (car rest)) })) 131
(newline)) 132
+133
((and (string=? group "topics") (string=? cmd "invite")+134
(>= (length rest) 2))+135
(display (tool-topics-invite+136
#{ topic_id: (string->number (car rest))+137
user: (cadr rest) }))+138
(newline))+139
140
;; posts 141
((and (string=? group "posts") (string=? cmd "get") 142
(>= (length rest) 1))@@ -207,6 +216,12 @@
216
(display (tool-users-list #{})) 217
(newline)) 218
+219
((and (string=? group "users") (string=? cmd "by-email")+220
(>= (length rest) 1))+221
(display (tool-users-by-email+222
#{ email: (car rest) }))+223
(newline))+224
225
((and (string=? group "users") (string=? cmd "create") 226
(>= (length rest) 4)) 227
(display (tool-users-createsrc/parlor/topics.sglmodified
@@ -21,7 +21,8 @@
21
tool-topics-close 22
tool-topics-open 23
tool-topics-pin−24
tool-topics-delete)+24
tool-topics-delete+25
tool-topics-invite) 26
(begin 27
28
;; ============================================================@@ -98,6 +99,14 @@
99
(description . "Topic ID"))))) 100
(required . ("topic_id")))) 101
+102
(define topics-invite-schema+103
'((type . "object")+104
(properties . ((topic_id . ((type . "integer")+105
(description . "Topic ID")))+106
(user . ((type . "string")+107
(description . "Email address or username to invite")))))+108
(required . ("topic_id" "user"))))+109
110
;; ============================================================ 111
;; Formatting 112
;; ============================================================@@ -224,6 +233,15 @@
233
topic-id) 234
(string-append "Deleted topic #" (number->string topic-id) "."))) 235
+236
(define (tool-topics-invite args)+237
(let ((topic-id (dict-ref args topic_id:))+238
(user (dict-ref args user:)))+239
(discourse-invite-to-topic+240
(parlor-base-url) (parlor-api-key) (parlor-username)+241
topic-id user)+242
(string-append "Invited " user " to topic #"+243
(number->string topic-id) ".")))+244
245
;; ============================================================ 246
;; MCP Registration 247
;; ============================================================@@ -287,6 +305,12 @@
305
"parlor/topics-delete" 306
"Delete a topic." 307
topics-delete-schema−290
tool-topics-delete))+308
tool-topics-delete)+309
+310
(register server+311
"parlor/topics-invite"+312
"Invite a user to a topic by email or username."+313
topics-invite-schema+314
tool-topics-invite)) 315
316
))src/parlor/users.sglmodified
@@ -13,6 +13,7 @@
13
(export register-user-tools! 14
tool-users-get 15
tool-users-list+16
tool-users-by-email 17
tool-users-create 18
tool-users-suspend 19
tool-users-unsuspend)@@ -33,6 +34,12 @@
34
(properties . ()) 35
(required . ()))) 36
+37
(define users-by-email-schema+38
'((type . "object")+39
(properties . ((email . ((type . "string")+40
(description . "Email address to search for")))))+41
(required . ("email"))))+42
43
(define users-create-schema 44
'((type . "object") 45
(properties . ((name . ((type . "string")@@ -116,6 +123,17 @@
123
"Active users:\n\n" 124
(string-join (map format-user (array->list users)) "\n"))))) 125
+126
(define (tool-users-by-email args)+127
(let* ((email (dict-ref args email:))+128
(users (discourse-list-users+129
(parlor-base-url) (parlor-api-key) (parlor-username)+130
email: email)))+131
(if (or (not users) (= (array-length users) 0))+132
(string-append "No user found with email \"" email "\".")+133
(string-append+134
"Users matching " email ":\n\n"+135
(string-join (map format-user (array->list users)) "\n")))))+136
137
(define (tool-users-create args) 138
(let* ((name (dict-ref args name:)) 139
(email (dict-ref args email:))@@ -162,6 +180,12 @@
180
users-list-schema 181
tool-users-list) 182
+183
(register server+184
"parlor/users-by-email"+185
"Find a user by their email address (admin endpoint)."+186
users-by-email-schema+187
tool-users-by-email)+188
189
(register server 190
"parlor/users-create" 191
"Create a new user account."test/test-tools.sgladded
@@ -0,0 +1,45 @@
+1
(import (sigil test)+2
(sigil core)+3
(sigil process)+4
(parlor users)+5
(parlor topics)+6
(parlor admin))+7
+8
;; ============================================================+9
;; users-by-email tool+10
;; ============================================================+11
+12
(test-group "users-by-email"+13
(test "handler returns message when no users found"+14
(setenv! "DISCOURSE_URL" "https://test.example.com")+15
(setenv! "DISCOURSE_API_KEY" "test-key")+16
(setenv! "DISCOURSE_USERNAME" "system")+17
;; We can't mock the API call, but we can verify the tool+18
;; function exists and is exported+19
(assert-true (procedure? tool-users-by-email)))+20
+21
(test "handler is exported alongside existing tools"+22
(assert-true (procedure? tool-users-get))+23
(assert-true (procedure? tool-users-list))+24
(assert-true (procedure? tool-users-by-email))+25
(assert-true (procedure? tool-users-create))+26
(assert-true (procedure? tool-users-suspend))+27
(assert-true (procedure? tool-users-unsuspend))))+28
+29
;; ============================================================+30
;; topics-invite tool+31
;; ============================================================+32
+33
(test-group "topics-invite"+34
(test "handler is exported alongside existing tools"+35
(assert-true (procedure? tool-topics-latest))+36
(assert-true (procedure? tool-topics-invite))+37
(assert-true (procedure? tool-topics-delete))))+38
+39
;; ============================================================+40
;; admin-invite tool (extended)+41
;; ============================================================+42
+43
(test-group "admin-invite"+44
(test "handler is exported"+45
(assert-true (procedure? tool-admin-invite))))