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
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ npm run check
| --- | --- | --- |
| [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. |
Expand All @@ -34,14 +35,37 @@ npm run example:petstore:live
# Real TypeChain → adapted TypeMCP tool call followed by a deterministic summary.
npm run example:petstore:agent

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

# Deterministic fixtures used by unit tests; no network request.
npm run example:petstore:typemcp:fixture
npm run example:petstore:agent:fixture
```

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.

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. The TypeChain workflow deterministically selects and calls the adapted `search_available_pets` tool; it is **not** an LLM-driven agent and it does not create an MCP client/session or host an MCP transport.
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()`.

### 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:

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

// Construct and authenticate a LangChain-compatible model in your application.
declare const applicationModel: Parameters<
typeof createPetstoreAgent
>[0]["model"];

const agent = await createPetstoreAgent({ model: applicationModel });
const response = await agent.invoke({
messages: [{ role: "user", content: "Which pets are available?" }],
});
```

`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

Expand Down
63 changes: 63 additions & 0 deletions docs/planning/2026-07-29-real-typechain-petstore-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Real TypeChain Petstore Agent Implementation Plan

> **For Hermes:** Implement this issue-scoped plan with test-first slices and preserve the `dev` → release-only `main` workflow.

**Goal:** Add a real LangChain agent factory that uses TypeChain `createTypeMcpAgent()` to operate the existing read-only Swagger Petstore TypeMCP tools, while keeping provider credentials and runtime selection application-owned.

**Architecture:** Create a narrowly scoped `createPetstoreAgent()` factory. It accepts a caller-provided LangChain chat model and an optional resolver-backed `PetstoreServer`, then delegates agent construction to TypeChain's `createTypeMcpAgent()`. A test uses LangChain's `FakeToolCallingModel` plus the existing deterministic fixture server to prove an actual agent loop selects and invokes `search_available_pets`; no network or model provider runs in CI. A separate manual composition example accepts a model from the caller rather than initializing a provider or reading a secret.

**Tech stack:** TypeScript, Vitest, LangChain `FakeToolCallingModel`, `@theorvane/type-chain/typemcp`, TypeMCP decorators, Zod.

---

### Task 1: Define and prove the real agent factory contract

**Files:**
- Create: `test/typechain-petstore-real-agent.test.ts`
- Create: `examples/typechain-petstore-real-agent.ts`

1. Write a failing integration test importing `createPetstoreAgent()`.
2. Use `FakeToolCallingModel` to emit one `search_available_pets` call with `{ limit: 2 }`, followed by an empty tool-call response.
3. Invoke the constructed agent with a user message and assert it includes a LangChain tool message for that call containing the fixture pet result.
4. Run the focused test and observe failure because the module/export does not exist.
5. Implement only the factory: accept `{ model, server? }`, default to a `PetstoreServer`, supply its explicit resolver to `createTypeMcpAgent()`, and return the LangChain agent.
6. Re-run the focused test, then lint and typecheck.

### Task 2: Add executable fixture agent-loop evidence

**Files:**
- Create: `examples/typechain-petstore-real-agent-fixture.ts`
- Modify: `package.json`
- Modify: `test/typechain-petstore-real-agent.test.ts`

1. Write a failing assertion for a runnable fixture command that reports the agent's selected tool and its tool result.
2. Implement the fixture runner with `FakeToolCallingModel` and the existing `createFixturePetstoreServer()`.
3. Add `example:petstore:agent:real:fixture`; do not add a provider package or live credential-dependent command.
4. Run the focused test and fixture command successfully.

### Task 3: Document real-agent composition without adding provider ownership

**Files:**
- Modify: `README.md`
- Modify: `docs/planning/2026-07-29-real-typechain-petstore-agent.md`

1. Document the distinction between the deterministic adapter workflow and the real `createTypeMcpAgent()` factory.
2. Show a short application-owned pseudo-composition snippet with a `model` supplied by the caller. Do not use an API key, `process.env`, or provider-specific package.
3. State that TypeChain constructs the in-process LangChain agent but does not select/configure a provider, host an MCP transport, create an MCP client/session, or authorize tool invocations.
4. Run formatting, full checks, audit, and whitespace checks.

### Task 4: Deliver through governed PRs

1. Commit the plan and implementation in focused commits.
2. Create a `dev` PR closing #21.
3. Obtain exact-head independent review, green `verify`, and resolved threads before squash merge.
4. Reconcile `main` ancestry into `dev` only if required by current remote history, via a two-parent merge with separate review.
5. Create a reviewed `dev` → `main` promotion; require exact-head `verify`, `release-promotion`, independent approval, and resolved threads before squash merge.
6. Clone current `main` cleanly and run `npm ci`, `npm run check`, and the real-agent fixture command.

## Explicit non-goals

- No provider SDK, API key, model identifier, or environment-variable model configuration.
- No automated live LLM invocation in CI.
- No MCP HTTP/stdio server, client/session, or cross-process transport.
- No Petstore write endpoint or credential path.
51 changes: 51 additions & 0 deletions examples/typechain-petstore-real-agent-fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ToolMessage } from "@langchain/core/messages";
import { FakeToolCallingModel } from "langchain";

import { createFixturePetstoreServer } from "./petstore-fixture.js";
import { createPetstoreAgent } from "./typechain-petstore-real-agent.js";

export async function run(): Promise<void> {
const agent = await createPetstoreAgent({
model: new FakeToolCallingModel({
toolCalls: [
[
{
id: "available-pets-call",
name: "search_available_pets",
args: { limit: 2 },
},
],
[],
],
}),
server: createFixturePetstoreServer(),
});
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Which pets are currently available?",
},
],
});
const toolMessage = result.messages.find(ToolMessage.isInstance);
if (!toolMessage) {
throw new Error("Expected the LangChain agent to execute a Petstore tool.");
}

console.log(
JSON.stringify(
{
tool: "search_available_pets",
toolCallId: toolMessage.tool_call_id,
result: JSON.parse(toolMessage.content as string) as unknown,
Comment thread
sjungwon03-ai marked this conversation as resolved.
},
null,
2,
),
);
}

if (import.meta.url === `file://${process.argv[1]}`) {
await run();
}
30 changes: 30 additions & 0 deletions examples/typechain-petstore-real-agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createTypeMcpAgent } from "@theorvane/type-chain/typemcp";

import { PetstoreServer } from "./typemcp-petstore-server.js";

type PetstoreAgentModel = Parameters<
typeof createTypeMcpAgent<PetstoreServer>
>[0]["model"];

export type CreatePetstoreAgentOptions = Readonly<{
/** The application selects, configures, and authenticates this LangChain model. */
model: PetstoreAgentModel;
/** Optional explicit Petstore resolver target; defaults to the live read-only server. */
server?: PetstoreServer;
Comment thread
sjungwon03-ai marked this conversation as resolved.
}>;

/**
* Builds a real LangChain agent from the in-process TypeMCP Petstore tools.
* It does not choose a model provider, read credentials, or start an MCP transport.
*/
export async function createPetstoreAgent({
model,
server,
}: CreatePetstoreAgentOptions) {
const resolvedServer = server ?? new PetstoreServer();
return createTypeMcpAgent({
model,
server: PetstoreServer,
resolver: { resolve: () => resolvedServer },
});
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"example:petstore:typemcp:fixture": "tsx examples/typemcp-petstore-fixture.ts",
"example:petstore:agent:fixture": "tsx examples/typechain-petstore-agent-fixture.ts",
"example:petstore:live": "tsx examples/petstore-live.ts",
"example:petstore:agent": "tsx examples/typechain-petstore-agent-live.ts"
"example:petstore:agent": "tsx examples/typechain-petstore-agent-live.ts",
"example:petstore:agent:real:fixture": "tsx examples/typechain-petstore-real-agent-fixture.ts"
},
"dependencies": {
"@langchain/core": "^1.2.3",
Expand Down
16 changes: 16 additions & 0 deletions test/typechain-petstore-real-agent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from "vitest";

import { runExample } from "./run-example.js";

describe("real TypeChain Swagger Petstore agent", () => {
it("runs a LangChain agent loop that selects and executes the TypeMCP search tool", () => {
expect(runExample("example:petstore:agent:real:fixture")).toEqual({
tool: "search_available_pets",
toolCallId: "available-pets-call",
result: [
{ id: 1, name: "Milo", status: "available" },
{ id: 2, name: "Nori", status: "available" },
],
});
});
});