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.
@@ -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"]