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: 2 additions & 4 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ jobs:
- run: npm run lint
- run: npm run build
- run: npm test
- run: npm run example:typechain
- run: npm run example:policy
- run: npm run example:typemcp
- run: npm run example:bridge
- run: npm run example:petstore:mcp:fixture
- run: npm run example:petstore:mcp-agent:fixture
- run: npm run audit:prod
119 changes: 73 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,84 +1,111 @@
# Theorvane Examples
# Theorvane Petstore MCP Example

Runnable, minimal examples for [TypeChain](https://github.com/Theorvane/type-chain) and [TypeMCP](https://github.com/Theorvane/type-mcp). Every example uses the published packages, explicit Zod schemas, standard TypeScript decorators, and application-owned runtime boundaries.
A single runnable example showing a truthful end-to-end path from the public [Swagger Petstore v2](https://petstore.swagger.io/) demo API to a TypeChain `@Agent()` that uses an **actual local MCP HTTP session**.

## What this proves

```text
Swagger Petstore v2 GET API
→ injected PetstoreClient
→ TypeMCP @McpServer() / @McpTool()
→ TypeMCP Streamable HTTP handler
→ local 127.0.0.1 ephemeral-port MCP runtime
→ official MCP SDK client session
→ TypeChain @Agent() + @Tool() MCP-client façade
→ TypeChain buildAgent() / LangChain agent loop
```

The fixture run proves the MCP SDK client performs protocol initialization, `tools/list`, and `tools/call` over loopback Streamable HTTP. It also proves a LangChain `FakeToolCallingModel` selects `search_available_pets`, and the TypeChain `@Agent()` façade sends that call through the MCP client rather than calling the REST client or TypeMCP server directly.

## Prerequisites

- Node.js 20 or newer
- npm 10 or newer

```bash
npm install
npm ci
npm run check
```

## Examples
## Run the deterministic MCP agent example

| Example | Run | Demonstrates |
| --- | --- | --- |
| [Swagger Petstore TypeMCP wrapper](examples/typemcp-petstore-server.ts) | `npm run example:petstore:live` | Wrap public Swagger Petstore **read-only** operations in `@McpServer()` / `@McpTool()` declarations. |
| [Swagger Petstore TypeChain workflow](examples/typechain-petstore-agent.ts) | `npm run example:petstore:agent` | Adapt the Petstore TypeMCP tool into an in-process TypeChain/LangChain-compatible tool and deterministically summarize live available pets. |
| [Swagger Petstore real TypeChain agent](examples/typechain-petstore-real-agent.ts) | `npm run example:petstore:agent:real:fixture` | Build a real LangChain agent loop with TypeChain `createTypeMcpAgent()` and an application-supplied model. |
| [TypeChain tool definition](examples/typechain-tool-definition.ts) | `npm run example:typechain` | Foundational `@Tool()` metadata and direct receiver-bound invocation. |
| [TypeChain policy guard](examples/typechain-policy-guard.ts) | `npm run example:policy` | Foundational `@Policy()` declaration plus an application-owned approval/audit decision. |
| [TypeMCP server definition](examples/typemcp-server-definition.ts) | `npm run example:typemcp` | Minimal decorator and explicit compilation reference. |
| [TypeMCP → TypeChain bridge](examples/typemcp-langchain-bridge.ts) | `npm run example:bridge` | Minimal in-process adapter reference. |
```bash
npm run example:petstore:mcp-agent:fixture
```

## Swagger Petstore scenario
It starts an in-process Node HTTP server bound only to `127.0.0.1` on an ephemeral port, connects the official MCP SDK `StreamableHTTPClientTransport`, discovers the tools, runs the agent loop, and shuts down the MCP client session and HTTP server in `finally` blocks.

Expected result contains fixture data:

```json
{
"discoveredTools": [
"search_available_pets",
"get_pet",
"get_petstore_inventory"
],
"tool": "search_available_pets",
"result": [
{ "id": 1, "name": "Milo", "status": "available" },
{ "id": 2, "name": "Nori", "status": "available" }
]
}
```

The primary scenario uses the public [Swagger Petstore v2](https://petstore.swagger.io/) demo API at the fixed base URL `https://petstore.swagger.io/v2`.
For the narrower protocol-only smoke run (loopback URL, 404 route boundary, `tools/list`, and `tools/call`), use:

```bash
npm run example:petstore:mcp:fixture
```

## Optional public REST smoke run

```bash
# Real public GET calls: available pets, one pet detail, and inventory.
npm run example:petstore:live
```

# Real TypeChain → adapted TypeMCP tool call followed by a deterministic summary.
npm run example:petstore:agent
This calls the mutable public Swagger Petstore demo API. Its data and uptime are outside this repository's control, so it is not used by tests or CI. The command is a GET-only REST smoke run; it does **not** demonstrate MCP client connectivity or a model-backed agent.

# Real LangChain agent-loop proof; no network or provider credential.
npm run example:petstore:agent:real:fixture
## Read-only Petstore contract

# Deterministic fixtures used by unit tests; no network request.
npm run example:petstore:typemcp:fixture
npm run example:petstore:agent:fixture
```
`PetstoreClient` has a fixed base URL, `https://petstore.swagger.io/v2`, and only sends `GET` requests. The TypeMCP server exposes exactly three tools:

The TypeMCP server exposes exactly three tools: `search_available_pets`, `get_pet`, and `get_petstore_inventory`. The client fixes the public HTTPS base URL, sends only `GET` requests, applies a timeout, validates JSON response shapes, and contains no API-key, credential, create, update, or delete operation.
| MCP tool | REST operation |
| --- | --- |
| `search_available_pets` | `GET /pet/findByStatus?status=available` |
| `get_pet` | `GET /pet/{petId}` |
| `get_petstore_inventory` | `GET /store/inventory` |

The public Petstore service is demo infrastructure: records, status counts, and availability can change or be unavailable. Consequently, CI and unit tests use injected fixtures, while the two `:live` commands are intentional manual smoke demonstrations. `summarizeAvailablePets()` is a deterministic adapter workflow, while `createPetstoreAgent()` is the separate real LangChain agent factory. The latter delegates tool selection and agent-loop execution to LangChain through TypeChain `createTypeMcpAgent()`.
The example has no create, update, delete, API-key, credential, environment-configured API host, or public listener path.

### Application-owned model runtime
## Application-owned model runtime

The real agent factory accepts an already configured LangChain-compatible chat model. The application—not this repository—chooses a provider/model and owns its package installation, credentials, authorization, retry policy, and observability:
The fixture uses `FakeToolCallingModel`, not a live provider. An application that wants a real model must install/configure that model itself, then pass it together with a connected MCP client to the agent builder:

```ts
import { createPetstoreAgent } from "./examples/typechain-petstore-real-agent.js";
import { buildPetstoreMcpAgent } from "./examples/typechain-petstore-mcp-agent.js";

// Construct and authenticate a LangChain-compatible model in your application.
// The consuming application chooses, authenticates, and authorizes this model.
declare const applicationModel: Parameters<
typeof createPetstoreAgent
typeof buildPetstoreMcpAgent
>[0]["model"];

const agent = await createPetstoreAgent({ model: applicationModel });
const response = await agent.invoke({
messages: [{ role: "user", content: "Which pets are available?" }],
// The application owns the lifecycle of this client connection.
declare const connectedMcpClient: Parameters<
typeof buildPetstoreMcpAgent
>[0]["client"];

const agent = buildPetstoreMcpAgent({
model: applicationModel,
client: connectedMcpClient,
});
```

`npm run example:petstore:agent:real:fixture` proves this is an actual agent loop without a live model: LangChain's `FakeToolCallingModel` selects `search_available_pets`, and the agent executes the TypeMCP-derived tool against fixture data. This repository intentionally supplies no provider SDK, API key, environment-variable model configuration, or automated live LLM command.

## Boundaries that the examples intentionally preserve

These examples show declaration and adapter boundaries, not a full hosted application:

- The application owns API credentials, authorization, approval, retries, timeouts, audit persistence, and redaction.
- TypeMCP transport selection and MCP session lifecycle are application decisions. The examples do not start a stdio or HTTP server.
- The bridge is in-process only. It adapts a decorated TypeMCP class to LangChain tools; it does not create an MCP client or network connection.
- No model provider is configured. Add a model only in your application after deciding its credentials, authorization, and runtime policy.
This repository deliberately does not select a provider/model, install provider SDKs, read credentials or `process.env`, run a live LLM command, or implement authorization/retry/audit/redaction policy.

## Dependency-security boundary
## Example-only runtime boundary

These examples use the published `@theorvane/type-mcp@^0.2.2` remediation. Its consumer-enforceable dependency contract resolves `@modelcontextprotocol/sdk@1.30.0` and `@hono/node-server@2.0.12`; `npm run audit:prod` verifies the installed production graph with no local npm override.
The loopback runtime is a deterministic integration example, not a production deployment recipe. It intentionally does **not** provide public hosting, authentication, authorization, durable sessions, a reverse proxy, observability, deployment configuration, or persistent MCP lifecycle management. A production application must own those decisions.

## Development

Expand Down
Loading