Skip to content

Commit 1b0b275

Browse files
committed
add new templates and instrumentation scripts
1 parent 564a907 commit 1b0b275

16 files changed

+338
-90
lines changed

.github/main.yaml

+35-8
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ on:
55
branches:
66
- main
77
workflow_dispatch: # Allows manual triggering
8+
pull_request:
9+
release:
10+
types: [published]
811

9-
permissions:
10-
contents: write
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
1115

1216
jobs:
13-
publish:
17+
build:
1418
runs-on: ubuntu-latest
1519

1620
steps:
@@ -24,19 +28,42 @@ jobs:
2428
with:
2529
node-version: '18'
2630
registry-url: 'https://registry.npmjs.org'
31+
cache: npm
2732

2833
- name: Install dependencies
2934
run: npm ci
3035

3136
- name: Build project
3237
run: npm run build
3338

34-
- name: Configure Git
39+
- name: Test creation from template
3540
run: |
36-
git config --local user.email "action@github.com"
37-
git config --local user.name "GitHub Action"
41+
./build/index.js test-server --name test-server --description "Here is a test server" --tool "Echo"
42+
43+
- name: Validate generated project
44+
run: npm install && npm run build && npm link
45+
working-directory: test-server
46+
47+
publish:
48+
runs-on: ubuntu-latest
49+
if: github.event_name == 'release'
50+
# environment: release
51+
needs: build
52+
53+
permissions:
54+
contents: read
55+
id-token: write
56+
57+
steps:
58+
- uses: actions/checkout@v4
59+
- uses: actions/setup-node@v4
60+
with:
61+
node-version: 18
62+
cache: npm
63+
registry-url: 'https://registry.npmjs.org'
64+
65+
- run: npm ci
3866

39-
- name: Publish to npm
40-
run: npm publish --access public
67+
- run: npm publish --access public
4168
env:
4269
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# custom
2+
/agentico-project
3+
14
# Logs
25
logs
36
*.log

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# mcp-create-tool ![NPM Version](https://img.shields.io/npm/v/%40agentico%2Fmcp-create-tool)
1+
# mcp-create-tool
2+
![NPM Version](https://img.shields.io/npm/v/%40agentico%2Fmcp-create-tool)
3+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
24
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fagentico%2Fmcp-create-tool.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2agentico%2Fmcp-create-tool?ref=badge_shield)
35

46
A command line tool for quickly scaffolding new MCP (Model Context Protocol) tools to be used with the Agentico's MCPServer, an open-source server `facade` implementation for the Model Context Protocol.

package-lock.json

+17-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
{
22
"name": "@agentico/mcp-create-tool",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "CLI tool to create new Agentico MCP tools",
55
"license": "MIT",
66
"author": {
77
"name": "Agentico",
88
"url": "https://agentico.dev"
99
},
10+
"contributors": [
11+
{
12+
"name": "Adrian Escutia",
13+
"url": "https://escutia.me/adrian"
14+
},
15+
{
16+
"name": "La Rebelion Labs",
17+
"url": "https://rebelion.la"
18+
}
19+
],
1020
"repository": {
1121
"type": "git",
12-
"url": "https://github.com/agentico-dev/mcp-create-tool"
22+
"url": "git+https://github.com/agentico-dev/mcp-create-tool.git"
1323
},
1424
"homepage": "https://agentico.dev",
1525
"bugs": "https://github.com/agentico-dev/mcp-create-tool/issues",
@@ -32,10 +42,12 @@
3242
"commander": "^13.0.0",
3343
"ejs": "^3.1.10",
3444
"is-wsl": "^3.1.0",
45+
"lodash": "^4.17.21",
3546
"ora": "^8.1.1"
3647
},
3748
"devDependencies": {
3849
"@types/ejs": "^3.1.5",
50+
"@types/lodash": "^4.17.15",
3951
"@types/node": "^20.11.24",
4052
"shx": "^0.3.4",
4153
"typescript": "^5.3.3"

src/index.ts

+80-36
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import { promisify } from 'util'
1212
import { fileURLToPath } from "url";
1313
import { ExitPromptError } from "@inquirer/core";
1414
import isWsl from 'is-wsl';
15+
import lodash from 'lodash';
1516

17+
const TRACES_DEFAULT_URL = "http://localhost:4318/v1/traces";
1618
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1719
async function getClaudeConfigDir(): Promise<string> {
1820
if (isWsl) {
@@ -150,7 +152,15 @@ async function createServer(directory: string, options: any = {}) {
150152
installForClaude: {
151153
message: "Would you like to install this server for Claude.app?",
152154
required: false,
153-
}
155+
},
156+
instrumentation: {
157+
message: "Would you like to add OpenTelemetry instrumentation?",
158+
required: false,
159+
},
160+
traceExporterUrl: {
161+
message: "What is the URL of the trace exporter?",
162+
required: false,
163+
},
154164
};
155165

156166
interface Question {
@@ -164,8 +174,11 @@ async function createServer(directory: string, options: any = {}) {
164174
description: Question;
165175
tool: Question;
166176
installForClaude: Question;
177+
instrumentation: Question;
178+
traceExporterUrl: Question;
167179
}
168180

181+
// Ask questions to the user and return the answers
169182
const answersInquirer = async (questions: Questions): Promise<any> => {
170183
const name = await input({ ...questions.name, validate: (value) => value.length > 3 || 'Name must be at least 4 characters' });
171184
const description = await input(questions.description);
@@ -180,30 +193,83 @@ async function createServer(directory: string, options: any = {}) {
180193
message: "Would you like to install this server for Claude.app?",
181194
default: true
182195
}) : false;
183-
console.debug({ name, description, tool, installForClaude });
184-
return { name, description, tool, installForClaude };
196+
const instrumentation = await confirm({
197+
message: "Would you like to add OpenTelemetry instrumentation?",
198+
default: false
199+
});
200+
let traceExporterUrl;
201+
if (instrumentation) {
202+
traceExporterUrl = await input({
203+
...questions.traceExporterUrl,
204+
message: "What is the URL of the trace exporter?",
205+
required: true,
206+
default: TRACES_DEFAULT_URL,
207+
});
208+
}
209+
return { name, description, tool, installForClaude, instrumentation, traceExporterUrl };
185210
};
186211

187212
const answers = await answersInquirer(questions);
188-
const { name, description, tool, installForClaude } = answers;
189213

214+
// Tool name in PascalCase, removing special characters
215+
const toolPascalCase = lodash.upperFirst(lodash.camelCase(answers.tool)).replace(/[^a-zA-Z0-9_]/g, '');
216+
// Configuration based on user answers, or default values
190217
const config = {
191-
name: options.name || name,
192-
description: options.description || description,
193-
tool: options.tool || tool,
194-
installForClaude: options.installForClaude || installForClaude
218+
// Remove special characters from name
219+
name: (options.name || answers.name).replace(/[^a-zA-Z0-9_]/g, ''),
220+
description: options.description || answers.description,
221+
tool: options.tool || answers.tool,
222+
installForClaude: options.installForClaude || answers.installForClaude,
223+
instrumentation: options.instrumentation || answers.instrumentation,
224+
traceExporterUrl: options.traceExporterUrl || answers.traceExporterUrl,
225+
toolPascalCase,
195226
};
196227

228+
console.debug(`Using configuration: ${JSON.stringify(config, null, 2)}`);
197229
const spinner = ora("Creating MCP server...").start();
198230

199231
try {
200232
// Create project directory
201233
await fs.mkdir(directory);
202234

203-
// Copy template files
204-
const templateDir = path.join(__dirname, "../template");
235+
// render template files
236+
const templateDir = path.join(__dirname, "../template/project");
205237
const files = await fs.readdir(templateDir, { recursive: true });
238+
await processTemplateFiles(files, templateDir);
239+
// Process additional files if needed
240+
if (config.instrumentation) {
241+
const instrumentationDir = path.join(__dirname, "../template/instrumentation");
242+
const instrumentationFiles = await fs.readdir(instrumentationDir, { recursive: true });
243+
await processTemplateFiles(instrumentationFiles, instrumentationDir);
244+
}
245+
246+
spinner.succeed(chalk.green("MCP server created successfully!"));
247+
248+
if (answers.installForClaude) {
249+
await updateClaudeConfig(config.name, directory);
250+
}
251+
252+
// Print next steps
253+
console.log("\nNext steps:");
254+
console.log(chalk.cyan(` cd ${directory}`));
255+
console.log(chalk.cyan(" npm install"));
256+
console.log(
257+
chalk.cyan(` npm run build ${chalk.reset("# or: npm run watch")}`),
258+
);
259+
console.log(
260+
chalk.cyan(
261+
` npm link ${chalk.reset("# optional, to make available globally")}\n`,
262+
),
263+
);
264+
console.log(chalk.yellow("Test it in your browser:"));
265+
console.log(chalk.cyan(" npm run inspector\n"));
266+
} catch (error) {
267+
spinner.fail(chalk.red("Failed to create MCP server"));
268+
console.error(error);
269+
process.exit(1);
270+
}
206271

272+
async function processTemplateFiles(files: string[], templateDir: string) {
207273
for (const file of files) {
208274
const sourcePath = path.join(templateDir, file);
209275
const stats = await fs.stat(sourcePath);
@@ -231,37 +297,12 @@ async function createServer(directory: string, options: any = {}) {
231297
// Write processed file
232298
await fs.writeFile(targetPath, content);
233299
}
234-
235-
spinner.succeed(chalk.green("MCP server created successfully!"));
236-
237-
if (answers.installForClaude) {
238-
await updateClaudeConfig(config.name, directory);
239-
}
240-
241-
// Print next steps
242-
console.log("\nNext steps:");
243-
console.log(chalk.cyan(` cd ${directory}`));
244-
console.log(chalk.cyan(" npm install"));
245-
console.log(
246-
chalk.cyan(` npm run build ${chalk.reset("# or: npm run watch")}`),
247-
);
248-
console.log(
249-
chalk.cyan(
250-
` npm link ${chalk.reset("# optional, to make available globally")}\n`,
251-
),
252-
);
253-
console.log(chalk.yellow("Test it in your browser:"));
254-
console.log(chalk.cyan(" npm run inspector\n"));
255-
} catch (error) {
256-
spinner.fail(chalk.red("Failed to create MCP server"));
257-
console.error(error);
258-
process.exit(1);
259300
}
260301
}
261302

262303
// detect ctrl+c and exit
263304
process.on("SIGINT", () => {
264-
console.log(chalk.yellow("\nAborted."));
305+
console.log(chalk.red("\nAborted, process interrupted."));
265306
process.exit(0);
266307
});
267308

@@ -273,6 +314,9 @@ try {
273314
.option("-n, --name <name>", "Name of the server")
274315
.option("-d, --description <description>", "Description of the server")
275316
.option("-t, --tool <tool>", "Name of the tool to create")
317+
// .option("--no-install", "Skip configure the server for Claude.app", true)
318+
.option("-i, --instrumentation", "Add OpenTelemetry instrumentation", false)
319+
.option("-e, --trace-exporter-url <url>", "URL of the trace exporter, only if instrumentation", TRACES_DEFAULT_URL)
276320
.action(createServer);
277321

278322
program.showHelpAfterError('(add --help for additional information)');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
receivers:
2+
otlp:
3+
protocols:
4+
http:
5+
include_metadata: true
6+
endpoint: 0.0.0.0:4318
7+
8+
processors:
9+
batch: # Batches spans before exporting
10+
timeout: 5s
11+
12+
exporters:
13+
# otlp: # Exports traces to Jaeger
14+
# endpoint: "http://host.docker.internal:14250"
15+
# tls:
16+
# insecure: true
17+
otlphttp:
18+
endpoint: "http://agenticotel-collector:4318"
19+
tls:
20+
insecure: true
21+
debug:
22+
verbosity: detailed
23+
24+
service:
25+
pipelines:
26+
traces:
27+
receivers: [otlp]
28+
processors: [batch]
29+
# exporters: [debug, otlphttp, otlp]
30+
exporters: [debug, otlphttp]
31+
logs:
32+
receivers: [otlp]
33+
processors: [batch]
34+
exporters: [otlphttp]

0 commit comments

Comments
 (0)