Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e3e7c8c
feat(mcp): generate an MCP server in TypeScript
mattzcarey Oct 20, 2026
c74e859
fix(mcp): import the tools without the MCP SDK
mattzcarey Oct 20, 2026
4025954
feat(mcp): generate each tool's response schemas
mattzcarey Oct 21, 2026
c8b6dc6
feat(mcp): generate each tool's output schema
mattzcarey Oct 22, 2026
d921a60
docs(mcp): describe how the MCP target maps the API onto MCP
mattzcarey Oct 27, 2026
171309d
refactor(mcp): keep serveTools inside the runtime
mattzcarey Oct 27, 2026
e4d9a06
feat(mcp): generate the server as a Worker module
mattzcarey Oct 27, 2026
dc86ee0
refactor(mcp): generate tools, not a server
mattzcarey Oct 27, 2026
6de519b
fix(mcp): trim the base URL's trailing slashes without a regular expr…
mattzcarey Oct 27, 2026
580804f
docs(mcp): answer review on names and path parameters
mattzcarey Oct 28, 2026
7c4763f
Merge branch 'main' into mcp
mattzcarey Oct 30, 2026
8bd67ce
refactor(mcp): generate each group on its own, camelCase names, argum…
mattzcarey Oct 30, 2026
43eaedd
docs(mcp): list param in the runtime
mattzcarey Oct 30, 2026
df2942f
fix(mcp): name model constants after their $defs key
mattzcarey Oct 31, 2026
9ebd355
feat(mcp): stricter, smaller input schemas, and the endpoint in each …
mattzcarey Oct 31, 2026
f3c775a
feat(mcp): export checkArguments for servers that fill in arguments
mattzcarey Oct 31, 2026
b380789
feat(mcp): refuse unknown parameters inside path, query and headers
mattzcarey Oct 31, 2026
30da0b3
feat(mcp): a tool for every body, sent as fetch would
mattzcarey Oct 31, 2026
7a7bdee
style(mcp): brace a one-line if, for oxlint's curly rule
mattzcarey Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
packages:
- packages/*
- src/bin/forge/ts/lib
- src/bin/forge/mcp/lib
allowBuilds:
esbuild: true
workerd: true
Expand Down
218 changes: 218 additions & 0 deletions rfcs/mcp.md

Large diffs are not rendered by default.

21 changes: 16 additions & 5 deletions src/bin/forge/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//!
//! ```text
//! forge openapi.json --target ts --out sdk
//! forge openapi.json --target mcp --out tools
//! forge openapi.json --target wasm://plugin.wasm --out sdk
//! ```

Expand All @@ -13,6 +14,7 @@ use std::process::ExitCode;
use clap::Parser;
use forge::{Err, Forge};

mod mcp;
mod ts;

/// Generates an SDK from an OpenAPI document.
Expand All @@ -21,8 +23,9 @@ mod ts;
struct Args {
/// The OpenAPI JSON document, or - to read it from standard input.
spec: PathBuf,
/// The language of the SDK: `ts` for TypeScript, or `wasm://PATH` for
/// the plugin compiled to a WebAssembly component at PATH.
/// The language of the SDK: `ts` for TypeScript, `mcp` for the tools of
/// an MCP server in TypeScript, or `wasm://PATH` for the plugin compiled
/// to a WebAssembly component at PATH.
#[arg(long, value_parser = Target::parse)]
target: Target,
/// The directory to write the SDK to.
Expand All @@ -35,18 +38,22 @@ struct Args {
enum Target {
/// The TypeScript generator.
Ts,
/// The MCP generator, which generates the tools of an MCP server in TypeScript.
Mcp,
/// A plugin compiled to a WebAssembly component, at the path.
#[cfg(feature = "wasm")]
Wasm(PathBuf),
}

impl Target {
fn parse(s: &str) -> Result<Target, String> {
if s == "ts" {
return Ok(Target::Ts);
match s {
"ts" => return Ok(Target::Ts),
"mcp" => return Ok(Target::Mcp),
_ => {}
}
let Some(path) = s.strip_prefix("wasm://") else {
return Err("possible values: ts, wasm://PATH".to_owned());
return Err("possible values: ts, mcp, wasm://PATH".to_owned());
};
if path.is_empty() {
return Err("wasm:// needs the path of a plugin, as in wasm://plugin.wasm".to_owned());
Expand Down Expand Up @@ -75,6 +82,10 @@ fn run(args: Args) -> Result<(), Err> {
let forge = read(&args.spec)?;
forge::write(&args.out, forge::run(&forge, &ts::Generator)?)
}
Target::Mcp => {
let forge = read(&args.spec)?;
forge::write(&args.out, forge::run(&forge, &mcp::Generator)?)
}
#[cfg(feature = "wasm")]
Target::Wasm(path) => {
// Compiling the plugin and reading the document are both slow,
Expand Down
Loading
Loading