Skip to content

Conversation

@hiskudin
Copy link

@hiskudin hiskudin commented Jan 26, 2026

This pull request introduces a new TypeScript-based StackOne AI Agent example application that uses the Vercel AI SDK and supports dynamic tool creation from StackOne's MCP endpoint. The agent allows direct tool invocation (without a meta-tool) and features an interactive CLI for user interaction. The implementation includes configuration, dependencies, agent logic, and TypeScript project setup.

The most important changes are:

New Application: StackOne TypeScript Agent

  • Added a new stackone-typescript-agent app, including configuration files (.env.example, package.json, tsconfig.json) and a complete agent implementation in src/agent.ts. [1] [2] [3] [4]

Agent Implementation and Features (src/agent.ts):

  • Implements dynamic discovery and creation of tools for all linked accounts via StackOne MCP, mapping each tool to its account and provider.
  • Provides an interactive CLI mode with conversation history, dynamic provider-aware system prompts, and contextual example queries based on connected services.
  • Integrates with the Vercel AI SDK and Anthropic's Claude model for natural language processing and tool invocation, supporting multi-step workflows and error handling.

Project Configuration and Dependencies:

  • Adds a .env.example for required environment variables, including API keys for StackOne and Anthropic.
  • Sets up package.json with necessary dependencies (@ai-sdk/anthropic, ai, dotenv, zod, etc.) and scripts for running and type-checking the agent.
  • Configures TypeScript via tsconfig.json for strict typing, ES module support, and output settings.

Summary by cubic

Adds a TypeScript StackOne agent example that discovers tools from MCP for each linked account and calls them directly via the Vercel SDK. Includes an interactive CLI with history and a provider-aware system prompt.

  • New Features

    • Discovers tools per linked account via MCP and exposes them as Vercel SDK tools.
    • Namespaces tool keys by provider and account to avoid collisions.
    • Converts MCP JSON Schema to Zod for tool parameters, with a safe fallback for unsupported cases.
    • Direct tool calls (no meta-tool); supports multi-step workflows and error handling.
    • Interactive CLI with conversation history, a /clear command, and dynamic examples.
    • Provider-aware system prompt built from available tools.
  • Dependencies

    • .env.example with STACKONE_API_KEY and ANTHROPIC_API_KEY.
    • package.json adds required SDKs and scripts: agent, check-types.
    • Strict TypeScript config via tsconfig.json.

Written for commit fa587dc. Summary will update on new commits.

Copilot AI review requested due to automatic review settings January 26, 2026 10:25
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new stackone-typescript-agent example app that uses the Vercel AI SDK + Anthropic to dynamically discover StackOne MCP tools and run an interactive CLI agent.

Changes:

  • Added a new TypeScript app with scripts, TS config, and environment variable example.
  • Implemented an interactive CLI agent that discovers linked accounts, lists MCP tools per account, and enables direct tool invocation via generateText.
  • Added dynamic system prompt and example queries based on connected providers.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
apps/stackone-typescript-agent/src/agent.ts Implements MCP account/tool discovery, tool execution wiring, prompt building, and CLI loop.
apps/stackone-typescript-agent/package.json Adds dependencies and scripts for running/typechecking the agent.
apps/stackone-typescript-agent/tsconfig.json TypeScript compiler configuration for the new app.
apps/stackone-typescript-agent/.env.example Documents required environment variables for running the agent.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 111 to 117
function createVercelTool(mcpTool: McpTool): CoreTool {
const toolName = mcpTool.name;

return {
description: mcpTool.description || `Call the ${toolName} tool`,
parameters: z.record(z.unknown()).describe("Arguments for the tool"),
execute: async (args: Record<string, unknown>) => {
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

McpTool includes inputSchema, but createVercelTool currently uses a generic z.record(z.unknown()) and ignores the provided schema. This loses parameter shape information and removes any validation/guidance for the model. Consider translating inputSchema into a Zod schema when available (and falling back to a permissive schema only when it’s missing).

Copilot uses AI. Check for mistakes.
Comment on lines 174 to 175
toolToAccount.set(mcpTool.name, account.id);
tools[mcpTool.name] = createVercelTool(mcpTool);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tools[mcpTool.name] and toolToAccount.set(mcpTool.name, …) will silently overwrite when multiple linked accounts expose the same MCP tool name (e.g., two Gmail accounts). That can route tool calls to the wrong account. Consider namespacing tool names (e.g., include account id/provider in the exposed tool name) or storing multiple accounts per tool and requiring disambiguation.

Suggested change
toolToAccount.set(mcpTool.name, account.id);
tools[mcpTool.name] = createVercelTool(mcpTool);
// Ensure tool names are unique across accounts to avoid silent overwrites.
const baseName = mcpTool.name;
let uniqueToolName = baseName;
if (tools[uniqueToolName]) {
// First try namespacing by provider.
uniqueToolName = `${account.provider}_${baseName}`;
}
if (tools[uniqueToolName]) {
// If still colliding (e.g., multiple accounts with same provider),
// append a numeric suffix until we find an available name.
let counter = 2;
let candidate = `${uniqueToolName}_${counter}`;
while (tools[candidate]) {
counter += 1;
candidate = `${uniqueToolName}_${counter}`;
}
uniqueToolName = candidate;
}
toolToAccount.set(uniqueToolName, account.id);
tools[uniqueToolName] = createVercelTool(mcpTool);

Copilot uses AI. Check for mistakes.
tools[mcpTool.name] = createVercelTool(mcpTool);
}

providerCounts[account.provider] = mcpTools.length;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

providerCounts[account.provider] = mcpTools.length overwrites counts when there are multiple active accounts for the same provider; the summary and system prompt will be inaccurate. Accumulate counts per provider (e.g., providerCounts[provider] = (providerCounts[provider] ?? 0) + mcpTools.length).

Suggested change
providerCounts[account.provider] = mcpTools.length;
providerCounts[account.provider] =
(providerCounts[account.provider] ?? 0) + mcpTools.length;

Copilot uses AI. Check for mistakes.
hiskudin and others added 3 commits January 26, 2026 10:34
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 4 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/stackone-typescript-agent/src/agent.ts">

<violation number="1" location="apps/stackone-typescript-agent/src/agent.ts:105">
P2: Using `||` here discards valid falsy tool results (0/""/false) and returns the wrapper object instead. Use nullish coalescing to preserve legitimate results.</violation>

<violation number="2" location="apps/stackone-typescript-agent/src/agent.ts:174">
P2: Tools from multiple linked accounts can overwrite each other because they’re keyed only by tool name. For users with multiple accounts on the same provider, the last account silently wins and tool calls may be routed to the wrong account.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

accountId
)) as { result?: unknown };

