Skip to content

Commit 2c889a5

Browse files
DisturbingDisturbing
andauthored
Bug/mcp types (#79)
* Fix simple prompt agent * docs(changeset): Introduce Env for Parent classes --------- Co-authored-by: Disturbing <hello@xavalabs.com>
1 parent 78d8eea commit 2c889a5

File tree

11 files changed

+2810
-2556
lines changed

11 files changed

+2810
-2556
lines changed

.changeset/sharp-baths-bathe.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nullshot/mcp": patch
3+
---
4+
5+
Introduce Env for Parent classes

examples/crud-mcp/src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { setupServerPrompts } from "./prompts";
99
/**
1010
* TodoMcpServer extends McpHonoServerDO for CRUD operations on todo items
1111
*/
12-
export class TodoMcpServer extends McpHonoServerDO {
12+
export class TodoMcpServer extends McpHonoServerDO<Env> {
1313
constructor(ctx: DurableObjectState, env: Env) {
1414
super(ctx, env);
1515
}

examples/dependent-agent/worker-configuration.d.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/* eslint-disable */
2-
// Generated by Wrangler by running `wrangler types` (hash: 09a3a2ff44392b0c191d5bb00ea2f8e3)
3-
// Runtime types generated with workerd@1.20250816.0 2025-06-01 nodejs_compat
2+
// Generated by Wrangler by running `wrangler types` (hash: 8c164cda8a902beb1409736cd08ba1ac)
3+
// Runtime types generated with workerd@1.20250823.0 2025-06-01 nodejs_compat
44
declare namespace Cloudflare {
55
interface Env {
6-
AI_PROVIDER: "anthropic";
6+
AI_PROVIDER_API_KEY: string;
7+
MODEL_ID: string;
8+
AI_PROVIDER: string;
79
AGENT: DurableObjectNamespace<import("./src/index").DependentAgent>;
810
MCP_SERVICE: Fetcher /* mcp */;
911
}
@@ -13,7 +15,7 @@ type StringifyValues<EnvType extends Record<string, unknown>> = {
1315
[Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
1416
};
1517
declare namespace NodeJS {
16-
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "AI_PROVIDER">> {}
18+
interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "AI_PROVIDER_API_KEY" | "MODEL_ID" | "AI_PROVIDER">> {}
1719
}
1820

1921
// Begin runtime types
@@ -6965,10 +6967,6 @@ declare namespace TailStream {
69656967
readonly type: "hibernatableWebSocket";
69666968
readonly info: HibernatableWebSocketEventInfoClose | HibernatableWebSocketEventInfoError | HibernatableWebSocketEventInfoMessage;
69676969
}
6968-
interface Resume {
6969-
readonly type: "resume";
6970-
readonly attachment?: any;
6971-
}
69726970
interface CustomEventInfo {
69736971
readonly type: "custom";
69746972
}
@@ -6996,17 +6994,14 @@ declare namespace TailStream {
69966994
readonly scriptTags?: string[];
69976995
readonly scriptVersion?: ScriptVersion;
69986996
readonly trigger?: Trigger;
6999-
readonly info: FetchEventInfo | JsRpcEventInfo | ScheduledEventInfo | AlarmEventInfo | QueueEventInfo | EmailEventInfo | TraceEventInfo | HibernatableWebSocketEventInfo | Resume | CustomEventInfo;
6997+
readonly info: FetchEventInfo | JsRpcEventInfo | ScheduledEventInfo | AlarmEventInfo | QueueEventInfo | EmailEventInfo | TraceEventInfo | HibernatableWebSocketEventInfo | CustomEventInfo;
70006998
}
70016999
interface Outcome {
70027000
readonly type: "outcome";
70037001
readonly outcome: EventOutcome;
70047002
readonly cpuTime: number;
70057003
readonly wallTime: number;
70067004
}
7007-
interface Hibernate {
7008-
readonly type: "hibernate";
7009-
}
70107005
interface SpanOpen {
70117006
readonly type: "spanOpen";
70127007
readonly name: string;
@@ -7036,13 +7031,6 @@ declare namespace TailStream {
70367031
readonly type: "return";
70377032
readonly info?: FetchResponseInfo;
70387033
}
7039-
interface Link {
7040-
readonly type: "link";
7041-
readonly label?: string;
7042-
readonly traceId: string;
7043-
readonly invocationId: string;
7044-
readonly spanId: string;
7045-
}
70467034
interface Attribute {
70477035
readonly name: string;
70487036
readonly value: string | string[] | boolean | boolean[] | number | number[] | bigint | bigint[];
@@ -7051,7 +7039,7 @@ declare namespace TailStream {
70517039
readonly type: "attributes";
70527040
readonly info: Attribute[];
70537041
}
7054-
type EventType = Onset | Outcome | Hibernate | SpanOpen | SpanClose | DiagnosticChannelEvent | Exception | Log | Return | Link | Attributes;
7042+
type EventType = Onset | Outcome | SpanOpen | SpanClose | DiagnosticChannelEvent | Exception | Log | Return | Attributes;
70557043
interface TailEvent<Event extends EventType> {
70567044
readonly invocationId: string;
70577045
readonly spanId: string;
@@ -7062,14 +7050,12 @@ declare namespace TailStream {
70627050
type TailEventHandler<Event extends EventType = EventType> = (event: TailEvent<Event>) => void | Promise<void>;
70637051
type TailEventHandlerObject = {
70647052
outcome?: TailEventHandler<Outcome>;
7065-
hibernate?: TailEventHandler<Hibernate>;
70667053
spanOpen?: TailEventHandler<SpanOpen>;
70677054
spanClose?: TailEventHandler<SpanClose>;
70687055
diagnosticChannel?: TailEventHandler<DiagnosticChannelEvent>;
70697056
exception?: TailEventHandler<Exception>;
70707057
log?: TailEventHandler<Log>;
70717058
return?: TailEventHandler<Return>;
7072-
link?: TailEventHandler<Link>;
70737059
attributes?: TailEventHandler<Attributes>;
70747060
};
70757061
type TailEventHandlerType = TailEventHandler | TailEventHandlerObject;

examples/expense-mcp/src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { setupServerResources } from "./resources";
2121
/**
2222
* ExpenseMcpServer extends McpHonoServerDO for CRUD operations on expenses
2323
*/
24-
export class ExpenseMcpServer extends McpHonoServerDO {
24+
export class ExpenseMcpServer extends McpHonoServerDO<Env> {
2525
constructor(ctx: DurableObjectState, env: Env) {
2626
super(ctx, env);
2727
}

examples/simple-prompt-agent/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "simple-prompt-agent",
3-
"version": "0.0.0",
3+
"version": "0.1.0",
44
"private": true,
55
"scripts": {
66
"deploy": "wrangler deploy",
@@ -12,14 +12,15 @@
1212
},
1313
"devDependencies": {
1414
"@cloudflare/vitest-pool-workers": "catalog:",
15+
"@types/node": "^24.3.0",
1516
"concurrently": "^9.1.2",
1617
"typescript": "catalog:",
1718
"vitest": "catalog:",
1819
"wrangler": "catalog:"
1920
},
2021
"dependencies": {
21-
"@ai-sdk/anthropic": "^1.2.10",
22-
"@ai-sdk/openai": "^1.3.20",
22+
"@ai-sdk/anthropic": "catalog:",
23+
"@ai-sdk/openai": "catalog:",
2324
"@nullshot/agent": "workspace:*",
2425
"@nullshot/cli": "workspace:*",
2526
"ai": "catalog:",

examples/simple-prompt-agent/src/index.ts

Lines changed: 19 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,40 @@
1-
/**
2-
* Welcome to Cloudflare Workers! This is your first worker.
3-
*
4-
* - Run `npm run dev` in your terminal to start a development server
5-
* - Open a browser tab at http://localhost:8787/ to see your worker in action
6-
* - Run `npm run deploy` to publish your worker
7-
*
8-
* Learn more at https://developers.cloudflare.com/workers/
9-
*/
10-
111
import { Hono } from 'hono';
12-
import { AgentEnv, applyPermissionlessAgentSessionRouter } from '@nullshot/agent';
13-
// Import the ToolsService directly
2+
import { applyPermissionlessAgentSessionRouter } from '@nullshot/agent';
143
import { ToolboxService } from '@nullshot/agent/services';
15-
import { LanguageModel } from 'ai';
4+
import { LanguageModel, stepCountIs, type Provider } from 'ai';
165
import { createAnthropic } from '@ai-sdk/anthropic';
176
import { createOpenAI } from '@ai-sdk/openai';
187
import { AiSdkAgent, AIUISDKMessage } from '@nullshot/agent/aisdk';
198
import mcpConfig from '../mcp.json';
20-
// Define AI provider type
21-
type AIProvider = 'anthropic' | 'openai' | 'deepseek';
22-
23-
// Define a type that extends both the autogenerated Env and AgentEnv
24-
type EnvWithAgent = Env & AgentEnv;
25-
26-
// Function to validate if a value is a valid AIProvider
27-
function isValidAIProvider(value: unknown): value is AIProvider {
28-
return value === 'anthropic' || value === 'openai' || value === 'deepseek';
29-
}
309

3110
// Use type assertion to make Hono app compatible with AgentRouterBuilder
32-
const app = new Hono<{ Bindings: EnvWithAgent }>();
11+
const app = new Hono<{ Bindings: Env }>();
3312
applyPermissionlessAgentSessionRouter(app);
3413

35-
export class SimplePromptAgent extends AiSdkAgent<EnvWithAgent> {
36-
constructor(state: DurableObjectState, env: EnvWithAgent) {
37-
// Validate AI_PROVIDER before using it
38-
if (!isValidAIProvider(env.AI_PROVIDER)) {
39-
throw new Error(`Invalid AI provider: ${env.AI_PROVIDER}. Expected 'anthropic', 'openai', or 'deepseek'.`);
40-
}
41-
14+
export class SimplePromptAgent extends AiSdkAgent<Env> {
15+
constructor(state: DurableObjectState, env: Env) {
16+
let provider: Provider;
4217
let model: LanguageModel;
4318
// This is just an example, ideally you only want ot inlcude models that you plan to use for your agent itself versus multiple models
4419
switch (env.AI_PROVIDER) {
4520
case 'anthropic':
46-
const anthropic = createAnthropic({
21+
provider = createAnthropic({
4722
apiKey: env.ANTHROPIC_API_KEY,
4823
});
49-
model = anthropic('claude-3-haiku-20240307');
24+
model = provider.languageModel('claude-3-haiku-20240307');
5025
break;
5126
case 'openai':
52-
const openai = createOpenAI({
27+
provider = createOpenAI({
5328
apiKey: env.OPEN_AI_API_KEY,
5429
});
55-
model = openai('gpt-3.5-turbo');
30+
model = provider.languageModel('gpt-3.5-turbo');
5631
break;
5732
case 'deepseek':
58-
const deepseek = createOpenAI({
33+
provider = createOpenAI({
5934
apiKey: env.DEEPSEEK_API_KEY,
6035
baseURL: 'https://api.deepseek.com',
6136
});
62-
model = deepseek('deepseek-chat');
37+
model = provider.languageModel('deepseek-chat');
6338
break;
6439
default:
6540
// This should never happen due to validation above, but TypeScript requires this
@@ -70,24 +45,25 @@ export class SimplePromptAgent extends AiSdkAgent<EnvWithAgent> {
7045
}
7146

7247
async processMessage(sessionId: string, messages: AIUISDKMessage): Promise<Response> {
73-
const result = await this.streamText(sessionId, {
74-
model: this.model,
48+
// Use the protected streamTextWithMessages method - model is handled automatically by the agent
49+
const result = await this.streamTextWithMessages(sessionId, messages.messages, {
7550
system: 'You will use tools to help manage and mark off tasks on a todo list.',
76-
messages: messages.messages,
7751
maxSteps: 10,
52+
stopWhen: stepCountIs(10),
53+
// Enable MCP tools from imported mcp.json
7854
experimental_toolCallStreaming: true,
79-
onError: (error) => {
55+
onError: (error: unknown) => {
8056
console.error('Error processing message', error);
8157
},
8258
});
8359

84-
return result.toDataStreamResponse();
60+
return result.toTextStreamResponse();
8561
}
8662
}
8763

8864
// Export the worker handler
8965
export default {
90-
async fetch(request: Request, env: EnvWithAgent, ctx: ExecutionContext): Promise<Response> {
66+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
9167
// Bootstrap the agent worker with the namespace
9268
return app.fetch(request, env, ctx);
9369
},

0 commit comments

Comments
 (0)