-
Notifications
You must be signed in to change notification settings - Fork 8
feat: add Google Gemini AI provider and expose all MCP tools #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,49 +1,111 @@ | ||
| import { GoogleGenerativeAI } from '@google/generative-ai'; | ||
| import { AiProvider, AiProviderConfig, AiProviderOptions, PrdGenerationInput } from './provider.js'; | ||
| import { logger } from '../../config/logging.js'; | ||
|
|
||
| /** | ||
| * Google Gemini provider implementation (stub) | ||
| * This is a placeholder that would need to be implemented with the actual Gemini SDK | ||
| * Google Gemini provider implementation | ||
| */ | ||
| export class GeminiProvider implements AiProvider { | ||
| readonly id = 'gemini'; | ||
| readonly name = 'Google Gemini'; | ||
|
|
||
|
|
||
| private client: GoogleGenerativeAI | null = null; | ||
| private config: AiProviderConfig; | ||
|
|
||
| constructor(config: AiProviderConfig) { | ||
| this.config = config; | ||
|
|
||
| if (!config.apiKey) { | ||
| logger.warn("[GeminiProvider] No API key provided. Provider will be unavailable."); | ||
| } else { | ||
| this.client = new GoogleGenerativeAI(config.apiKey); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check if the provider is properly configured and available | ||
| */ | ||
| async isAvailable(): Promise<boolean> { | ||
| // This would check if the Gemini API key is valid | ||
| return !!this.config.apiKey; | ||
| return !!this.client; | ||
| } | ||
|
|
||
| /** | ||
| * Generate a PRD using Google's Gemini API | ||
| * Note: This is a stub implementation that would need to be updated | ||
| * with the actual Gemini API client once available/needed | ||
| */ | ||
| async generatePrd(input: PrdGenerationInput, options?: AiProviderOptions): Promise<string> { | ||
| if (!this.config.apiKey) { | ||
| throw new Error('Gemini API key not configured. Check your configuration.'); | ||
| if (!this.client) { | ||
| throw new Error('Gemini client not initialized. Check your API key configuration.'); | ||
| } | ||
|
|
||
| try { | ||
| logger.info(`Generating PRD with Gemini for product: ${input.productName}`); | ||
|
|
||
| // This is where the actual implementation would go | ||
| // For now, we'll just throw a not implemented error | ||
|
|
||
| throw new Error('Gemini PRD generation not yet implemented'); | ||
|
|
||
|
|
||
| // Default options | ||
| const defaultOptions = { | ||
| temperature: 0.7, | ||
| maxTokens: 4000, | ||
| }; | ||
|
|
||
| // Merge with user-provided options | ||
| const mergedOptions = { ...defaultOptions, ...options }; | ||
|
|
||
| // Format features and constraints as bullet points | ||
| const featuresFormatted = input.coreFeatures.map(f => `• ${f}`).join('\n'); | ||
| const constraintsFormatted = input.constraints ? input.constraints.map(c => `• ${c}`).join('\n') : 'None specified'; | ||
|
|
||
| // Build system prompt | ||
| const systemPrompt = `You are an expert product manager with deep experience in writing detailed, professional Product Requirements Documents (PRDs). | ||
| Your task is to create a comprehensive PRD for the product described by the user. | ||
| Structure the PRD with clear sections including: | ||
| - Introduction and product overview | ||
| - Target audience analysis | ||
| - Detailed core features explanation | ||
| - Technical and business constraints | ||
| - Implementation considerations | ||
| - Success metrics | ||
| Use professional language, be thorough, and format the document in clean markdown.`; | ||
|
|
||
| // Build user prompt | ||
| const userPrompt = `Please create a detailed PRD for the following product: | ||
|
|
||
| Product Name: ${input.productName} | ||
|
|
||
| Product Description: ${input.productDescription} | ||
|
|
||
| Target Audience: ${input.targetAudience} | ||
|
|
||
| Core Features: | ||
| ${featuresFormatted} | ||
|
|
||
| Constraints: | ||
| ${constraintsFormatted} | ||
|
|
||
| ${input.additionalContext ? `Additional Context: ${input.additionalContext}` : ''} | ||
|
|
||
| Format the PRD as a well-structured markdown document with appropriate headings, bullet points, and sections.`; | ||
|
|
||
| const model = this.client.getGenerativeModel({ | ||
| model: this.config.model || 'gemini-2.5-flash', | ||
| systemInstruction: systemPrompt, | ||
| generationConfig: { | ||
| temperature: mergedOptions.temperature, | ||
| maxOutputTokens: mergedOptions.maxTokens, | ||
| }, | ||
| }); | ||
|
|
||
| const result = await model.generateContent(userPrompt); | ||
| const prdContent = result.response.text(); | ||
|
|
||
| if (!prdContent) { | ||
| throw new Error('Empty response from Gemini'); | ||
| } | ||
|
|
||
| logger.info(`PRD generated successfully for "${input.productName}" using Gemini`); | ||
| return prdContent; | ||
|
|
||
| } catch (error) { | ||
| logger.error(`Error generating PRD with Gemini: ${error instanceof Error ? error.message : String(error)}`); | ||
| throw new Error(`Failed to generate PRD with Gemini: ${error instanceof Error ? error.message : 'Unknown error'}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,8 +3,7 @@ import 'dotenv/config'; | |||||||||||||||
|
|
||||||||||||||||
| import { Server } from '@modelcontextprotocol/sdk/server/index.js'; | ||||||||||||||||
| import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; | ||||||||||||||||
| import { HttpServerTransport } from '@modelcontextprotocol/sdk/server/http.js'; | ||||||||||||||||
| import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; | ||||||||||||||||
|
|
||||||||||||||||
| import { configureLogging, logger } from './config/logging.js'; | ||||||||||||||||
| import { registerTools } from './tools/index.js'; | ||||||||||||||||
| import { registerResources } from './resources/index.js'; | ||||||||||||||||
|
|
@@ -40,7 +39,7 @@ async function main() { | |||||||||||||||
| const server = new Server( | ||||||||||||||||
| { | ||||||||||||||||
| name: 'PRD Creator', | ||||||||||||||||
| version: '0.1.0', | ||||||||||||||||
| version: '0.1.1', | ||||||||||||||||
| }, | ||||||||||||||||
| { | ||||||||||||||||
| capabilities: { | ||||||||||||||||
|
|
@@ -54,14 +53,6 @@ async function main() { | |||||||||||||||
| registerTools(server); | ||||||||||||||||
| registerResources(server); | ||||||||||||||||
|
|
||||||||||||||||
| // Register ListTools handler to show available tools | ||||||||||||||||
| server.setRequestHandler(ListToolsRequestSchema, async () => ({ | ||||||||||||||||
| tools: [ | ||||||||||||||||
| // ...tools unchanged | ||||||||||||||||
| // (content truncated for brevity—no change needed, see above) | ||||||||||||||||
| ] | ||||||||||||||||
| })); | ||||||||||||||||
|
|
||||||||||||||||
| // Set up error handling | ||||||||||||||||
| server.onerror = (error) => { | ||||||||||||||||
| logger.error('Server error:', error); | ||||||||||||||||
|
|
@@ -74,15 +65,10 @@ async function main() { | |||||||||||||||
| Number(process.env.PORT) || | ||||||||||||||||
| Number(process.env.HTTP_PORT) || | ||||||||||||||||
| 3000; // Default for glama.ai/cloud | ||||||||||||||||
| logger.info(`Connecting server with HTTP transport on port ${port}...`); | ||||||||||||||||
| const httpTransport = new HttpServerTransport({ | ||||||||||||||||
| port, | ||||||||||||||||
| cors: { | ||||||||||||||||
| origin: '*', | ||||||||||||||||
| }, | ||||||||||||||||
| }); | ||||||||||||||||
| await server.connect(httpTransport); | ||||||||||||||||
| logger.info(`PRD Creator MCP Server running with HTTP transport on port ${port}`); | ||||||||||||||||
| logger.warn(`HTTP transport requested on port ${port}, but HttpServerTransport is not available in this SDK version. Falling back to STDIO.`); | ||||||||||||||||
| const stdioFallback = new StdioServerTransport(); | ||||||||||||||||
| await server.connect(stdioFallback); | ||||||||||||||||
| logger.info('PRD Creator MCP Server running with STDIO transport (HTTP fallback)'); | ||||||||||||||||
|
Comment on lines
+68
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Inspect logger transports without executing repository code.
sed -n '1,130p' src/config/logging.ts
rg -n -C 3 'configureLogging|Console|stdout|stderr|transports' src/config/logging.tsRepository: Saml1211/PRD-MCP-Server Length of output: 2119 🏁 Script executed: #!/bin/bash
sed -n '45,80p' src/index.ts
rg -n 'logger\.(error|warn|info|http|verbose|debug|silly)' src --glob '*.ts'
sed -n '1,120p' package.jsonRepository: Saml1211/PRD-MCP-Server Length of output: 15849 Route Winston diagnostics to stderr. The Winston 🐛 Suggested fixnew winston.transports.Console({
+ stderrLevels: ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'],
format: winston.format.combine(🤖 Prompt for AI AgentsSource: Learnings 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: git show 888bb3fd9148f06d3b0ac5272f4c2b24724b952c:src/index.ts | sed -n '1,90p'
sed -n '50,90p' src/index.ts
grep -rn 'HttpServerTransport\|StreamableHTTP\|SSEServerTransport' node_modules/@modelcontextprotocol/sdk/dist 2>/dev/null | head -5
grep -n 'modelcontextprotocol' package.json
ls Dockerfile* docker-compose* 2>/dev/null; grep -rn 'PORT\|MCP_TRANSPORT' Dockerfile* docker-compose* README.md 2>/dev/null | head -20Repository: Saml1211/PRD-MCP-Server Length of output: 4268 🏁 Script executed: set -eu
printf '%s\n' '--- tracked candidate files ---'
git ls-files | grep -E '(^|/)(package(-lock)?\.json|npm-shrinkwrap\.json|Dockerfile[^/]*|docker-compose[^/]*|README[^/]*|\.env(\.example)?$)' || true
printf '%s\n' '--- package metadata at base and head ---'
for rev in 888bb3fd9148f06d3b0ac5272f4c2b24724b952c fb083802da859243632238055a503a3eda15c154; do
echo "### $rev package.json"
git show "$rev:package.json" | sed -n '1,140p'
for lock in package-lock.json npm-shrinkwrap.json; do
if git cat-file -e "$rev:$lock" 2>/dev/null; then
echo "### $rev $lock"
git show "$rev:$lock" | grep -n -A18 -B3 '"node_modules/@modelcontextprotocol/sdk"\|"@modelcontextprotocol/sdk"' | head -60
fi
done
done
printf '%s\n' '--- deployment and documentation references at head ---'
for f in $(git ls-files | grep -E '(^|/)(Dockerfile[^/]*|docker-compose[^/]*|README[^/]*|\.env\.example)$' || true); do
echo "### $f"
grep -n -E 'PORT|HTTP_PORT|MCP_TRANSPORT|http|stdio|EXPOSE|CMD|ENTRYPOINT' "$f" || true
done
printf '%s\n' '--- SDK package availability in checkout ---'
find . -path '*/node_modules/@modelcontextprotocol/sdk/*' -o -path '*/@modelcontextprotocol/sdk/server/http.js' | head -30Repository: Saml1211/PRD-MCP-Server Length of output: 8112 🏁 Script executed: set -eu
printf '%s\n' '--- SDK 1.7.0 package exports and files ---'
tmp=$(mktemp)
curl -fsSL 'https://registry.npmjs.org/@modelcontextprotocol%2fsdk/1.7.0' > "$tmp"
python3 - "$tmp" <<'PY'
import json, sys
p=json.load(open(sys.argv[1]))
print("version:", p.get("version"))
print("dist.tarball:", p.get("dist", {}).get("tarball"))
print("exports:", json.dumps(p.get("exports"), indent=2, sort_keys=True))
PY
url=$(python3 - "$tmp" <<'PY'
import json, sys
print(json.load(open(sys.argv[1]))["dist"]["tarball"])
PY
)
curl -fsSL "$url" | tar -tzf - | grep -E '(^|/)(server/(http|streamableHttp|sse)\.(js|d\.ts)|package\.json)$' | sort
rm -f "$tmp"
printf '%s\n' '--- Dockerfile deployment lines ---'
cat -n Dockerfile | sed -n '30,55p'
printf '%s\n' '--- README deployment-related sections ---'
grep -n -i -E 'docker|deploy|container|port|transport|stdio|http' README.md | head -80
printf '%s\n' '--- environment assignments across deployment/docs files ---'
for f in $(git ls-files | grep -E '(^|/)(Dockerfile[^/]*|docker-compose[^/]*|README[^/]*|\.env\.example)$' || true); do
grep -n -E '(^|[[:space:]])(ENV|export)[[:space:]]+[^#]*(PORT|HTTP_PORT|MCP_TRANSPORT)|(^|[[:space:]])(-e|--env)[=[:space:]]*[^#]*(PORT|HTTP_PORT|MCP_TRANSPORT)|MCP_TRANSPORT[[:space:]]*=[[:space:]]*http' "$f" || true
doneRepository: Saml1211/PRD-MCP-Server Length of output: 3932 Reject unsupported HTTP configuration instead of falling back to STDIO.
Suggested fix- logger.warn(`HTTP transport requested on port ${port}, but HttpServerTransport is not available in this SDK version. Falling back to STDIO.`);
- const stdioFallback = new StdioServerTransport();
- await server.connect(stdioFallback);
- logger.info('PRD Creator MCP Server running with STDIO transport (HTTP fallback)');
+ throw new Error(
+ `HTTP transport requested on port ${port}, but this SDK version does not provide an HTTP transport.`
+ );-# Transport mode (options: stdio, http - default: stdio)
+# Transport mode (stdio only)
# MCP_TRANSPORT=stdio
-
-# Server port (used when MCP_TRANSPORT=http)
-# PORT=3000📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| } else { | ||||||||||||||||
| // STDIO (CLI/local) | ||||||||||||||||
| logger.info('Connecting server with STDIO transport...'); | ||||||||||||||||
|
|
@@ -97,4 +83,4 @@ async function main() { | |||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Start the server | ||||||||||||||||
| main(); | ||||||||||||||||
| main(); | ||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: Saml1211/PRD-MCP-Server
Length of output: 1876
🏁 Script executed:
Repository: Saml1211/PRD-MCP-Server
Length of output: 7384
🌐 Web query:
@google/generative-ai 0.24.1 response.text getResponseText MAX_TOKENS formatFinishReasonErrorMessage source💡 Result:
🏁 Script executed:
Repository: Saml1211/PRD-MCP-Server
Length of output: 309
🏁 Script executed:
Repository: Saml1211/PRD-MCP-Server
Length of output: 212
🏁 Script executed:
Repository: Saml1211/PRD-MCP-Server
Length of output: 20738
Reject
MAX_TOKENSresponses before returning the PRD.Blocked responses already preserve the SDK error.
response.text()includes the block or finish reason, and the catch block appendserror.message.MAX_TOKENSis different. The SDK does not treat it as a blocking finish reason. When the candidate contains content parts,response.text()returns those parts, and this provider returns the partial PRD as successful output. CheckfinishReasonand reject or report every non-STOPresult.🐛 Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents