Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,17 @@ OpenAPIToolGenerator (src/generator.ts)
| `src/overlay.ts` | OpenAPI Overlay 1.0 application with a JSONPath subset (filters, recursive descent); `OverlayError` |
| `src/lint.ts` | `lintDocument` agent-readiness findings (severity + fix hints); `PAGINATION_PARAM` shared regex |
| `src/sdk.ts` | `toSdkTool` — registerTool-shaped output for the official MCP SDK (no SDK dependency) |
| `src/type-signature.ts` | `emitToolTypeScript` — TypeScript signature/declaration rendering of a tool's call contract (`emitTypeSignatures` option → `metadata.typescript`) |
| `src/naming-presets.ts` | `dottedNaming` — two-segment `ns.method` naming preset for CodeCall namespace binding; `CODECALL_RESERVED_NAMESPACES` |
| `src/elicitation.ts` | `deriveSecurityElicitations` — MCP elicitation descriptors (`{message, requestedSchema}`) from a tool's security data |
| `src/arazzo.ts` | `fromArazzo()` — Arazzo 1.0 parsing, source/operation resolution, workflow IR + consolidated tools (companions: `arazzo-types.ts`, `arazzo-expressions.ts`) |
| `src/parameter-resolver.ts` | Resolves OpenAPI parameters + requestBody into flat inputSchema with conflict resolution; flattens `allOf` bodies, flags `wholeBody`/`binary` |
| `src/response-builder.ts` | Builds outputSchema from OpenAPI responses with content-type and status code preferences |
| `src/format-resolver.ts` | Format-to-schema resolution. Built-in resolvers for uuid, date-time, email, int32, etc. |
| `src/schema-builder.ts` | Static utilities: merge, union, clone, flatten, simplify, withFormat, etc. |
| `src/security-resolver.ts` | Resolves security schemes (Bearer, Basic, Digest, API Key, OAuth2, OpenID Connect) |
| `src/validator.ts` | Validates OpenAPI document structure |
| `src/errors.ts` | Error class hierarchy: LoadError, ParseError, ValidationError, GenerationError, SchemaError |
| `src/errors.ts` | Error class hierarchy: LoadError, ParseError, ValidationError, GenerationError, SchemaError, OverlayError, RequestBuildError, ArazzoError |
| `src/index.ts` | Barrel file for public exports |

## Development Commands
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ for (const tool of await generator.generateTools({ target: "claude" })) {
| [Request Builder](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/request-builder.md) | `buildHttpRequest` — full OpenAPI parameter serialization |
| [Client Targets](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/client-targets.md) | Per-client schema dialects (Claude, OpenAI, Gemini) |
| [Curation](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/curation.md) | Token budgets, overlays, lint, trimming, response hints |
| [Type Signatures](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/type-signatures.md) | TypeScript call contracts for code-execution surfaces |
| [Modern MCP Fields](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/modern-mcp-fields.md) | Tool `_meta`, icons, `x-mcp-header`, elicitation descriptors |
| [Arazzo Workflows](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/arazzo.md) | fromArazzo() — Arazzo 1.0 workflows as consolidated MCP tools |
| [Response Schemas](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/response-schemas.md) | Output schemas, status codes, oneOf unions |
| [Annotations & Extensions](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/annotations.md) | Tool title, annotation inference, `x-mcp` extension family |
| [Security](https://github.com/agentfront/mcp-from-openapi/blob/main/docs/security.md) | SecurityResolver, all auth types, custom resolvers |
Expand Down
6 changes: 3 additions & 3 deletions docs/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Disable inference with `{ inferAnnotations: false }`; extension overrides (below

## Extension Overrides

Spec authors can override the tool name, title, description, and annotations — and exclude operations entirely — through the `x-mcp` extension family at the **operation level**. Three dialects are read, in ascending precedence (later wins field-by-field):
Spec authors can override the tool name, title, description, annotations, `_meta` entries, and icons — and exclude operations entirely — through the `x-mcp` extension family at the **operation level**. Three dialects are read, in ascending precedence (later wins field-by-field; `meta` merges key-by-key, `icons` replaces wholesale, and `x-speakeasy-mcp` supports neither):
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### 1. `x-speakeasy-mcp` (interop)

Expand Down Expand Up @@ -78,7 +78,7 @@ x-mcp:

### 3. `x-frontmcp` (canonical, highest precedence)

Only its `annotations` block participates here (including `annotations.title`); the rest of the extension (cache, codecall, tags, examples, ...) flows through `tool.metadata.frontmcp` untouched — see [x-frontmcp Extension](./x-frontmcp.md).
Its `annotations` block (including `annotations.title`, which also becomes the tool title), `meta`, and `icons` map onto tool overrides; the rest of the extension (cache, codecall, tags, examples, ...) flows through `tool.metadata.frontmcp` untouched — see [x-frontmcp Extension](./x-frontmcp.md).

```yaml
x-frontmcp:
Expand Down Expand Up @@ -117,7 +117,7 @@ inferAnnotationsFromMethod('delete');
// { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }

extractExtensionOverrides(operation);
// { disabled?, name?, title?, description?, annotations? }
// { disabled?, name?, title?, description?, annotations?, meta?, icons? }
```

---
Expand Down
34 changes: 34 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,40 @@ Apply a client dialect's schema transforms (`'claude' | 'openai' | 'gemini' | 's
applyClientTarget(schema: JsonSchema, target: ClientTarget): JsonSchema
```

### fromArazzo / parseRuntimeExpression

Convert an Arazzo 1.0 workflow document into consolidated MCP tools (one per workflow, IR on `metadata.workflow`); parse Arazzo runtime expressions standalone. Throws `ArazzoError` with a JSON-Pointer `path`. See [Arazzo Workflows](./arazzo.md).

```typescript
fromArazzo(document: ArazzoDocument | string, options: FromArazzoOptions): Promise<McpOpenAPITool[]>
parseRuntimeExpression(raw: string, docPath?: string): RuntimeExpressionAST
```

### deriveSecurityElicitations

Derive MCP-elicitation-compatible `{ message, requestedSchema }` credential requests from a tool's security data. See [Modern MCP Fields](./modern-mcp-fields.md).

```typescript
deriveSecurityElicitations(tool: McpOpenAPITool): SecurityElicitation[]
```

### dottedNaming

Naming preset producing two-segment `ns.method` tool names bindable by code-execution namespaces (FrontMCP CodeCall). See [Naming Strategies](./naming-strategies.md).

```typescript
dottedNaming(options?: DottedNamingOptions): NamingStrategy
```

### emitToolTypeScript / toPascalIdentifier

Render a tool's call contract as TypeScript text (one-line `signature` + self-contained `declaration`). Also emitted during generation via `GenerateOptions.emitTypeSignatures` as `metadata.typescript`. See [Type Signatures](./type-signatures.md).

```typescript
emitToolTypeScript(toolName: string, description: string | undefined, inputSchema: JsonSchema, outputSchema?: JsonSchema, options?: TypeSignatureOptions): ToolTypeScriptInfo
toPascalIdentifier(toolName: string): string
```

### analyzeToolSet / estimateToolTokens

Context-budget analysis: per-tool token estimates (heaviest first) and curation warnings. See [Curation](./curation.md).
Expand Down
83 changes: 83 additions & 0 deletions docs/arazzo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Arazzo Workflows

[Home](../README.md) | [Configuration](./configuration.md) | [API Reference](./api-reference.md)

---

Tool consolidation is the ecosystem's consensus answer to context bloat, and [Arazzo 1.0](https://spec.openapis.org/arazzo/v1.0.0.html) is its standards-track format: a document describing multi-step workflows over one or more OpenAPI APIs. `fromArazzo()` turns each workflow into **one** consolidated MCP tool — workflow inputs become the tool's input schema, workflow outputs derive its output schema, and a pure, JSON-serializable IR carries the step sequence. The library never fetches source URLs, performs HTTP, or evaluates expressions — an executor (e.g. a framework like FrontMCP) drives the IR.

## Quick start

```typescript
import { fromArazzo } from 'mcp-from-openapi';

const tools = await fromArazzo(arazzoYamlOrObject, {
sources: { pets: petstoreDocument, orders: ordersGenerator }, // name → document or generator
generateOptions: { target: 'claude', emitTypeSignatures: true },
});
// one McpOpenAPITool per workflow, in document order
```

## Sources

`sources` maps every source description **name** to a resolved OpenAPI document or a pre-built `OpenAPIToolGenerator`. URLs in `sourceDescriptions` are **never fetched** — supplying documents keeps loading under the caller's control (and its SSRF posture). A source used by any step must be supplied; unknown keys are rejected; `type: 'arazzo'` sources cannot be used by steps. Step operations resolve by `operationId` (searched across all supplied sources; ambiguity is an error — pin with `$sourceDescriptions.<name>.<operationId>`) or by `operationPath` (`{$sourceDescriptions.pets.url}#/paths/~1pets~1{petId}/get`).

## The workflow IR

The tool's `metadata.workflow` is the complete, self-contained execution plan:

```typescript
const ir = tool.metadata.workflow!;
ir.steps[0];
// {
// kind: 'operation', stepId: 'fetch', source: 'pets',
// path: '/pets/{petId}', method: 'get', operationId: 'getPet',
// parameters: [{ name: 'petId', in: 'path', value: { kind: 'expression', expression: {...} } }],
// operation: { inputSchema, outputSchema, mapper, security, servers }, // no second spec pass needed
// outputs: { pet: { type: 'response', source: 'body', raw: '$response.body', ... } },
// }
```

Each operation step embeds the resolved operation's essentials — its `mapper` feeds [`buildHttpRequest`](./request-builder.md) directly. Nested workflow invocations appear as `{ kind: 'workflow', workflowId }` steps (recursion is rejected). `successCriteria` conditions are carried **raw** and never evaluated; `onSuccess`/`onFailure` actions (`end`/`goto`/`retry` with `retryAfter`/`retryLimit`) are captured faithfully. Request bodies keep the verbatim `payload` plus a pointer-keyed `payloadExpressions` substitution list (RFC 6901) and parsed `replacements`.

**Placeholders:** a workflow tool's `metadata.path` is `arazzo:<workflowId>` and `method` is `'post'` — never feed the workflow tool itself to `buildHttpRequest`; its top-level `mapper` is `[]` by design. Executors drive each step's `operation.mapper`.

**Treat the IR as immutable:** steps referencing the same operation share embedded schema/mapper structure in memory (JSON serialization is unaffected). Documents are normalized through a JSON round-trip on input — YAML anchors expand into distinct nodes, YAML-only scalars become their JSON forms, and cyclic or absurdly deep documents are rejected with `ArazzoError`. Cross-document `dependsOn` entries (`$sourceDescriptions.<name>.<workflowId>`) are accepted and carried verbatim; cross-document *step* invocations are not supported. One documented strictness deviation: `workflowId`/`stepId` must match `[A-Za-z0-9_-]+` (a SHOULD in the spec, enforced here so `$steps.<id>` references stay parseable).

## Runtime expressions

Every Arazzo runtime expression is parsed into a serializable AST (`{ type, raw, path, source?, name?, pointer? }`) — `$inputs.x`, `$steps.id.outputs.y`, `$response.body#/json/pointer`, `$request.header.Name`, `$message.body`, `$statusCode`, `$url`, `$method`, `$workflows.*`, `$sourceDescriptions.*`, `$components.*`. Strings with embedded `{$...}` become templates; strings whose `$` prefix matches no known root (like `"$50"` or `"$request-id"`) stay literals. The parser is exported standalone:

```typescript
import { parseRuntimeExpression } from 'mcp-from-openapi';
parseRuntimeExpression('$steps.fetch.outputs.pet');
// { type: 'steps', raw: '...', path: ['fetch', 'outputs', 'pet'] }
```

## Output schema derivation

Workflow `outputs` derive the tool's output schema best-effort: `$statusCode` → `number`; `$url` / `$method` / header refs → `string`; `$inputs.<name>` → that input's schema; `$steps.<id>.outputs.<name>` is chased (depth-capped) into the step's `$response.body` schema, following `#/pointers` through `properties`/`items`. Anything unresolvable degrades to an unconstrained schema. Every derived property keeps the raw expression in its `description` (`Arazzo output: $steps.fetch.outputs.pet`), and outputs are never `required` — they exist only after successful execution.

## Options

`ArazzoGenerateOptions` is the schema-shaping subset of [`GenerateOptions`](./configuration.md): `target`, `maxSchemaDepth`, `maxProperties`, `maxDescriptionLength`, `stripExamples`, `includeExamples`, `resolveFormats`/`formatResolvers`, `preferredStatusCodes`, `includeAllResponses`, `maxToolNameLength`, `includeSecurityInInput`, and `emitTypeSignatures`. They apply to the per-step embedded schemas AND the consolidated workflow schemas, in the same order as `generateTool` (formats → depth truncation → trims → client target). Operation-filtering options have no meaning here and are not accepted.

## Errors

Every failure throws `ArazzoError` with a JSON-Pointer `path` into the Arazzo document:

```typescript
try {
await fromArazzo(doc, { sources });
} catch (error) {
if (error instanceof ArazzoError) {
console.error(error.message, error.path); // e.g. '/workflows/0/steps/2'
}
}
```

Structural violations (missing ids, duplicate names, malformed criteria/actions), unresolvable references (`$components.*`, unknown operationIds, missing sources), cyclic `dependsOn` chains, and recursive workflow invocations are all rejected at parse time.

---

**Related:** [Request Builder](./request-builder.md) | [Type Signatures](./type-signatures.md) | [Configuration](./configuration.md)
3 changes: 3 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ const tools = await generator.generateTools({
| `maxProperties` | `number` | - | Cap object nodes to their first N properties (drop noted); root input params never dropped |
| `maxDescriptionLength` | `number` | - | Ellipsis-truncate every schema description at N chars |
| `stripExamples` | `boolean` | `false` | Remove all `examples` arrays from generated schemas |
| `emitTypeSignatures` | `boolean` | `false` | Render `metadata.typescript = { signature, declaration }` — see [Type Signatures](./type-signatures.md) |
| `emitMeta` | `boolean` | `false` | Emit the `dev.agentfront.openapi/operation` entry on tool `_meta` — see [Modern MCP Fields](./modern-mcp-fields.md) |
| `inheritDocumentIcons` | `boolean` | `false` | Fall back to `info['x-logo']` as a tool icon when no extension icons exist |

### Filtering Operations

Expand Down
Loading