Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ priv-*
# temporary files
flask_session

# local user workspace
gunger/

# node modules
/node_modules
/package-lock.json
Expand All @@ -42,3 +45,4 @@ flask_session
**/my_chart.png
**/sample_pie.csv
**/sample_stacked_column.csv
application/external_apps/mcp/.env.azure
9 changes: 9 additions & 0 deletions application/external_apps/mcp/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.env
logs/
__pycache__/
*.pyc
*.pyo
*.pyd
.venv/
venv/
.env.*
17 changes: 17 additions & 0 deletions application/external_apps/mcp/Dockerfile
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason this would not run in a distroless (mcr.microsoft.com/azurelinux/distroless/python:3.12) multi-step build like the core app?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So no issues if distroless is a requirement. But at this point in the development, it is premature for the MCP Server itself and for non-production use-cases. We could easily have multiple docker files to accomodate distroless and not distroless.

Using a distroless container image means your container includes only your application and its runtime — no shell, no package manager, no debugging tools.

That sounds ideal, especially prematurely when adding new functionality/components to a system that may require higher visibility/observability in a given system.

Here are the main reasons why using a distroless image can be a bad idea:
#1 Debugging Becomes Painful
Distroless images don’t include:
bash / sh
curl
ps
netstat
apt / yum

  • i.e. Most standard Linux tools

If something goes wrong in production, you can’t exec into the container and inspect it. Because there is no shell.

This makes incident response slower and more complex. You need:

  • Sidecar debug containers
  • Ephemeral debug images
  • Rebuilds just to inspect behavior

That’s fine for mature software. Not so great for fast-moving agile software development.

#2 Local Development Becomes Friction-Filled
Distroless is great for production minimalism.
It’s terrible for:

  • Experimentation
  • Rapid troubleshooting
  • Investigating weird runtime edge cases

Developers often end up maintaining:

  • One Dockerfile for dev
  • One for prod

Now you have divergence risk.

#3 Observability Tooling Limitations
Many debugging or monitoring tools assume basic OS utilities exist.
If you rely on:

  • Runtime profilers
  • On-the-fly tracing
  • Crash dump analysis
  • Temporary diagnostics
    You may discover the container simply doesn’t support what you need.

#4 Operational Complexity Increases
Distroless pushes complexity outward.
Instead of “Let’s exec in and look.”
You now need:

  • Debug sidecars
  • Separate debug images
  • Stronger logging discipline
  • More complete telemetry
    If your observability is weak, distroless will expose it brutally.

#5 Compatibility Surprises
Some apps assume:

  • Timezone data exists
  • CA certificates are present
  • Locale data is available
  • /bin/sh exists for subprocesses
    Distroless images may omit or minimally include these.
    You discover issues at runtime.

#6 Security Gains May Be Marginal
Distroless is often sold as “more secure.”
But:

  • The real attack surface is usually your application.
  • Most container exploits rely on runtime vulnerabilities, not bash.
  • A properly hardened minimal distro (e.g., Alpine or slim Debian) may provide nearly identical risk reduction.
    Security benefit is real — but sometimes overstated.

#7 Not Ideal for Dynamic Environments
If your application:

  • Installs plugins dynamically
  • Runs scripts
  • Uses subprocess-heavy workflows
  • Executes user-provided commands
    Distroless can break those assumptions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be a requirement for production (a ton of customer have requested it because of the amount of CVEs that are eliminated), but your points are valid for rapid iteration/development. That said, for rapid iteration, you should also be able to run it locally/dev container that has obserability/tools. We also prefer to rely on high and strong logging discipline.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENV PYTHONUNBUFFERED=1
ENV FASTMCP_HOST=0.0.0.0
ENV FASTMCP_PORT=8000

EXPOSE 8000

CMD ["python", "server_minimal.py"]
107 changes: 107 additions & 0 deletions application/external_apps/mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# SimpleChat MCP Server (FastMCP)

This MCP server provides **14 tools** for interacting with SimpleChat via the Model Context Protocol (Streamable HTTP transport).

Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This README documents the MCP server, but the repo’s main feature docs live under docs/explanation/features/<version>/.... Consider adding a corresponding feature doc there (and linking to this README) so the new external integration is discoverable alongside other SimpleChat features.

Suggested change
For a high-level overview of this integration alongside other SimpleChat features, see the MCP feature documentation under `docs/explanation/features/<version>/MCP_SERVER_INTEGRATION.md`.

Copilot uses AI. Check for mistakes.
## Tools

### Authentication (2 tools)
| Tool | Auth | Description |
|------|------|-------------|
| **login_via_oauth** | None | Starts device-code OAuth login. Returns `user_code` + `verification_uri` for sign-in. |
| **oauth_login_status** | Optional | Returns current login status for both PRM (bearer token) and device-code flows. |

### User Profile (1 tool)
| Tool | Auth | SimpleChat API | Description |
|------|------|----------------|-------------|
| **show_user_profile** | Required | `POST /external/login` | Returns the authenticated user's profile, roles, and all token claims. |

### Conversations & Chat (3 tools)
| Tool | Auth | SimpleChat API | Description |
|------|------|----------------|-------------|
| **list_conversations** | Required | `GET /api/get_conversations` | Returns all conversations for the authenticated user. |
| **get_conversation_messages** | Required | `GET /api/get_messages` | Returns messages for a specific conversation. Params: `conversation_id` (required). |
| **send_chat_message** | Required | `POST /api/chat` | Sends a message and returns AI response. Params: `message` (required), `conversation_id` (optional — creates new if empty). |

### Personal Workspace (2 tools)
| Tool | Auth | SimpleChat API | Description |
|------|------|----------------|-------------|
| **list_personal_documents** | Required | `GET /api/documents` | Returns paginated personal documents. Params: `page`, `page_size`, `search`, `classification`, `author`, `keywords`. |
| **list_personal_prompts** | Required | `GET /api/prompts` | Returns paginated personal prompts. Params: `page`, `page_size`, `search`. |

### Group Workspaces (3 tools)
| Tool | Auth | SimpleChat API | Description |
|------|------|----------------|-------------|
| **list_group_workspaces** | Required | `GET /api/groups` | Returns paginated group workspaces the user is a member of. Params: `page`, `page_size`, `search`. |
| **list_group_documents** | Required | `GET /api/group_documents` | Returns documents from the user's active group. Params: `page`, `page_size`, `search`, `classification`, `author`, `keywords`. |
| **list_group_prompts** | Required | `GET /api/group_prompts` | Returns prompts from the user's active group. Params: `page`, `page_size`, `search`. |

### Public Workspaces (3 tools)
| Tool | Auth | SimpleChat API | Description |
|------|------|----------------|-------------|
| **list_public_workspaces** | Required | `GET /api/public_workspaces` | Returns paginated public workspaces. Params: `page`, `page_size`, `search`. |
| **list_public_documents** | Required | `GET /api/public_documents` | Returns documents from the user's active public workspace. Params: `page`, `page_size`, `search`. |
| **list_public_prompts** | Required | `GET /api/public_prompts` | Returns prompts from the user's active public workspace. Params: `page`, `page_size`, `search`. |

## Prerequisites

- SimpleChat running locally (default <https://localhost:5000>)
- Entra bearer token issued for the SimpleChat API (audience `api://<CLIENT_ID>`)
- The token includes the **ExternalApi** role (or equivalent scope)

## Setup

1. Create a `.env` file based on `example.env`.
2. Install dependencies:
- `pip install -r requirements.txt`

## Run

- The MCP server always listens on: `http://localhost:8000/mcp`
- Run locally via script: `.\run_mcp_server.ps1`
- Run locally directly: `python server_minimal.py`

## Environment Variables

### Required — SimpleChat Connection
- `SIMPLECHAT_BASE_URL` — Base URL for SimpleChat (e.g. `https://localhost:5000`)
- `SIMPLECHAT_VERIFY_SSL` — Whether to verify SSL certificates (`true` or `false`)

### Required — MCP Server Configuration
- `MCP_REQUIRE_AUTH` — Enable PRM authentication (`true` or `false`)
- `MCP_PRM_METADATA_PATH` — Path to PRM metadata JSON file (e.g. `prm_metadata.json`)
- `MCP_SESSION_TOKEN_TTL_SECONDS` — TTL in seconds for cached MCP session tokens (e.g. `3600`)
- `FASTMCP_HOST` — Bind host (set by `run_mcp_server.ps1` to `0.0.0.0`)
- `FASTMCP_PORT` — Bind port (set by `run_mcp_server.ps1` to `8000`)
- `FASTMCP_SCHEME` — URL scheme for PRM metadata (`http` for local, `https` for production)

### Required — OAuth / Device-Code Flow
- `OAUTH_AUTHORIZATION_URL` — Entra authorization endpoint
- `OAUTH_TOKEN_URL` — Entra token endpoint
- `OAUTH_DEVICE_CODE_URL` — Entra device-code endpoint (auto-inferred from `OAUTH_TOKEN_URL` if omitted)
- `OAUTH_CLIENT_ID` — App registration client ID
- `OAUTH_CLIENT_SECRET` — App registration client secret (not sent in device-code flow for public clients)
- `OAUTH_SCOPES` — Space-separated scopes (e.g. `api://<CLIENT_ID>/ExternalApi User.Read offline_access openid profile`)
- `OAUTH_TIMEOUT_SECONDS` — Device-code polling timeout in seconds (e.g. `900`)

### Optional
- `OAUTH_REDIRECT_PORT` — Redirect port for auth-code flow (default: `53682`)
- `OAUTH_USE_DEVICE_CODE` — Enable device-code flow (`true` or `false`)
- `OAUTH_OPEN_BROWSER` — Auto-open browser during device-code flow (`false` by default)

## OAuth Login Notes

- `login_via_oauth` uses the device-code flow. The tool response includes `user_code` + `verification_uri`, and the server prints a message to stdout.
- Background polling exchanges the device code for an access token, then creates a SimpleChat session via `/external/login`.
- PRM authentication (bearer token from MCP client) is also supported and takes priority over device-code flow.

## PRM (Protected Resource Metadata)

The MCP server serves PRM metadata at:
`http://localhost:8000/.well-known/oauth-protected-resource`

Update `prm_metadata.json` with your Entra tenant, client, and scopes.

## Deployment

Use `deploy_mcp_containerapp.ps1` to build and deploy to Azure Container Apps.
The Dockerfile is included for container builds.
Loading
Loading