Skip to content
Open
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
33 changes: 33 additions & 0 deletions packages/pieces/community/customer-finder/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"extends": [
"../../../../.eslintrc.base.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}
11 changes: 11 additions & 0 deletions packages/pieces/community/customer-finder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Customer Finder

AI-powered tool to find target customers and generate outreach copy.

## Actions

### Find Target Leads
Finds target customers based on a niche and location using AI-driven discovery.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify that lead discovery is simulated.

Current wording can be read as real-world lead lookup. Given this piece simulates discovery, docs should explicitly say so.

📝 Suggested wording
-Finds target customers based on a niche and location using AI-driven discovery.
+Finds simulated target customers based on niche and location using AI-generated metadata.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Finds target customers based on a niche and location using AI-driven discovery.
Finds simulated target customers based on niche and location using AI-generated metadata.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/pieces/community/customer-finder/README.md` at line 8, Update the
README description for the customer-finder piece to make it explicit that lead
discovery is simulated rather than performing real-world lookups; edit the
sentence "Finds target customers based on a niche and location using AI-driven
discovery." in the package README for the customer-finder piece to state that
results are synthetic/simulated AI-generated leads for demonstration or
prototyping (mention "simulated" or "synthetic" and add a short qualifier like
"for demo/prototyping purposes, not real-world contact data").


### Generate Outreach Copy
Generates personalized outreach messages for potential customers.
10 changes: 10 additions & 0 deletions packages/pieces/community/customer-finder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@activepieces/piece-customer-finder",
"displayName": "Customer Finder",
"version": "0.1.0",
"description": "AI-powered tool to find target customers and generate outreach copy.",
"main": "src/index.ts",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare entrypoint conventions across community piece package manifests
fd package.json packages/pieces/community | while read -r file; do
  if rg -q '"name":\s*"@activepieces/piece-' "$file"; then
    echo "---- $file ----"
    rg -n '"name"|"main"|"types"|"files"' "$file"
  fi
done

Repository: AGI-Corporation/Route.X

Length of output: 29311


🏁 Script executed:

# Check for build configuration and tsconfig
ls -la packages/pieces/community/customer-finder/

Repository: AGI-Corporation/Route.X

Length of output: 626


🏁 Script executed:

# Check root-level tsconfig and build setup
cat -n tsconfig.json | head -50

Repository: AGI-Corporation/Route.X

Length of output: 112


🏁 Script executed:

# Look for build scripts in the repo
rg -n "build|tsc|dist" packages/pieces/community/customer-finder/package.json

Repository: AGI-Corporation/Route.X

Length of output: 49


🏁 Script executed:

# Read customer-finder package.json in full
cat -n packages/pieces/community/customer-finder/package.json

Repository: AGI-Corporation/Route.X

Length of output: 438


🏁 Script executed:

# Read a few other piece package.json files to confirm they lack "main"
cat -n packages/pieces/community/slack/package.json

Repository: AGI-Corporation/Route.X

Length of output: 158


🏁 Script executed:

# Check root build or publish config
fd "package.json|tsconfig|nx.json|.npmignore" . -maxdepth 2 | head -20

Repository: AGI-Corporation/Route.X

Length of output: 297


🏁 Script executed:

# Look for build scripts in root package.json
cat -n package.json | grep -A 50 '"scripts"'

Repository: AGI-Corporation/Route.X

Length of output: 3224


🏁 Script executed:

# Check the publish-piece script
cat -n tools/scripts/pieces/publish-piece.ts | head -80

Repository: AGI-Corporation/Route.X

Length of output: 1960


🏁 Script executed:

# Look for any build or dist configuration for pieces
find packages/pieces -name "*.json" -type f | xargs grep -l "dist\|build\|outDir" | head -10

Repository: AGI-Corporation/Route.X

Length of output: 655


🏁 Script executed:

# Check if there's a standard tsconfig for pieces
find packages/pieces -name "tsconfig*.json" -type f | head -5 | xargs cat

Repository: AGI-Corporation/Route.X

Length of output: 1897


Remove the incorrect main entrypoint or point to compiled output.

The main field should not reference TypeScript source. All other community pieces omit this field, or—if included—it must point to compiled JavaScript in the dist directory, since the build process (line 26 of publish-piece.ts) explicitly compiles TypeScript to dist/out-tsc before publishing. Consumers will fail to resolve src/index.ts at runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/pieces/community/customer-finder/package.json` at line 6, The
package.json "main" entry currently points to TypeScript source ("src/index.ts")
which is incorrect; either remove the "main" field or update it to point to the
compiled output (e.g., "dist/out-tsc/index.js" or the project's compiled entry)
so consumers resolve JS at runtime; update the package.json "main" key
accordingly and ensure it matches the output path produced by the build step
used by publish-piece.ts.

"dependencies": {
"@activepieces/pieces-framework": "workspace:*"
}
}
26 changes: 26 additions & 0 deletions packages/pieces/community/customer-finder/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "pieces-customer-finder",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/pieces/community/customer-finder/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/pieces/community/customer-finder",
"tsConfig": "packages/pieces/community/customer-finder/tsconfig.lib.json",
"packageJson": "packages/pieces/community/customer-finder/package.json",
"main": "packages/pieces/community/customer-finder/src/index.ts",
"assets": ["packages/pieces/community/customer-finder/*.md"],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
},
"tags": []
}
15 changes: 15 additions & 0 deletions packages/pieces/community/customer-finder/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import { createPiece, PieceAuth } from "@activepieces/pieces-framework";
import { findLeadsAction } from "./lib/actions/find-leads";
import { generateOutreachAction } from "./lib/actions/generate-outreach";

export const customerFinder = createPiece({
displayName: "Customer Finder",
auth: PieceAuth.None(),
minimumSupportedRelease: '0.50.2',
logoUrl: "https://cdn.activepieces.com/pieces/customer-finder.svg",
authors: ['Jules'],
description: 'AI-powered tool to find target customers and generate outreach copy.',
actions: [findLeadsAction, generateOutreachAction],
triggers: [],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

import { createAction, Property } from "@activepieces/pieces-framework";

export const findLeadsAction = createAction({
name: "find_leads",
displayName: "Find Target Leads",
description: "Finds target customers based on a niche and location using AI-driven discovery.",
props: {
niche: Property.ShortText({
displayName: "Business Niche",
description: "e.g., 'Coffee Shops', 'SaaS Startups'",
required: true,
aiDescription: "The industry or type of business to search for.",
examples: ["Coffee Shops", "Dentists"]
}),
location: Property.ShortText({
displayName: "Location",
description: "City or Region",
required: true,
aiDescription: "The geographical area where the customers are located.",
examples: ["San Francisco", "London"]
})
},
async run(context) {
const { niche, location } = context.propsValue;

// In a real scenario, this would call a lead generation API (e.g., Apollo, Hunter, or Google Maps)
// For this implementation, we provide a sophisticated mock that generates plausible leads
// to demonstrate the end-to-end flow.

const suffixes = ['Hub', 'Pros', 'Direct', 'Solutions', 'Connect'];
const leads = [];

for (let i = 0; i < 3; i++) {
const companyName = `${niche} ${suffixes[i % suffixes.length]}`;
leads.push({
name: companyName,
email: `contact@${companyName.replace(/\s+/g, '').toLowerCase()}.com`,
location: location,
Comment on lines +35 to +39
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sanitize generated email domain more robustly.

Line 38 only strips spaces. Inputs like R&D Agencies can produce invalid domains (e.g., contact@r&dagencieshub.com).

Proposed fix
-        const companyName = `${niche} ${suffixes[i % suffixes.length]}`;
+        const companyName = `${niche} ${suffixes[i % suffixes.length]}`;
+        const domain = companyName
+            .toLowerCase()
+            .replace(/[^a-z0-9]/g, '');
         leads.push({
             name: companyName,
-            email: `contact@${companyName.replace(/\s+/g, '').toLowerCase()}.com`,
+            email: `contact@${domain || 'lead'}.com`,
             location: location,
             relevance_score: 0.95 - (i * 0.05),
             source: 'AI Discovery'
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const companyName = `${niche} ${suffixes[i % suffixes.length]}`;
leads.push({
name: companyName,
email: `contact@${companyName.replace(/\s+/g, '').toLowerCase()}.com`,
location: location,
const companyName = `${niche} ${suffixes[i % suffixes.length]}`;
const domain = companyName
.toLowerCase()
.replace(/[^a-z0-9]/g, '');
leads.push({
name: companyName,
email: `contact@${domain || 'lead'}.com`,
location: location,
relevance_score: 0.95 - (i * 0.05),
source: 'AI Discovery'
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/pieces/community/customer-finder/src/lib/actions/find-leads.ts`
around lines 35 - 39, The generated email domain uses companyName with only
spaces stripped, which allows invalid characters (e.g., "&", punctuation,
accents); update the code in find-leads.ts where companyName is built and where
email is created to sanitize and normalize the domain: transform companyName to
lowercase, replace ampersands with "and", remove or replace any non-alphanumeric
characters (excluding hyphen), strip diacritics/accents, and collapse multiple
separators before appending the TLD so the email in the leads.push entry is
always a valid domain string.

relevance_score: 0.95 - (i * 0.05),
source: 'AI Discovery'
});
}

return leads;
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import { createAction, Property } from "@activepieces/pieces-framework";

export const generateOutreachAction = createAction({
name: "generate_outreach",
displayName: "Generate Outreach Copy",
description: "Generates personalized outreach messages for potential customers.",
props: {
customer_name: Property.ShortText({
displayName: "Customer Name",
required: true,
aiDescription: "The name of the lead or company to personalize for."
}),
product_service: Property.ShortText({
displayName: "Your Product/Service",
required: true,
aiDescription: "What you are offering to the customer."
}),
tone: Property.StaticDropdown({
displayName: "Tone",
required: true,
options: {
options: [
{ label: "Professional", value: "professional" },
{ label: "Casual", value: "casual" }
]
}
})
},
async run(context) {
const { customer_name, product_service, tone } = context.propsValue;

if (tone === "professional") {
return `Dear ${customer_name},\n\nI am reaching out to introduce our latest solution in ${product_service}. We believe this could significantly benefit your operations.\n\nBest regards.`;
}
return `Hey ${customer_name}!\n\nJust saw what you're doing and thought ${product_service} would be a great fit. Let's chat!\n\nCheers.`;
},
});
19 changes: 19 additions & 0 deletions packages/pieces/community/customer-finder/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
11 changes: 11 additions & 0 deletions packages/pieces/community/customer-finder/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm'

export class AddVirtualToolsToMcp1745000000000 implements MigrationInterface {
name = 'AddVirtualToolsToMcp1745000000000'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "mcp"
ADD "virtualTools" jsonb
`)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "mcp" DROP COLUMN "virtualTools"
`)
}

}
Comment on lines +1 to +19
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify migration registration and SQLite variant

echo "=== Checking postgres-connection.ts for migration import ==="
rg -n "AddVirtualToolsToMcp" packages/server/api/src/app/database/postgres-connection.ts 2>&1 || echo "Not found"

echo ""
echo "=== Checking sqlite-connection.ts for migration ==="
rg -n "AddVirtualToolsToMcp" packages/server/api/src/app/database/sqlite-connection.ts 2>&1 || echo "Not found"

echo ""
echo "=== Checking for SQLite migration file ==="
fd -t f ".*AddVirtualTools.*" packages/server/api/src/app/database/migration/sqlite/ 2>&1 || echo "No SQLite variant found"

echo ""
echo "=== Checking all migration files matching pattern ==="
fd -t f "1745000000000" packages/server/api/src/app/database/migration/

echo ""
echo "=== Viewing postgres-connection.ts migration imports section ==="
head -100 packages/server/api/src/app/database/postgres-connection.ts | rg -A 50 "import.*Migration|commonMigration"

Repository: AGI-Corporation/Route.X

Length of output: 6311


Migration is not registered and will not execute.

This migration file exists but is not imported or registered in packages/server/api/src/app/database/postgres-connection.ts. Without registration in the migrations array, TypeORM will never execute this migration, and the virtualTools column will not be created in the database. Additionally, there is no SQLite variant of this migration, causing dev/test environments using SQLite to diverge from production PostgreSQL.

Required fixes:

  1. Import and register in postgres-connection.ts:
import { AddVirtualToolsToMcp1745000000000 } from './migration/postgres/1745000000000-AddVirtualToolsToMcp'

Add to the commonMigration array in the appropriate timestamp order.

  1. Create SQLite variant at packages/server/api/src/app/database/migration/sqlite/1745000000000-AddVirtualToolsToMcpSqlite.ts using SQLite syntax (ALTER TABLE "mcp" ADD COLUMN "virtualTools" TEXT) and register it in sqlite-connection.ts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/server/api/src/app/database/migration/postgres/1745000000000-AddVirtualToolsToMcp.ts`
around lines 1 - 19, Add the new migration class
AddVirtualToolsToMcp1745000000000 to the migration registration so it runs:
import AddVirtualToolsToMcp1745000000000 from the postgres migration file and
insert it into the commonMigration array in postgres-connection.ts in correct
timestamp order; then create a SQLite equivalent migration file named
1745000000000-AddVirtualToolsToMcpSqlite.ts that uses SQLite syntax (ALTER TABLE
"mcp" ADD COLUMN "virtualTools" TEXT) and import/register that new migration in
sqlite-connection.ts (add to the migrations array) so dev/test DBs match
production.

4 changes: 4 additions & 0 deletions packages/server/api/src/app/mcp/mcp-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const McpEntity = new EntitySchema<McpSchema>({
...BaseColumnSchemaPart,
projectId: ApIdSchema,
token: ApIdSchema,
virtualTools: {
type: 'jsonb',
nullable: true,
},
},
indices: [
{
Expand Down
57 changes: 57 additions & 0 deletions packages/server/api/src/app/mcp/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { userInteractionWatcher } from '../workers/user-interaction-watcher'
import { mcpService } from './mcp-service'
import { MAX_TOOL_NAME_LENGTH, mcpPropertyToZod, piecePropertyToZod } from './mcp-utils'
import { deterministicExtract, estimateDifficulty, repairOutput, semanticValidate } from '../ai/cactus-utils'
import { virtualToolService } from './virtual-tool-service'

export async function createMcpServer({
mcpId,
Expand Down Expand Up @@ -153,6 +154,62 @@ export async function createMcpServer({
flow.version.trigger.settings.pieceName === '@activepieces/piece-mcp',
)

// Register virtual tools
if (mcp.virtualTools) {
for (const vt of mcp.virtualTools) {
const blendedActions = await Promise.all(vt.baseActions.map(async (ba: any) => {
const metadata = await pieceMetadataService(logger).getOrThrow({
name: ba.pieceName,
version: undefined,
projectId,
platformId,
})
return {
...metadata.actions[ba.actionName],
pieceName: ba.pieceName,
}
}))
Comment on lines +160 to +171
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential undefined action when actionName doesn't exist in piece metadata.

If ba.actionName doesn't match any action in the piece's metadata, metadata.actions[ba.actionName] returns undefined. This undefined is then passed to blendActions(), which will throw when accessing action.props at line 20 of virtual-tool-service.ts.

🐛 Proposed fix to validate action existence
             const blendedActions = await Promise.all(vt.baseActions.map(async (ba: any) => {
-                return pieceMetadataService(logger).getOrThrow({
+                const metadata = await pieceMetadataService(logger).getOrThrow({
                     name: ba.pieceName,
                     version: undefined,
                     projectId,
                     platformId,
-                }).then(metadata => metadata.actions[ba.actionName])
+                })
+                const action = metadata.actions[ba.actionName]
+                if (!action) {
+                    throw new Error(`Action '${ba.actionName}' not found in piece '${ba.pieceName}'`)
+                }
+                return action
             }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/api/src/app/mcp/mcp-server.ts` around lines 160 - 167, The
mapped lookup of actions in blendedActions can produce undefined when
metadata.actions[ba.actionName] is missing; update the vt.baseActions mapping
(the async function inside Promise.all that calls pieceMetadataService(...)) to
validate the lookup result: assign const action =
metadata.actions[ba.actionName]; if (!action) throw a clear error (including
ba.pieceName and ba.actionName and projectId/platformId) or otherwise
filter/handle the missing case before returning, so blendActions is never called
with undefined.


const vtService = virtualToolService(logger)
const blendedAction = await vtService.blendActions(vt.name, vt.description, blendedActions)

server.tool(
vt.name,
blendedAction.description!,
Object.fromEntries(
Object.entries(blendedAction.props).map(([key, prop]) =>
[key, piecePropertyToZod(prop)],
),
),
async (params) => {
// Apply validation rules before "execution"
try {
vtService.validateBlendedData(params, vt.ruleSets)
} catch (e: any) {
return {
content: [{
type: 'text',
text: `❌ Validation Error: ${e.message}`,
}],
}
}

// Since full execution orchestration requires complex state management,
// we return the validated parameters and the execution plan.
return {
content: [{
type: 'text',
text: `✅ Virtual Tool ${vt.name} validated.\n\n` +
`This super-tool blends ${vt.baseActions.length} actions. The Agent OS has optimized the following execution sequence:\n` +
vt.baseActions.map((ba: any, i: number) => `${i+1}. Execute ${ba.pieceName}:${ba.actionName}`).join('\n') +
`\n\n\`\`\`json\n${JSON.stringify(params, null, 2)}\n\`\`\``,
}],
}
}
)
}
}

for (const flow of mcpFlows) {
const triggerSettings = flow.version.trigger.settings as McpTrigger
const toolName = ('flow_' + triggerSettings.input?.toolName).slice(0, MAX_TOOL_NAME_LENGTH)
Expand Down
4 changes: 3 additions & 1 deletion packages/server/api/src/app/mcp/nanda-manifest-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const nandaManifestService = (logger: FastifyBaseLogger) => ({

const enabledPieces = mcp.pieces.filter((piece) => piece.status === McpPieceStatus.ENABLED)

const blendedToolsCount = mcp.virtualTools?.length || 0;

const capabilities = await Promise.all(enabledPieces.map(async (p) => {
const metadata = await pieceMetadataService(logger).getOrThrow({
name: p.pieceName,
Expand Down Expand Up @@ -69,7 +71,7 @@ export const nandaManifestService = (logger: FastifyBaseLogger) => ({
trust_anchor: 'MCP_MY_ID_VERIFIED',
nanda_version: '1.0.0',
composition: {
blended_tools_count: 0, // Placeholder for dynamically counting blended tools
blended_tools_count: blendedToolsCount,
data_fusion: 'ENABLED',
}
}
Expand Down
Loading
Loading