Skip to content

IA-5394: IASO mcp - #3302

Open
beygorghor wants to merge 14 commits into
developfrom
feat/IA-5394-mcp
Open

beygorghor wants to merge 14 commits into
developfrom
feat/IA-5394-mcp

Conversation

@beygorghor

@beygorghor beygorghor commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator

What problem is this PR solving?

Give Cursor / Claude a first-class IASO MCP on this Django process (JSON-RPC + OAuth), so agents can read/write IASO as the logged-in user.

Related JIRA tickets

IA-5394

Changes

  • New core app iaso.mcp: JSON-RPC at /mcp, OAuth 2.1 + PKCE + DCR at /oauth/ and /.well-known/.
  • Tools run in-process as request.user (same DRF permissions). Writes need confirm=true.
  • Catalog SPA at /mcp/ (browser) vs tools JSON (MCP clients). Prod image now npm run builds iaso/mcp/frontend.
  • Recipes: create pyramid from Excel, fill account from Excel, reattach instances.
  • Kill switch: MCP_ENABLED (on in DEBUG/tests and local compose; production must set MCP_ENABLED=true).

How to test

  1. MCP_ENABLED=true (already in docker-compose.yml). docker compose up.
  2. Browser: http://localhost:8081/mcp/ — log in, catalog UI.
  3. Cursor: MCP URL http://localhost8081/mcp/ (or ngrok) → Connect / OAuth.
  4. iaso_whoami, then a read (list_org_units). A write only after confirming url / user / account / project / source.
  5. docker compose run --rm iaso manage test iaso.mcp

Print screen / video

Screenshot 2026-09-04 at 13 03 45 Screenshot 2026-09-04 at 13 03 33

Notes

  • POST /mcp is Bearer-only (no session cookies). /mcp/me/ is session GET.
  • DCR is host-allowlisted and rate-limited. Excel paths are jailed to MEDIA_ROOT/mcp/ + packaged samples.
  • ToolCall audit log redacts secrets (xlsx_base64, tokens, passwords, json_body).

Doc

iaso/mcp/README.md

@beygorghor beygorghor changed the title MCP ac core IASO app IA-5394: IASO mcp Sep 4, 2026
Comment thread iaso/mcp/recipes/fill_account/apply.py Fixed
@beygorghor
beygorghor marked this pull request as ready for review September 4, 2026 11:03

@Bewi Bewi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I mostly reviewed the FE and with what I know of the project, don't hesitate to correct me or reject comments if it was intended.

I wonder if we should use MUI to keep in sync with other apps.

Until this day, stil no arrow functions found :D

A lot of functions should be FunctionalComponent.

Not sure about this syntax () => void copy() and saw it in a few places, why do we need to specify the void ?

Comment thread iaso/mcp/frontend/public/iaso-mark.png Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't this shared somewhere ?
In case it changes one day we are sur to not forget a place and that all apps are using the same one.

Comment thread iaso/mcp/frontend/src/pages/Install.tsx Outdated
import { CodeBlock, PageShell, primaryButtonClass } from "../ui";

type TabId =
| "cursor"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe move those ids in an enum or object, so we don't have hardcoded string in multiple places

const next = resumeOauth || catalogHome();

useEffect(() => {
void getMe()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Never saw this, what's the void for ?

Comment thread iaso/mcp/frontend/src/pages/Tools.tsx Outdated
return prop.type || "any";
}

function ToolCard({ tool }: { tool: Tool }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Functional component ?

Comment thread iaso/mcp/frontend/src/pages/Tools.tsx Outdated
);
}

export function Tools() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Functional Component ?


def infer_field_type(name: str, values: list[str]) -> str:
lowered = name.lower()
if lowered in {"sex", "gender"}:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd moved this in a dict so the logic here never changes, we only have to maintain the dict.
The entire if statement :D

survey.append(["type", "name", "label", "required"])
survey.append(["start", "start", "Start", ""])
survey.append(["end", "end", "End", ""])
needs_sex = False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😏

)


def _error_message(exc: IasoHTTPError) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I've seen this in other places, let's make it reusable

@@ -0,0 +1,355 @@
id,org_unit_id

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What are those org_unit ids ?
Where do them come from ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't this one define already ?

Comment thread hat/settings.py Outdated
USE_S3 = env.bool("USE_S3", default=False)
# MCP is a core app (iaso.mcp), not a plugin. On in local/dev/tests.
# Production must set MCP_ENABLED=true explicitly.
MCP_ENABLED = env.bool("MCP_ENABLED", default=DEBUG or IN_TESTS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I development I think you want to honor also the MCP_ENABLED and not magically start the mcp.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

note that it happen that I use DEBUG=true in prod to collect sql queries, don't want to have the MCP server starting or failing to start.

@beygorghor

Copy link
Copy Markdown
Collaborator Author
  • MCP_ENABLED no longer follows DEBUG. It is on only when the env var is true. Tests still turn it on, and Compose plus .env.example still set MCP_ENABLED=true. That matches the note that DEBUG=true in production (SQL logging) must not start MCP.
  • One API error helper. http_error_message in iaso/mcp/client.py replaces the three copies in the pyramid, fill-account, and reattach recipes.
  • XLSForm field types are a name map plus a short substring list. The sex-choices flag is include_sex_choices.
  • Install tab ids live in INSTALL_TAB. The two tool components Bewi flagged are FunctionComponents.
  • Failed JSON error bodies are logged with console.warn instead of being swallowed.
  • One IASO mark. iaso/mcp/static/mcp/iaso-mark.png is the file Django already serves at /iaso-mark.png (checked: HTTP 200, image/png). The Vite app points at that URL, and the two extra copies are removed.
  • instances.csv is removed. Nothing imported it. The reattach tools use instances-test.csv and org_unit_mapping.csv. The module docstring says those ids are from one account export, not a generic fixture.

@beygorghor beygorghor added the postrelease Should be merged just after the release label Oct 2, 2026
@beygorghor
beygorghor requested review from Bewi and mestachs October 2, 2026 09:53

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

postrelease Should be merged just after the release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants