withfeedback-mcp 0.1.0__tar.gz
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.
- withfeedback_mcp-0.1.0/.gitignore +304 -0
- withfeedback_mcp-0.1.0/PKG-INFO +199 -0
- withfeedback_mcp-0.1.0/README.md +186 -0
- withfeedback_mcp-0.1.0/pyproject.toml +26 -0
- withfeedback_mcp-0.1.0/src/withfeedback_mcp/__init__.py +3 -0
- withfeedback_mcp-0.1.0/src/withfeedback_mcp/client.py +335 -0
- withfeedback_mcp-0.1.0/src/withfeedback_mcp/server.py +540 -0
- withfeedback_mcp-0.1.0/tests/conftest.py +25 -0
- withfeedback_mcp-0.1.0/tests/test_mcp_client.py +338 -0
- withfeedback_mcp-0.1.0/tests/test_mcp_http.py +97 -0
- withfeedback_mcp-0.1.0/tests/test_mcp_tools.py +286 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
local_password.txt
|
|
2
|
+
AGENTS-local.md
|
|
3
|
+
dev.sh
|
|
4
|
+
/media/*
|
|
5
|
+
### VisualStudioCode template
|
|
6
|
+
.vscode/*
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Local History for Visual Studio Code
|
|
10
|
+
.history/
|
|
11
|
+
|
|
12
|
+
# Built Visual Studio Code Extensions
|
|
13
|
+
*.vsix
|
|
14
|
+
|
|
15
|
+
### JetBrains template
|
|
16
|
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
17
|
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
18
|
+
.idea/*
|
|
19
|
+
|
|
20
|
+
# Gradle and Maven with auto-import
|
|
21
|
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
22
|
+
# since they will be recreated, and may cause churn. Uncomment if using
|
|
23
|
+
# auto-import.
|
|
24
|
+
# .idea/artifacts
|
|
25
|
+
# .idea/compiler.xml
|
|
26
|
+
# .idea/jarRepositories.xml
|
|
27
|
+
# .idea/modules.xml
|
|
28
|
+
# .idea/*.iml
|
|
29
|
+
# .idea/modules
|
|
30
|
+
# *.iml
|
|
31
|
+
# *.ipr
|
|
32
|
+
|
|
33
|
+
# CMake
|
|
34
|
+
cmake-build-*/
|
|
35
|
+
|
|
36
|
+
# Mongo Explorer plugin
|
|
37
|
+
.idea/**/mongoSettings.xml
|
|
38
|
+
|
|
39
|
+
# File-based project format
|
|
40
|
+
*.iws
|
|
41
|
+
|
|
42
|
+
# IntelliJ
|
|
43
|
+
out/
|
|
44
|
+
|
|
45
|
+
# mpeltonen/sbt-idea plugin
|
|
46
|
+
.idea_modules/
|
|
47
|
+
|
|
48
|
+
# JIRA plugin
|
|
49
|
+
atlassian-ide-plugin.xml
|
|
50
|
+
|
|
51
|
+
# Cursive Clojure plugin
|
|
52
|
+
.idea/replstate.xml
|
|
53
|
+
|
|
54
|
+
# SonarLint plugin
|
|
55
|
+
.idea/sonarlint/
|
|
56
|
+
|
|
57
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
58
|
+
com_crashlytics_export_strings.xml
|
|
59
|
+
crashlytics.properties
|
|
60
|
+
crashlytics-build.properties
|
|
61
|
+
fabric.properties
|
|
62
|
+
|
|
63
|
+
# Editor-based Rest Client
|
|
64
|
+
.idea/httpRequests
|
|
65
|
+
|
|
66
|
+
# Android studio 3.1+ serialized cache file
|
|
67
|
+
.idea/caches/build_file_checksums.ser
|
|
68
|
+
|
|
69
|
+
### Linux template
|
|
70
|
+
*~
|
|
71
|
+
|
|
72
|
+
# temporary files which can be created if a process still has a handle open of a deleted file
|
|
73
|
+
.fuse_hidden*
|
|
74
|
+
|
|
75
|
+
# KDE directory preferences
|
|
76
|
+
.directory
|
|
77
|
+
|
|
78
|
+
# Linux trash folder which might appear on any partition or disk
|
|
79
|
+
.Trash-*
|
|
80
|
+
|
|
81
|
+
# .nfs files are created when an open file is removed but is still being accessed
|
|
82
|
+
.nfs*
|
|
83
|
+
|
|
84
|
+
### Windows template
|
|
85
|
+
# Windows thumbnail cache files
|
|
86
|
+
Thumbs.db
|
|
87
|
+
Thumbs.db:encryptable
|
|
88
|
+
ehthumbs.db
|
|
89
|
+
ehthumbs_vista.db
|
|
90
|
+
|
|
91
|
+
# Dump file
|
|
92
|
+
*.stackdump
|
|
93
|
+
|
|
94
|
+
# Folder config file
|
|
95
|
+
[Dd]esktop.ini
|
|
96
|
+
|
|
97
|
+
# Recycle Bin used on file shares
|
|
98
|
+
$RECYCLE.BIN/
|
|
99
|
+
|
|
100
|
+
# Windows Installer files
|
|
101
|
+
*.cab
|
|
102
|
+
*.msi
|
|
103
|
+
*.msix
|
|
104
|
+
*.msm
|
|
105
|
+
*.msp
|
|
106
|
+
|
|
107
|
+
# Windows shortcuts
|
|
108
|
+
*.lnk
|
|
109
|
+
|
|
110
|
+
### macOS template
|
|
111
|
+
# General
|
|
112
|
+
.DS_Store
|
|
113
|
+
.AppleDouble
|
|
114
|
+
.LSOverride
|
|
115
|
+
|
|
116
|
+
# Icon must end with two \r
|
|
117
|
+
Icon
|
|
118
|
+
|
|
119
|
+
# Thumbnails
|
|
120
|
+
._*
|
|
121
|
+
|
|
122
|
+
# Files that might appear in the root of a volume
|
|
123
|
+
.DocumentRevisions-V100
|
|
124
|
+
.fseventsd
|
|
125
|
+
.Spotlight-V100
|
|
126
|
+
.TemporaryItems
|
|
127
|
+
.Trashes
|
|
128
|
+
.VolumeIcon.icns
|
|
129
|
+
.com.apple.timemachine.donotpresent
|
|
130
|
+
|
|
131
|
+
# Directories potentially created on remote AFP share
|
|
132
|
+
.AppleDB
|
|
133
|
+
.AppleDesktop
|
|
134
|
+
Network Trash Folder
|
|
135
|
+
Temporary Items
|
|
136
|
+
.apdisk
|
|
137
|
+
|
|
138
|
+
### Python template
|
|
139
|
+
# Byte-compiled / optimized / DLL files
|
|
140
|
+
__pycache__/
|
|
141
|
+
*.py[cod]
|
|
142
|
+
*$py.class
|
|
143
|
+
|
|
144
|
+
# C extensions
|
|
145
|
+
*.so
|
|
146
|
+
|
|
147
|
+
# Distribution / packaging
|
|
148
|
+
.Python
|
|
149
|
+
build/
|
|
150
|
+
develop-eggs/
|
|
151
|
+
dist/
|
|
152
|
+
downloads/
|
|
153
|
+
eggs/
|
|
154
|
+
.eggs/
|
|
155
|
+
lib/
|
|
156
|
+
lib64/
|
|
157
|
+
parts/
|
|
158
|
+
sdist/
|
|
159
|
+
var/
|
|
160
|
+
wheels/
|
|
161
|
+
share/python-wheels/
|
|
162
|
+
*.egg-info/
|
|
163
|
+
.installed.cfg
|
|
164
|
+
*.egg
|
|
165
|
+
MANIFEST
|
|
166
|
+
|
|
167
|
+
# PyInstaller
|
|
168
|
+
# Usually these files are written by a python script from a template
|
|
169
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
170
|
+
*.manifest
|
|
171
|
+
*.spec
|
|
172
|
+
|
|
173
|
+
# Installer logs
|
|
174
|
+
pip-log.txt
|
|
175
|
+
pip-delete-this-directory.txt
|
|
176
|
+
|
|
177
|
+
# Unit test / coverage reports
|
|
178
|
+
htmlcov/
|
|
179
|
+
.tox/
|
|
180
|
+
.nox/
|
|
181
|
+
.coverage
|
|
182
|
+
.coverage.*
|
|
183
|
+
.cache
|
|
184
|
+
nosetests.xml
|
|
185
|
+
coverage.xml
|
|
186
|
+
*.cover
|
|
187
|
+
*.py,cover
|
|
188
|
+
.hypothesis/
|
|
189
|
+
.pytest_cache/
|
|
190
|
+
cover/
|
|
191
|
+
|
|
192
|
+
# Translations
|
|
193
|
+
*.mo
|
|
194
|
+
*.pot
|
|
195
|
+
|
|
196
|
+
# Django stuff:
|
|
197
|
+
*.log
|
|
198
|
+
local_settings.py
|
|
199
|
+
db.sqlite3
|
|
200
|
+
db.sqlite3-journal
|
|
201
|
+
|
|
202
|
+
# Flask stuff:
|
|
203
|
+
instance/
|
|
204
|
+
.webassets-cache
|
|
205
|
+
|
|
206
|
+
# Scrapy stuff:
|
|
207
|
+
.scrapy
|
|
208
|
+
|
|
209
|
+
# Sphinx documentation
|
|
210
|
+
docs/_build/
|
|
211
|
+
|
|
212
|
+
# PyBuilder
|
|
213
|
+
.pybuilder/
|
|
214
|
+
target/
|
|
215
|
+
|
|
216
|
+
# Jupyter Notebook
|
|
217
|
+
.ipynb_checkpoints
|
|
218
|
+
|
|
219
|
+
# IPython
|
|
220
|
+
profile_default/
|
|
221
|
+
ipython_config.py
|
|
222
|
+
|
|
223
|
+
# pyenv
|
|
224
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
225
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
226
|
+
# .python-version
|
|
227
|
+
|
|
228
|
+
# pipenv
|
|
229
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
230
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
231
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
232
|
+
# install all needed dependencies.
|
|
233
|
+
#Pipfile.lock
|
|
234
|
+
|
|
235
|
+
# poetry
|
|
236
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
237
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
238
|
+
# commonly ignored for libraries.
|
|
239
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
240
|
+
#poetry.lock
|
|
241
|
+
|
|
242
|
+
# pdm
|
|
243
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
244
|
+
#pdm.lock
|
|
245
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
246
|
+
# in version control.
|
|
247
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
248
|
+
.pdm.toml
|
|
249
|
+
|
|
250
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
251
|
+
__pypackages__/
|
|
252
|
+
|
|
253
|
+
# Celery stuff
|
|
254
|
+
celerybeat-schedule
|
|
255
|
+
celerybeat.pid
|
|
256
|
+
|
|
257
|
+
# SageMath parsed files
|
|
258
|
+
*.sage.py
|
|
259
|
+
|
|
260
|
+
# Environments
|
|
261
|
+
.env
|
|
262
|
+
.venv
|
|
263
|
+
env/
|
|
264
|
+
venv/
|
|
265
|
+
ENV/
|
|
266
|
+
env.bak/
|
|
267
|
+
venv.bak/
|
|
268
|
+
|
|
269
|
+
# Spyder project settings
|
|
270
|
+
.spyderproject
|
|
271
|
+
.spyproject
|
|
272
|
+
|
|
273
|
+
# Rope project settings
|
|
274
|
+
.ropeproject
|
|
275
|
+
|
|
276
|
+
# mkdocs documentation
|
|
277
|
+
/site
|
|
278
|
+
|
|
279
|
+
# mypy
|
|
280
|
+
.mypy_cache/
|
|
281
|
+
.dmypy.json
|
|
282
|
+
dmypy.json
|
|
283
|
+
|
|
284
|
+
# Pyre type checker
|
|
285
|
+
.pyre/
|
|
286
|
+
|
|
287
|
+
# pytype static type analyzer
|
|
288
|
+
.pytype/
|
|
289
|
+
|
|
290
|
+
# Cython debug symbols
|
|
291
|
+
cython_debug/
|
|
292
|
+
|
|
293
|
+
# PyCharm
|
|
294
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
295
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
296
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
297
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
298
|
+
#.idea/
|
|
299
|
+
|
|
300
|
+
/env/
|
|
301
|
+
/tailwindcss
|
|
302
|
+
|
|
303
|
+
node_modules/
|
|
304
|
+
/staticfiles/*
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: withfeedback-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for withfeedback.com — testimonials, feedback moderation, widgets, and NPS for AI agents
|
|
5
|
+
Project-URL: Homepage, https://withfeedback.com
|
|
6
|
+
Project-URL: Documentation, https://withfeedback.com/api/docs/
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: feedback,mcp,testimonials,withfeedback
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: httpx>=0.27
|
|
11
|
+
Requires-Dist: mcp>=1.2
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# withfeedback-mcp
|
|
15
|
+
|
|
16
|
+
MCP (Model Context Protocol) server for [withfeedback.com](https://withfeedback.com):
|
|
17
|
+
lets AI assistants (Claude, Cursor, …) list and moderate feedback submissions,
|
|
18
|
+
create and bulk-import testimonials, inspect widgets and embed codes, and read
|
|
19
|
+
NPS survey results.
|
|
20
|
+
|
|
21
|
+
Transports: **stdio** (default) and **streamable HTTP** (`withfeedback-mcp --http`
|
|
22
|
+
or `WITHFEEDBACK_MCP_TRANSPORT=http`). HTTP mode binds `127.0.0.1` only —
|
|
23
|
+
see the security note below before changing that.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install withfeedback-mcp
|
|
29
|
+
# or run without installing:
|
|
30
|
+
uvx withfeedback-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start with a personal access token (PAT)
|
|
34
|
+
|
|
35
|
+
The recommended setup for a local/stdio MCP server:
|
|
36
|
+
|
|
37
|
+
1. Log in at your withfeedback.com instance and open **Account → API Tokens**
|
|
38
|
+
(`/accounts/tokens/`).
|
|
39
|
+
2. Create a token with the scopes you need (see the table below). Copy it —
|
|
40
|
+
it is shown only once.
|
|
41
|
+
3. Configure your MCP client with `WITHFEEDBACK_TOKEN` (examples below).
|
|
42
|
+
|
|
43
|
+
API access requires a plan that includes the API/CLI/MCP feature (Pro and
|
|
44
|
+
above). Requests from lower plans return HTTP 403.
|
|
45
|
+
|
|
46
|
+
## Claude Desktop / Claude Code configuration
|
|
47
|
+
|
|
48
|
+
Claude Desktop (`claude_desktop_config.json`):
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"withfeedback": {
|
|
54
|
+
"command": "uvx",
|
|
55
|
+
"args": ["withfeedback-mcp"],
|
|
56
|
+
"env": {
|
|
57
|
+
"WITHFEEDBACK_TOKEN": "spd_your_token_here"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Prefer the OAuth2 device flow over a PAT? Omit `WITHFEEDBACK_TOKEN` — the
|
|
65
|
+
official public client id `7yQMsnY2Is2f5tCuwwgItoQu3fRkEX2wnzIRj0Vh` is the
|
|
66
|
+
built-in default (shown here explicitly; only needed to override):
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"withfeedback": {
|
|
72
|
+
"command": "uvx",
|
|
73
|
+
"args": ["withfeedback-mcp"],
|
|
74
|
+
"env": {
|
|
75
|
+
"WITHFEEDBACK_CLIENT_ID": "7yQMsnY2Is2f5tCuwwgItoQu3fRkEX2wnzIRj0Vh"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Claude Code:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
claude mcp add withfeedback --env WITHFEEDBACK_TOKEN=spd_your_token_here -- uvx withfeedback-mcp
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Cursor configuration
|
|
89
|
+
|
|
90
|
+
`~/.cursor/mcp.json` (or the project's `.cursor/mcp.json`):
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"withfeedback": {
|
|
96
|
+
"command": "uvx",
|
|
97
|
+
"args": ["withfeedback-mcp"],
|
|
98
|
+
"env": {
|
|
99
|
+
"WITHFEEDBACK_TOKEN": "spd_your_token_here"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## OAuth2 device flow (alternative to a PAT)
|
|
107
|
+
|
|
108
|
+
If `WITHFEEDBACK_TOKEN` is not set, the server falls back to the OAuth2
|
|
109
|
+
device flow using the **official public client id**
|
|
110
|
+
`7yQMsnY2Is2f5tCuwwgItoQu3fRkEX2wnzIRj0Vh` (public by design — device-flow
|
|
111
|
+
clients carry no secret). Set `WITHFEEDBACK_CLIENT_ID` to use a different
|
|
112
|
+
client (e.g. on a self-hosted instance). On the first tool call the server
|
|
113
|
+
prints a verification URL and a short code on stderr (and tries to open your
|
|
114
|
+
browser):
|
|
115
|
+
|
|
116
|
+
1. The server calls `POST /o/device-authorization/` with the client id.
|
|
117
|
+
2. You open the printed URL, enter the code, and approve the requested scopes.
|
|
118
|
+
3. The server polls `POST /o/token/` until approval and then uses the access
|
|
119
|
+
token for all API calls.
|
|
120
|
+
|
|
121
|
+
Request a custom scope set with `WITHFEEDBACK_SCOPES` (space-separated).
|
|
122
|
+
|
|
123
|
+
**Note on Dynamic Client Registration:** open DCR (`POST /o/register/`) is
|
|
124
|
+
**disabled in production** (`DCR_ENABLED=False` is the production default) —
|
|
125
|
+
the endpoint is unauthenticated and unthrottled, so withfeedback.com does not
|
|
126
|
+
expose it. Use the published official client id (or a PAT); self-hosted
|
|
127
|
+
instances can register additional clients via Django admin or the
|
|
128
|
+
`create_oauth2_app` management command.
|
|
129
|
+
|
|
130
|
+
## Environment variables
|
|
131
|
+
|
|
132
|
+
| Variable | Default | Purpose |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `WITHFEEDBACK_API_URL` | `https://withfeedback.com` | API base URL (self-hosted/staging override) |
|
|
135
|
+
| `WITHFEEDBACK_TOKEN` | — | Personal access token (primary auth for stdio) |
|
|
136
|
+
| `WITHFEEDBACK_CLIENT_ID` | official public client id | OAuth2 client id for the device-flow fallback |
|
|
137
|
+
| `WITHFEEDBACK_SCOPES` | all tool scopes | Scopes requested during device flow |
|
|
138
|
+
| `WITHFEEDBACK_MCP_TRANSPORT` | `stdio` | Set to `http` for streamable HTTP |
|
|
139
|
+
| `WITHFEEDBACK_MCP_HOST` | `127.0.0.1` | HTTP bind address (loopback only by default) |
|
|
140
|
+
| `WITHFEEDBACK_MCP_PORT` | `8000` | HTTP bind port |
|
|
141
|
+
| `WITHFEEDBACK_MCP_ALLOW_REMOTE` | unset | Must be `1` to allow a non-loopback HTTP bind (read the security note first) |
|
|
142
|
+
|
|
143
|
+
On startup the server checks `GET /.well-known/speedpy.json` once and warns
|
|
144
|
+
(never fails) when the manifest is unreachable or the API is older than the
|
|
145
|
+
minimum this client expects.
|
|
146
|
+
|
|
147
|
+
## HTTP transport security
|
|
148
|
+
|
|
149
|
+
The streamable HTTP transport has **no per-caller authentication**: the
|
|
150
|
+
process holds a single account token (your PAT or device-flow grant), and
|
|
151
|
+
every HTTP caller acts as that account. Therefore:
|
|
152
|
+
|
|
153
|
+
- HTTP mode binds **`127.0.0.1` by default** and is intended for local
|
|
154
|
+
clients on the same machine.
|
|
155
|
+
- Binding a non-loopback address is **refused** unless you explicitly set
|
|
156
|
+
`WITHFEEDBACK_MCP_ALLOW_REMOTE=1`, and even then the server prints a
|
|
157
|
+
prominent warning: only do this behind a reverse proxy that authenticates
|
|
158
|
+
callers before they can reach the port.
|
|
159
|
+
- A properly multi-tenant **remote MCP server with per-user OAuth is a
|
|
160
|
+
future server-side deployment of withfeedback.com** — it is not something
|
|
161
|
+
this package provides. Do not expose this process to the internet.
|
|
162
|
+
|
|
163
|
+
## Tools and required scopes
|
|
164
|
+
|
|
165
|
+
Give your PAT (or device-flow grant) the scopes for the tools you plan to use:
|
|
166
|
+
|
|
167
|
+
| Tool | Endpoint(s) | Required scope |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `list_teams` | `GET /api/v1/teams/` | `read:teams` |
|
|
170
|
+
| `list_projects` | `GET /api/v1/teams/{team}/projects/` | `read:feedback` |
|
|
171
|
+
| `list_submissions` | `GET …/projects/{id}/submissions/` | `read:feedback` |
|
|
172
|
+
| `get_submission` | `GET …/submissions/{id}/` | `read:feedback` |
|
|
173
|
+
| `approve_submission` | `POST …/submissions/{id}/approve/` | `moderate:submissions` |
|
|
174
|
+
| `reject_submission` | `POST …/submissions/{id}/reject/` | `moderate:submissions` |
|
|
175
|
+
| `create_testimonial` | `POST …/projects/{id}/submissions/` | `write:testimonials` |
|
|
176
|
+
| `import_testimonials` | `POST …/projects/{id}/submissions/` (looped, ≤100 items) | `write:testimonials` |
|
|
177
|
+
| `list_widgets` | `GET …/projects/{id}/widgets/` | `read:widgets` |
|
|
178
|
+
| `get_widget_embed_code` | `GET …/widgets/{id}/embed-code/` | `read:widgets` |
|
|
179
|
+
| `get_nps_summary` | `GET …/surveys/` + `GET …/surveys/{id}/results/` | `read:surveys` |
|
|
180
|
+
|
|
181
|
+
Notes:
|
|
182
|
+
|
|
183
|
+
- **Moderation is explicit and audited.** `approve_submission` /
|
|
184
|
+
`reject_submission` call the dedicated action endpoints; `write:*` scopes
|
|
185
|
+
cannot approve — only `moderate:submissions` can. Responses include the
|
|
186
|
+
submission `id`, `status`, `moderated_by_id`, and `moderated_at`, so agent
|
|
187
|
+
actions stay attributable in the moderation audit trail.
|
|
188
|
+
- **Nothing is auto-published.** `create_testimonial` and
|
|
189
|
+
`import_testimonials` always create *pending* submissions that a moderation
|
|
190
|
+
step (human or explicit approve call) must publish.
|
|
191
|
+
- API errors surface as readable tool errors: HTTP 402 = plan quota/billing
|
|
192
|
+
block, HTTP 403 = missing scope/role/plan feature, HTTP 409 = invalid
|
|
193
|
+
moderation state transition.
|
|
194
|
+
|
|
195
|
+
## Development
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
uv run --with pytest --with httpx --with mcp pytest packages/withfeedback-mcp/
|
|
199
|
+
```
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# withfeedback-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [withfeedback.com](https://withfeedback.com):
|
|
4
|
+
lets AI assistants (Claude, Cursor, …) list and moderate feedback submissions,
|
|
5
|
+
create and bulk-import testimonials, inspect widgets and embed codes, and read
|
|
6
|
+
NPS survey results.
|
|
7
|
+
|
|
8
|
+
Transports: **stdio** (default) and **streamable HTTP** (`withfeedback-mcp --http`
|
|
9
|
+
or `WITHFEEDBACK_MCP_TRANSPORT=http`). HTTP mode binds `127.0.0.1` only —
|
|
10
|
+
see the security note below before changing that.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install withfeedback-mcp
|
|
16
|
+
# or run without installing:
|
|
17
|
+
uvx withfeedback-mcp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start with a personal access token (PAT)
|
|
21
|
+
|
|
22
|
+
The recommended setup for a local/stdio MCP server:
|
|
23
|
+
|
|
24
|
+
1. Log in at your withfeedback.com instance and open **Account → API Tokens**
|
|
25
|
+
(`/accounts/tokens/`).
|
|
26
|
+
2. Create a token with the scopes you need (see the table below). Copy it —
|
|
27
|
+
it is shown only once.
|
|
28
|
+
3. Configure your MCP client with `WITHFEEDBACK_TOKEN` (examples below).
|
|
29
|
+
|
|
30
|
+
API access requires a plan that includes the API/CLI/MCP feature (Pro and
|
|
31
|
+
above). Requests from lower plans return HTTP 403.
|
|
32
|
+
|
|
33
|
+
## Claude Desktop / Claude Code configuration
|
|
34
|
+
|
|
35
|
+
Claude Desktop (`claude_desktop_config.json`):
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"withfeedback": {
|
|
41
|
+
"command": "uvx",
|
|
42
|
+
"args": ["withfeedback-mcp"],
|
|
43
|
+
"env": {
|
|
44
|
+
"WITHFEEDBACK_TOKEN": "spd_your_token_here"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Prefer the OAuth2 device flow over a PAT? Omit `WITHFEEDBACK_TOKEN` — the
|
|
52
|
+
official public client id `7yQMsnY2Is2f5tCuwwgItoQu3fRkEX2wnzIRj0Vh` is the
|
|
53
|
+
built-in default (shown here explicitly; only needed to override):
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"withfeedback": {
|
|
59
|
+
"command": "uvx",
|
|
60
|
+
"args": ["withfeedback-mcp"],
|
|
61
|
+
"env": {
|
|
62
|
+
"WITHFEEDBACK_CLIENT_ID": "7yQMsnY2Is2f5tCuwwgItoQu3fRkEX2wnzIRj0Vh"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Claude Code:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
claude mcp add withfeedback --env WITHFEEDBACK_TOKEN=spd_your_token_here -- uvx withfeedback-mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Cursor configuration
|
|
76
|
+
|
|
77
|
+
`~/.cursor/mcp.json` (or the project's `.cursor/mcp.json`):
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"withfeedback": {
|
|
83
|
+
"command": "uvx",
|
|
84
|
+
"args": ["withfeedback-mcp"],
|
|
85
|
+
"env": {
|
|
86
|
+
"WITHFEEDBACK_TOKEN": "spd_your_token_here"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## OAuth2 device flow (alternative to a PAT)
|
|
94
|
+
|
|
95
|
+
If `WITHFEEDBACK_TOKEN` is not set, the server falls back to the OAuth2
|
|
96
|
+
device flow using the **official public client id**
|
|
97
|
+
`7yQMsnY2Is2f5tCuwwgItoQu3fRkEX2wnzIRj0Vh` (public by design — device-flow
|
|
98
|
+
clients carry no secret). Set `WITHFEEDBACK_CLIENT_ID` to use a different
|
|
99
|
+
client (e.g. on a self-hosted instance). On the first tool call the server
|
|
100
|
+
prints a verification URL and a short code on stderr (and tries to open your
|
|
101
|
+
browser):
|
|
102
|
+
|
|
103
|
+
1. The server calls `POST /o/device-authorization/` with the client id.
|
|
104
|
+
2. You open the printed URL, enter the code, and approve the requested scopes.
|
|
105
|
+
3. The server polls `POST /o/token/` until approval and then uses the access
|
|
106
|
+
token for all API calls.
|
|
107
|
+
|
|
108
|
+
Request a custom scope set with `WITHFEEDBACK_SCOPES` (space-separated).
|
|
109
|
+
|
|
110
|
+
**Note on Dynamic Client Registration:** open DCR (`POST /o/register/`) is
|
|
111
|
+
**disabled in production** (`DCR_ENABLED=False` is the production default) —
|
|
112
|
+
the endpoint is unauthenticated and unthrottled, so withfeedback.com does not
|
|
113
|
+
expose it. Use the published official client id (or a PAT); self-hosted
|
|
114
|
+
instances can register additional clients via Django admin or the
|
|
115
|
+
`create_oauth2_app` management command.
|
|
116
|
+
|
|
117
|
+
## Environment variables
|
|
118
|
+
|
|
119
|
+
| Variable | Default | Purpose |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `WITHFEEDBACK_API_URL` | `https://withfeedback.com` | API base URL (self-hosted/staging override) |
|
|
122
|
+
| `WITHFEEDBACK_TOKEN` | — | Personal access token (primary auth for stdio) |
|
|
123
|
+
| `WITHFEEDBACK_CLIENT_ID` | official public client id | OAuth2 client id for the device-flow fallback |
|
|
124
|
+
| `WITHFEEDBACK_SCOPES` | all tool scopes | Scopes requested during device flow |
|
|
125
|
+
| `WITHFEEDBACK_MCP_TRANSPORT` | `stdio` | Set to `http` for streamable HTTP |
|
|
126
|
+
| `WITHFEEDBACK_MCP_HOST` | `127.0.0.1` | HTTP bind address (loopback only by default) |
|
|
127
|
+
| `WITHFEEDBACK_MCP_PORT` | `8000` | HTTP bind port |
|
|
128
|
+
| `WITHFEEDBACK_MCP_ALLOW_REMOTE` | unset | Must be `1` to allow a non-loopback HTTP bind (read the security note first) |
|
|
129
|
+
|
|
130
|
+
On startup the server checks `GET /.well-known/speedpy.json` once and warns
|
|
131
|
+
(never fails) when the manifest is unreachable or the API is older than the
|
|
132
|
+
minimum this client expects.
|
|
133
|
+
|
|
134
|
+
## HTTP transport security
|
|
135
|
+
|
|
136
|
+
The streamable HTTP transport has **no per-caller authentication**: the
|
|
137
|
+
process holds a single account token (your PAT or device-flow grant), and
|
|
138
|
+
every HTTP caller acts as that account. Therefore:
|
|
139
|
+
|
|
140
|
+
- HTTP mode binds **`127.0.0.1` by default** and is intended for local
|
|
141
|
+
clients on the same machine.
|
|
142
|
+
- Binding a non-loopback address is **refused** unless you explicitly set
|
|
143
|
+
`WITHFEEDBACK_MCP_ALLOW_REMOTE=1`, and even then the server prints a
|
|
144
|
+
prominent warning: only do this behind a reverse proxy that authenticates
|
|
145
|
+
callers before they can reach the port.
|
|
146
|
+
- A properly multi-tenant **remote MCP server with per-user OAuth is a
|
|
147
|
+
future server-side deployment of withfeedback.com** — it is not something
|
|
148
|
+
this package provides. Do not expose this process to the internet.
|
|
149
|
+
|
|
150
|
+
## Tools and required scopes
|
|
151
|
+
|
|
152
|
+
Give your PAT (or device-flow grant) the scopes for the tools you plan to use:
|
|
153
|
+
|
|
154
|
+
| Tool | Endpoint(s) | Required scope |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `list_teams` | `GET /api/v1/teams/` | `read:teams` |
|
|
157
|
+
| `list_projects` | `GET /api/v1/teams/{team}/projects/` | `read:feedback` |
|
|
158
|
+
| `list_submissions` | `GET …/projects/{id}/submissions/` | `read:feedback` |
|
|
159
|
+
| `get_submission` | `GET …/submissions/{id}/` | `read:feedback` |
|
|
160
|
+
| `approve_submission` | `POST …/submissions/{id}/approve/` | `moderate:submissions` |
|
|
161
|
+
| `reject_submission` | `POST …/submissions/{id}/reject/` | `moderate:submissions` |
|
|
162
|
+
| `create_testimonial` | `POST …/projects/{id}/submissions/` | `write:testimonials` |
|
|
163
|
+
| `import_testimonials` | `POST …/projects/{id}/submissions/` (looped, ≤100 items) | `write:testimonials` |
|
|
164
|
+
| `list_widgets` | `GET …/projects/{id}/widgets/` | `read:widgets` |
|
|
165
|
+
| `get_widget_embed_code` | `GET …/widgets/{id}/embed-code/` | `read:widgets` |
|
|
166
|
+
| `get_nps_summary` | `GET …/surveys/` + `GET …/surveys/{id}/results/` | `read:surveys` |
|
|
167
|
+
|
|
168
|
+
Notes:
|
|
169
|
+
|
|
170
|
+
- **Moderation is explicit and audited.** `approve_submission` /
|
|
171
|
+
`reject_submission` call the dedicated action endpoints; `write:*` scopes
|
|
172
|
+
cannot approve — only `moderate:submissions` can. Responses include the
|
|
173
|
+
submission `id`, `status`, `moderated_by_id`, and `moderated_at`, so agent
|
|
174
|
+
actions stay attributable in the moderation audit trail.
|
|
175
|
+
- **Nothing is auto-published.** `create_testimonial` and
|
|
176
|
+
`import_testimonials` always create *pending* submissions that a moderation
|
|
177
|
+
step (human or explicit approve call) must publish.
|
|
178
|
+
- API errors surface as readable tool errors: HTTP 402 = plan quota/billing
|
|
179
|
+
block, HTTP 403 = missing scope/role/plan feature, HTTP 409 = invalid
|
|
180
|
+
moderation state transition.
|
|
181
|
+
|
|
182
|
+
## Development
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
uv run --with pytest --with httpx --with mcp pytest packages/withfeedback-mcp/
|
|
186
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "withfeedback-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for withfeedback.com — testimonials, feedback moderation, widgets, and NPS for AI agents"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
keywords = ["mcp", "testimonials", "feedback", "withfeedback"]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"mcp>=1.2",
|
|
11
|
+
"httpx>=0.27",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://withfeedback.com"
|
|
16
|
+
Documentation = "https://withfeedback.com/api/docs/"
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
withfeedback-mcp = "withfeedback_mcp.server:main"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/withfeedback_mcp"]
|