feat(iri): MCP server + skill card for ALCF IRI tools - #199
Open
JinchuLi2002 wants to merge 14 commits into
Open
feat(iri): MCP server + skill card for ALCF IRI tools#199JinchuLi2002 wants to merge 14 commits into
JinchuLi2002 wants to merge 14 commits into
Conversation
_start_reauth and the 401 recovery hint used to always emit "invoke alcf_auth with action='complete_reauth'..." -- correct for the category tool set but wrong for flat, where the tool is alcf_auth_complete_reauth(auth_code=...). Under flat, gpt-4o would read the mismatched instruction, fail to find the named tool, and either hallucinate "session expired" or restart the whole reauth loop. Walk the call stack to detect which tool-wrapper module is on it, then emit the matching phrasing. Default is category when neither module is on the stack (e.g. bare-CLI use). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps the same 43 flat tools shipped by single_agent_iri as a standalone MCP server (chemgraph.mcp.alcf_iri_mcp) so any MCP-speaking client -- Claude Desktop, other agent frameworks, or main_agents MCP wiring -- can reach the ALCF IRI API without going through LangChain directly. - Reuses chemgraph.tools.alcf_iri_core.CATEGORIES verbatim; the MCP file is a thin adapter that builds per-action wrapper functions with the right signatures and registers them via FastMCP.add_tool. - Tool naming matches the LangChain flat set (alcf_<category>_<action>) so agents that switch backends can reuse the same tool references. - Auth, refresh, unsafe-write gating, and error surface are inherited from alcf_iri_core -- no divergence to maintain. - Default port 9010 for streamable_http. Verified end-to-end against the live public /status/resources endpoint (no token required): list_tools returns 43 entries with correct JSON schemas, and alcf_status_list_resources returns the same payload the category tools and LangChain flat tools already return. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Creates src/chemgraph/skills/ (new dir) with alcf_iri.md, a capability card the main_agent workflow can read to decide when to route a query at the ALCF IRI tool set. Format follows the Anthropic Skills convention: YAML frontmatter (name, description, mcp_server, tool_prefix) plus markdown sections for when-to-use, auth, safety, tool naming, example flows, and common failure modes. This is a first pass; the exact fields are open to renaming once main_agent settles on a schema. Content is skill-router-oriented (not developer-oriented like the opencode SKILL.md): tells the agent what problems this skill handles, what problems it does NOT handle, the auth prerequisites it enforces, and the multi-hop patterns that come up most often. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- docs/mcp_servers.md: adds an ALCF IRI section under "Specialized servers" with the launch commands, auth summary, and cross-refs to the LangGraph workflow and the skill card. - examples/iri/README.md: appends a short "Also available as an MCP server" section so readers who land on the LangGraph example discover the MCP path (and vice versa). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MCP server now mirrors the LangGraph workflow's flat-vs-category split. Selection precedence: CLI --variant > $CHEMGRAPH_IRI_MCP_VARIANT > "flat". The env var exists for MCP client configs that pass env but not argv (e.g. Claude Desktop mcpServers entries). - register(variant) is public so embedders that import the module can pick either shape after construction. - Default (flat) is registered at import time so plain from chemgraph.mcp import alcf_iri_mcp still yields a populated server, matching the previous behaviour of this file. - CLI path re-parses --variant and only rebuilds the tool set if it differs from what import time chose; FastMCPs tool store is not part of its public API so we swap in a fresh ToolManager rather than mutate. Verified end-to-end: flat registers 43 tools (alcf_facility_get, ..., alcf_task_list), category registers 7 dispatchers (alcf_facility, ..., alcf_auth), env var and CLI flag both work, --variant is stripped from sys.argv before run_mcp_server sees it. Docs (docs/mcp_servers.md, examples/iri/README.md, skill card) updated to describe both variants and how to pick between them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JinchuLi2002
marked this pull request as ready for review
August 20, 2026 19:46
tdpham2
reviewed
Aug 20, 2026
Collaborator
There was a problem hiding this comment.
Could you make an additional skill.md file that is aimed for a coding agent to launch bash command (curl) to use the IRI, not describing the MCP.
Please test it with Claude code to make sure it works well based on the benchmark you created earlier. For the benchmark scoring, make it score either 0 or 1, so that the style doesn't matter, only final answer matters.
I will try to give you a deepagent graph for you to test (similar to Claude code) for you to test next week with the skills.
Collaborator
There was a problem hiding this comment.
@JinchuLi2002 Just want to follow up if you have the skills ready. Thanks!
Followup on PR argonne-lcf#199: coding agents whose runtime is bash+curl (not the ChemGraph MCP server) also need an ALCF IRI skill card. The target audience is Claude Code, or a forthcoming deepagent-style graph. This adds: - src/chemgraph/skills/alcf_iri_bash.md -- curl-oriented capability card. Same when-to-use / auth / safety framing as alcf_iri.md, but the endpoint recipes are runnable bash (curl + jq) rather than MCP tool names. Covers all 43 IRI actions grouped by category, with multi-hop patterns (resolve names to UUIDs, paginate list_jobs) and identity-picker guidance for the Globus login flow. - examples/iri/bench_claude_code.py -- headless-Claude-Code harness. Loads the skill via --append-system-prompt, restricts tools to Bash, runs each of the 15 qeval questions, and writes a JSONL with the answer + full tool-call trace + wall time. Designed to feed the notebooks binary judge cell so Claude+curl and single_agent_iri can be compared under identical scoring. - examples/iri/README.md -- documents both. The notebooks binary-judge cell itself lives in the untracked notebooks/iri_qeval.ipynb (per PR argonne-lcf#173 policy that notebooks are personal analysis, not shipped). The judge system prompt is: Return 1 if the final answer factual claims match the trace, else 0. Ignore formatting and style. Ignore verbosity. Pagination gotcha: reporting N from a single page of ~100 is 0. Same rubric idea, style-blind, so cross-runtime comparisons are fair. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JinchuLi2002
added a commit
to JinchuLi2002/ChemGraph
that referenced
this pull request
Aug 25, 2026
Per PI followup on PR argonne-lcf#199: coding agents whose runtime is bash+curl (not the ChemGraph MCP server) also need an ALCF IRI skill card. The target audience is Claude Code, or PIs forthcoming deepagent-style graph. This adds: - src/chemgraph/skills/alcf_iri_bash.md -- curl-oriented capability card. Same when-to-use / auth / safety framing as alcf_iri.md, but the endpoint recipes are runnable bash (curl + jq) rather than MCP tool names. Covers all 43 IRI actions grouped by category, with multi-hop patterns (resolve names to UUIDs, paginate list_jobs) and identity-picker guidance for the Globus login flow. - examples/iri/bench_claude_code.py -- headless-Claude-Code harness. Loads the skill via --append-system-prompt, restricts tools to Bash, runs each of the 15 qeval questions, and writes a JSONL with the answer + full tool-call trace + wall time. Designed to feed the notebooks binary judge cell so Claude+curl and single_agent_iri can be compared under identical scoring. - examples/iri/README.md -- documents both. The notebooks binary-judge cell itself lives in the untracked notebooks/iri_qeval.ipynb (per PR argonne-lcf#173 policy that notebooks are personal analysis, not shipped). The judge system prompt is: Return 1 if the final answer factual claims match the trace, else 0. Ignore formatting and style. Ignore verbosity. Pagination gotcha: reporting N from a single page of ~100 is 0. Same rubric idea, style-blind, so cross-runtime comparisons are fair. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps bench_claude_code.py in a self-contained Jupyter notebook so reviewers can Run All instead of stitching together a 5-step manual procedure across notebook + CLI + judge cell. Also threads a --bare / bare=True flag through the harness so each Claude Code subprocess runs isolated from ambient project state (memory, CLAUDE.md, hooks, plugins). That gives the fair signal originally requested -- only what the skill file teaches the agent should count. Notebook flow (Run All): 1. Preflight (claude/curl/jq on PATH, ALCF_API_TOKEN or on-disk cache, ANTHROPIC_API_KEY, harness importable). 2. Config (qids, trials, concurrency, bare, judge model, output paths). 3. Sweep: run_all() spawns one fresh `claude -p --bare` per (q, trial) with only Bash enabled and the skill appended to the system prompt. Live tqdm updates with per-question wall+cost. 4. Binary judge: AsyncAnthropic against claude-opus-4-20250514, strict 0/1 style-blind rubric identical to the one in iri_qeval.ipynb. 5. Report: per-question pass table with turns/tokens/wall/cost + one representative failure rationale. 6. Drill-down: prints question, answer, and trace tail for each failing run so the skill can be iterated on cheaply. Also fixes a text bug -- the benchmark has 16 questions (q1-q15 + q20), not 15 as the earlier README claimed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two blockers for running the notebook without ANTHROPIC_API_KEY: 1. Preflight hard-asserted ANTHROPIC_API_KEY, but Claude Code accepts OAuth via `claude login` too. Softened to a warning -- if the sweep needs the key it will surface a clear Claude-Code error. 2. Binary judge was hardcoded to AsyncAnthropic + Anthropic model id. ALCF users typically dont have ANTHROPIC_API_KEY; they route through the argo shim (as iri_qeval.ipynb does). Rewired to use chemgraph.models.loader.load_chat_model which dispatches by model name prefix, so the same call site works for both. New config cell exposes JUDGE_PROVIDER with auto-detect: argo -- if $ARGO_USER set or local proxy at 127.0.0.1:18085 responds anthropic -- if $ANTHROPIC_API_KEY set Override with $BENCH_JUDGE_PROVIDER=argo|anthropic. Judge model defaults follow the provider: argo -> argo:claude-opus-4.7 anthropic -> claude-opus-4-20250514 Argo path monkey-patches ARGO_LOCAL_OPENAI_MODEL_MAP with the current claude-opus family entries (same lift as iri_qeval.ipynb) so the shim recognises the judge model id. README bumped with the auto-detect behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without stdin redirected, claude -p waits ~3s for piped input, warns "no stdin data received in 3s, proceeding without it", and exits 1 without processing the prompt. Every question in the sweep failed silently this way (16/16 wall=3s, no answer, no cost). Set stdin=DEVNULL on the create_subprocess_exec call. Verified the warning is gone and subprocesses proceed to actual inference. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude Code's --bare explicitly disables OAuth keychain reads, so running `claude -p --bare` without ANTHROPIC_API_KEY returns "Not logged in · Please run /login" and exit 0 (with is_error=true in the JSON). Previous sweep failed 16/16 with $0 cost and no traces. Two changes: 1. Notebook cell 2 gates BARE on presence of ANTHROPIC_API_KEY. BARE_REQUESTED preserves user intent; effective BARE falls back to False with a printed NOTE explaining the tradeoff (ambient project state may leak into results). Set ANTHROPIC_API_KEY to get truly isolated skill-only measurement. 2. Harness now respects the JSON is_error field on claude subprocess output -- treats it as failure and surfaces the message as the row's error, so the JSONL is diagnostic instead of silently ok. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude -p --output-format json returns only {result, usage, cost,
num_turns} -- no message history. That made every runs trace_rendered
come back (no tool calls) regardless of whether Bash actually ran.
The binary judge then scored those as unsupported / hallucinated,
even when curl had genuinely executed and returned the correct
answer. Diagnosed by smoke-testing a raw curl on q1: sweep answer 9
matched the live API count, but trace was empty -> false-negative
fabrication verdict.
Switch to --output-format stream-json --verbose which emits one JSON
object per line, including tool_use and tool_result events. Rewrote
_render_trace to iterate the flat event list, still emitting the
CALL <tool>(args) / RESULT: <text> shape the judge expects. Also
grabs usage/cost/num_turns from the final result event.
Smoke q1 (bare=False, ALCF_API_TOKEN via refreshed nested-shape cache):
ok: True
answer: 9
turns: 2 cost: $0.308
trace: CALL Bash({...curl .../status/resources | jq length})
RESULT: 9
Not pushing per user instruction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Compound shell scripts Claude Code emits for multi-hop questions (env-var setup + curl + jq) run 1-2kB. Truncating at 500 chars cuts off the actual jq filter, so the binary judge cant verify whether the query was correct -- observed on q15 where the answer was plausibly right but rationale said 'no visible query filtering queued jobs and sorting by age.' Not pushing per user instruction. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-ship cleanup for reviewer-visible surfaces: - src/chemgraph/tools/alcf_iri_core.py: change hard-coded jinchuli@alcf.anl.gov in the LLM re-auth prompts to <user>@alcf.anl.gov placeholder. This text goes to the LLM as guidance and shouldnt name any specific user. - examples/iri/bench_claude_code.ipynb: change ARGO_USER default from a hard-coded name to empty string (still reads $ARGO_USER from env). Adds a warning when the argo judge path is selected without ARGO_USER set. Clears all cell outputs (they contained local paths and argo user identity from a prior run). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JinchuLi2002
force-pushed
the
jinchu/iri-mcp-and-skill
branch
from
August 25, 2026 17:21
86dccf8 to
e174e56
Compare
- examples/iri/bench_claude_code.py: drop unused import subprocess (last use went away when we switched to asyncio.subprocess). - examples/iri/bench_claude_code.ipynb: split multi-import in the preflight cell (ruff E401), and drop the redundant import asyncio from the judge cell (ruff F811 -- already imported in the sweep cell; ruff treats notebook cells as one logical file). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follows up merged PR #173:
chemgraph.mcp.alcf_iri_mcp— 43 flat tools, one per endpoint, launchable viapython -m chemgraph.mcp.alcf_iri_mcp(stdio) or--transport streamable_http --port 9010. Wraps the samealcf_iri_coreimplementation assingle_agent_iri, so auth / refresh / unsafe-write gating / error surface are identical. Any MCP-speaking client (Claude Desktop,main_agent's MCP wiring, other agent frameworks) can now reach the IRI API without going through LangChain.src/chemgraph/skills/alcf_iri.md— first entry in a newskills/directory, following the Anthropic Skills convention (YAML frontmatter + when-to-use + auth + example flows + failure modes). Intended formain_agent-style skill routing.Also folds in one small fix carried over from #173 review:
alcf_iri_core.py:_start_reauthand the 401 recovery hint now emit tool names that match whichever wrapper module is on the call stack (flat vs category). Was producing category-flavoured instructions ("invoke alcf_auth with action='complete_reauth'") under the flat variant, which caused gpt-4o to hallucinate "session expired" instead of callingalcf_auth_complete_reauth(auth_code=...).Files
src/chemgraph/mcp/alcf_iri_mcp.py(new)src/chemgraph/skills/alcf_iri.md(new dir + file)src/chemgraph/tools/alcf_iri_core.pydocs/mcp_servers.md,examples/iri/README.mdTest plan
python -c "from chemgraph.mcp import alcf_iri_mcp; ..."— server module imports cleanly, registers 43 tools with the expected names (alcf_facility_get,alcf_compute_submit_job,alcf_auth_start_reauth,alcf_auth_complete_reauth, ...)./status/resourcesendpoint viamcp.call_tool('alcf_status_list_resources', {})returns the same payload as the LangChain flat tools and category tools (HTTP 200, 9 resources, 4 up: Sophia, Crux, Aurora, Polaris).params_schemainalcf_iri_core.CATEGORIES— required fields marked required, optional fields typedOptional[T]withdefault: null.categorywhen neither module is present.main_agent— deferred until main_agent's MCP wiring for IRI lands; nothing here should require changes there.Open questions for review
skills/directory format is a first pass following the Anthropic Skills convention. Happy to rename frontmatter fields (mcp_server,tool_prefix) or restructure entirely to whatevermain_agentends up expecting.streamable_http. If there's an existing port-allocation table for ChemGraph MCP servers, easy to move.🤖 Generated with Claude Code