Conversation
Serve the Model Context Protocol over stdin/stdout with three tools: gam_syntax searches GamCommands.txt, gam_docs reads and searches the wiki, gam_run executes a command in-process, read-only unless the server is started with allowwrites. Standard library only; the module is loaded on demand like the YubiKey support.
|
My standard policy on AI pull requests: I need a human being to respond with:
|
|
Sorry for not saying this in the description: the PR was generated with Claude Code (model Claude Fable 5.1), from a brief I wrote and with me reviewing at checkpoints. 1. What I'm trying to do. I admin a Workspace domain and already use an assistant to work out GAM command lines from the wiki. The assistant gets syntax wrong from memory, which is exactly the risk Zircoz raised on 2. How it was made. I wrote a brief fixing the constraints: one PR, no new dependencies, GAM's conventions ( I did not write the code by hand. If a PR this size from an AI is not something you want, the |
The server could exit before a deferred gam_run reply was written when the client closed stdin right after the request. Count accepted commands and wait until each has replied. Redact quoted token values without dropping their quotes.
Closes #1855.
What this adds
gam mcp [allowwrites] [maxrows <Number>] [timeout <Number>] [nowiki]serves the Model Context Protocol over stdin/stdout, so an AI assistant (Claude Desktop, Claude Code, Gemini CLI, ...) that spawnsgam mcpgets three tools and a set of resources:gam_syntax- keyword search overGamCommands.txt; returns the verbatim syntax block, its section, and the definitions of the non-terminals it references. This is the direct answer to mcp server? #1855.gam_docs- read a wiki page or a section of it, or search page titles and headings.gam_run- run one GAM command in-process and returnrc,stdout,stderrand, forprintcommands, the CSV rows as a list of objects. Read-only by default.gam://syntax,gam://syntax/<Section>,gam://wiki/<Page>.Zero new dependencies: the transport and the JSON-RPC methods are standard library; fetches go through
getHttpObj(). The module is loaded on demand withLazyLoader, likeyubikey, and listed inhiddenimports.Why this shape
A fixed set of tools cannot cover GAM. dbarks/mcp-gam wraps a dozen user, group and OU commands and will always cover a sliver of the roughly one thousand command shapes behind GAM's parser. A syntax lookup plus a guarded runner covers all of it, and it stays current because it reads
GamCommands.txtand the wiki rather than a hand-written copy of them.Safety model
ProcessGAMCommand(MAIN_COMMANDS,MAIN_COMMANDS_WITH_OBJECTS, the calendar, course and resource subcommand tables,USER_COMMANDS*,CROS_COMMANDS*), stopping before any function is called and before any API is touched. TheActcode from the table is the verdict:INFO,LIST,PRINT,SHOW,REPORT,CHECK,EXISTS,LOOKUP,COMMENT,GET_COMMAND_RESULT, plusversionandhelp, are read-only; everything else requiresallowwrites;DOWNLOADis refused even then. Unresolvable command lines are refused, never guessed.batch,tbatch,csv,csvtest,loop,redirect,config,multiprocessexit,select ... save,oauth,audit,sendemail,sendreply, every file based entity selector (file,csvfile,datafile,csvkmd,csvsubkey,csvdata,crosfile*,croscsv*), and non read-only commands onproject,svcacctandsakeyobjects.todriveis refused on a read-only server.ProcessGAMCommandMulticapture pattern:StringIObuffers inGM.Globals[GM.STDOUT]/[GM.STDERR]and a collector inGM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE], thenCallGAMCommand. Rows come straight from the queue payload, no CSV text is parsed. One command at a time, with a timeout.debug_leveloutput and subprocesses cannot corrupt the channel. Access and refresh tokens are redacted from captured output.Docs sourcing
GamCommands.txtis read from the GAM folder (it ships with every release) or fromsrc/in a checkout; apip install gam7falls back to fetching it from GitHub once a day into<cache_dir>/mcp/. Wiki pages come fromwiki/in a checkout, else fromraw.githubusercontent.com/wiki/GAM-team/GAM/with a 24 hour cache;nowikidisables fetches for air-gapped installs. Nothing from the wiki is bundled.Protocol
Both eras of the specification are served: the per-request
_metamodel withserver/discover(2026-07-28) and theinitializehandshake (2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05). Result shapes and error codes follow whichever the client uses.How to try it
or, for Claude Desktop and Gemini CLI,
{"mcpServers": {"gam": {"command": "/path/to/gam", "args": ["mcp"]}}}. Full configuration and examples are in the new wiki page MCP-Server. Without an assistant:Files changed
src/gam/gamlib/glmcp.py- new; transport, docs index and cache, classifier, runner, protocol dispatch, in separate classes.src/gam/__init__.py-LazyLoaderline,doMCPServernext todoUsage,'mcp'inMAIN_COMMANDS.src/gam/gamlib/glmsgs.py- newMCP_*messages.src/gam.spec- hidden import.src/GamCommands.txt-# MCP Serversection after# Version and Help.wiki/MCP-Server.md,wiki/_Sidebar.md- new page, linked under Command Processing.src/GamUpdate.txt- entry under 7.49.00; renumber as you see fit,__version__is untouched..github/workflows/build.yml- a stdio smoke test after the version check on every job:initialize,tools/list, agam_syntaxcall and agam_runofversion simple, asserting the three tool names, the syntax block, the version string, and that stdout holds nothing but JSON-RPC. No secrets, no network.Open for feedback
allowwrites,maxrows,timeout,nowiki.Actset, in particularCOMMENTandGET_COMMAND_RESULT.maxrows500,timeout300.todriveshould ever be allowed, and whethersendreply,enable apisand theproject/svcacct/sakeywrites should stay refused withallowwrites.getcommand|issuecommand ... csv; a position-aware rule would be possible if that matters.gam_runis not a filesystem sandbox: options such aslocalfileortargetfolderare not blocked; the wiki page says so. Happy to enumerate more if you prefer.mcpas a[project.scripts]alias.SetGlobalVariables()prints "Config File ... Initialized" to stdout before any command runs, so the first evergam mcpbreaks the transport; the wiki says to rungam versiononce. Routing that message to stderr would remove the caveat but touchesSetGlobalVariables(), so I left it alone.