From 3ef010f2207009731d9763d7e04c9019dfb70197 Mon Sep 17 00:00:00 2001 From: Jungwon Sohn Date: Wed, 29 Jul 2026 11:51:36 +0900 Subject: [PATCH] release: promote real TypeChain Petstore agent (#26) * ci: require verified dev-to-main promotions * fix: pin examples MCP SDK to audited graph (#3) Co-authored-by: sjungwon03 <> * fix(deps): adopt TypeMCP 0.2.2 remediation (#7) * chore: reconcile main release history into dev (#11) * release: publish verified TypeChain and TypeMCP examples (#1) Co-authored-by: sjungwon03 <> * release: promote audited MCP SDK graph to production (#5) * ci: require verified dev-to-main promotions * fix: pin examples MCP SDK to audited graph (#3) Co-authored-by: sjungwon03 <> --------- Co-authored-by: sjungwon03 <> * fix: retain published TypeMCP dependency contract * fix: remove obsolete reconciliation override * feat: add read-only Swagger Petstore agent examples (#16) * docs(planning): add Swagger Petstore agent plan * feat: add read-only Swagger Petstore client * feat: wrap read-only Petstore API in TypeMCP * feat: add TypeChain Petstore agent workflow * feat: add live Swagger Petstore demonstrations * feat: add real TypeChain Petstore agent factory (#22) * docs(planning): define real Petstore agent delivery * feat: add real TypeChain Petstore agent factory * docs: explain application-owned Petstore agent runtime --------- Co-authored-by: sjungwon03 <> --- README.md | 26 +++++++- ...026-07-29-real-typechain-petstore-agent.md | 63 +++++++++++++++++++ .../typechain-petstore-real-agent-fixture.ts | 51 +++++++++++++++ examples/typechain-petstore-real-agent.ts | 30 +++++++++ package.json | 3 +- test/typechain-petstore-real-agent.test.ts | 16 +++++ 6 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 docs/planning/2026-07-29-real-typechain-petstore-agent.md create mode 100644 examples/typechain-petstore-real-agent-fixture.ts create mode 100644 examples/typechain-petstore-real-agent.ts create mode 100644 test/typechain-petstore-real-agent.test.ts diff --git a/README.md b/README.md index be90134..2ac148e 100644 --- a/README.md +++ b/README.md @@ -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. | @@ -34,6 +35,9 @@ 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 @@ -41,7 +45,27 @@ 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 diff --git a/docs/planning/2026-07-29-real-typechain-petstore-agent.md b/docs/planning/2026-07-29-real-typechain-petstore-agent.md new file mode 100644 index 0000000..3080906 --- /dev/null +++ b/docs/planning/2026-07-29-real-typechain-petstore-agent.md @@ -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. diff --git a/examples/typechain-petstore-real-agent-fixture.ts b/examples/typechain-petstore-real-agent-fixture.ts new file mode 100644 index 0000000..db2b333 --- /dev/null +++ b/examples/typechain-petstore-real-agent-fixture.ts @@ -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 { + 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, + }, + null, + 2, + ), + ); +} + +if (import.meta.url === `file://${process.argv[1]}`) { + await run(); +} diff --git a/examples/typechain-petstore-real-agent.ts b/examples/typechain-petstore-real-agent.ts new file mode 100644 index 0000000..30d2ede --- /dev/null +++ b/examples/typechain-petstore-real-agent.ts @@ -0,0 +1,30 @@ +import { createTypeMcpAgent } from "@theorvane/type-chain/typemcp"; + +import { PetstoreServer } from "./typemcp-petstore-server.js"; + +type PetstoreAgentModel = Parameters< + typeof createTypeMcpAgent +>[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; +}>; + +/** + * 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 }, + }); +} diff --git a/package.json b/package.json index 6bf2cf5..1df3527 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/typechain-petstore-real-agent.test.ts b/test/typechain-petstore-real-agent.test.ts new file mode 100644 index 0000000..32df43c --- /dev/null +++ b/test/typechain-petstore-real-agent.test.ts @@ -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" }, + ], + }); + }); +});