diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..397c6da --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,277 @@ +# SEMOSS MCP Tool Development + +Comprehensive guide for building SEMOSS MCP tools with custom UIs. + +--- + +## Quick Reference + +### Architecture +- **Key modules**: `client` (React/Vite UI), `portals` (published UI), `mcp` (generated manifests), `py` (Python MCP tools) +- **Flow**: UI in `client` builds to `portals` and publishes via SEMOSS UI. UI calls MCP tools via SDK and returns data to the client +- **Frontend**: shadcn/ui + Radix + Tailwind, contexts in `client/src/contexts`, routes in `client/src/pages`, keep components keyboard accessible +- **Integrations**: SEMOSS SDK (`@semoss/sdk`) and SEMOSS runtime + +### Development Workflow +1. **Setup**: Install Node + pnpm and Python +2. **Common commands**: + - `pnpm i` (root and `client`) + - `pnpm dev` (development server) + - `pnpm build` (production build) +3. **Environment**: Replace the app id in `client/.env` with: + ``` + APP="your-app-id" + ``` +4. **Publish**: After changes, build and publish via SEMOSS UI; if `portals/` is missing, run `pnpm i` and `pnpm build` at client folder root + +### MCP Development Basics +- **Manifests**: `mcp/py_mcp.json` (Python) is generated by reactors. Do not edit manually +- **Tools location**: Add tools to `py/mcp_driver.py`. Only expose end tools; move helpers to separate modules +- **Docstrings**: Add concise docstrings that describe behavior, inputs, and outputs +- **Metadata**: Every tool must use `@mcp_metadata` from `smssutil.py` +- **ROOT**: Injected into `mcp_driver.py`; propagate it to dependencies +- **smssutil**: The `smssutil` module (containing `@mcp_metadata`) is automatically injected by SEMOSS, similar to `ROOT`. You don't need to create it +- **Generation**: Generate `mcp/py_mcp.json` via the SEMOSS reactor (see `MakePythonMCPReactor`) + +--- + +## Python MCP Tools + +### Tool Structure +```python +@mcp_metadata({ + 'loadingMessage': 'Processing...', + 'resourceURI': None, + 'execution': 'ask', # 'auto', 'ask', or 'disabled' + 'displayLocation': 'inline' # 'inline', 'sidebar', or 'hidden' +}) +def your_tool_name(param: str, model_id: str) -> str: + """ + Brief description of what the tool does. + + Args: + param: Description of parameter + model_id: The ID of the AI model to use + + Returns: + JSON string with results + """ + try: + # Your logic here + result = {'success': True, 'data': your_data} + return json.dumps(result) + except Exception as e: + import traceback + return json.dumps({ + 'success': False, + 'error': str(e), + 'traceback': traceback.format_exc() + }) +``` + +### Using Model Engines +Always require `model_id` as a parameter: + +```python +from ai_server import ModelEngine + +def your_tool(content: str, model_id: str) -> str: + model = ModelEngine(engine_id=model_id) + response = model.ask(command=prompt, param_dict={ + "temperature": 0.7, + "max_completion_tokens": 2000 + }) + # Parse and return response +``` + +### File Operations +Saving to insights folder: + +```python +from datetime import datetime +from pathlib import Path +import os + +# ROOT is injected by SEMOSS +file_path = os.path.join(Path(ROOT), filename) + +with open(file_path, 'w', encoding='utf-8') as f: + f.write(content) +``` + +--- + +## React UI Development + +### SDK Hooks +- **`useInsight()`**: Provides `actions`, `isReady`, `tool`, etc. +- **`useAppContext()`**: Custom context with `runPixel`, `sendMCPResponseToPlayground` + +```typescript +import { useInsight } from "@semoss/sdk/react"; +import { useAppContext } from "./contexts/AppContext"; + +const { actions, isReady } = useInsight(); // For MCP tools +const { runPixel } = useAppContext(); // For Pixel commands +``` + +### Model Selection Pattern +Fetching available models: + +```typescript +interface Engine { + app_id: string; + app_name: string; +} + +// Text generation models +const models = await runPixel( + `MyEngines( metaKeys = [], metaFilters = [{ "tag" : "text-generation" }], engineTypes = [ 'MODEL' ])` +); + +### Calling MCP Tools +```typescript +const { output } = await actions.runMCPTool("tool_name", { + param1: value1, + model_id: selectedModelId, +}); +``` + +### Response Parsing +MCP tool responses can be encoded multiple ways. Always handle all cases: + +```typescript +let data: YourResultType; + +if (typeof output === "string") { + try { + const parsed = JSON.parse(output); + // Check if double-encoded + if (typeof parsed === "string") { + data = JSON.parse(parsed) as YourResultType; + } else { + data = parsed as YourResultType; + } + } catch { + throw new Error("Failed to parse response"); + } +} else { + // Already an object + data = output as YourResultType; +} +``` + +--- + +## Common Gotchas + +### 1. Response Parsing +❌ **Wrong**: Assuming output is always a string +✅ **Right**: Handle object, string, and double-encoded cases + +### 2. MCP Metadata +❌ **Wrong**: Forgetting `@mcp_metadata` decorator +✅ **Right**: Every MCP tool must have `@mcp_metadata()` + +### 3. Parameter Passing +❌ **Wrong**: Using kwargs +✅ **Right**: Pass the parameter name and type - they will be interpretted as required parameters + +### 4. MCP Execution +- UI must target a specific tool name (from `mcp_driver.py`) +- Execute tools with `actions.runMCPTool(name, params)` +- Forward results with `actions.sendMCPResponseToPlayground` when needed + +--- + +## Best Practices + +### Python +- Use type hints for all parameters and return values +- Wrap MCP tool logic in try-catch blocks +- Return structured JSON from all MCP tools +- Keep MCP tool execution under 30 seconds +- Use streaming for long operations +- Cache expensive operations when appropriate + +### TypeScript +- Define interfaces for all data structures +- Handle all response encoding formats +- Validate user inputs before calling MCP tools +- Provide loading states during MCP execution +- Show meaningful error messages to users + +### Security +- Never expose API keys in frontend code +- Use environment variables for sensitive data +- Validate all user inputs in Python +- Sanitize HTML content before rendering + +### Testing Checklist +- [ ] Test with different models +- [ ] Test with empty/invalid inputs +- [ ] Test error handling +- [ ] Build without errors (`pnpm build`) +- [ ] Test in playground mode after publishing + +--- + +## File Pointers + +### Entry Points +- `portals/index.html` - Published app entry +- `client/src/index.tsx` - React entry point +- `client/src/App.tsx` - Main app component + +### MCP Tools +- `py/mcp_driver.py` - Python MCP tool definitions +- `mcp/py_mcp.json` - Generated Python manifest + +### Configuration +- `client/vite.config.ts` - Vite build config +- `client/tailwind.config.js` - Tailwind CSS config +- `biome.json` - Linting/formatting config + +### Generated +- Avoid direct edits to: `portals/`, `classes/`, `target/` + +--- + +## Do and Do Not + +### Do +- Keep UI in `client` +- Put custom logic in `py/mcp_driver.py` +- Build then publish via SEMOSS UI +- Use type hints in Python +- Handle all response encoding formats +- Validate inputs before processing + +### Do Not +- Edit built assets in `portals/` +- Manually edit `mcp/*.json` +- Commit secrets in `.env.local` +- Assume response format +- Skip error handling +- Forget `@mcp_metadata` decorator + +--- + +## Resources + +### Documentation +- [SEMOSS GitHub](https://github.com/SEMOSS/Semoss) (docs: https://github.com/SEMOSS/Semoss/tree/main/docs) +- [Monolith](https://github.com/SEMOSS/Monolith) +- [SEMOSS UI](https://github.com/SEMOSS/semoss-ui) + +### SDK +- `@semoss/sdk` and `@semoss/sdk/react` +- Python SDK: `ModelEngine`, `DatabaseEngine`, `Insight` from SEMOSS modules + +### Getting Help +When debugging: +1. Check browser console for frontend errors +2. Check SEMOSS logs for backend errors +3. Verify MCP manifest generated correctly +4. Test Python functions independently +5. Use network tab to inspect MCP tool responses \ No newline at end of file