return result.result || result;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using || here discards valid falsy tool results (0/""/false) and returns the wrapper object instead. Use nullish coalescing to preserve legitimate results.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/stackone-typescript-agent/src/agent.ts, line 105:

<comment>Using `||` here discards valid falsy tool results (0/""/false) and returns the wrapper object instead. Use nullish coalescing to preserve legitimate results.</comment>

<file context>
@@ -0,0 +1,400 @@
+    accountId
+  )) as { result?: unknown };
+
+  return result.result || result;
+}
+
</file context>
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name=".gitignore">

<violation number="1" location=".gitignore:40">
P2: Avoid ignoring lock files globally. Lockfiles should be committed so installs are reproducible across machines and CI.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 180 to 188
headers: {
Authorization: `Basic ${Buffer.from(STACKONE_API_KEY + ":").toString("base64")}`,
"x-account-id": accountId,
"Content-Type": "application/json",
Accept: "application/json, text/event-stream",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: `req-${method}-${Date.now()}`,
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request advertises Accept: application/json, text/event-stream but the response is always parsed via response.json(). If the server chooses text/event-stream (SSE), this will fail at runtime. Either request only JSON here or add handling for SSE responses.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,22 @@
{
"name": "stackone-typescript-agent",
"version": "1.0.0",
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This app's package.json is missing "private": true. Other example apps in this repo (e.g. apps/oauth-redirect-proxy/package.json) mark themselves private to prevent accidental publication to npm.

Suggested change
"version": "1.0.0",
"version": "1.0.0",
"private": true,

Copilot uses AI. Check for mistakes.
Comment on lines 6 to 9
"scripts": {
"agent": "tsx src/agent.ts",
"typecheck": "tsc --noEmit"
},
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This app defines a typecheck script, but the repo's root uses turbo run check-types (see turbo.json + root package.json). As-is, this app won't participate in the standard type-check task. Rename/add a check-types script (and optionally keep typecheck as an alias) to integrate with the monorepo workflows.

Copilot uses AI. Check for mistakes.
Comment on lines 72 to 74
// For mixed enums, use union of literals
const literals = schema.enum.map((v) => z.literal(v as string | number | boolean));
const zodUnion = z.union(literals as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]]);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For non-string enums, this builds a z.union(...) from schema.enum. If the enum has only 1 value (e.g., [1] or [null]), z.union will throw because it requires at least 2 options. Handle single-value enums by returning z.literal(value) (and include null as a supported literal type).

Suggested change
// For mixed enums, use union of literals
const literals = schema.enum.map((v) => z.literal(v as string | number | boolean));
const zodUnion = z.union(literals as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]]);
// For mixed enums, use literals; handle single-value enums without z.union
const literals = schema.enum.map((v) =>
z.literal(v as string | number | boolean | null),
);
if (literals.length === 1) {
const singleLiteral = literals[0];
return schema.description
? singleLiteral.describe(schema.description)
: singleLiteral;
}
const zodUnion = z.union(
literals as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]],
);

Copilot uses AI. Check for mistakes.
hiskudin and others added 6 commits January 26, 2026 11:18
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@hiskudin hiskudin self-assigned this Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants