Skip to content

feat(mcp): full self-service MCP surface for scoped users - #77

Merged
jaymeklein merged 3 commits into
mainfrom
feat/mcp-full-self-service
Jul 27, 2026
Merged

feat(mcp): full self-service MCP surface for scoped users#77
jaymeklein merged 3 commits into
mainfrom
feat/mcp-full-self-service

Conversation

@jaymeklein

Copy link
Copy Markdown
Owner

Summary

Turns the embedded MCP server into a complete self-service plane: a scoped user can run EmbedBase end-to-end from an AI chat using their own API key, with every tool enforcing that user's grants exactly like the REST API. Admin-plane concerns (app config, user/key management) stay in the console.

What's included

  • MCP tool surface (~27 tools) — presigned two-step upload (request_upload → PUT → confirm_upload), download_document, document status/chunks/reprocess/delete, workspace/collection/tag CRUD, list_ingestion_jobs/get_ingestion_stats, and get_rate_limit.
  • manage_tags capability — a new grantable privilege honoured by REST, MCP, and the console, so a non-admin can manage tags in workspaces they can see (gated by read on the workspace + write on the collection/document for assignment).
  • Per-file retentionretention_days (1–30; omit = permanent) on every upload path, superseding the global temp-retention knob.
  • Live tool catalogueGET /mcp-tools renders the Settings → MCP tool list and the downloadable SKILL.md from the actually-registered tools, so they can never drift; a drift-guard test asserts the catalogue equals the live tool set. SKILL.md also documents the accepted upload formats (.pdf, .md/.markdown, .txt) and how to convert a structured PDF to full text — markdown tables for tables, description + transcription for images.
  • Per-user MCP rate limit — set per user in the console (users.rate_limit_rpm; 0 = inherit the global mcp.rate_limit_rpm), enforced by a post-auth per-user token bucket keyed by user id (exact under concurrency; the pre-auth guard still protects the auth path).
  • Optional email — user accounts no longer require an email address (identified by username); UNIQUE(email) still forbids duplicate addresses while permitting many no-email accounts.

Migrations: 0010 (users.rate_limit_rpm), 0011 (users.email nullable, via batch mode for SQLite).

Testing

  • ruff ✅ · mypy ✅ (122 files) · pytest917 passed, 13 skipped
  • frontend tsc --noEmit
  • docs/openapi.yaml regenerated
  • /standards-check clean; independent adversarial correctness reviews on the rate-limit and optional-email changes (all findings fixed and re-verified)

Expand the embedded MCP server from a handful of read tools into a complete
self-service plane so a scoped user can run EmbedBase end-to-end from an AI
chat with their own key, each tool enforcing that user's grants exactly like
its REST counterpart.

- mcp: presigned two-step upload (request_upload/confirm_upload), download,
  document status/chunks/reprocess/delete, workspace/collection/tag CRUD,
  ingestion jobs & stats, and rate-limit introspection.
- permissions: new grantable `manage_tags` capability (REST + MCP + console)
  so non-admins can manage tags in workspaces they can see.
- ingestion: per-file `retention_days` (1-30) replacing the global temp knob.
- mcp: live tool catalogue (GET /mcp-tools) drives the console tool list and
  the generated SKILL.md from the registered tools so they never drift; SKILL.md
  documents accepted upload formats and PDF-to-full-text conversion (markdown
  tables, image description + transcription).
- mcp: per-user request rate limit, configurable per user in the console
  (users.rate_limit_rpm; 0 = inherit the global mcp.rate_limit_rpm), enforced
  by a post-auth per-user token bucket.
- users: email is now optional (an account is identified by its username).

Adds Alembic 0010 (users.rate_limit_rpm) and 0011 (users.email nullable).
…e the agent skill

Three additions to the self-service MCP surface (PR #77):

- Attach an original source file to a document — request_original_upload /
  confirm_original_upload store the raw source under a ".orig" key; it is never
  embedded and is fetched via download_document(original=true). Worker delete
  removes both the parse object and the original.
- Validate content-type at both presigned confirm steps — Storage.read_head +
  upload.validate_content magic-byte-check the uploaded object, returning 415 and
  purging it on a type mismatch, so a reserved .pdf can't be satisfied with a PNG.
- Deliver the agent skill as a progressive-disclosure bundle — a lean SKILL.md plus
  references/{searching,uploading,tools}.md, downloaded as a zip (dependency-free
  store-only writer), so a read-only session never loads the upload rules.
  search_documents is documented as the primary retrieval tool; get_document_chunks
  as a follow-up for specific chunk_ids.
…rvice

# Conflicts:
#	api/services/documents.py
#	api/services/upload.py
#	tests/unit/test_upload.py
@jaymeklein
jaymeklein merged commit d062355 into main Jul 27, 2026
8 checks passed
@jaymeklein
jaymeklein deleted the feat/mcp-full-self-service branch July 27, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant