CourseForgeDocs v5.3.0

Automate with MCP

MCP tool reference

Every tool the MCP endpoint offers, grouped by scope, with its arguments - generated from the tool registry of this release.

CourseForge 5.3.0 offers 107 tools in 11 groups. This page is generated from the tool registry itself, so it lists exactly what tools/list returns to a connection with every scope. Each tool also carries protocol annotations - whether it reads or writes, whether it is destructive, whether it spends credit - which clients may use to warn before calling it.

reads changes nothing   writes changes data   destructive deletes something   spends credit calls the account's own AI provider   admin administrators only   * required argument

The groups#

Group Tools What it is for Who Spends
courses 6 List, read, create, rename and delete courses. everyone no
research 3 Take the research assignment for a course, and store what searching the web found. The client does the searching, so nothing here spends anything. everyone no
structure 6 Read the outline brief, write an outline, and have CourseForge design one itself. everyone yes
pages 8 List and read pages, take a writing brief, store a finished page, and generate one through the profile's model. Also retitles pages and chapters and rewrites a chapter description, which edits the stored outline, because chapters and pages are matched by title. everyone yes
details 5 The thirteen switchable elements and seven values, at course, chapter or page level. everyone no
tags 8 The tag library and what is tagged with what. everyone no
runs 8 Start, watch and cancel background and batch runs. This is the group that spends money at scale. everyone yes
profiles 25 AI accounts, models and BookStack instances. Keys are never readable, but they can be replaced. everyone no
publish 5 Push a course into BookStack and resolve cross references. everyone no
account 9 Who you are connected as, your own password and display name, and your own connections. Never anybody else's. everyone no
admin 24 Accounts, settings, the cron token, diagnostics and updates. Only ever available to an administrator. administrators no

How to connect, narrow a connection and choose between writing pages yourself and having CourseForge write them: Connect an AI client.

Courses#

List, read, create, rename and delete courses.

get_next_step#

What to do next reads

Where a course has got to and the single next tool call that moves it forward, with the reason and the arguments. Call it with no course_id to be pointed at the right course, or with one to be told the next step for that course. Start here if you do not know the sequence, and come back to it whenever you lose track: the answer is worked out from the database every time, so it is correct even after a restart, and it never depends on remembering anything. The whole sequence runs without an AI account configured in CourseForge - you do the writing. Costs nothing.

Argument Type Description
course_id integer The course to report on. Omit to be pointed at one.

list_courses#

List courses reads

Every course this account can see, with its progress: how many chapters and pages it has, how many are written, how many are published, and whether a generation run is open. An administrator sees every account's courses, each marked with its owner.

Argument Type Description
owner string Administrators only: list one account's courses instead of all of them.

get_course#

Read a course reads

The full outline of one course: every chapter and page with its id, title and status, plus the book title, the topic, the resolved content details and the publishing state. Use it to see what still needs writing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
include_content boolean Include the Markdown of the written pages. A finished course holds far more text than one answer can carry, so this fills up to about 300,000 characters of it and then stops, whole pages at a time; the outline always comes back complete either way. Leave it off unless you need the text.
content_from_page_id integer Where the text should start. Omit it for the beginning of the course; to read on past a previous answer, pass the page id that answer returned as content.next_page_id. Only meaningful together with include_content.

create_course#

Create a course writes

Creates an empty course from a one-line brief. It has no outline yet: follow this with generate_structure to have CourseForge design one, or with get_structure_brief and apply_structure to write one yourself. Pass web_research for a subject that moves - a framework, an API, a product, a standard - and every brief this course hands out afterwards asks for the current state of it to be established first, starting with the outline.

Argument Type Description
name * string A short name for the course, for the course list.
topic string The brief the outline is designed from. One or two sentences saying what the course covers, for whom, and anything that must be assumed - tooling, prerequisites, depth.
profile_id integer The profile to generate with. Omit to use the only one, if there is only one.
web_research boolean Switches web research on for the whole course, rather than leaving it to the installation default. With it on, get_research_brief hands you the assignment for the subject, get_structure_brief asks for the outline to be designed against what you found, and every get_page_brief asks the page to be written against it and to cite sources. A client that can search the web - Claude Code, for instance - does that searching itself, so it costs nothing here; a course generated by CourseForge's own model instead uses that provider's search tool, which does cost. Leave it out to inherit the default.
research_max_searches integer Roughly how many searches one page may make while it is being written. 0 leaves it to whoever is doing the searching. Only meaningful with web_research on.

update_course#

Change a course writes

Renames a course, changes its brief, assigns a different profile, or sets the book title and description that are published to BookStack. Only the fields you give are changed.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
name string A new name for the course.
topic string A new brief. This does not rewrite an existing outline by itself.
profile_id integer A different profile to generate with.
book_title string The title of the published book.
book_description string The description of the published book.
bookstack_instance string The id of the BookStack instance in the course's profile to publish into. list_profiles shows which a profile has. A course may publish into several at once: this replaces the first of them - naming a different instance means the course stops publishing to the one it names now - and leaves any others it has behind that first one alone. set_publish_targets writes the whole list, which is what to use to add a second destination rather than change the one there is.
shelf_id integer The BookStack shelf the book belongs on in that first instance. list_bookstack_shelves finds it.
shelf_name string The name of that shelf, for display.
auto_tags boolean Whether the outline generator may invent tags for the course, written as {{Tag}} markers in the outline.
tag_pool string A comma-separated list of tag names the generator should choose from. Leave empty to let it choose freely.
tag_pool_strict boolean Whether the generator must stay inside that pool.

delete_course#

Delete a course writes destructive

Removes a course and everything in it: chapters, pages, written content, tag links and run records. This cannot be undone, and it does not remove anything already published to BookStack. Requires the course name as confirmation.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
confirm_name * string The exact name of the course, as a confirmation that the right one is being deleted.

Research#

Take the research assignment for a course, and store what searching the web found. The client does the searching, so nothing here spends anything.

get_research_brief#

Get the assignment for researching a course reads

The research assignment for a whole course: what to establish about the subject before any of it is designed or written - the current stable version and its release date, what changed recently enough that a model would get it wrong, what has been deprecated and what replaced it, where the official documentation now lives, and what practitioners currently recommend. Search the web from this brief with your own tools, then send what you found to store_research. It is stored once on the course and then read by the outline and by every single page, so a course about something that moves - WordPress, a framework, an API, a standard - is researched one time rather than once per page. Costs nothing: no model is called and no search is run on this server.

Argument Type Description
course_id * integer The course id, as returned by list_courses.

store_research#

Store what the research found writes

Stores the findings from get_research_brief on the course and stamps them with today's date. From then on they travel into the outline brief and into every page brief, and the model is told when they were established so it can say which version a statement holds for. Send Markdown: short sections, dense bullets, and a closing "## Sources" list of what you actually read. Sending an empty string clears the findings. Replaces whatever was stored before - to add to a briefing rather than replace it, read the old one with get_research first and send both.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
findings * string The research findings as Markdown. Anything past 12,000 characters is cut at a line boundary, because this text is carried in the context of every page of the course and is therefore paid for once per page. Send an empty string to clear.

get_research#

Read the stored research reads

The research findings stored on a course, with the date they were established and how old that makes them. Nothing here ever expires by itself - old findings still beat a guess - so this is what a client reads to decide whether the subject has moved far enough to be worth researching again. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.

Outlines#

Read the outline brief, write an outline, and have CourseForge design one itself.

get_structure_brief#

Get the brief for designing an outline reads

The complete brief for designing a course outline, or for revising the one that already exists: the system instructions and user message CourseForge would send its own model, the course topic, the current outline, the requested changes, and the exact Markdown format that apply_structure parses. Pass feedback to be given the revision brief instead of the design brief - a revision must reproduce every unaffected title character for character, because pages are matched by title. Design the outline from this brief and send it to apply_structure. Costs nothing: no model is called.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
feedback string The changes you want made to the existing outline. Omit to design one from scratch.

apply_structure#

Replace the outline writes destructive

Replaces a course's outline with the Markdown you give it. Chapters and pages are matched by title: a page whose title is unchanged keeps its text and its details, a page with a new title starts empty, and a page that is no longer in the outline is deleted along with everything written on it. Always send the complete outline - anything left out is removed. Run preview_structure first when the Markdown did not come from get_structure_brief. If the outline would remove a page that has text on it, this refuses and names those pages unless confirm_removals is true, so an outline that only adds and renames applies in one call. Returns what was added, what was kept and what was removed. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
structure * string The complete outline in CourseForge's Markdown format, as described by get_structure_brief.
confirm_removals boolean Set true only when you mean to delete written pages that this outline leaves out. Without it the call is refused and the pages at stake are named.

generate_structure#

Have CourseForge design the outline writes destructive spends credit

CourseForge calls the course's own model and designs a complete outline. Existing chapters and pages are matched by title, so a new outline over a course that has already been written would delete every page it does not name. When it would take a page that has text on it, the outline is handed back for you to look at instead of being applied, and applying it then costs nothing - so the model is never called twice over the same decision. Pass confirm_removals to have it applied whatever it removes. Pass feedback to revise the existing outline rather than design a new one. It blocks while the model works, which on a large course takes a minute or two. To design an outline without spending anything at all, use get_structure_brief and apply_structure instead.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
feedback string The changes you want made to the existing outline. Omit to design one from scratch.
confirm_removals boolean Set true only when you mean to delete written pages the new outline leaves out. Without it the designed outline is returned unapplied and the pages at stake are named.

plan_page_scopes#

Have CourseForge plan what each page teaches writes spends credit

Every page in an outline can carry a scope: one line, indented under its title, naming what that page and no other page teaches. Every page brief carries every page's scope, which is how a page knows what the others cover and leaves it to them. Outlines designed now come with scopes; this fills them in for pages that have none - an older outline, pages added by hand - in one request to the course's outline model, and writes them into the stored outline. Pass all to plan every page afresh. No page text is touched. To plan them without spending anything, add the scope lines to the outline yourself and store it with apply_structure.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
all boolean Plan every page afresh instead of only the pages that have no scope.

get_structure#

Read the outline reads

The stored outline of a course exactly as it is, plus what CourseForge reads out of it: the book title and description, how many chapters and pages there are, and every title. Read this before revising an outline, so that the titles you mean to keep stay byte-identical and their pages keep their text. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.

preview_structure#

Check outline Markdown before applying it reads

Parses outline Markdown and reports what CourseForge understood from it - the book title, the description, every chapter with its pages, and any {{Tag}} markers - without touching any course. It takes no course_id and changes nothing. Use it whenever an outline was assembled by hand, because apply_structure deletes every page the outline leaves out and there is no undo. Reports duplicate titles, chapters with no pages and anything the parser could not read. Costs nothing.

Argument Type Description
structure * string The outline Markdown to check.

Pages#

List and read pages, take a writing brief, store a finished page, and generate one through the profile's model. Also retitles pages and chapters and rewrites a chapter description, which edits the stored outline, because chapters and pages are matched by title.

get_page_brief#

Get the writing brief for a page reads

The complete writing brief for one page: the system instructions and the user prompt CourseForge would send a model, the content details resolved for it, and the shared course context - the course map, in which every page carries the scope it alone teaches, plus the chapter it belongs to. Write the page from this brief and send the result to write_page. Omit page_id to be given the next page that has not been written yet. The shared context is the same for page after page: pass the ids you were given back as known_context, and while they still match that text is left out of the answer instead of being sent again. Costs nothing: no model is called.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
page_id integer A specific page. Omit for the next unwritten one.
feedback string Revision instructions, when rewriting a page that already has text.
known_context string The shared_context_id and chapter_context_id you already hold from earlier briefs of this course, comma-separated. A segment whose id is listed is not repeated; one that has changed since - an edited outline, the next chapter - is sent again under a new id.

write_page#

Store a written page writes

Stores the Markdown of one page. The text must follow the brief: GitHub-flavoured Markdown, no level-1 heading, no raw HTML. Returns how many pages are still unwritten, so a loop of brief - write - store knows when it is finished.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
page_id * integer The page this text belongs to.
content * string The finished page, in Markdown.

list_pages#

List the pages of a course reads

Every page of a course in reading order, with its chapter, status, word count and whether it has been published. Lighter than get_course when all you need is what to write next.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
only string Which pages to return. One of all, written, unwritten, failed, unpublished.

get_page#

Read one page reads

One page in full: its title, its Markdown, its extra context, its status and where it sits in the course.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
page_id * integer The page to read.

update_page#

Edit a page writes

Changes a page's title, its scope, its Markdown, or the extra context that is added to its brief the next time it is written. Only the fields you give are changed. A new title or scope also rewrites the stored outline, because both are lines of it and chapters and pages are matched by title.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
page_id * integer The page to change.
title string A new title.
scope string What this page and no other page teaches, as one line - concepts separated by semicolons. Every page brief shows every page's scope. An empty string removes it.
content string New Markdown for the page.
extra_context string Notes that are added to this page's brief - things this page in particular must cover, or must not. They are instructions to the writer, not text that appears on the page.

update_chapter#

Edit a chapter writes

Changes a chapter's title or its description. Renaming a chapter rewrites the stored outline, because chapters are matched by title.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
chapter_id * integer The chapter to change.
title string A new title.
description string A new description. This is part of what the pages are written from.

generate_page#

Have CourseForge write a page writes spends credit

CourseForge calls the course's own model and writes one page, start to finish, and stores it. This is the opposite of get_page_brief: the work happens on the server and is billed to the profile's AI account. It blocks until the page is finished, which on a long page can take minutes - to write a whole course, use start_run instead.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
page_id integer The page to write. Omit for the next unwritten one.
feedback string Revision instructions, when rewriting a page that already has text.
extra_context string Notes to store on the page and add to its brief before writing it - things this page in particular must cover, or must not. Saved with the page, so a later rewrite carries them too. Omit to leave whatever the page already holds; update_page sets it without spending anything.

fix_typography#

Correct the punctuation of what is already written writes destructive

Sets the quotation marks, apostrophes, ellipses, dashes and spacing of a course, one chapter or one page the way the course language sets them - German gets โ€ž and โ€œ where the model wrote " and ", French gets its guillemets and its narrow spaces, an ellipsis becomes one character and a hyphen between two words becomes a dash. Code, links, formulas, HTML and anything the author escaped are never touched. Costs nothing: no model is called, the rules are all in code. page_id wins over chapter_id, which wins over the course, so course_id alone corrects the whole course. Send preview to be told what would change without changing it. Running it twice changes nothing the second time.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
chapter_id integer One chapter and its pages. Omit for the whole course.
page_id integer One page. Omit for the whole chapter or course.
language string Set the text as this language instead of the course's own.
preview boolean Report what would change and write nothing.

Content details#

The thirteen switchable elements and seven values, at course, chapter or page level.

get_detail_catalogue#

List every content detail reads

Every content detail this installation has, with what it does and its installation-wide default: the switchable features - summary, exercises, diagrams, formulas, flashcards, code examples, tables, callouts, emojis, auto links and the rest - and the values such as minimum and maximum length, diagram count, card count and audience. This is the only place the valid keys, the value types and the permitted ranges are listed, so read it before calling set_details. Takes no arguments and costs nothing.

No arguments.

get_details#

Read the content details of one level reads

What the content details are at one level and where each of them comes from. Give course_id alone for the course, add chapter_id for one chapter, or page_id for one page - page_id wins over chapter_id, which wins over the course. Every detail is reported three ways: what this level stores of its own, what it would inherit if it stored nothing, and what actually applies to a page written here. The chain is installation default, then the course's profile (see set_profile_details), then course, chapter, page. Features are tri-state, so a stored 0 means inherited, never off. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
chapter_id integer Read one chapter's level instead of the course's.
page_id integer Read one page's level. Takes precedence over chapter_id.

list_detail_overrides#

List every content detail a course overrides reads

Every chapter and page of one course that stores a detail of its own, and what it stores - the whole of what the Details tab calls "overrides below the course", in one call. get_details answers for one level and says nothing about the rest, so finding the three pages in a fifty-page course that were given exercises meant fifty calls. Levels that inherit everything are left out, because inheriting is the ordinary case and listing it would bury the answer. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.

set_details#

Set content details at one level writes

Sets content detail overrides on the course, on one chapter or on one page. The ids choose the level: page_id wins over chapter_id, which wins over the course, so course_id alone changes the whole course. features is an object keyed by feature - 1 or true switches it on, -1 or false switches it off, 0 or null removes the override so that level inherits again. values is an object keyed by value - a number or a string sets it, null clears it back to inherited. Only the keys you send are touched; everything else stays as it was. Keys are checked against get_detail_catalogue and a wrong one is rejected with the list of valid ones. Returns the resolved details afterwards so you can see the effect. This changes what the next generation writes and never edits a page that already has text.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
chapter_id integer Set the override on one chapter instead of the course.
page_id integer Set the override on one page. Takes precedence over chapter_id.
features object Feature keys mapped to 1 / true (on), -1 / false (off) or 0 / null (inherit). For example {"exercises": 1, "emojis": -1, "mermaid": 0}.
values object Value keys mapped to a number, a string, or null to inherit. For example {"min_length": 800, "diagram_max": 1, "audience": "experienced developers"}.

reset_details#

Clear the content details of one level writes destructive

Removes every content detail override at one level so it inherits everything again: a page falls back to its chapter, a chapter to its course, and the course to the installation defaults. The ids choose the level the same way as set_details - page_id wins over chapter_id, which wins over the course. Nothing else is touched: resetting a chapter leaves its pages' own overrides in place. The cleared overrides are returned, so the same set_details call can put them back if this was a mistake.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
chapter_id integer Clear one chapter's overrides instead of the course's.
page_id integer Clear one page's overrides. Takes precedence over chapter_id.

Tags#

The tag library and what is tagged with what.

list_tags#

List the tag library reads

One account's tag library: every tag with its name, its value, and how many courses, chapters and pages it is attached to. Tags belong to an account, not to a course, so the same tag is reused everywhere. An administrator sees every account's tags, each marked with its owner - a library stays your own unless you widen it. Costs nothing.

Argument Type Description
owner string Administrators only: one other account's library.
all boolean Administrators only: widen to every account on the installation. Without it a listing is your own.

create_tag#

Add a tag to the library writes

Adds a tag to your library. It is attached to nothing yet, so this is only worth calling when you want the tag to exist before it is used - attach_tag creates a missing tag itself. A name already in the library is refused; change that one with update_tag instead. Costs nothing.

Argument Type Description
name * string The tag name, as it will appear in BookStack.
value string The tag value. BookStack shows a tag as "name: value"; leave it out for a bare label.

update_tag#

Rename or revalue a tag writes

Changes a tag's name or value in the library, which changes it everywhere it is attached at once. Anything already in BookStack keeps the old tag until that course is pushed again, so follow this with publish_course for any course that is live. Only the fields you give are changed. Costs nothing.

Argument Type Description
tag_id * integer The tag to change, as returned by list_tags.
name string A new name.
value string A new value. Send an empty string to clear it.

delete_tag#

Delete a tag writes destructive

Removes a tag from the library and from every course, chapter and page it is attached to. This cannot be undone, and it does not remove the tag from anything already published - push those courses again afterwards. Requires the tag name as confirmation. To stop one tag being published from one place without losing it everywhere, use set_tag_link with enabled false instead. Costs nothing.

Argument Type Description
tag_id * integer The tag to delete, as returned by list_tags.
confirm_name * string The exact name of the tag, as a confirmation that the right one is being deleted.

get_course_tags#

Read every tag in a course reads

Every tag in play anywhere in one course: what is attached at the course, at each chapter and at each page, which of those inherit downwards, which CourseForge created itself from {{Tag}} markers in the outline, and which are switched off. Each item also carries its effective tags - what would actually be published for it, its own plus everything inherited from above. Items with no tags at all are left out. Costs nothing: no model is called.

Argument Type Description
course_id * integer The course id, as returned by list_courses.

attach_tag#

Attach a tag writes

Attaches a tag to the course, to one chapter or to one page, adding it to the owner's library first if it is not there yet. Set inherit to have it apply to everything below as well - from the course to every chapter and page, from a chapter to its own pages - which is how a course-wide tag is done without one link per page. Attaching by hand also turns an automatic tag into a manual one and switches it back on. Call publish_course afterwards to push the change to BookStack. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
name * string The tag name. A tag of this name is created if the library has none.
value string The tag value. This is the value in the library, so setting it here changes the tag everywhere it is used. Leave it out to keep whatever the tag already has.
target string What to attach it to: the whole course (the default), one chapter, or one page. One of course, project, chapter, page.
target_id integer The chapter id or page id, when target is chapter or page.
inherit boolean Also apply this tag to everything below the item. Ignored on a page, which has nothing below it.

detach_tag#

Detach a tag writes

Removes one link between a tag and a course, chapter or page. The tag stays in the library and stays attached to everything else. A tag CourseForge created from a {{Tag}} marker comes back the next time the outline is generated, so for one of those use set_tag_link with enabled false - that decision is kept. Name the tag with exactly one of tag_id and name, whichever you have to hand. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
tag_id integer The tag to detach, as returned by get_course_tags. Give this or name, and not both.
name string The tag to detach, by name, looked up in the library of the account that owns the course - the same name attach_tag takes. Give this or tag_id, and not both.
target string Where to detach it from: the whole course (the default), one chapter, or one page. One of course, project, chapter, page.
target_id integer The chapter id or page id, when target is chapter or page.

Change how a tag is attached writes

Changes an existing link without removing it: whether it inherits downwards, and whether it is switched on. A switched-off link is kept but ignored everywhere - it reaches no writing brief and no BookStack push - which is how an automatic tag is stopped from being published without deleting it, and unlike a detach it survives a regenerated outline. Give at least one of inherit and enabled. The tag has to be attached to that item already; use attach_tag if it is not. Name the tag with exactly one of tag_id and name, whichever you have to hand. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
tag_id integer The tag whose link is being changed, as returned by get_course_tags. Give this or name, and not both.
name string The tag whose link is being changed, by name, looked up in the library of the account that owns the course. Give this or tag_id, and not both.
target string Which link: the one on the course (the default), on one chapter, or on one page. One of course, project, chapter, page.
target_id integer The chapter id or page id, when target is chapter or page.
inherit boolean Whether the tag also applies to everything below. Cannot be set on a page.
enabled boolean Whether the link counts at all. False keeps it but hides it from every brief and every push.

Generation runs#

Start, watch and cancel background and batch runs. This is the group that spends money at scale.

start_run#

Write pages on the server writes spends credit

Starts writing a set of pages on the server, and returns at once. The run is recorded before any work begins, so it carries on after this client disconnects, after the session ends and across a restart of the server - start it, go away, and call poll_run or list_runs later to see where it got to. A live run is written by CourseForge's own scheduler in the background and therefore needs cron to be configured: when it is not, this tool starts nothing and says so, and the ways round it are to have an administrator set app.cron_token, to switch to a batch run, or to write pages one at a time with generate_page. A batch run hands the whole selection to the provider's own queue at roughly half the price and is answered within 24 hours, which is the right choice for a large course where time is not critical. mode auto reads the profile's page model: a model ending in ":batch" queues, anything else goes to the background worker. Call estimate_run first to see the size of it.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
pages any Which pages to write. Omit it, or "unwritten", for every page that has no text yet - the usual case. "all" rewrites the whole course, pages that are already written included. "failed" retries the ones whose last attempt errored. Or give a list of page ids from list_pages. Pages already claimed by another run are left out of the keyword selections.
chapter_id integer Restrict the run to one chapter. It combines with pages: the selection is then made inside that chapter only.
mode string How the work is done. auto reads the profile's page model and is almost always right. live means CourseForge writes the pages itself from the scheduler; batch means the provider's queue, at about half the price, answered within a day. One of auto, live, batch.

estimate_run#

What a run would cost reads

What start_run would do with the same arguments, without doing it and without spending anything: how many pages, which model and provider, whether it would go to the background worker or to the batch queue, and roughly how many input and output tokens are involved. The input figure is measured from the real prompts - the same ones the run would send - sampled across the selection and scaled up, and the answer says how many pages were measured and at how many characters to the token. No price is given: CourseForge does not know what an account pays, and a confident number in the wrong currency is worse than a token count. Read the warnings: the usual one is that the provider has a batch queue nobody is using. The prompts it measures are built and thrown away and no model is ever called, so finding out what a run would cost is itself free. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
pages any Which pages to write. Omit it, or "unwritten", for every page that has no text yet - the usual case. "all" rewrites the whole course, pages that are already written included. "failed" retries the ones whose last attempt errored. Or give a list of page ids from list_pages. Pages already claimed by another run are left out of the keyword selections.
chapter_id integer Restrict the estimate to one chapter.
mode string Which way to cost it. auto reads the profile's page model, as start_run would. One of auto, live, batch.

list_runs#

List generation runs reads

Every run this account can see, newest first, with the course it belongs to, whether it is live or batch, its status, and how many of its pages are written, still pending, skipped or failed. This is the tool to call after reconnecting, to find out whether work ordered earlier is still going. only=open lists just the runs that have not ended; all and finished look at the 100 newest. Follow with get_run for the detail of one, or poll_run to make a batch check in now. Costs nothing.

Argument Type Description
course_id integer One course only. Omit for every course this account can see.
only string Which runs to return. open means anything that has not ended. One of all, open, finished.

get_run#

Read one run reads

One run in detail: its status, its mode, the model and provider behind it, the page counts, when it started, when it was last checked, and - for a batch - the provider's own id and the moment its results expire and stop being collectable. include_items adds every page in the run with its own status and error message, which is how you find out which three of five hundred pages failed and why. This reads what is stored and asks the provider nothing; poll_run is the one that goes out to the network. Costs nothing.

Argument Type Description
run_id * integer The run, as returned by list_runs or start_run.
include_items boolean Include every page in the run with its own status and error. On a large run this is a long list.

poll_run#

Check a run now writes

Asks the provider now whether a batch has finished, and writes home every page that has, instead of waiting for the next scheduler tick to do it. Use it when you want the answer in this conversation. On a live run there is no provider to ask: it reports where the background worker has got to and closes the run if the last page has landed. Safe to call as often as you like - the same page is never written twice - and it spends nothing beyond what the run has already committed. It reports how many pages arrived since the previous check. Costs nothing.

Argument Type Description
run_id * integer The run to check.

cancel_run#

Stop a run writes destructive

Stops a run and releases the pages it was holding back to unwritten. At the provider it is best effort: a batch that has already finished cannot be cancelled, and is collected and written home instead of being thrown away. A page a live worker is writing at this very moment is left to finish, because it has already been paid for. Pages that were written before the cancel stay written - this stops the work, it does not undo it. Use delete_run afterwards to remove the record. Costs nothing.

Argument Type Description
run_id * integer The run to stop.

delete_run#

Forget a finished run writes destructive

Removes the record of a run that has ended. This is bookkeeping only: every page the run wrote stays exactly as it is, and nothing is refunded or recalled. A run that is still open is refused - stop it with cancel_run first, then delete it. Costs nothing.

Argument Type Description
run_id * integer The finished run to forget.

scheduler_status#

Is anything collecting background work reads

Whether background work will actually be picked up on this installation: whether a cron token is configured, when the scheduler last called in, how many worker slots it has, and how many runs and pages are waiting for it. Ask before starting a live run if you want to be sure something will collect it, because a background run on an installation with no scheduler sits untouched for ever. A batch run can be submitted without the scheduler - the provider holds that work - but then its results have to be collected with poll_run. Costs nothing.

No arguments.

Profiles#

AI accounts, models and BookStack instances. Keys are never readable, but they can be replaced.

list_profiles#

List profiles reads

The profiles this account can generate with: the id to pass to update_course, the AI accounts each one holds and what kind they are, whether each account has a key stored, the models chosen for the outline and the page slot, the BookStack instances, and the language. API keys are never included - only whether one is set. An administrator sees every account's profiles, each marked with its owner - a working set stays your own unless you widen it. Costs nothing.

Argument Type Description
owner string Administrators only: one other account's profiles.
all boolean Administrators only: widen to every account on the installation. Without it a listing is your own.

get_profile#

Read a profile reads

One profile in full: every AI account with its kind, base URL and whether a key is stored, every BookStack instance, both model slots with their temperature and token ceiling, the language, the concurrency, the courses using it, and the prompt slots this profile overrides - so you can see how it differs from the installation defaults. No credential is ever returned. Costs nothing.

Argument Type Description
profile_id * integer The profile id, as returned by list_profiles.
include_prompt_text boolean Include the full text of each prompt override rather than only which slots are overridden. These can be long.

create_profile#

Create a profile writes

Builds a working profile in one call: one AI account, both model slots pointed at it, the language, and optionally a BookStack instance. Call list_providers first to see which account kinds this installation speaks and which of them need a key. The key is stored on the server and can never be read back - only replaced. The profile belongs to the account that creates it. Follow with check_profile to prove the credentials work and list_models to choose a model. Costs nothing.

Argument Type Description
name * string A short name for the profile, for the picker.
ai_kind string The kind of AI account. Call list_providers for what each one is and whether it needs a key. One of openai, anthropic, gemini, openrouter, oai-compat.
api_key string The API key for the account. Required for every kind whose needs_key is true. Written only: no tool will ever hand it back.
base_url string The endpoint. Omit to use the default list_providers gives for this kind, which is right for everything but a self-hosted or proxied gateway.
preset_key string A row of the provider catalogue rather than a raw driver kind - "groq", "together", "deepseek", "llamacpp" and the rest, as list_providers reports them. Naming one picks the endpoint and the label with it, which is what the browser's provider picker does; ai_kind is then optional. Every preset runs on the OpenAI-compatible driver.
organization string The OpenAI organization id, for an account that belongs to one.
site_url string OpenRouter only: the site this traffic is attributed to.
site_name string OpenRouter only: the name shown beside that attribution.
model string The model that writes the pages - where the budget goes. Add ":batch" to route a whole course through the provider's queue at about half price.
structure_model string The model that designs the outline. One call per course, so it can be the expensive one. Omit to use the same model as the pages.
language string The language the course is written in.
temperature number Sampling temperature for both slots. 0.7 unless you have a reason. Ignored by reasoning models and by every Claude model.
max_tokens integer The token ceiling for one reply. 0 means the provider's own default, which is usually right.
concurrency integer How many pages a run writes at once.
bookstack_url string The BookStack instance this course publishes to, without a trailing slash.
bookstack_token_id string The BookStack API token id. Not a secret; it is readable back.
bookstack_token_secret string The BookStack API token secret. Written only: no tool will ever hand it back.

update_profile#

Change a profile writes

Changes one profile. Only the fields you give are touched, and everything else - the other accounts, the prompt overrides - is left exactly as it was. An omitted or empty api_key keeps the stored key: passing "" does not clear it, and there is no way to blank a stored key at all, so an account whose key must go has to be replaced with a new one or the profile deleted and made again. Give ai_id when the profile holds more than one AI account, and bookstack_id when it holds more than one instance. Costs nothing.

Argument Type Description
profile_id * integer The profile to change.
name string A new name for the profile.
ai_id string Which AI account to change, when the profile holds more than one. get_profile lists the ids.
ai_name string A new name for that AI account, for the picker.
ai_kind string Change the account to another kind. The base URL follows the new kind's default unless you gave one yourself or had typed one. One of openai, anthropic, gemini, openrouter, oai-compat.
api_key string A new key, replacing the stored one. Empty or omitted keeps what is stored.
base_url string A new endpoint for the account.
preset_key string A row of the provider catalogue rather than a raw driver kind - "groq", "together", "deepseek", "llamacpp" and the rest, as list_providers reports them. Naming one picks the endpoint and the label with it, which is what the browser's provider picker does; ai_kind is then optional. Every preset runs on the OpenAI-compatible driver.
organization string The OpenAI organization id, for an account that belongs to one.
site_url string OpenRouter only: the site this traffic is attributed to.
site_name string OpenRouter only: the name shown beside that attribution.
model string The model that writes the pages. Add ":batch" to queue the run at about half price.
structure_model string The model that designs the outline.
language string The language the course is written in.
temperature number Sampling temperature. Applies to both model slots; ignored by reasoning models and by every Claude model.
max_tokens integer The token ceiling for one reply, for both slots. 0 is the provider default.
concurrency integer How many pages a run writes at once.
bookstack_id string Which BookStack instance to change, when the profile holds more than one.
bookstack_name string A new name for that BookStack instance, for the picker.
bookstack_url string The BookStack base URL.
bookstack_token_id string The BookStack API token id.
bookstack_token_secret string A new BookStack token secret. Empty or omitted keeps the stored one.
bookstackdev_id integer The BookStackDev look this instance wears, by id - list_bookstackdev_profiles has them. 0 takes the look off, back to plain BookStack.
typography boolean Whether a page generated with this profile has its punctuation set the way the course language sets it before it is stored. Omitted leaves the profile as it is; a profile that has never said follows the installation. This decides nothing about courses that are already written - fix_typography corrects one of those whatever this says.

delete_profile#

Delete a profile writes destructive

Removes a profile and the credentials in it. The keys are gone and cannot be recovered, because nothing here can read them back to copy them elsewhere first. A profile a course still points at is refused, with the names of the courses - move them to another profile with update_course before deleting this one. Requires the profile name as confirmation. Costs nothing.

Argument Type Description
profile_id * integer The profile to delete.
confirm_name * string The exact name of the profile, as a confirmation that the right one is being deleted.

add_ai_account#

Add an AI account to a profile writes

Adds another AI account to a profile that already has one - an OpenAI key beside an Anthropic one, a self-hosted gateway beside both, the Claude subscription beside a metered key. update_profile changes an account a profile already holds; this is how it gets a second. Call list_providers first for the kinds this installation speaks. Nothing points at the new account until you say so: use set_model_slot to send the outline or the pages through it. The key is stored on the server and can never be read back. Costs nothing.

Argument Type Description
profile_id * integer The profile to add the account to.
kind string The kind of AI account. Call list_providers for what each one is and whether it needs a key. One of openai, anthropic, gemini, openrouter, oai-compat.
name string A name for this account, which is how the model slots refer to it. Defaults to the provider's own label.
api_key string The API key. Required for every kind whose needs_key is true. Written only: no tool will ever hand it back.
base_url string The endpoint. Omit to use the default list_providers gives for this kind.
preset_key string A row of the provider catalogue rather than a raw driver kind - "groq", "together", "deepseek", "llamacpp" and the rest, as list_providers reports them. Naming one picks the endpoint and the label with it, which is what the browser's provider picker does; ai_kind is then optional. Every preset runs on the OpenAI-compatible driver.
organization string The OpenAI organization id, for an account that belongs to one.
site_url string OpenRouter only: the site this traffic is attributed to.
site_name string OpenRouter only: the name shown beside that attribution.

delete_ai_account#

Remove an AI account from a profile writes destructive

Takes one AI account out of a profile. Its key goes with it and cannot be recovered, because nothing here can read a key back to copy it elsewhere first. A model slot pointing at the account is left pointing at nothing, which stops that slot generating until it is pointed somewhere with set_model_slot - so the answer says which slots those were. Removing the last account of a profile is refused: a profile with no account cannot write a word, and deleting the profile is the honest way to say that. Costs nothing.

Argument Type Description
profile_id * integer The profile the account belongs to.
ai_id * string The account to remove. get_profile lists the ids.

add_bookstack_instance#

Add a BookStack instance to a profile writes

Adds another BookStack instance to a profile that already has one - a staging wiki beside a live one, the wikis of two departments, a customer's install beside your own. update_profile changes an instance a profile already holds; this is how it gets a second, which is what a course needs before set_publish_targets can send it to more than one wiki. The token secret is stored on the server and can never be read back. Costs nothing.

Argument Type Description
profile_id * integer The profile to add the instance to.
url * string The BookStack base URL, without a trailing slash.
token_id * string The BookStack API token id. Not a secret; it is readable back.
token_secret * string The BookStack API token secret. Written only: no tool will ever hand it back.
name string A name for this instance, which is how a course names its destination.
bookstackdev_id integer The BookStackDev look this instance should wear, by id. Omit for plain BookStack.

delete_bookstack_instance#

Remove a BookStack instance from a profile writes destructive

Takes one BookStack instance out of a profile, with its token. A course that publishes to that instance loses the destination and the record of the book it made there, so publishing to the same wiki later would create a second book beside the first - the call is refused with the names of those courses unless you send confirm. Nothing is deleted inside BookStack itself, here or ever. Costs nothing.

Argument Type Description
profile_id * integer The profile the instance belongs to.
bookstack_id * string The instance to remove. get_profile lists the ids.
confirm boolean Go through with it even though courses publish to this instance. Their destinations and the record of what was published there are forgotten.

set_model_slot#

Point one model slot at an account and a model writes

Sets the outline slot or the page slot on its own: which AI account it runs through, which model, and that slot's own temperature and token ceiling. update_profile sets both slots together, which is right when a profile has one account and wrong as soon as it has two - the outline is one call and can afford the expensive model, while the pages are hundreds and may want a cheaper one, a different provider, or ":batch" on the end to go through the provider's queue at about half price. Call list_models for the ids an account will accept. Costs nothing.

Argument Type Description
profile_id * integer The profile to change.
slot * string Which slot: "outline" designs the course structure, "page" writes the pages. One of outline, page.
model string The model id, as list_models reports it. Add ":batch" to route this slot through the provider's queue.
ai_id string The AI account this slot runs through. Omit to leave it where it points, or when the profile holds only one account.
temperature number Sampling temperature for this slot alone. 0.7 unless you have a reason.
max_tokens integer The token ceiling for one reply from this slot. 0 means the provider's own default.

list_prompt_slots#

List the prompt slots a profile can override reads

Every prompt slot this installation has: its key, the group it belongs to, what it is for, the placeholders it may use, and the wording currently in force for the installation - which is what a profile that overrides nothing uses. This is the read a profile needs before set_profile_prompts can change one, and unlike get_prompts it needs no administrator, because overriding a slot for one profile is not the same power as rewriting it for everybody. Costs nothing.

Argument Type Description
group string One group only, as the group field of a slot reports it.
include_text boolean Include the full wording of each slot rather than only what it is for. These are long.

set_profile_prompts#

Override prompt slots for one profile writes

Replaces the wording this profile uses for one or more prompt slots, leaving the installation's own prompts alone for every other profile. This is the per-profile half of get_prompts and set_prompts, which are the installation-wide pair and need an administrator: a profile override needs only the profile. Call get_prompts for the slot names and what each one is for, and get_profile with include_prompt_text to read back what this profile currently overrides. A slot named in reset goes back to the installation's wording; an empty string is a deliberate override that sends nothing for that slot, which is not the same thing. Costs nothing.

Argument Type Description
profile_id * integer The profile to change.
prompts object Slot name to the text this profile should use instead. Unknown slot names are refused rather than silently stored.
reset array Slot names to stop overriding, so the installation's wording applies again.

set_profile_details#

Set the content defaults of one profile writes

Decides content details for every course written with this profile - which elements a page gets and the values that size them - one level above the course. The chain is: installation default, then this profile, then course, chapter, page, the closest level winning. A course inherits whatever the profile decides until it overrides it, so this is where a house style lives: "every course on this profile opens with learning objectives and aims at 1,500 words". Call get_detail_catalogue for the keys, the types and the ranges. features maps a key to 1 (on), -1 (off) or 0 (stop deciding, so the installation default applies again); values maps a key to a number or a piece of text, or null to stop deciding. reset_all drops everything this profile decided. Nothing already written changes - the next page generated on any course of this profile follows the new answer. Costs nothing.

Argument Type Description
profile_id * integer The profile to change.
features object Feature key to 1 (on), -1 (off) or 0 (inherit the installation default). Unknown keys are refused rather than silently dropped.
values object Value key to the number or text this profile decides, or null to stop deciding it. Numbers are clamped to the range get_detail_catalogue reports.
reset_all boolean Drop every content detail this profile decided, so its courses follow the installation defaults again. Can be combined with features and values, which are applied afterwards.

list_models#

List the models an account can use reads

Asks the provider which models this account may actually call, and marks which of them its batch queue accepts. Writing a model as "claude-opus-5:batch" in a profile routes the whole course through that queue at about half the price, at the cost of waiting - up to a day for the answer, which is why the suffix belongs on the page slot and not on the outline. An empty batch list means the provider would not say, not that nothing can be queued: supports_batch is the answer then. This is a live call to the provider and can take a few seconds, but no provider charges for a model listing. Pass the chosen id back to update_profile as model or structure_model. Reaches the provider but buys nothing.

Argument Type Description
profile_id * integer The profile the account belongs to.
ai_id string Which AI account to ask, when the profile holds more than one.
refresh boolean Accepted and ignored: the list is fetched from the provider on every call, so it is never stale.

check_profile#

Check that an account works writes

Answers "does this account work" before a whole course depends on it, by making one live call and reporting what came back. For a key-based account that call is a model listing, which is the cheapest thing that proves the base URL and the key are both right, and no provider bills for it; for the Claude subscription account, which has no model listing to fetch, it reports whether the CLI is installed, whether it is signed in, and whether an API key in the server environment has quietly taken over the billing. Use list_models when the question is which models to choose rather than whether the credentials are good. Reaches the provider but buys nothing.

Argument Type Description
profile_id * integer The profile the account belongs to.
ai_id string Which AI account to check, when the profile holds more than one.

list_providers#

List the account kinds reads

Every kind of AI account this installation can speak: its key for ai_kind, its label, the base URL it defaults to, whether it needs an API key, whether it has a batch queue, and a line of guidance on what belongs there. Call this before create_profile - the catalogue is what create_profile validates ai_kind against, and it grows. Read from this installation, so nothing is contacted. Costs nothing.

No arguments.

list_bookstack_shelves#

List BookStack shelves reads

The shelves the profile's BookStack instance offers, with their ids, so a course can be filed onto one when it is published. This is a network call to the BookStack server and also proves the token works. A course carries the shelf on its own publishing settings, not on the profile: pass the id you choose to update_course as shelf_id, with the name as shelf_name. Reaches the BookStack server but buys nothing.

Argument Type Description
profile_id * integer The profile the instance belongs to.
bookstack_id string Which BookStack instance to ask, when the profile holds more than one. get_profile lists the ids.

list_bookstackdev_profiles#

List BookStackDev looks reads

The looks this account has made for its BookStack instances: name, which instances wear it, which other addresses the link is allowed on, and the embed line. A look is a BookStackDev configuration - Shiki code highlighting, Mermaid, MathJax, link embeds, an audio player, external-link marks, a light/dark button, page styling - served to a wiki by one script tag. Costs nothing.

Argument Type Description
owner string Administrators only: the account whose looks to list.
all boolean Administrators only: every account's looks.

list_bookstackdev_options#

What a look can be told reads

Every option a BookStackDev look has, grouped by feature, with its type, its default and its range - the keys create_bookstackdev_profile and update_bookstackdev_profile take in settings. Also the Shiki themes the code highlighter knows. Costs nothing.

No arguments.

get_bookstackdev_profile#

Read one look reads

One look in full: every setting, the instances wearing it, every address the link works on, the embed line for a wiki's custom head, and the conventions check - whether the prompts of the profiles publishing into it agree with what it renders. Costs nothing.

Argument Type Description
bookstackdev_id * integer The look, as returned by list_bookstackdev_profiles.

create_bookstackdev_profile#

Create a look writes

Makes a look with every feature switched on the way the shipped BookStackDev is, changed by whatever settings you give. Name the BookStack instances that should wear it (get_profile lists their ids) and any other address the link should work on - a wiki CourseForge holds no credentials for. The answer carries the embed line. Costs nothing.

Argument Type Description
name * string What to call it, for the picker.
settings object Settings to change from the defaults, keyed by group then field - see list_bookstackdev_options. {"codeBlocks": {"themeDark": "github-dark"}}
instance_ids array BookStack instance ids, from get_profile, that wear this look.
origins array Other addresses the link may be loaded on, as origins: https://wiki.example.com. The instances above are allowed without being listed here.

update_bookstackdev_profile#

Change a look writes

Changes one look. Only what you give is touched: a settings object changes the fields it names and no other, instance_ids replaces the list of instances wearing it, origins replaces the list of extra addresses. Costs nothing.

Argument Type Description
bookstackdev_id * integer The look to change.
name string A new name.
settings object Fields to change, keyed by group then field - see list_bookstackdev_options.
instance_ids array The complete list of instance ids that wear this look afterwards.
origins array The complete list of extra origins afterwards.

delete_bookstackdev_profile#

Delete a look writes destructive

Removes a look. Its link stops answering at once, and every instance wearing it goes back to plain BookStack. Requires the name as confirmation. Costs nothing.

Argument Type Description
bookstackdev_id * integer The look to delete.
confirm_name * string The exact name of the look, as a confirmation that the right one is being deleted.

Regenerate the link of a look writes destructive

Gives a look a new key, and so a new embed line. The old line is refused from this moment wherever it was pasted, which is how a link that got copied somewhere it should not have been is taken back. Every wiki using it has to paste the new line. Costs nothing.

Argument Type Description
bookstackdev_id * integer The look whose link to regenerate.

check_bookstackdev_conventions#

Check a look against the prompts reads

Whether the prompts of every profile publishing into an instance wearing this look agree with what the look renders: formulas written for delimiters it does not typeset, diagrams or formulas asked for while the feature is off. A finding about a prompt carries the recommended wording, which set_profile_prompts can write. Costs nothing.

Argument Type Description
bookstackdev_id * integer The look to check.

Publishing#

Push a course into BookStack and resolve cross references.

get_publish_status#

What has been published reads

The publishing state of one course, worked out from CourseForge's own records without contacting BookStack: every BookStack instance the course publishes to with its shelf, its book and how much of the course is in each of them, how many chapters and pages are in sync, how many have changed since the last push, how many have never been pushed, how many pages have no text to push yet, and how many cross-reference markers would still fail to become links. A course may publish to several instances at once; the counts are folded across the ones that are switched on, so "in sync" means in sync everywhere and "changed" means at least one of them would be written to. Read this before publish_course to see what a push would do. It is counts and a summary rather than a list, so it stays small whatever the size of the course, and it changes nothing. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.

publish_course#

Publish to BookStack writes destructive

Pushes the course into every BookStack instance the course points at. For each of them it creates the book if it is not there yet, puts it on that instance's chosen shelf, then creates or updates every chapter and every page, carrying the effective tags with them. Items that have not changed since the last push to that instance are skipped and an item that was deleted there is recreated, so re-publishing never duplicates anything. Pages with no text yet are skipped. Each instance holds its own book with its own ids, and a page's cross references point inside the wiki it is in, so the same page is written slightly differently to each. An instance that cannot be reached does not stop the others: its failure is reported and the push carries on. This is a long sequence of API calls against somebody else's live wiki, times the number of instances, and can take several minutes on a large course; existing chapters and pages are overwritten in place, and none of it can be undone from CourseForge. part "all" publishes the whole course and resolves cross references afterwards; part "chapter" or "page" publishes one item and needs target_id, creates the book and the parent chapter around it, and leaves cross references alone - call resolve_links after those. force re-sends items whose fingerprint says they are unchanged, which also overwrites edits made in BookStack by hand. The course has to say where it publishes first: set_publish_targets sets the whole list, and update_course still sets a single one as bookstack_instance. No model is called - this reaches the BookStack server but buys nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
part string How much of the course to publish. "all" is the whole course and is the usual answer; "book" creates or updates the book itself and its shelf without touching a single chapter or page, which is how a renamed course or a moved shelf is pushed without rewriting five hundred pages; "chapter" is one chapter with its pages; "page" is a single page. The last two need target_id. One of all, book, chapter, page.
target_id integer The chapter id or page id to publish, required when part is "chapter" or "page" and not allowed otherwise. Ids come from get_course or list_pages.
instances array Publish to these BookStack instances only, named by their instance id. Leave it out to publish to every instance the course has switched on, which is the usual answer; name one when a single wiki was behind or failed and the others are already up to date. get_publish_status lists the ids.
force boolean Re-send every item even when its fingerprint says it is unchanged. Use this to repair a book somebody has edited in BookStack; their edits are replaced.

Turn cross references into links writes

Rewrites the ( ๐Ÿ”— Title ) markers the writer left in the text into real BookStack links, using the URLs of what has already been published, and re-sends only the pages whose text actually changed. It calls no model and can be run as often as you like - the stored page always keeps the raw marker, so resolving is repeatable and never corrupts the source text. A reference whose target is not published yet keeps its marker and resolves on a later run; a reference that matches no chapter or page in the course goes out as plain text, and list_unresolved_links says which ones those are. A full publish_course already does this, so run it after publishing a single chapter or page, or after a page was rewritten. Each rewritten page is written back to the live wiki - this reaches the BookStack server but buys nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
instances array Resolve into these BookStack instances only, named by their instance id. Leave it out for every instance the course has switched on. Each wiki gets links pointing inside itself.
force boolean Re-send every page that holds a marker, even one whose text has not changed.

set_publish_targets#

Where a course publishes writes destructive

Sets the whole list of BookStack instances a course publishes to, in one call. A course may have several: the same book written into a staging wiki and a live one, or into the wikis of two departments. Each entry names an instance the course's profile defines - list_profiles shows them - and may carry a shelf and a switch. The first entry is the one the course reports as its own book and shelf everywhere a single answer is wanted. An entry left off the list is forgotten together with the record of what was published into it, so the next push there creates a second book rather than updating the first: to stop publishing to a wiki without losing that record, send it with enabled false instead. Nothing here reaches BookStack and nothing already published is changed or deleted - this only says where the next push goes. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
targets * array Every instance this course publishes to, in order. An empty list means the course publishes nowhere, which clears its book and shelf.

Cross references that will not become links reads

Every cross-reference marker that does not turn into a link, and the page it sits on. Two kinds: markers whose title matches no chapter or page in this course, each with the closest existing title as a suggestion - those go out as plain text and are the ones people never notice - and markers whose target does exist but has not been published yet, which resolve by themselves once it is. This is what to check after a publish. It lists one row per affected page, so on a large course with many cross references the answer is long. Reads the stored text only: no model, no BookStack, nothing changed. Costs nothing.

Argument Type Description
course_id * integer The course id, as returned by list_courses.

This account#

Who you are connected as, your own password and display name, and your own connections. Never anybody else's.

whoami#

Who am I connected as reads

The account this connection belongs to, its role, and what this installation of CourseForge can currently do - whether the scheduler is running, so background runs are possible, and how many courses the account can see.

No arguments.

get_my_account#

Read my account reads

Everything this installation records about the account this connection belongs to - its user name, display name, role, when it was made and when it last signed in - together with every MCP connection it owns and the tool groups each of those carries. Passwords are stored only as hashes and tokens only as digests, so neither appears here. Costs nothing.

No arguments.

set_my_display_name#

Rename myself writes

Changes the name shown for this account in the interface and against its courses. The user name is the key everything is filed under and cannot be changed; this is only the friendly name beside it. An empty value falls back to the user name. Costs nothing.

Argument Type Description
display_name * string The name to show for this account.

change_my_password#

Change my password writes

Sets a new password for this account. The current one has to be given and has to be right, so a connection that has been handed round cannot lock the owner out of their own installation. A password needs at least 10 characters and has to differ from the current one. This does not sign anything out and does not revoke any connection: open browser sessions and every MCP connection, this one included, carry on working. Costs nothing.

Argument Type Description
current_password * string The password this account has now. Ask the person for it rather than guessing - a wrong value changes nothing and is refused.
new_password * string The password to set, at least 10 characters. Leading and trailing spaces are kept, because they are part of a password.

list_my_connections#

List my MCP connections reads

The MCP connections this account owns: the name each was given, the tool groups it is limited to, when it was made, when it was last used, how often, and whether it still works. Two things stop one working without removing it - passing its expiry date, and being older than the last password an administrator set for this account. Only connections belonging to this account are ever listed, whatever its role. Tokens are stored as hashes and can never be shown again. Costs nothing.

No arguments.

revoke_my_connection#

Revoke one of my connections writes destructive

Deletes the token of one connection belonging to this account, so the next request it makes is refused. Revoking the connection you are talking through ends this conversation immediately and no further tool call will work - and this tool cannot tell you whether that is the one, because a handler is told which account is calling and never which connection. Call list_my_connections first and match the name against the client you are running in. This cannot be undone: a replacement has to be made from the Connect screen in the browser. Requires the connection name as confirmation.

Argument Type Description
connection_id * integer The connection to revoke, as returned by list_my_connections.
confirm_name * string The exact name of that connection, as a confirmation that the right one is being cut off.

list_scopes#

List the tool groups a connection can be limited to reads

Every tool group this installation has: its key, what it covers, whether it needs an administrator and whether anything in it spends money on the AI account. It also says which groups this account may hold at all, and which the connection you are talking through holds right now - which is the ceiling on what create_my_connection will issue. Costs nothing.

No arguments.

create_my_connection#

Issue a new MCP connection for this account writes

Mints a token for another Claude client to connect with, in this account's name - the same thing the Connect screen does, for setting a second machine up without opening a browser. The token is returned exactly once and is never recoverable: the database keeps only a hash. A new connection may hold no tool group that the connection asking for it does not already hold, so a narrowed token can copy itself or make something smaller and never something larger; naming a group this connection lacks is refused rather than quietly dropped. Leaving scopes out means the same groups this connection has. Call list_scopes for the keys. Costs nothing.

Argument Type Description
name * string What to call the connection, so it is recognisable in the list.
scopes array Tool groups the new connection may use, by key. Omit for the same groups this connection holds.
ttl_days integer Days until it expires by itself. 0 means it never does, which is what the browser offers by default.
note string A line about which machine it is for, shown beside it in the list.

rename_my_connection#

Rename one of my connections writes

Changes the name or the note on a connection belonging to this account. Only those two: the tool groups and the expiry are what the token was issued to do, and widening them afterwards would make the record of what was issued a lie - to change either, revoke it and issue another. An omitted field keeps what is stored. Costs nothing.

Argument Type Description
connection_id * integer The connection to rename, as returned by list_my_connections.
name string A new name.
note string A new note.

Administration#

Accounts, settings, the cron token, diagnostics and updates. Only ever available to an administrator. Administrators only.

transfer_course#

Hand a course to another account writes admin

Administrators only. Moves a course to another account. Its profile belongs to the old owner and will not resolve for the new one, so the assignment is cleared and has to be made again.

Argument Type Description
course_id * integer The course id, as returned by list_courses.
to * string The user name of the account that should own the course.

list_users#

List accounts reads admin

Every account on this installation: user name, role, whether it is disabled, when it last signed in, and how much it owns - courses, pages, profiles, tags and connections. Read this before changing or deleting an account, because those content counts are what a deletion is actually about. Costs nothing.

No arguments.

create_user#

Create an account writes admin

Creates an account. Leave password out and CourseForge generates one and returns it in this answer exactly once - only a hash is stored, so it cannot be read again afterwards. The account is asked to choose its own password at the first sign-in unless must_change_password is false. To let somebody create their own account instead of being handed a password, use issue_invite. Costs nothing.

Argument Type Description
username * string The user name to sign in with. Letters, digits, spaces and . _ @ + - are allowed.
role string user owns its own courses and nothing else. admin can also manage accounts, settings and updates. Defaults to user. One of user, admin.
password string A password of at least 10 characters. Omit it to have one generated and returned here.
display_name string The name shown in the interface. Defaults to the user name.
must_change_password boolean Ask this account to choose a new password at the first sign-in. Defaults to true.

update_user#

Change an account writes admin

Changes one account: its role, its display name, whether it is disabled, or its password. Only the fields you give are changed. Setting a password here replaces the old one without knowing it, and asks the account to choose a new one at the next sign-in. The last enabled administrator cannot be demoted or disabled, and this connection's own account cannot demote or disable itself. Costs nothing.

Argument Type Description
username * string The account to change, as returned by list_users.
role string A new role. One of user, admin.
display_name string A new display name.
disabled boolean true refuses every sign-in and every connection token this account owns, without deleting anything. false lets it back in.
password string A new password, of at least 10 characters. Setting one cuts off every MCP connection the account made before now, because a reset is how somebody who has held this password is cut off.

delete_user#

Delete an account writes destructive admin

Removes an account. content says what happens to everything it owns: "delete" destroys its courses, pages, profiles, tags and connections, and "transfer" hands them to transfer_to, or to you if that is left out. Deleted content cannot be recovered, so confirm_username has to match the account name exactly. Call list_users first to see how much is at stake. Costs nothing.

Argument Type Description
username * string The account to delete.
content * string What happens to the courses, profiles, tags and connections this account owns. One of delete, transfer.
transfer_to string The account that inherits the content when content is "transfer". Defaults to the account this connection belongs to.
confirm_username * string The exact user name again, as a confirmation that the right account is being deleted.

issue_invite#

Issue an invite code writes admin

Returns a fresh invite code exactly once, so somebody can create their own account from the sign-in screen rather than being handed a password over a chat. The database keeps only a hash, so the code cannot be read back later - pass it on now. Several invites may be open at once, each with its own label, role, expiry and number of places; issuing one leaves the others alone. It is good for a single account unless max_uses says otherwise. Costs nothing.

Argument Type Description
role string The role the account created with this code gets. Defaults to user. One of user, admin.
label string Who this invite is for - "marketing team", "the contractor" - so it can be told from the others in list_users and revoked by name. Up to 80 characters.
ttl_hours integer How long the code stays valid. Defaults to 48 hours.
max_uses integer How many accounts this one code may create. Defaults to 1. Anything above that is a code worth several accounts to whoever finds the file it is written in, so raise it only for a group you are expecting.

revoke_invite#

Revoke an open invite code writes destructive admin

Cancels an open invite, so the code stops working and nobody else can redeem it. list_users shows the open invites with their ids and labels; name one with invite_id, or leave it out to revoke the newest. Accounts already created from it keep working - revoking closes the door, it does not undo who came through. Refused when there is no open invite. Costs nothing.

Argument Type Description
invite_id integer The invite to revoke, from list_users. Omit for the newest open one.

get_cron_url#

Show the scheduler URL reads admin

The finished URL a scheduler calls to run background and batch work, built from the cron token this installation already has. generate_cron_token returns the same URL but makes a new token first, which stops every scheduler still calling the old one - so this is the tool for "what should I paste into cron", and that one is for "the secret has leaked". Refused when no token has been generated yet. Costs nothing.

No arguments.

list_settings#

List settings reads admin

Every setting this installation has: key, label, description, type, the current value, the value the release ships, and whether this installation has overridden it. The value of a secret - the cron token, the GitHub token - is never returned; you are told only whether one is stored. Use the keys from here with set_settings. Costs nothing.

Argument Type Description
group string One group only: general, content, scheduler, batch, updates, mcp, security, timeouts. Omit for all of them.

set_settings#

Change settings writes admin

Writes one or more settings. values is an object of setting key to new value, and every value is validated against the catalogue before anything is written, so one bad value changes nothing at all. An unknown key is refused and the valid ones are listed. An empty string for a secret leaves the stored secret alone rather than clearing it - reset_setting clears one. Call list_settings first for the keys, their types and their ranges. Costs nothing.

Argument Type Description
values * object Setting key to new value, for instance {"app.name": "Team CourseForge", "app.cron_workers": 4}.

reset_setting#

Reset a setting writes admin

Drops this installation's override for one setting, so the value the release ships applies again. Use it to undo a change without having to know what the default was. Resetting app.cron_token clears the cron secret and stops background runs until generate_cron_token makes a new one. Costs nothing.

Argument Type Description
key * string The setting to reset, as returned by list_settings.

generate_cron_token#

Generate the cron token writes admin

Makes a new cron secret, stores it, and returns the finished URL to paste into a hosting control panel as a job that runs once a minute. This URL is what makes background and batch runs possible at all: with no token, cron.php answers 404 to everybody and long runs are not offered. The token is returned here once and cannot be read back afterwards, and any scheduler still calling the old URL stops working the moment this returns - so only generate a new one when you are able to update the scheduler as well. Costs nothing.

No arguments.

set_up_php#

Measure PHP and raise what is too low writes admin

Reads the PHP configuration this host gives CourseForge, works out which limits are below what it needs, and writes a .user.ini raising exactly those. Every number is a floor and every run only ever raises: a directive already set is never removed or lowered, and a limit the host is generous about is left alone. Reports what it could not change, because some directives are fixed by the host and a line for them would be ignored in silence. Idempotent - running it twice writes nothing the second time. Pass dry_run to see the plan without touching anything at all. Pass release to take CourseForge's settings back out and let the host's own values return, which is what moving to different hosting wants. Costs nothing.

Argument Type Description
dry_run boolean Report what would happen, writing and storing nothing.
release boolean Remove CourseForge's block from .user.ini and let this host's own values return. They come back once PHP re-reads the file, which takes up to user_ini.cache_ttl seconds.

get_diagnostics#

Run the installation check reads admin

The installation check: the PHP runtime and its extensions, directory permissions, the database, the scheduler, updates, the MCP endpoint and the AI providers, each as a row with a status and a hint about what to do. This is the first thing to call when something is not working. It reads only: nothing here is changed. Costs nothing.

No arguments.

get_audit_log#

Read the audit log reads admin

Recent administrative actions - accounts created, roles changed, settings saved, connections revoked, updates installed - newest first, with who did it, when, from where, and whether it came through the web interface or through MCP. action matches the start of the action name, so "user" covers user.create, user.role and user.delete. The values of settings are never recorded here, only their keys. Costs nothing.

Argument Type Description
action string Keep only actions starting with this, for instance "user", "settings" or "update".
limit integer How many entries to return. Defaults to 100.

list_connections#

List MCP connections reads admin

Every MCP connection on this installation: its name, the account it belongs to, the tool groups it is limited to, when it was made, when it was last used and how often. Tokens are stored only as hashes and can never be shown again. Use the connection_id from here with revoke_connection. Your own unless you name another account or ask for all of them. Costs nothing.

Argument Type Description
owner string One other account's connections.
all boolean Administrators only: widen to every account on the installation. Without it a listing is your own.

revoke_connection#

Revoke an MCP connection writes destructive admin

Deletes one connection's token, so the next request it makes is refused. Revoking the connection you are talking through ends this conversation immediately, and no further tool call will work - call list_connections first and be sure which one you have, and check the name against the one this conversation is using. This cannot be undone: a new connection has to be made from the Connect screen. Costs nothing.

Argument Type Description
connection_id * integer The connection to revoke, as returned by list_connections.
confirm_name * string The exact name of that connection, as a confirmation that the right one is being cut off.

check_for_update#

Check for a new version writes admin

Asks GitHub whether a newer release exists and reports what is installed, what is available and whether the two differ. force ignores the cached answer and asks again, which is worth doing straight after a release and not much otherwise, because GitHub allows anonymous callers only sixty requests an hour per address. Nothing is downloaded or replaced here - install_update does that. Costs nothing.

Argument Type Description
force boolean Ignore the cached answer and ask GitHub again.

get_update_status#

Update status reads admin

What the updater knows: the installed version, when it last checked, whether an update is waiting, whether automatic updates are switched on, and which backups are on disk to roll back to. It answers from the cached check where it can and asks GitHub only when that cache has gone stale, so it is the cheap way to ask; check_for_update is the one that always goes out. Costs nothing.

No arguments.

install_update#

Install the update writes destructive admin

Downloads the latest release and replaces the application's own files with it. A backup of the current version is taken first, so a version that fails to start can be put back with rollback_update. This takes minutes and rewrites the code that is answering you, so do not run it while a generation run is writing pages, and expect this call to be slow. Call check_for_update first to see what would be installed, because confirm_version has to name it exactly. Settings and data are never touched: they live outside the release directory. Costs nothing.

Argument Type Description
confirm_version * string The version that would be installed, exactly as check_for_update or get_update_status reports it under latest.version. The install is refused unless the two agree.

rollback_update#

Roll an update back writes destructive admin

Puts the previously installed version back from the backup the last update took. Use it when a new version does not start or breaks something. With no backup on disk there is nothing to restore and the call reports that rather than doing anything. get_update_status lists what backups exist. confirm has to be true, because this replaces the running application with an older one and anything the newer version wrote to the database stays as it is. Costs nothing.

Argument Type Description
confirm * boolean Must be true. Without it nothing is restored and the call reports what it would have done.

get_update_history#

Read the update log reads admin

Every update and rollback this installation has attempted, newest first: which version it came from and went to, who asked for it, whether it was a manual or an automatic attempt, how it ended, the log it wrote and the error if it failed. This is where to look when a version is not the one somebody expected. Alongside it: the backups on disk and whether an update is running right now. Costs nothing.

No arguments.

get_prompts#

Read the prompt library reads admin

The prompt library: every instruction CourseForge sends a model, as a named slot you can read and rewrite. A slot is one piece of the eventual prompt - the persona the model writes as, the format contract an outline has to obey, the audience block, the tagging rules, the language instruction - and they are joined together for each job. Each slot comes back with its group, its label, what it is for, the placeholders it may use, the text this installation currently sends, the text the release ships, and whether the two differ. These are the installation-wide base layer: a profile may still override any slot for its own courses, and get_profile shows what a profile has changed. Costs nothing.

Argument Type Description
group string One group of slots only, as returned in groups. Omit for the whole library.

set_prompts#

Rewrite the prompt library writes admin

Rewrites one or more prompt slots for the whole installation, which is how the house style of everything CourseForge writes gets tuned. prompts is an object of slot key to new text, and only the slots you name are touched. An empty string is not an empty prompt: it drops this installation's override and puts the shipped default back. An unknown slot key is refused and nothing is written. Keep the placeholders the slot declares - they are filled in at generation time and a slot that loses one silently loses the thing it named. Read get_prompts first for the keys, the current text and the defaults. This is the base layer only: a profile can still override any of these for its own courses. Costs nothing.

Argument Type Description
prompts * object Slot key to new text, for instance {"global_system": "You are a patient technical writer."}. An empty string resets that slot to the shipped default.

Type to search every page. โ†‘ โ†“ to move, Enter to open.